diff --git a/caddy.go b/caddy.go index 5854565f0..eda695dd6 100644 --- a/caddy.go +++ b/caddy.go @@ -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{})} diff --git a/caddytls/setup.go b/caddytls/setup.go index 9b98f3ee8..ef65acb55 100644 --- a/caddytls/setup.go +++ b/caddytls/setup.go @@ -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 } diff --git a/vendor/github.com/mholt/certmagic/certificates.go b/vendor/github.com/mholt/certmagic/certificates.go index 4f6e6985a..02a175e67 100644 --- a/vendor/github.com/mholt/certmagic/certificates.go +++ b/vendor/github.com/mholt/certmagic/certificates.go @@ -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 timeLeft < renewDurationBefore + return time.Until(c.NotAfter) < renewDurationBefore } // CacheManagedCertificate loads the certificate for domain into the diff --git a/vendor/github.com/mholt/certmagic/certmagic.go b/vendor/github.com/mholt/certmagic/certmagic.go index cb480115f..d7a030d33 100644 --- a/vendor/github.com/mholt/certmagic/certmagic.go +++ b/vendor/github.com/mholt/certmagic/certmagic.go @@ -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 httpServer.Serve(hln) + return httpsServer.Serve(hsln) } func httpRedirectHandler(w http.ResponseWriter, r *http.Request) { diff --git a/vendor/github.com/mholt/certmagic/client.go b/vendor/github.com/mholt/certmagic/client.go index 314b3967c..2af8f9f13 100644 --- a/vendor/github.com/mholt/certmagic/client.go +++ b/vendor/github.com/mholt/certmagic/client.go @@ -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.TryLock(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.TryLock(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 diff --git a/vendor/github.com/mholt/certmagic/config.go b/vendor/github.com/mholt/certmagic/config.go index db2968f4c..9f256bc59 100644 --- a/vendor/github.com/mholt/certmagic/config.go +++ b/vendor/github.com/mholt/certmagic/config.go @@ -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() +} diff --git a/vendor/github.com/mholt/certmagic/crypto.go b/vendor/github.com/mholt/certmagic/crypto.go index ec8080951..695ac72d0 100644 --- a/vendor/github.com/mholt/certmagic/crypto.go +++ b/vendor/github.com/mholt/certmagic/crypto.go @@ -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, + } +) diff --git a/vendor/github.com/mholt/certmagic/filestorage.go b/vendor/github.com/mholt/certmagic/filestorage.go index f0005cc67..adfe1b26b 100644 --- a/vendor/github.com/mholt/certmagic/filestorage.go +++ b/vendor/github.com/mholt/certmagic/filestorage.go @@ -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,10 +116,127 @@ 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)) } +// 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) + + for { + err := createLockfile(filename) + if err == nil { + // got the lock, yay + return nil + } + if !os.IsExist(err) { + // unexpected error + return fmt.Errorf("creating lock file: %v", err) + } + + // lock file already exists + + info, err := os.Stat(filename) + switch { + case os.IsNotExist(err): + // must have just been removed; try again to create it + continue + + case err != nil: + // unexpected error + return fmt.Errorf("accessing lock file: %v", err) + + 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, 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) + } + } +} + +// Unlock releases the lock for name. +func (fs *FileStorage) Unlock(key string) error { + return removeLockfile(fs.lockFilename(key)) +} + +func (fs *FileStorage) String() string { + return "FileStorage:" + fs.Path +} + +func (fs *FileStorage) lockFilename(key string) string { + return filepath.Join(fs.lockDir(), StorageKeys.safe(key)+".lock") +} + +func (fs *FileStorage) lockDir() string { + return filepath.Join(fs.Path, "locks") +} + +func fileLockIsStale(info os.FileInfo) bool { + if info == nil { + return true + } + return time.Since(info.ModTime()) > staleLockDuration +} + +// 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 +} + +// 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) +} + +// 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 +} + // homeDir returns the best guess of the current user's home // directory from environment variables. If unknown, "." (the // current directory) is returned instead. @@ -149,160 +264,12 @@ func dataDir() string { return filepath.Join(baseDir, "certmagic") } -// 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 - } - - // 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 - } - - fw = &fileStorageWaiter{ - key: key, - filename: filepath.Join(lockDir, StorageKeys.safe(key)+".lock"), - wg: new(sync.WaitGroup), - } - - var checkedStaleLock bool // sentinel value to avoid infinite goto-ing - -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 - - // check to see if the lock is stale, if we haven't already - if !checkedStaleLock { - checkedStaleLock = true - if fs.lockFileStale(fw.filename) { - log.Printf("[INFO][%s] Lock for '%s' is stale; removing then retrying: %s", - fs, key, fw.filename) - os.Remove(fw.filename) - goto createLock - } - } - - // 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() - } - - // clean up in memory - fw.wg.Done() - delete(fileStorageNameLocks, key) - - return nil -} - -// 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) lockFileStale(filename string) bool { - info, err := os.Stat(filename) - if err != nil { - return true // no good way to handle this, really; lock is useless? - } - return time.Since(info.ModTime()) > staleLockDuration -} - -func (fs FileStorage) lockDir() string { - return filepath.Join(fs.Path, "locks") -} - -func (fs FileStorage) String() string { - return "FileStorage:" + fs.Path -} - -// 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 -} - -// 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 - } - time.Sleep(1 * time.Second) - } -} - -var fileStorageNameLocks = make(map[string]*fileStorageWaiter) -var fileStorageNameLocksMu sync.Mutex - -var _ Storage = FileStorage{} -var _ Waiter = &fileStorageWaiter{} - // 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) diff --git a/vendor/github.com/mholt/certmagic/storage.go b/vendor/github.com/mholt/certmagic/storage.go index 6c30b5055..54d41f6ec 100644 --- a/vendor/github.com/mholt/certmagic/storage.go +++ b/vendor/github.com/mholt/certmagic/storage.go @@ -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 TryLock 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 diff --git a/vendor/manifest b/vendor/manifest index 10ba7e473..71afc830a 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -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 },