Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
caddy
Commits
0b83014f
Commit
0b83014f
authored
Dec 19, 2018
by
Matthew Holt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
caddytls: Use latest certmagic package, with updated Storage interface
parent
0684cf86
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
282 additions
and
227 deletions
+282
-227
caddy.go
caddy.go
+0
-1
caddytls/setup.go
caddytls/setup.go
+1
-1
vendor/github.com/mholt/certmagic/certificates.go
vendor/github.com/mholt/certmagic/certificates.go
+1
-2
vendor/github.com/mholt/certmagic/certmagic.go
vendor/github.com/mholt/certmagic/certmagic.go
+32
-3
vendor/github.com/mholt/certmagic/client.go
vendor/github.com/mholt/certmagic/client.go
+26
-18
vendor/github.com/mholt/certmagic/config.go
vendor/github.com/mholt/certmagic/config.go
+49
-17
vendor/github.com/mholt/certmagic/crypto.go
vendor/github.com/mholt/certmagic/crypto.go
+33
-0
vendor/github.com/mholt/certmagic/filestorage.go
vendor/github.com/mholt/certmagic/filestorage.go
+128
-161
vendor/github.com/mholt/certmagic/storage.go
vendor/github.com/mholt/certmagic/storage.go
+11
-23
vendor/manifest
vendor/manifest
+1
-1
No files found.
caddy.go
View file @
0b83014f
...
...
@@ -487,7 +487,6 @@ func Start(cdyfile Input) (*Instance, error) {
return
nil
,
fmt
.
Errorf
(
"constructing cluster plugin %s: %v"
,
clusterPluginName
,
err
)
}
certmagic
.
DefaultStorage
=
storage
OnProcessExit
=
append
(
OnProcessExit
,
certmagic
.
DefaultStorage
.
UnlockAllObtained
)
}
inst
:=
&
Instance
{
serverType
:
cdyfile
.
ServerType
(),
wg
:
new
(
sync
.
WaitGroup
),
Storage
:
make
(
map
[
interface
{}]
interface
{})}
...
...
caddytls/setup.go
View file @
0b83014f
...
...
@@ -432,5 +432,5 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error {
}
func
constructDefaultClusterPlugin
()
(
certmagic
.
Storage
,
error
)
{
return
certmagic
.
FileStorage
{
Path
:
caddy
.
AssetsPath
()},
nil
return
&
certmagic
.
FileStorage
{
Path
:
caddy
.
AssetsPath
()},
nil
}
vendor/github.com/mholt/certmagic/certificates.go
View file @
0b83014f
...
...
@@ -64,12 +64,11 @@ func (c Certificate) NeedsRenewal() bool {
if
c
.
NotAfter
.
IsZero
()
{
return
false
}
timeLeft
:=
c
.
NotAfter
.
UTC
()
.
Sub
(
time
.
Now
()
.
UTC
())
renewDurationBefore
:=
DefaultRenewDurationBefore
if
len
(
c
.
configs
)
>
0
&&
c
.
configs
[
0
]
.
RenewDurationBefore
>
0
{
renewDurationBefore
=
c
.
configs
[
0
]
.
RenewDurationBefore
}
return
time
Left
<
renewDurationBefore
return
time
.
Until
(
c
.
NotAfter
)
<
renewDurationBefore
}
// CacheManagedCertificate loads the certificate for domain into the
...
...
vendor/github.com/mholt/certmagic/certmagic.go
View file @
0b83014f
...
...
@@ -52,6 +52,16 @@ import (
// HTTPS serves mux for all domainNames using the HTTP
// and HTTPS ports, redirecting all HTTP requests to HTTPS.
//
// This high-level convenience function is opinionated and
// applies sane defaults for production use, including
// timeouts for HTTP requests and responses. To allow very
// long-lived requests or connections, you should make your
// own http.Server values and use this package's Listen(),
// TLS(), or Config.TLSConfig() functions to customize to
// your needs. For example, servers which need to support
// large uploads or downloads with slow clients may need to
// use longer timeouts, thus this function is not suitable.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
func
HTTPS
(
domainNames
[]
string
,
mux
http
.
Handler
)
error
{
...
...
@@ -96,13 +106,32 @@ func HTTPS(domainNames []string, mux http.Handler) error {
hln
,
hsln
:=
httpLn
,
httpsLn
lnMu
.
Unlock
()
httpHandler
:=
cfg
.
HTTPChallengeHandler
(
http
.
HandlerFunc
(
httpRedirectHandler
))
// create HTTP/S servers that are configured
// with sane default timeouts and appropriate
// handlers (the HTTP server solves the HTTP
// challenge and issues redirects to HTTPS,
// while the HTTPS server simply serves the
// user's handler)
httpServer
:=
&
http
.
Server
{
ReadHeaderTimeout
:
5
*
time
.
Second
,
ReadTimeout
:
5
*
time
.
Second
,
WriteTimeout
:
5
*
time
.
Second
,
IdleTimeout
:
5
*
time
.
Second
,
Handler
:
cfg
.
HTTPChallengeHandler
(
http
.
HandlerFunc
(
httpRedirectHandler
)),
}
httpsServer
:=
&
http
.
Server
{
ReadHeaderTimeout
:
10
*
time
.
Second
,
ReadTimeout
:
30
*
time
.
Second
,
WriteTimeout
:
2
*
time
.
Minute
,
IdleTimeout
:
5
*
time
.
Minute
,
Handler
:
mux
,
}
log
.
Printf
(
"%v Serving HTTP->HTTPS on %s and %s"
,
domainNames
,
hln
.
Addr
(),
hsln
.
Addr
())
go
http
.
Serve
(
hln
,
httpHandler
)
return
http
.
Serve
(
hsln
,
mux
)
go
http
Server
.
Serve
(
hln
)
return
http
sServer
.
Serve
(
hsln
)
}
func
httpRedirectHandler
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
...
...
vendor/github.com/mholt/certmagic/client.go
View file @
0b83014f
...
...
@@ -208,6 +208,8 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
return
c
,
nil
}
// lockKey returns a key for a lock that is specific to the operation
// named op being performed related to domainName and this config's CA.
func
(
cfg
*
Config
)
lockKey
(
op
,
domainName
string
)
string
{
return
fmt
.
Sprintf
(
"%s:%s:%s"
,
op
,
domainName
,
cfg
.
CA
)
}
...
...
@@ -215,30 +217,34 @@ func (cfg *Config) lockKey(op, domainName string) string {
// Obtain obtains a single certificate for name. It stores the certificate
// on the disk if successful. This function is safe for concurrent use.
//
// Right now our storage mechanism only supports one name per certificate,
// so this function (along with Renew and Revoke) only accepts one domain
// as input. It can be easily modified to support SAN certificates if our
// storage mechanism is upgraded later.
// Our storage mechanism only supports one name per certificate, so this
// function (along with Renew and Revoke) only accepts one domain as input.
// It could be easily modified to support SAN certificates if our storage
// mechanism is upgraded later, but that will increase logical complexity
// in other areas.
//
// Callers who have access to a Config value should use the ObtainCert
// method on that instead of this lower-level method.
func
(
c
*
acmeClient
)
Obtain
(
name
string
)
error
{
// ensure idempotency of the obtain operation for this name
lockKey
:=
c
.
config
.
lockKey
(
"cert_acme"
,
name
)
waiter
,
err
:=
c
.
config
.
certCache
.
storage
.
Try
Lock
(
lockKey
)
err
:=
c
.
config
.
certCache
.
storage
.
Lock
(
lockKey
)
if
err
!=
nil
{
return
err
}
if
waiter
!=
nil
{
log
.
Printf
(
"[INFO] Certificate for %s is already being obtained elsewhere and stored; waiting"
,
name
)
waiter
.
Wait
()
return
nil
// we assume the process with the lock succeeded, rather than hammering this execution path again
}
defer
func
()
{
if
err
:=
c
.
config
.
certCache
.
storage
.
Unlock
(
lockKey
);
err
!=
nil
{
log
.
Printf
(
"[ERROR]
Unable to unlock obtain call for %s: %v"
,
name
,
err
)
log
.
Printf
(
"[ERROR]
[%s] Obtain: Unable to unlock '%s': %v"
,
name
,
lockKey
,
err
)
}
}()
// check if obtain is still needed -- might have
// been obtained during lock
if
c
.
config
.
storageHasCertResources
(
name
)
{
log
.
Printf
(
"[INFO][%s] Obtain: Certificate already exists in storage"
,
name
)
return
nil
}
for
attempts
:=
0
;
attempts
<
2
;
attempts
++
{
request
:=
certificate
.
ObtainRequest
{
Domains
:
[]
string
{
name
},
...
...
@@ -280,19 +286,15 @@ func (c *acmeClient) Obtain(name string) error {
// Callers who have access to a Config value should use the RenewCert
// method on that instead of this lower-level method.
func
(
c
*
acmeClient
)
Renew
(
name
string
)
error
{
// ensure idempotency of the renew operation for this name
lockKey
:=
c
.
config
.
lockKey
(
"cert_acme"
,
name
)
waiter
,
err
:=
c
.
config
.
certCache
.
storage
.
Try
Lock
(
lockKey
)
err
:=
c
.
config
.
certCache
.
storage
.
Lock
(
lockKey
)
if
err
!=
nil
{
return
err
}
if
waiter
!=
nil
{
log
.
Printf
(
"[INFO] Certificate for %s is already being renewed elsewhere and stored; waiting"
,
name
)
waiter
.
Wait
()
return
nil
// assume that the worker that renewed the cert succeeded to avoid hammering this path over and over
}
defer
func
()
{
if
err
:=
c
.
config
.
certCache
.
storage
.
Unlock
(
lockKey
);
err
!=
nil
{
log
.
Printf
(
"[ERROR]
Unable to unlock renew call for %s: %v"
,
name
,
err
)
log
.
Printf
(
"[ERROR]
[%s] Renew: Unable to unlock '%s': %v"
,
name
,
lockKey
,
err
)
}
}()
...
...
@@ -302,6 +304,12 @@ func (c *acmeClient) Renew(name string) error {
return
err
}
// Check if renew is still needed - might have been renewed while waiting for lock
if
!
c
.
config
.
managedCertNeedsRenewal
(
certRes
)
{
log
.
Printf
(
"[INFO][%s] Renew: Certificate appears to have been renewed already"
,
name
)
return
nil
}
// Perform renewal and retry if necessary, but not too many times.
var
newCertMeta
*
certificate
.
Resource
var
success
bool
...
...
vendor/github.com/mholt/certmagic/config.go
View file @
0b83014f
...
...
@@ -21,6 +21,7 @@ import (
"time"
"github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego"
...
...
@@ -277,12 +278,6 @@ func (cfg *Config) ObtainCert(name string, interactive bool) error {
return
nil
}
// we expect this to be a new site; if the
// cert already exists, then no-op
if
cfg
.
certCache
.
storage
.
Exists
(
StorageKeys
.
SiteCert
(
cfg
.
CA
,
name
))
{
return
nil
}
client
,
err
:=
cfg
.
newACMEClient
(
interactive
)
if
err
!=
nil
{
return
err
...
...
@@ -317,24 +312,37 @@ func (cfg *Config) RevokeCert(domain string, interactive bool) error {
return
client
.
Revoke
(
domain
)
}
// TLSConfig returns a TLS configuration that
// can be used to configure TLS listeners. It
// supports the TLS-ALPN challenge and serves
// up certificates managed by cfg.
// TLSConfig is an opinionated method that returns a
// recommended, modern TLS configuration that can be
// used to configure TLS listeners, which also supports
// the TLS-ALPN challenge and serves up certificates
// managed by cfg.
//
// Unlike the package TLS() function, this method does
// not, by itself, enable certificate management for
// any domain names.
//
// Feel free to further customize the returned tls.Config,
// but do not mess with the GetCertificate or NextProtos
// fields unless you know what you're doing, as they're
// necessary to solve the TLS-ALPN challenge.
func
(
cfg
*
Config
)
TLSConfig
()
*
tls
.
Config
{
return
&
tls
.
Config
{
// these two fields necessary for TLS-ALPN challenge
GetCertificate
:
cfg
.
GetCertificate
,
NextProtos
:
[]
string
{
"h2"
,
"http/1.1"
,
tlsalpn01
.
ACMETLS1Protocol
},
// the rest recommended for modern TLS servers
MinVersion
:
tls
.
VersionTLS12
,
CurvePreferences
:
[]
tls
.
CurveID
{
tls
.
X25519
,
tls
.
CurveP256
,
},
CipherSuites
:
preferredDefaultCipherSuites
(),
PreferServerCipherSuites
:
true
,
}
}
// RenewAllCerts triggers a renewal check of all
// certificates in the cache. It only renews
// certificates if they need to be renewed.
// func (cfg *Config) RenewAllCerts(interactive bool) error {
// return cfg.certCache.RenewManagedCertificates(interactive)
// }
// preObtainOrRenewChecks perform a few simple checks before
// obtaining or renewing a certificate with ACME, and returns
// whether this name should be skipped (like if it's not
...
...
@@ -356,3 +364,27 @@ func (cfg *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool,
return
false
,
nil
}
// storageHasCertResources returns true if the storage
// associated with cfg's certificate cache has all the
// resources related to the certificate for domain: the
// certificate, the private key, and the metadata.
func
(
cfg
*
Config
)
storageHasCertResources
(
domain
string
)
bool
{
certKey
:=
StorageKeys
.
SiteCert
(
cfg
.
CA
,
domain
)
keyKey
:=
StorageKeys
.
SitePrivateKey
(
cfg
.
CA
,
domain
)
metaKey
:=
StorageKeys
.
SiteMeta
(
cfg
.
CA
,
domain
)
return
cfg
.
certCache
.
storage
.
Exists
(
certKey
)
&&
cfg
.
certCache
.
storage
.
Exists
(
keyKey
)
&&
cfg
.
certCache
.
storage
.
Exists
(
metaKey
)
}
// managedCertNeedsRenewal returns true if certRes is
// expiring soon or already expired, or if the process
// of checking the expiration returned an error.
func
(
cfg
*
Config
)
managedCertNeedsRenewal
(
certRes
certificate
.
Resource
)
bool
{
cert
,
err
:=
cfg
.
makeCertificate
(
certRes
.
Certificate
,
certRes
.
PrivateKey
)
if
err
!=
nil
{
return
true
}
return
cert
.
NeedsRenewal
()
}
vendor/github.com/mholt/certmagic/crypto.go
View file @
0b83014f
...
...
@@ -19,12 +19,14 @@ import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"hash/fnv"
"github.com/klauspost/cpuid"
"github.com/xenolf/lego/certificate"
)
...
...
@@ -153,3 +155,34 @@ func hashCertificateChain(certChain [][]byte) string {
}
return
fmt
.
Sprintf
(
"%x"
,
h
.
Sum
(
nil
))
}
// preferredDefaultCipherSuites returns an appropriate
// cipher suite to use depending on hardware support
// for AES-NI.
//
// See https://github.com/mholt/caddy/issues/1674
func
preferredDefaultCipherSuites
()
[]
uint16
{
if
cpuid
.
CPU
.
AesNi
()
{
return
defaultCiphersPreferAES
}
return
defaultCiphersPreferChaCha
}
var
(
defaultCiphersPreferAES
=
[]
uint16
{
tls
.
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
,
tls
.
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
,
tls
.
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
,
tls
.
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
,
}
defaultCiphersPreferChaCha
=
[]
uint16
{
tls
.
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
,
tls
.
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
,
tls
.
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
,
tls
.
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
,
}
)
vendor/github.com/mholt/certmagic/filestorage.go
View file @
0b83014f
...
...
@@ -22,7 +22,6 @@ import (
"path"
"path/filepath"
"runtime"
"sync"
"time"
)
...
...
@@ -34,13 +33,13 @@ type FileStorage struct {
}
// Exists returns true if key exists in fs.
func
(
fs
FileStorage
)
Exists
(
key
string
)
bool
{
func
(
fs
*
FileStorage
)
Exists
(
key
string
)
bool
{
_
,
err
:=
os
.
Stat
(
fs
.
Filename
(
key
))
return
!
os
.
IsNotExist
(
err
)
}
// Store saves value at key.
func
(
fs
FileStorage
)
Store
(
key
string
,
value
[]
byte
)
error
{
func
(
fs
*
FileStorage
)
Store
(
key
string
,
value
[]
byte
)
error
{
filename
:=
fs
.
Filename
(
key
)
err
:=
os
.
MkdirAll
(
filepath
.
Dir
(
filename
),
0700
)
if
err
!=
nil
{
...
...
@@ -50,7 +49,7 @@ func (fs FileStorage) Store(key string, value []byte) error {
}
// Load retrieves the value at key.
func
(
fs
FileStorage
)
Load
(
key
string
)
([]
byte
,
error
)
{
func
(
fs
*
FileStorage
)
Load
(
key
string
)
([]
byte
,
error
)
{
contents
,
err
:=
ioutil
.
ReadFile
(
fs
.
Filename
(
key
))
if
os
.
IsNotExist
(
err
)
{
return
nil
,
ErrNotExist
(
err
)
...
...
@@ -59,8 +58,7 @@ func (fs FileStorage) Load(key string) ([]byte, error) {
}
// Delete deletes the value at key.
// TODO: Delete any empty folders caused by this operation
func
(
fs
FileStorage
)
Delete
(
key
string
)
error
{
func
(
fs
*
FileStorage
)
Delete
(
key
string
)
error
{
err
:=
os
.
Remove
(
fs
.
Filename
(
key
))
if
os
.
IsNotExist
(
err
)
{
return
ErrNotExist
(
err
)
...
...
@@ -69,7 +67,7 @@ func (fs FileStorage) Delete(key string) error {
}
// List returns all keys that match prefix.
func
(
fs
FileStorage
)
List
(
prefix
string
,
recursive
bool
)
([]
string
,
error
)
{
func
(
fs
*
FileStorage
)
List
(
prefix
string
,
recursive
bool
)
([]
string
,
error
)
{
var
keys
[]
string
walkPrefix
:=
fs
.
Filename
(
prefix
)
...
...
@@ -100,7 +98,7 @@ func (fs FileStorage) List(prefix string, recursive bool) ([]string, error) {
}
// Stat returns information about key.
func
(
fs
FileStorage
)
Stat
(
key
string
)
(
KeyInfo
,
error
)
{
func
(
fs
*
FileStorage
)
Stat
(
key
string
)
(
KeyInfo
,
error
)
{
fi
,
err
:=
os
.
Stat
(
fs
.
Filename
(
key
))
if
os
.
IsNotExist
(
err
)
{
return
KeyInfo
{},
ErrNotExist
(
err
)
...
...
@@ -118,191 +116,160 @@ func (fs FileStorage) Stat(key string) (KeyInfo, error) {
// Filename returns the key as a path on the file
// system prefixed by fs.Path.
func
(
fs
FileStorage
)
Filename
(
key
string
)
string
{
func
(
fs
*
FileStorage
)
Filename
(
key
string
)
string
{
return
filepath
.
Join
(
fs
.
Path
,
filepath
.
FromSlash
(
key
))
}
// homeDir returns the best guess of the current user's home
// directory from environment variables. If unknown, "." (the
// current directory) is returned instead.
func
homeDir
()
string
{
home
:=
os
.
Getenv
(
"HOME"
)
if
home
==
""
&&
runtime
.
GOOS
==
"windows"
{
drive
:=
os
.
Getenv
(
"HOMEDRIVE"
)
path
:=
os
.
Getenv
(
"HOMEPATH"
)
home
=
drive
+
path
if
drive
==
""
||
path
==
""
{
home
=
os
.
Getenv
(
"USERPROFILE"
)
}
}
if
home
==
""
{
home
=
"."
}
return
home
}
func
dataDir
()
string
{
baseDir
:=
filepath
.
Join
(
homeDir
(),
".local"
,
"share"
)
if
xdgData
:=
os
.
Getenv
(
"XDG_DATA_HOME"
);
xdgData
!=
""
{
baseDir
=
xdgData
}
return
filepath
.
Join
(
baseDir
,
"certmagic"
)
}
// Lock obtains a lock named by the given key. It blocks
// until the lock can be obtained or an error is returned.
func
(
fs
*
FileStorage
)
Lock
(
key
string
)
error
{
start
:=
time
.
Now
()
filename
:=
fs
.
lockFilename
(
key
)
// TryLock attempts to get a lock for name, otherwise it returns
// a Waiter value to wait until the other process is finished.
func
(
fs
FileStorage
)
TryLock
(
key
string
)
(
Waiter
,
error
)
{
fileStorageNameLocksMu
.
Lock
()
defer
fileStorageNameLocksMu
.
Unlock
()
// see if lock already exists within this process - allows
// for faster unlocking since we don't have to poll the disk
fw
,
ok
:=
fileStorageNameLocks
[
key
]
if
ok
{
// lock already created within process, let caller wait on it
return
fw
,
nil
for
{
err
:=
createLockfile
(
filename
)
if
err
==
nil
{
// got the lock, yay
return
nil
}
// attempt to persist lock to disk by creating lock file
// parent dir must exist
lockDir
:=
fs
.
lockDir
()
if
err
:=
os
.
MkdirAll
(
lockDir
,
0700
);
err
!=
nil
{
return
nil
,
err
if
!
os
.
IsExist
(
err
)
{
// unexpected error
return
fmt
.
Errorf
(
"creating lock file: %v"
,
err
)
}
fw
=
&
fileStorageWaiter
{
key
:
key
,
filename
:
filepath
.
Join
(
lockDir
,
StorageKeys
.
safe
(
key
)
+
".lock"
),
wg
:
new
(
sync
.
WaitGroup
),
}
// lock file already exists
var
checkedStaleLock
bool
// sentinel value to avoid infinite goto-ing
info
,
err
:=
os
.
Stat
(
filename
)
switch
{
case
os
.
IsNotExist
(
err
)
:
// must have just been removed; try again to create it
continue
createLock
:
// create the file in a special mode such that an
// error is returned if it already exists
lf
,
err
:=
os
.
OpenFile
(
fw
.
filename
,
os
.
O_CREATE
|
os
.
O_EXCL
,
0644
)
if
err
!=
nil
{
if
os
.
IsExist
(
err
)
{
// another process has the lock
case
err
!=
nil
:
// unexpected error
return
fmt
.
Errorf
(
"accessing lock file: %v"
,
err
)
// check to see if the lock is stale, if we haven't already
if
!
checkedStaleLock
{
checkedStaleLock
=
true
if
fs
.
lockFileStale
(
fw
.
filename
)
{
case
fileLockIsStale
(
info
)
:
// lock file is stale - delete it and try again to create one
log
.
Printf
(
"[INFO][%s] Lock for '%s' is stale; removing then retrying: %s"
,
fs
,
key
,
fw
.
filename
)
os
.
Remove
(
fw
.
filename
)
goto
createLock
fs
,
key
,
filename
)
removeLockfile
(
filename
)
continue
case
time
.
Since
(
start
)
>
staleLockDuration
*
2
:
// should never happen, hopefully
return
fmt
.
Errorf
(
"possible deadlock: %s passed trying to obtain lock for %s"
,
time
.
Since
(
start
),
key
)
default
:
// lockfile exists and is not stale;
// just wait a moment and try again
time
.
Sleep
(
fileLockPollInterval
)
}
}
// if lock is not stale, wait upon it
return
fw
,
nil
}
// otherwise, this was some unexpected error
return
nil
,
err
}
lf
.
Close
()
// looks like we get the lock
fw
.
wg
.
Add
(
1
)
fileStorageNameLocks
[
key
]
=
fw
return
nil
,
nil
}
// Unlock releases the lock for name.
func
(
fs
FileStorage
)
Unlock
(
key
string
)
error
{
fileStorageNameLocksMu
.
Lock
()
defer
fileStorageNameLocksMu
.
Unlock
()
fw
,
ok
:=
fileStorageNameLocks
[
key
]
if
!
ok
{
return
fmt
.
Errorf
(
"FileStorage: no lock to release for %s"
,
key
)
}
// remove lock file
os
.
Remove
(
fw
.
filename
)
// if parent folder is now empty, remove it too to keep it tidy
dir
,
err
:=
os
.
Open
(
fs
.
lockDir
())
// OK to ignore error here
if
err
==
nil
{
items
,
_
:=
dir
.
Readdirnames
(
3
)
// OK to ignore error here
if
len
(
items
)
==
0
{
os
.
Remove
(
dir
.
Name
())
}
dir
.
Close
()
}
func
(
fs
*
FileStorage
)
Unlock
(
key
string
)
error
{
return
removeLockfile
(
fs
.
lockFilename
(
key
))
}
// clean up in memory
fw
.
wg
.
Done
()
delete
(
fileStorageNameLocks
,
key
)
func
(
fs
*
FileStorage
)
String
()
string
{
return
"FileStorage:"
+
fs
.
Path
}
return
nil
func
(
fs
*
FileStorage
)
lockFilename
(
key
string
)
string
{
return
filepath
.
Join
(
fs
.
lockDir
(),
StorageKeys
.
safe
(
key
)
+
".lock"
)
}
// UnlockAllObtained removes all locks obtained by
// this instance of fs.
func
(
fs
FileStorage
)
UnlockAllObtained
()
{
for
key
,
fw
:=
range
fileStorageNameLocks
{
err
:=
fs
.
Unlock
(
fw
.
key
)
if
err
!=
nil
{
log
.
Printf
(
"[ERROR][%s] Releasing obtained lock for %s: %v"
,
fs
,
key
,
err
)
}
}
func
(
fs
*
FileStorage
)
lockDir
()
string
{
return
filepath
.
Join
(
fs
.
Path
,
"locks"
)
}
func
(
fs
FileStorage
)
lockFileStale
(
filename
string
)
bool
{
info
,
err
:=
os
.
Stat
(
filename
)
if
err
!=
nil
{
return
true
// no good way to handle this, really; lock is useless?
func
fileLockIsStale
(
info
os
.
FileInfo
)
bool
{
if
info
==
nil
{
return
true
}
return
time
.
Since
(
info
.
ModTime
())
>
staleLockDuration
}
func
(
fs
FileStorage
)
lockDir
()
string
{
return
filepath
.
Join
(
fs
.
Path
,
"locks"
)
// createLockfile atomically creates the lockfile
// identified by filename. A successfully created
// lockfile should be removed with removeLockfile.
func
createLockfile
(
filename
string
)
error
{
err
:=
atomicallyCreateFile
(
filename
)
if
err
==
nil
{
// if the app crashes in removeLockfile(), there is a
// small chance the .unlock file is left behind; it's
// safe to simply remove it as it's a guard against
// double removal of the .lock file.
os
.
Remove
(
filename
+
".unlock"
)
}
return
err
}
func
(
fs
FileStorage
)
String
()
string
{
return
"FileStorage:"
+
fs
.
Path
// removeLockfile atomically removes filename,
// which must be a lockfile created by createLockfile.
// See discussion in PR #7 for more background:
// https://github.com/mholt/certmagic/pull/7
func
removeLockfile
(
filename
string
)
error
{
unlockFilename
:=
filename
+
".unlock"
if
err
:=
atomicallyCreateFile
(
unlockFilename
);
err
!=
nil
{
if
os
.
IsExist
(
err
)
{
// another process is handling the unlocking
return
nil
}
return
err
}
defer
os
.
Remove
(
unlockFilename
)
return
os
.
Remove
(
filename
)
}
// fileStorageWaiter waits for a file to disappear; it
// polls the file system to check for the existence of
// a file. It also uses a WaitGroup to optimize the
// polling in the case when this process is the only
// one waiting. (Other processes that are waiting for
// the lock will still block, but must wait for the
// polling to get their answer.)
type
fileStorageWaiter
struct
{
key
string
filename
string
wg
*
sync
.
WaitGroup
// atomicallyCreateFile atomically creates the file
// identified by filename if it doesn't already exist.
func
atomicallyCreateFile
(
filename
string
)
error
{
// no need to check this, we only really care about the file creation error
os
.
MkdirAll
(
filepath
.
Dir
(
filename
),
0700
)
f
,
err
:=
os
.
OpenFile
(
filename
,
os
.
O_CREATE
|
os
.
O_EXCL
,
0644
)
if
err
==
nil
{
f
.
Close
()
}
return
err
}
// Wait waits until the lock is released.
func
(
fw
*
fileStorageWaiter
)
Wait
()
{
start
:=
time
.
Now
()
fw
.
wg
.
Wait
()
for
time
.
Since
(
start
)
<
1
*
time
.
Hour
{
_
,
err
:=
os
.
Stat
(
fw
.
filename
)
if
os
.
IsNotExist
(
err
)
{
return
// homeDir returns the best guess of the current user's home
// directory from environment variables. If unknown, "." (the
// current directory) is returned instead.
func
homeDir
()
string
{
home
:=
os
.
Getenv
(
"HOME"
)
if
home
==
""
&&
runtime
.
GOOS
==
"windows"
{
drive
:=
os
.
Getenv
(
"HOMEDRIVE"
)
path
:=
os
.
Getenv
(
"HOMEPATH"
)
home
=
drive
+
path
if
drive
==
""
||
path
==
""
{
home
=
os
.
Getenv
(
"USERPROFILE"
)
}
}
time
.
Sleep
(
1
*
time
.
Second
)
if
home
==
""
{
home
=
"."
}
return
home
}
var
fileStorageNameLocks
=
make
(
map
[
string
]
*
fileStorageWaiter
)
var
fileStorageNameLocksMu
sync
.
Mutex
var
_
Storage
=
FileStorage
{}
var
_
Waiter
=
&
fileStorageWaiter
{}
func
dataDir
()
string
{
baseDir
:=
filepath
.
Join
(
homeDir
(),
".local"
,
"share"
)
if
xdgData
:=
os
.
Getenv
(
"XDG_DATA_HOME"
);
xdgData
!=
""
{
baseDir
=
xdgData
}
return
filepath
.
Join
(
baseDir
,
"certmagic"
)
}
// staleLockDuration is the length of time
// before considering a lock to be stale.
const
staleLockDuration
=
2
*
time
.
Hour
// fileLockPollInterval is how frequently
// to check the existence of a lock file
const
fileLockPollInterval
=
1
*
time
.
Second
var
_
Storage
=
(
*
FileStorage
)(
nil
)
vendor/github.com/mholt/certmagic/storage.go
View file @
0b83014f
...
...
@@ -64,22 +64,24 @@ type Storage interface {
// Locker facilitates synchronization of certificate tasks across
// machines and networks.
type
Locker
interface
{
// TryLock will attempt to acquire the lock for key. If a
// lock could be obtained, nil values are returned as no
// waiting is required. If not (meaning another process is
// already working on key), a Waiter value will be returned,
// upon which you should Wait() until it is finished.
// Lock acquires the lock for key, blocking until the lock
// can be obtained or an error is returned. Note that, even
// after acquiring a lock, an idempotent operation may have
// already been performed by another process that acquired
// the lock before - so always check to make sure idempotent
// operations still need to be performed after acquiring the
// lock.
//
// The actual implementation of obtaining of a lock must be
// an atomic operation so that multiple
Try
Lock calls at the
// an atomic operation so that multiple Lock calls at the
// same time always results in only one caller receiving the
// lock
. TryLock always returns without waiting
.
// lock
at any given time
.
//
// To prevent deadlocks, all implementations (where this concern
// is relevant) should put a reasonable expiration on the lock in
// case Unlock is unable to be called due to some sort of network
// or system failure or crash.
TryLock
(
key
string
)
(
Waiter
,
error
)
Lock
(
key
string
)
error
// Unlock releases the lock for key. This method must ONLY be
// called after a successful call to TryLock where no Waiter was
...
...
@@ -89,20 +91,6 @@ type Locker interface {
// TryLock or if Unlock was not called at all. Unlock should also
// clean up any unused resources allocated during TryLock.
Unlock
(
key
string
)
error
// UnlockAllObtained removes all locks obtained by this process,
// upon which others may be waiting. The importer should call
// this on shutdowns (and crashes, ideally) to avoid leaving stale
// locks, but Locker implementations must NOT rely on this being
// the case and should anticipate and handle stale locks. Errors
// should be printed or logged, since there could be multiple,
// with no good way to handle them anyway.
UnlockAllObtained
()
}
// Waiter is a type that can block until a lock is released.
type
Waiter
interface
{
Wait
()
}
// KeyInfo holds information about a key in storage.
...
...
@@ -281,7 +269,7 @@ type ErrNotExist interface {
// defaultFileStorage is a convenient, default storage
// implementation using the local file system.
var
defaultFileStorage
=
FileStorage
{
Path
:
dataDir
()}
var
defaultFileStorage
=
&
FileStorage
{
Path
:
dataDir
()}
// DefaultStorage is the default Storage implementation.
var
DefaultStorage
Storage
=
defaultFileStorage
vendor/manifest
View file @
0b83014f
...
...
@@ -138,7 +138,7 @@
"importpath": "github.com/mholt/certmagic",
"repository": "https://github.com/mholt/certmagic",
"vcs": "git",
"revision": "
fe722057f2654b33cd528b8fd8b90e53fa495564
",
"revision": "
a3b276a1b44e1c2c3dcab752729976ea04f4839b
",
"branch": "master",
"notests": true
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment