mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	caddytls: Use latest certmagic package, with updated Storage interface
This commit is contained in:
		
							parent
							
								
									0684cf8611
								
							
						
					
					
						commit
						0b83014ff8
					
				
							
								
								
									
										1
									
								
								caddy.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								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{})}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								vendor/github.com/mholt/certmagic/certificates.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mholt/certmagic/certificates.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								vendor/github.com/mholt/certmagic/certmagic.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/mholt/certmagic/certmagic.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@ -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) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										44
									
								
								vendor/github.com/mholt/certmagic/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										44
									
								
								vendor/github.com/mholt/certmagic/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										66
									
								
								vendor/github.com/mholt/certmagic/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										66
									
								
								vendor/github.com/mholt/certmagic/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@ -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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								vendor/github.com/mholt/certmagic/crypto.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/mholt/certmagic/crypto.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@ -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,
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										293
									
								
								vendor/github.com/mholt/certmagic/filestorage.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										293
									
								
								vendor/github.com/mholt/certmagic/filestorage.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@ -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)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								vendor/github.com/mholt/certmagic/storage.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/mholt/certmagic/storage.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							@ -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
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user