Upgrade certmagic from v0.14.1 to v0.15.2 (#18138)
This commit is contained in:
		
							parent
							
								
									385dc6a992
								
							
						
					
					
						commit
						e9c9a35a61
					
				|  | @ -9,6 +9,7 @@ import ( | |||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/modules/graceful" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 
 | ||||
|  | @ -47,7 +48,7 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) | |||
| 	magic.Issuers = []certmagic.Issuer{myACME} | ||||
| 
 | ||||
| 	// this obtains certificates or renews them if necessary
 | ||||
| 	err := magic.ManageSync([]string{domain}) | ||||
| 	err := magic.ManageSync(graceful.GetManager().HammerContext(), []string{domain}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										3
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										3
									
								
								go.mod
								
								
								
								
							|  | @ -22,7 +22,7 @@ require ( | |||
| 	github.com/blevesearch/bleve/v2 v2.3.0 | ||||
| 	github.com/boombuler/barcode v1.0.1 // indirect | ||||
| 	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect | ||||
| 	github.com/caddyserver/certmagic v0.14.1 | ||||
| 	github.com/caddyserver/certmagic v0.15.2 | ||||
| 	github.com/chi-middleware/proxy v1.1.1 | ||||
| 	github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect | ||||
| 	github.com/couchbase/gomemcached v0.1.2 // indirect | ||||
|  | @ -78,7 +78,6 @@ require ( | |||
| 	github.com/mattn/go-sqlite3 v1.14.8 | ||||
| 	github.com/mholt/archiver/v3 v3.5.0 | ||||
| 	github.com/microcosm-cc/bluemonday v1.0.16 | ||||
| 	github.com/miekg/dns v1.1.43 // indirect | ||||
| 	github.com/minio/md5-simd v1.1.2 // indirect | ||||
| 	github.com/minio/minio-go/v7 v7.0.12 | ||||
| 	github.com/minio/sha256-simd v1.0.0 // indirect | ||||
|  |  | |||
							
								
								
									
										10
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										10
									
								
								go.sum
								
								
								
								
							|  | @ -193,8 +193,8 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl | |||
| github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= | ||||
| github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= | ||||
| github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= | ||||
| github.com/caddyserver/certmagic v0.14.1 h1:8RIFS/LbGne/I7Op56Kkm2annnei7io9VW/IWDttE9U= | ||||
| github.com/caddyserver/certmagic v0.14.1/go.mod h1:oRQOZmUVKwlpgNidslysHt05osM9uMrJ4YMk+Ot4P4Q= | ||||
| github.com/caddyserver/certmagic v0.15.2 h1:OMTakTsLM1ZfzMDjwvYprfUgFzpVPh3u87oxMPwmeBc= | ||||
| github.com/caddyserver/certmagic v0.15.2/go.mod h1:qhkAOthf72ufAcp3Y5jF2RaGE96oip3UbEQRIzwe3/8= | ||||
| github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= | ||||
| github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= | ||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||
|  | @ -767,7 +767,6 @@ github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s | |||
| github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= | ||||
| github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||
| github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||
| github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||
| github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= | ||||
| github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||
| github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= | ||||
|  | @ -848,14 +847,13 @@ github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxz | |||
| github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||||
| github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk= | ||||
| github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= | ||||
| github.com/mholt/acmez v1.0.1 h1:J7uquHOKEmo71UDnVApy1sSLA0oF/r+NtVrNzMKKA9I= | ||||
| github.com/mholt/acmez v1.0.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= | ||||
| github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= | ||||
| github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= | ||||
| github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc= | ||||
| github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= | ||||
| github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= | ||||
| github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= | ||||
| github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= | ||||
| github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= | ||||
| github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= | ||||
|  |  | |||
|  | @ -10,9 +10,9 @@ | |||
| </p> | ||||
| 
 | ||||
| 
 | ||||
| Caddy's automagic TLS features—now for your own Go programs—in one powerful and easy-to-use library! | ||||
| Caddy's [automagic TLS features](https://caddyserver.com/docs/automatic-https)—now for your own Go programs—in one powerful and easy-to-use library! | ||||
| 
 | ||||
| CertMagic is the most mature, robust, and capable ACME client integration for Go... and perhaps ever. | ||||
| CertMagic is the most mature, robust, and powerful ACME client integration for Go... and perhaps ever. | ||||
| 
 | ||||
| With CertMagic, you can add one line to your Go application to serve securely over TLS, without ever having to touch certificates. | ||||
| 
 | ||||
|  | @ -40,11 +40,6 @@ Compared to other ACME client libraries for Go, only CertMagic supports the full | |||
| CertMagic - Automatic HTTPS using Let's Encrypt | ||||
| =============================================== | ||||
| 
 | ||||
| **Sponsored by Relica - Cross-platform local and cloud file backup:** | ||||
| 
 | ||||
| <a href="https://relicabackup.com"><img src="https://caddyserver.com/resources/images/sponsors/relica.png" width="220" alt="Relica - Cross-platform file backup to the cloud, local disks, or other computers"></a> | ||||
| 
 | ||||
| 
 | ||||
| ## Menu | ||||
| 
 | ||||
| - [Features](#features) | ||||
|  | @ -116,6 +111,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt | |||
| 
 | ||||
| ## Requirements | ||||
| 
 | ||||
| 0. ACME server (can be a publicly-trusted CA, or your own) | ||||
| 1. Public DNS name(s) you control | ||||
| 2. Server reachable from public Internet | ||||
| 	- Or use the DNS challenge to waive this requirement | ||||
|  | @ -270,7 +266,7 @@ myACME := certmagic.NewACMEManager(magic, certmagic.ACMEManager{ | |||
| magic.Issuer = myACME | ||||
| 
 | ||||
| // this obtains certificates or renews them if necessary | ||||
| err := magic.ManageSync([]string{"example.com", "sub.example.com"}) | ||||
| err := magic.ManageSync(context.TODO(), []string{"example.com", "sub.example.com"}) | ||||
| if err != nil { | ||||
| 	return err | ||||
| } | ||||
|  | @ -279,6 +275,10 @@ if err != nil { | |||
| // you can get a TLS config to use in a TLS listener! | ||||
| tlsConfig := magic.TLSConfig() | ||||
| 
 | ||||
| // be sure to customize NextProtos if serving a specific | ||||
| // application protocol after the TLS handshake, for example: | ||||
| tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...) | ||||
| 
 | ||||
| //// OR //// | ||||
| 
 | ||||
| // if you already have a TLS config you don't want to replace, | ||||
|  |  | |||
|  | @ -405,10 +405,6 @@ var ( | |||
| 	discoveredEmailMu sync.Mutex | ||||
| ) | ||||
| 
 | ||||
| // agreementTestURL is set during tests to skip requiring
 | ||||
| // setting up an entire ACME CA endpoint.
 | ||||
| var agreementTestURL string | ||||
| 
 | ||||
| // stdin is used to read the user's input if prompted;
 | ||||
| // this is changed by tests during tests.
 | ||||
| var stdin = io.ReadWriter(os.Stdin) | ||||
|  |  | |||
|  | @ -370,11 +370,11 @@ var ( | |||
| 
 | ||||
| 	// RateLimitEvents is how many new events can be allowed
 | ||||
| 	// in RateLimitEventsWindow.
 | ||||
| 	RateLimitEvents = 20 | ||||
| 	RateLimitEvents = 10 | ||||
| 
 | ||||
| 	// RateLimitEventsWindow is the size of the sliding
 | ||||
| 	// window that throttles events.
 | ||||
| 	RateLimitEventsWindow = 1 * time.Minute | ||||
| 	RateLimitEventsWindow = 10 * time.Second | ||||
| ) | ||||
| 
 | ||||
| // Some default values passed down to the underlying ACME client.
 | ||||
|  |  | |||
|  | @ -194,6 +194,14 @@ func (certCache *Cache) cacheCertificate(cert Certificate) { | |||
| func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) { | ||||
| 	// no-op if this certificate already exists in the cache
 | ||||
| 	if _, ok := certCache.cache[cert.hash]; ok { | ||||
| 		if certCache.logger != nil { | ||||
| 			certCache.logger.Debug("certificate already cached", | ||||
| 				zap.Strings("subjects", cert.Names), | ||||
| 				zap.Time("expiration", cert.Leaf.NotAfter), | ||||
| 				zap.Bool("managed", cert.managed), | ||||
| 				zap.String("issuer_key", cert.issuerKey), | ||||
| 				zap.String("hash", cert.hash)) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  | @ -209,6 +217,13 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) { | |||
| 		i := 0 | ||||
| 		for _, randomCert := range certCache.cache { | ||||
| 			if i == rnd { | ||||
| 				if certCache.logger != nil { | ||||
| 					certCache.logger.Debug("cache full; evicting random certificate", | ||||
| 						zap.Strings("removing_subjects", randomCert.Names), | ||||
| 						zap.String("removing_hash", randomCert.hash), | ||||
| 						zap.Strings("inserting_subjects", cert.Names), | ||||
| 						zap.String("inserting_hash", cert.hash)) | ||||
| 				} | ||||
| 				certCache.removeCertificate(randomCert) | ||||
| 				break | ||||
| 			} | ||||
|  | @ -223,6 +238,17 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) { | |||
| 	for _, name := range cert.Names { | ||||
| 		certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.hash) | ||||
| 	} | ||||
| 
 | ||||
| 	if certCache.logger != nil { | ||||
| 		certCache.logger.Debug("added certificate to cache", | ||||
| 			zap.Strings("subjects", cert.Names), | ||||
| 			zap.Time("expiration", cert.Leaf.NotAfter), | ||||
| 			zap.Bool("managed", cert.managed), | ||||
| 			zap.String("issuer_key", cert.issuerKey), | ||||
| 			zap.String("hash", cert.hash), | ||||
| 			zap.Int("cache_size", len(certCache.cache)), | ||||
| 			zap.Int("cache_capacity", certCache.options.Capacity)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // removeCertificate removes cert from the cache.
 | ||||
|  | @ -233,9 +259,10 @@ func (certCache *Cache) removeCertificate(cert Certificate) { | |||
| 	// delete all mentions of this cert from the name index
 | ||||
| 	for _, name := range cert.Names { | ||||
| 		keyList := certCache.cacheIndex[name] | ||||
| 		for i, cacheKey := range keyList { | ||||
| 			if cacheKey == cert.hash { | ||||
| 		for i := 0; i < len(keyList); i++ { | ||||
| 			if keyList[i] == cert.hash { | ||||
| 				keyList = append(keyList[:i], keyList[i+1:]...) | ||||
| 				i-- | ||||
| 			} | ||||
| 		} | ||||
| 		if len(keyList) == 0 { | ||||
|  | @ -247,6 +274,17 @@ func (certCache *Cache) removeCertificate(cert Certificate) { | |||
| 
 | ||||
| 	// delete the actual cert from the cache
 | ||||
| 	delete(certCache.cache, cert.hash) | ||||
| 
 | ||||
| 	if certCache.logger != nil { | ||||
| 		certCache.logger.Debug("removed certificate from cache", | ||||
| 			zap.Strings("subjects", cert.Names), | ||||
| 			zap.Time("expiration", cert.Leaf.NotAfter), | ||||
| 			zap.Bool("managed", cert.managed), | ||||
| 			zap.String("issuer_key", cert.issuerKey), | ||||
| 			zap.String("hash", cert.hash), | ||||
| 			zap.Int("cache_size", len(certCache.cache)), | ||||
| 			zap.Int("cache_capacity", certCache.options.Capacity)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // replaceCertificate atomically replaces oldCert with newCert in
 | ||||
|  | @ -260,7 +298,7 @@ func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) { | |||
| 	certCache.mu.Unlock() | ||||
| 	if certCache.logger != nil { | ||||
| 		certCache.logger.Info("replaced certificate in cache", | ||||
| 			zap.Strings("identifiers", newCert.Names), | ||||
| 			zap.Strings("subjects", newCert.Names), | ||||
| 			zap.Time("new_expiration", newCert.Leaf.NotAfter)) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -283,8 +283,6 @@ func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error { | |||
| 		return fmt.Errorf("certificate has no names") | ||||
| 	} | ||||
| 
 | ||||
| 	// save the hash of this certificate (chain) and
 | ||||
| 	// expiration date, for necessity and efficiency
 | ||||
| 	cert.hash = hashCertificateChain(cert.Certificate.Certificate) | ||||
| 
 | ||||
| 	return nil | ||||
|  |  | |||
|  | @ -73,7 +73,7 @@ func HTTPS(domainNames []string, mux http.Handler) error { | |||
| 	DefaultACME.Agreed = true | ||||
| 	cfg := NewDefault() | ||||
| 
 | ||||
| 	err := cfg.ManageSync(domainNames) | ||||
| 	err := cfg.ManageSync(context.Background(), domainNames) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -178,7 +178,7 @@ func TLS(domainNames []string) (*tls.Config, error) { | |||
| 	DefaultACME.Agreed = true | ||||
| 	DefaultACME.DisableHTTPChallenge = true | ||||
| 	cfg := NewDefault() | ||||
| 	return cfg.TLSConfig(), cfg.ManageSync(domainNames) | ||||
| 	return cfg.TLSConfig(), cfg.ManageSync(context.Background(), domainNames) | ||||
| } | ||||
| 
 | ||||
| // Listen manages certificates for domainName and returns a
 | ||||
|  | @ -195,7 +195,7 @@ func Listen(domainNames []string) (net.Listener, error) { | |||
| 	DefaultACME.Agreed = true | ||||
| 	DefaultACME.DisableHTTPChallenge = true | ||||
| 	cfg := NewDefault() | ||||
| 	err := cfg.ManageSync(domainNames) | ||||
| 	err := cfg.ManageSync(context.Background(), domainNames) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | @ -223,9 +223,9 @@ func Listen(domainNames []string) (net.Listener, error) { | |||
| //
 | ||||
| // Calling this function signifies your acceptance to
 | ||||
| // the CA's Subscriber Agreement and/or Terms of Service.
 | ||||
| func ManageSync(domainNames []string) error { | ||||
| func ManageSync(ctx context.Context, domainNames []string) error { | ||||
| 	DefaultACME.Agreed = true | ||||
| 	return NewDefault().ManageSync(domainNames) | ||||
| 	return NewDefault().ManageSync(ctx, domainNames) | ||||
| } | ||||
| 
 | ||||
| // ManageAsync is the same as ManageSync, except that
 | ||||
|  |  | |||
|  | @ -247,8 +247,28 @@ func newWithCache(certCache *Cache, cfg Config) *Config { | |||
| // of the given domainNames. This behavior is recommended for
 | ||||
| // interactive use (i.e. when an administrator is present) so
 | ||||
| // that errors can be reported and fixed immediately.
 | ||||
| func (cfg *Config) ManageSync(domainNames []string) error { | ||||
| 	return cfg.manageAll(context.Background(), domainNames, false) | ||||
| func (cfg *Config) ManageSync(ctx context.Context, domainNames []string) error { | ||||
| 	return cfg.manageAll(ctx, domainNames, false) | ||||
| } | ||||
| 
 | ||||
| // ManageAsync is the same as ManageSync, except that ACME
 | ||||
| // operations are performed asynchronously (in the background).
 | ||||
| // This method returns before certificates are ready. It is
 | ||||
| // crucial that the administrator monitors the logs and is
 | ||||
| // notified of any errors so that corrective action can be
 | ||||
| // taken as soon as possible. Any errors returned from this
 | ||||
| // method occurred before ACME transactions started.
 | ||||
| //
 | ||||
| // As long as logs are monitored, this method is typically
 | ||||
| // recommended for non-interactive environments.
 | ||||
| //
 | ||||
| // If there are failures loading, obtaining, or renewing a
 | ||||
| // certificate, it will be retried with exponential backoff
 | ||||
| // for up to about 30 days, with a maximum interval of about
 | ||||
| // 24 hours. Cancelling ctx will cancel retries and shut down
 | ||||
| // any goroutines spawned by ManageAsync.
 | ||||
| func (cfg *Config) ManageAsync(ctx context.Context, domainNames []string) error { | ||||
| 	return cfg.manageAll(ctx, domainNames, true) | ||||
| } | ||||
| 
 | ||||
| // ClientCredentials returns a list of TLS client certificate chains for the given identifiers.
 | ||||
|  | @ -274,26 +294,6 @@ func (cfg *Config) ClientCredentials(ctx context.Context, identifiers []string) | |||
| 	return chains, nil | ||||
| } | ||||
| 
 | ||||
| // ManageAsync is the same as ManageSync, except that ACME
 | ||||
| // operations are performed asynchronously (in the background).
 | ||||
| // This method returns before certificates are ready. It is
 | ||||
| // crucial that the administrator monitors the logs and is
 | ||||
| // notified of any errors so that corrective action can be
 | ||||
| // taken as soon as possible. Any errors returned from this
 | ||||
| // method occurred before ACME transactions started.
 | ||||
| //
 | ||||
| // As long as logs are monitored, this method is typically
 | ||||
| // recommended for non-interactive environments.
 | ||||
| //
 | ||||
| // If there are failures loading, obtaining, or renewing a
 | ||||
| // certificate, it will be retried with exponential backoff
 | ||||
| // for up to about 30 days, with a maximum interval of about
 | ||||
| // 24 hours. Cancelling ctx will cancel retries and shut down
 | ||||
| // any goroutines spawned by ManageAsync.
 | ||||
| func (cfg *Config) ManageAsync(ctx context.Context, domainNames []string) error { | ||||
| 	return cfg.manageAll(ctx, domainNames, true) | ||||
| } | ||||
| 
 | ||||
| func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bool) error { | ||||
| 	if ctx == nil { | ||||
| 		ctx = context.Background() | ||||
|  | @ -863,20 +863,28 @@ func (cfg *Config) RevokeCert(ctx context.Context, domain string, reason int, in | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // 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.
 | ||||
| // TLSConfig is an opinionated method that returns a recommended, modern
 | ||||
| // TLS configuration that can be used to configure TLS listeners. Aside
 | ||||
| // from safe, modern defaults, this method sets two critical fields on the
 | ||||
| // TLS config which are required to enable automatic certificate
 | ||||
| // management: GetCertificate and NextProtos.
 | ||||
| //
 | ||||
| // Unlike the package TLS() function, this method does
 | ||||
| // not, by itself, enable certificate management for
 | ||||
| // any domain names.
 | ||||
| // The GetCertificate field is necessary to get certificates from memory
 | ||||
| // or storage, including both manual and automated certificates. You
 | ||||
| // should only change this field if you know what you are doing.
 | ||||
| //
 | ||||
| // 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.
 | ||||
| // The NextProtos field is pre-populated with a special value to enable
 | ||||
| // solving the TLS-ALPN ACME challenge. Because this method does not
 | ||||
| // assume any particular protocols after the TLS handshake is completed,
 | ||||
| // you will likely need to customize the NextProtos field by prepending
 | ||||
| // your application's protocols to the slice. For example, to serve
 | ||||
| // HTTP, you will need to prepend "h2" and "http/1.1" values. Be sure to
 | ||||
| // leave the acmez.ACMETLS1Protocol value intact, however, or TLS-ALPN
 | ||||
| // challenges will fail (which may be acceptable if you are not using
 | ||||
| // ACME, or specifically, the TLS-ALPN challenge).
 | ||||
| //
 | ||||
| // Unlike the package TLS() function, this method does not, by itself,
 | ||||
| // enable certificate management for any domain names.
 | ||||
| func (cfg *Config) TLSConfig() *tls.Config { | ||||
| 	return &tls.Config{ | ||||
| 		// these two fields necessary for TLS-ALPN challenge
 | ||||
|  |  | |||
|  | @ -72,6 +72,10 @@ func encodePrivateKey(key crypto.PrivateKey) ([]byte, error) { | |||
| func decodePrivateKey(keyPEMBytes []byte) (crypto.Signer, error) { | ||||
| 	keyBlockDER, _ := pem.Decode(keyPEMBytes) | ||||
| 
 | ||||
| 	if keyBlockDER == nil { | ||||
| 		return nil, fmt.Errorf("failed to decode PEM block containing private key") | ||||
| 	} | ||||
| 
 | ||||
| 	if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") { | ||||
| 		return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type) | ||||
| 	} | ||||
|  | @ -142,14 +146,14 @@ func (cfg *Config) saveCertResource(issuer Issuer, cert CertificateResource) err | |||
| 	certKey := cert.NamesKey() | ||||
| 
 | ||||
| 	all := []keyValue{ | ||||
| 		{ | ||||
| 			key:   StorageKeys.SiteCert(issuerKey, certKey), | ||||
| 			value: cert.CertificatePEM, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:   StorageKeys.SitePrivateKey(issuerKey, certKey), | ||||
| 			value: cert.PrivateKeyPEM, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:   StorageKeys.SiteCert(issuerKey, certKey), | ||||
| 			value: cert.CertificatePEM, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:   StorageKeys.SiteMeta(issuerKey, certKey), | ||||
| 			value: metaBytes, | ||||
|  |  | |||
|  | @ -3,10 +3,10 @@ module github.com/caddyserver/certmagic | |||
| go 1.14 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/klauspost/cpuid/v2 v2.0.6 | ||||
| 	github.com/klauspost/cpuid/v2 v2.0.9 | ||||
| 	github.com/libdns/libdns v0.2.1 | ||||
| 	github.com/mholt/acmez v0.1.3 | ||||
| 	github.com/miekg/dns v1.1.42 | ||||
| 	github.com/mholt/acmez v1.0.1 | ||||
| 	github.com/miekg/dns v1.1.43 | ||||
| 	go.uber.org/zap v1.17.0 | ||||
| 	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a | ||||
| 	golang.org/x/net v0.0.0-20210525063256-abc453219eb5 | ||||
|  |  | |||
|  | @ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c | |||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | ||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||
| github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI= | ||||
| github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||
| github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= | ||||
| github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||
| github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
|  | @ -13,10 +13,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | |||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= | ||||
| github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= | ||||
| github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk= | ||||
| github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= | ||||
| github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY= | ||||
| github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= | ||||
| github.com/mholt/acmez v1.0.1 h1:J7uquHOKEmo71UDnVApy1sSLA0oF/r+NtVrNzMKKA9I= | ||||
| github.com/mholt/acmez v1.0.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= | ||||
| github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= | ||||
| github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= | ||||
| github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
|  |  | |||
|  | @ -125,23 +125,6 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate, | |||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// check the certCache directly to see if the SNI name is
 | ||||
| 		// already the key of the certificate it wants; this implies
 | ||||
| 		// that the SNI can contain the hash of a specific cert
 | ||||
| 		// (chain) it wants and we will still be able to serve it up
 | ||||
| 		// (this behavior, by the way, could be controversial as to
 | ||||
| 		// whether it complies with RFC 6066 about SNI, but I think
 | ||||
| 		// it does, soooo...)
 | ||||
| 		// (this is how we solved the former ACME TLS-SNI challenge)
 | ||||
| 		cfg.certCache.mu.RLock() | ||||
| 		directCert, ok := cfg.certCache.cache[name] | ||||
| 		cfg.certCache.mu.RUnlock() | ||||
| 		if ok { | ||||
| 			cert = directCert | ||||
| 			matched = true | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// otherwise, we're bingo on ammo; see issues
 | ||||
|  | @ -162,18 +145,48 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate, | |||
| // then all certificates in the cache will be passed in
 | ||||
| // for the cfg.CertSelection to make the final decision.
 | ||||
| func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certificate, bool) { | ||||
| 	logger := loggerNamed(cfg.Logger, "handshake") | ||||
| 	choices := cfg.certCache.getAllMatchingCerts(name) | ||||
| 	if len(choices) == 0 { | ||||
| 		if cfg.CertSelection == nil { | ||||
| 			if logger != nil { | ||||
| 				logger.Debug("no matching certificates and no custom selection logic", zap.String("identifier", name)) | ||||
| 			} | ||||
| 			return Certificate{}, false | ||||
| 		} | ||||
| 		if logger != nil { | ||||
| 			logger.Debug("no matching certificate; will choose from all certificates", zap.String("identifier", name)) | ||||
| 		} | ||||
| 		choices = cfg.certCache.getAllCerts() | ||||
| 	} | ||||
| 	if logger != nil { | ||||
| 		logger.Debug("choosing certificate", | ||||
| 			zap.String("identifier", name), | ||||
| 			zap.Int("num_choices", len(choices))) | ||||
| 	} | ||||
| 	if cfg.CertSelection == nil { | ||||
| 		cert, err := DefaultCertificateSelector(hello, choices) | ||||
| 		if logger != nil { | ||||
| 			logger.Debug("default certificate selection results", | ||||
| 				zap.Error(err), | ||||
| 				zap.String("identifier", name), | ||||
| 				zap.Strings("subjects", cert.Names), | ||||
| 				zap.Bool("managed", cert.managed), | ||||
| 				zap.String("issuer_key", cert.issuerKey), | ||||
| 				zap.String("hash", cert.hash)) | ||||
| 		} | ||||
| 		return cert, err == nil | ||||
| 	} | ||||
| 	cert, err := cfg.CertSelection.SelectCertificate(hello, choices) | ||||
| 	if logger != nil { | ||||
| 		logger.Debug("custom certificate selection results", | ||||
| 			zap.Error(err), | ||||
| 			zap.String("identifier", name), | ||||
| 			zap.Strings("subjects", cert.Names), | ||||
| 			zap.Bool("managed", cert.managed), | ||||
| 			zap.String("issuer_key", cert.issuerKey), | ||||
| 			zap.String("hash", cert.hash)) | ||||
| 	} | ||||
| 	return cert, err == nil | ||||
| } | ||||
| 
 | ||||
|  | @ -213,28 +226,54 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat | |||
| //
 | ||||
| // This function is safe for concurrent use.
 | ||||
| func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) { | ||||
| 	log := loggerNamed(cfg.Logger, "on_demand") | ||||
| 	log := loggerNamed(cfg.Logger, "handshake") | ||||
| 
 | ||||
| 	// First check our in-memory cache to see if we've already loaded it
 | ||||
| 	cert, matched, defaulted := cfg.getCertificate(hello) | ||||
| 	if matched { | ||||
| 		if log != nil { | ||||
| 			log.Debug("matched certificate in cache", | ||||
| 				zap.Strings("subjects", cert.Names), | ||||
| 				zap.Bool("managed", cert.managed), | ||||
| 				zap.Time("expiration", cert.Leaf.NotAfter), | ||||
| 				zap.String("hash", cert.hash)) | ||||
| 		} | ||||
| 		if cert.managed && cfg.OnDemand != nil && obtainIfNecessary { | ||||
| 			// It's been reported before that if the machine goes to sleep (or
 | ||||
| 			// suspends the process) that certs which are already loaded into
 | ||||
| 			// memory won't get renewed in the background, so we need to check
 | ||||
| 			// expiry on each handshake too, sigh:
 | ||||
| 			// https://caddy.community/t/local-certificates-not-renewing-on-demand/9482
 | ||||
| 			return cfg.optionalMaintenance(log, cert, hello) | ||||
| 			return cfg.optionalMaintenance(loggerNamed(cfg.Logger, "on_demand"), cert, hello) | ||||
| 		} | ||||
| 		return cert, nil | ||||
| 	} | ||||
| 
 | ||||
| 	name := cfg.getNameFromClientHello(hello) | ||||
| 
 | ||||
| 	// If OnDemand is enabled, then we might be able to load or
 | ||||
| 	// obtain a needed certificate
 | ||||
| 	if cfg.OnDemand != nil && loadIfNecessary { | ||||
| 	// We might be able to load or obtain a needed certificate. Load from
 | ||||
| 	// storage if OnDemand is enabled, or if there is the possibility that
 | ||||
| 	// a statically-managed cert was evicted from a full cache.
 | ||||
| 	cfg.certCache.mu.RLock() | ||||
| 	cacheSize := len(cfg.certCache.cache) | ||||
| 	cfg.certCache.mu.RUnlock() | ||||
| 
 | ||||
| 	// A cert might have still been evicted from the cache even if the cache
 | ||||
| 	// is no longer completely full; this happens if the newly-loaded cert is
 | ||||
| 	// itself evicted (perhaps due to being expired or unmanaged at this point).
 | ||||
| 	// Hence, we use an "almost full" metric to allow for the cache to not be
 | ||||
| 	// perfectly full while still being able to load needed certs from storage.
 | ||||
| 	// See https://caddy.community/t/error-tls-alert-internal-error-592-again/13272
 | ||||
| 	// and caddyserver/caddy#4320.
 | ||||
| 	cacheAlmostFull := float64(cacheSize) >= (float64(cfg.certCache.options.Capacity) * .9) | ||||
| 	loadDynamically := cfg.OnDemand != nil || cacheAlmostFull | ||||
| 
 | ||||
| 	if loadDynamically && loadIfNecessary { | ||||
| 		// Then check to see if we have one on disk
 | ||||
| 		// TODO: As suggested here, https://caddy.community/t/error-tls-alert-internal-error-592-again/13272/30?u=matt,
 | ||||
| 		// it might be a good idea to check with the DecisionFunc or allowlist first before even loading the certificate
 | ||||
| 		// from storage, since if we can't renew it, why should we even try serving it (it will just get evicted after
 | ||||
| 		// we get a return value of false anyway)?
 | ||||
| 		loadedCert, err := cfg.CacheManagedCertificate(name) | ||||
| 		if _, ok := err.(ErrNotExist); ok { | ||||
| 			// If no exact match, try a wildcard variant, which is something we can still use
 | ||||
|  | @ -243,6 +282,13 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece | |||
| 			loadedCert, err = cfg.CacheManagedCertificate(strings.Join(labels, ".")) | ||||
| 		} | ||||
| 		if err == nil { | ||||
| 			if log != nil { | ||||
| 				log.Debug("loaded certificate from storage", | ||||
| 					zap.Strings("subjects", loadedCert.Names), | ||||
| 					zap.Bool("managed", loadedCert.managed), | ||||
| 					zap.Time("expiration", loadedCert.Leaf.NotAfter), | ||||
| 					zap.String("hash", loadedCert.hash)) | ||||
| 			} | ||||
| 			loadedCert, err = cfg.handshakeMaintenance(hello, loadedCert) | ||||
| 			if err != nil { | ||||
| 				if log != nil { | ||||
|  | @ -253,7 +299,7 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece | |||
| 			} | ||||
| 			return loadedCert, nil | ||||
| 		} | ||||
| 		if obtainIfNecessary { | ||||
| 		if cfg.OnDemand != nil && obtainIfNecessary { | ||||
| 			// By this point, we need to ask the CA for a certificate
 | ||||
| 			return cfg.obtainOnDemandCertificate(hello) | ||||
| 		} | ||||
|  | @ -261,9 +307,28 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece | |||
| 
 | ||||
| 	// Fall back to the default certificate if there is one
 | ||||
| 	if defaulted { | ||||
| 		if log != nil { | ||||
| 			log.Debug("fell back to default certificate", | ||||
| 				zap.Strings("subjects", cert.Names), | ||||
| 				zap.Bool("managed", cert.managed), | ||||
| 				zap.Time("expiration", cert.Leaf.NotAfter), | ||||
| 				zap.String("hash", cert.hash)) | ||||
| 		} | ||||
| 		return cert, nil | ||||
| 	} | ||||
| 
 | ||||
| 	if log != nil { | ||||
| 		log.Debug("no certificate matching TLS ClientHello", | ||||
| 			zap.String("server_name", hello.ServerName), | ||||
| 			zap.String("remote", hello.Conn.RemoteAddr().String()), | ||||
| 			zap.String("identifier", name), | ||||
| 			zap.Uint16s("cipher_suites", hello.CipherSuites), | ||||
| 			zap.Float64("cert_cache_fill", float64(cacheSize)/float64(cfg.certCache.options.Capacity)), // may be approximate! because we are not within the lock
 | ||||
| 			zap.Bool("load_if_necessary", loadIfNecessary), | ||||
| 			zap.Bool("obtain_if_necessary", obtainIfNecessary), | ||||
| 			zap.Bool("on_demand", cfg.OnDemand != nil)) | ||||
| 	} | ||||
| 
 | ||||
| 	return Certificate{}, fmt.Errorf("no certificate available for '%s'", name) | ||||
| } | ||||
| 
 | ||||
|  | @ -371,7 +436,8 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif | |||
| 	} | ||||
| 
 | ||||
| 	// TODO: use a proper context; we use one with timeout because retries are enabled because interactive is false
 | ||||
| 	ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second) | ||||
| 	// (timeout duration is based on https://caddy.community/t/zerossl-dns-challenge-failing-often-route53-plugin/13822/24?u=matt)
 | ||||
| 	ctx, cancel := context.WithTimeout(context.TODO(), 180*time.Second) | ||||
| 	defer cancel() | ||||
| 
 | ||||
| 	// Obtain the certificate
 | ||||
|  | @ -459,7 +525,7 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe | |||
| 			// renewing it, so we might as well serve what we have without blocking
 | ||||
| 			if log != nil { | ||||
| 				log.Debug("certificate expires soon but is already being renewed; serving current certificate", | ||||
| 					zap.Strings("identifiers", currentCert.Names), | ||||
| 					zap.Strings("subjects", currentCert.Names), | ||||
| 					zap.Duration("remaining", timeLeft)) | ||||
| 			} | ||||
| 			return currentCert, nil | ||||
|  | @ -470,7 +536,7 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe | |||
| 
 | ||||
| 		if log != nil { | ||||
| 			log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete", | ||||
| 				zap.Strings("identifiers", currentCert.Names), | ||||
| 				zap.Strings("subjects", currentCert.Names), | ||||
| 				zap.Time("expired", currentCert.Leaf.NotAfter)) | ||||
| 		} | ||||
| 
 | ||||
|  | @ -501,7 +567,7 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe | |||
| 	if log != nil { | ||||
| 		log.Info("attempting certificate renewal", | ||||
| 			zap.String("server_name", name), | ||||
| 			zap.Strings("identifiers", currentCert.Names), | ||||
| 			zap.Strings("subjects", currentCert.Names), | ||||
| 			zap.Time("expiration", currentCert.Leaf.NotAfter), | ||||
| 			zap.Duration("remaining", timeLeft)) | ||||
| 	} | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ import ( | |||
| 	"github.com/libdns/libdns" | ||||
| 	"github.com/mholt/acmez" | ||||
| 	"github.com/mholt/acmez/acme" | ||||
| 	"github.com/miekg/dns" | ||||
| ) | ||||
| 
 | ||||
| // httpSolver solves the HTTP challenge. It must be
 | ||||
|  | @ -131,10 +132,12 @@ func (s *tlsALPNSolver) Present(ctx context.Context, chal acme.Challenge) error | |||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	key := challengeKey(chal) | ||||
| 	activeChallengesMu.Lock() | ||||
| 	chalData := activeChallenges[chal.Identifier.Value] | ||||
| 	chalData := activeChallenges[key] | ||||
| 	chalData.data = cert | ||||
| 	activeChallenges[chal.Identifier.Value] = chalData | ||||
| 	activeChallenges[key] = chalData | ||||
| 	activeChallengesMu.Unlock() | ||||
| 
 | ||||
| 	// the rest of this function increments the
 | ||||
|  | @ -215,10 +218,6 @@ func (*tlsALPNSolver) handleConn(conn net.Conn) { | |||
| // CleanUp removes the challenge certificate from the cache, and if
 | ||||
| // it is the last one to finish, stops the TLS server.
 | ||||
| func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error { | ||||
| 	s.config.certCache.mu.Lock() | ||||
| 	delete(s.config.certCache.cache, tlsALPNCertKeyName(chal.Identifier.Value)) | ||||
| 	s.config.certCache.mu.Unlock() | ||||
| 
 | ||||
| 	solversMu.Lock() | ||||
| 	defer solversMu.Unlock() | ||||
| 	si := getSolverInfo(s.address) | ||||
|  | @ -236,14 +235,6 @@ func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // tlsALPNCertKeyName returns the key to use when caching a cert
 | ||||
| // for use with the TLS-ALPN ACME challenge. It is simply to help
 | ||||
| // avoid conflicts (although at time of writing, there shouldn't
 | ||||
| // be, since the cert cache is keyed by hash of certificate chain).
 | ||||
| func tlsALPNCertKeyName(sniName string) string { | ||||
| 	return sniName + ":acme-tls-alpn" | ||||
| } | ||||
| 
 | ||||
| // DNS01Solver is a type that makes libdns providers usable
 | ||||
| // as ACME dns-01 challenge solvers.
 | ||||
| // See https://github.com/libdns/libdns
 | ||||
|  | @ -478,7 +469,7 @@ func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) e | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err = dhs.storage.Store(dhs.challengeTokensKey(chal.Identifier.Value), infoBytes) | ||||
| 	err = dhs.storage.Store(dhs.challengeTokensKey(challengeKey(chal)), infoBytes) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -501,7 +492,7 @@ func (dhs distributedSolver) Wait(ctx context.Context, challenge acme.Challenge) | |||
| // CleanUp invokes the underlying solver's CleanUp method
 | ||||
| // and also cleans up any assets saved to storage.
 | ||||
| func (dhs distributedSolver) CleanUp(ctx context.Context, chal acme.Challenge) error { | ||||
| 	err := dhs.storage.Delete(dhs.challengeTokensKey(chal.Identifier.Value)) | ||||
| 	err := dhs.storage.Delete(dhs.challengeTokensKey(challengeKey(chal))) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -648,6 +639,18 @@ type Challenge struct { | |||
| 	data interface{} | ||||
| } | ||||
| 
 | ||||
| // challengeKey returns the map key for a given challenge; it is the identifier
 | ||||
| // unless it is an IP address using the TLS-ALPN challenge.
 | ||||
| func challengeKey(chal acme.Challenge) string { | ||||
| 	if chal.Type == acme.ChallengeTypeTLSALPN01 && chal.Identifier.Type == "ip" { | ||||
| 		reversed, err := dns.ReverseAddr(chal.Identifier.Value) | ||||
| 		if err == nil { | ||||
| 			return reversed[:len(reversed)-1] // strip off '.'
 | ||||
| 		} | ||||
| 	} | ||||
| 	return chal.Identifier.Value | ||||
| } | ||||
| 
 | ||||
| // solverWrapper should be used to wrap all challenge solvers so that
 | ||||
| // we can add the challenge info to memory; this makes challenges globally
 | ||||
| // solvable by a single HTTP or TLS server even if multiple servers with
 | ||||
|  | @ -656,7 +659,7 @@ type solverWrapper struct{ acmez.Solver } | |||
| 
 | ||||
| func (sw solverWrapper) Present(ctx context.Context, chal acme.Challenge) error { | ||||
| 	activeChallengesMu.Lock() | ||||
| 	activeChallenges[chal.Identifier.Value] = Challenge{Challenge: chal} | ||||
| 	activeChallenges[challengeKey(chal)] = Challenge{Challenge: chal} | ||||
| 	activeChallengesMu.Unlock() | ||||
| 	return sw.Solver.Present(ctx, chal) | ||||
| } | ||||
|  | @ -670,7 +673,7 @@ func (sw solverWrapper) Wait(ctx context.Context, chal acme.Challenge) error { | |||
| 
 | ||||
| func (sw solverWrapper) CleanUp(ctx context.Context, chal acme.Challenge) error { | ||||
| 	activeChallengesMu.Lock() | ||||
| 	delete(activeChallenges, chal.Identifier.Value) | ||||
| 	delete(activeChallenges, challengeKey(chal)) | ||||
| 	activeChallengesMu.Unlock() | ||||
| 	return sw.Solver.CleanUp(ctx, chal) | ||||
| } | ||||
|  |  | |||
|  | @ -111,7 +111,7 @@ func (c *Client) GetCertificateChain(ctx context.Context, account Account, certU | |||
| 	// heuristics to decide which is optimal." §7.4.2
 | ||||
| 	alternates := extractLinks(resp, "alternate") | ||||
| 	for _, altURL := range alternates { | ||||
| 		resp, err = addChain(altURL) | ||||
| 		_, err = addChain(altURL) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("retrieving alternate certificate chain at %s: %w", altURL, err) | ||||
| 		} | ||||
|  |  | |||
|  | @ -117,8 +117,7 @@ func (c *Client) httpPostJWS(ctx context.Context, privateKey crypto.Signer, | |||
| 		break | ||||
| 	} | ||||
| 
 | ||||
| 	return resp, fmt.Errorf("request to %s failed after %d attempts: %w", | ||||
| 		endpoint, attempts, err) | ||||
| 	return resp, fmt.Errorf("attempt %d: %s: %w", attempts, endpoint, err) | ||||
| } | ||||
| 
 | ||||
| // httpReq robustly performs an HTTP request using the given method to the given endpoint, honoring
 | ||||
|  | @ -272,8 +271,8 @@ func (c *Client) doHTTPRequest(req *http.Request, buf *bytes.Buffer) (resp *http | |||
| 			zap.String("method", req.Method), | ||||
| 			zap.String("url", req.URL.String()), | ||||
| 			zap.Reflect("headers", req.Header), | ||||
| 			zap.Int("status_code", resp.StatusCode), | ||||
| 			zap.Reflect("response_headers", resp.Header)) | ||||
| 			zap.Reflect("response_headers", resp.Header), | ||||
| 			zap.Int("status_code", resp.StatusCode)) | ||||
| 	} | ||||
| 
 | ||||
| 	// "The server MUST include a Replay-Nonce header field
 | ||||
|  |  | |||
|  | @ -14,7 +14,11 @@ | |||
| 
 | ||||
| package acme | ||||
| 
 | ||||
| import "fmt" | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| // Problem carries the details of an error from HTTP APIs as
 | ||||
| // defined in RFC 7807: https://tools.ietf.org/html/rfc7807
 | ||||
|  | @ -77,6 +81,9 @@ func (p Problem) Error() string { | |||
| 	if len(p.Subproblems) > 0 { | ||||
| 		for _, v := range p.Subproblems { | ||||
| 			s += fmt.Sprintf(", problem %q: %s", v.Type, v.Detail) | ||||
| 			if v.Identifier.Type != "" || v.Identifier.Value != "" { | ||||
| 				s += fmt.Sprintf(" (%s_identifier=%s)", v.Identifier.Type, v.Identifier.Value) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if p.Instance != "" { | ||||
|  | @ -85,6 +92,17 @@ func (p Problem) Error() string { | |||
| 	return s | ||||
| } | ||||
| 
 | ||||
| // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface.
 | ||||
| // This allows problems to be serialized by the zap logger.
 | ||||
| func (p Problem) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||
| 	enc.AddString("type", p.Type) | ||||
| 	enc.AddString("title", p.Title) | ||||
| 	enc.AddString("detail", p.Detail) | ||||
| 	enc.AddString("instance", p.Instance) | ||||
| 	enc.AddArray("subproblems", loggableSubproblems(p.Subproblems)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Subproblem describes a more specific error in a problem according to
 | ||||
| // RFC 8555 §6.7.1: "An ACME problem document MAY contain the
 | ||||
| // 'subproblems' field, containing a JSON array of problem documents,
 | ||||
|  | @ -97,6 +115,26 @@ type Subproblem struct { | |||
| 	Identifier Identifier `json:"identifier,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface.
 | ||||
| // This allows subproblems to be serialized by the zap logger.
 | ||||
| func (sp Subproblem) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||
| 	enc.AddString("identifier_type", sp.Identifier.Type) | ||||
| 	enc.AddString("identifier", sp.Identifier.Value) | ||||
| 	enc.AddObject("subproblem", sp.Problem) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type loggableSubproblems []Subproblem | ||||
| 
 | ||||
| // MarshalLogArray satisfies the zapcore.ArrayMarshaler interface.
 | ||||
| // This allows a list of subproblems to be serialized by the zap logger.
 | ||||
| func (ls loggableSubproblems) MarshalLogArray(enc zapcore.ArrayEncoder) error { | ||||
| 	for _, sp := range ls { | ||||
| 		enc.AppendObject(sp) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Standard token values for the "type" field of problems, as defined
 | ||||
| // in RFC 8555 §6.7: https://tools.ietf.org/html/rfc8555#section-6.7
 | ||||
| //
 | ||||
|  |  | |||
|  | @ -134,16 +134,23 @@ func (c *Client) ObtainCertificateUsingCSR(ctx context.Context, account acme.Acc | |||
| 		// for some errors, we can retry with different challenge types
 | ||||
| 		var problem acme.Problem | ||||
| 		if errors.As(err, &problem) { | ||||
| 			authz := problem.Resource.(acme.Authorization) | ||||
| 			authz, haveAuthz := problem.Resource.(acme.Authorization) | ||||
| 			if c.Logger != nil { | ||||
| 				c.Logger.Error("validating authorization", | ||||
| 					zap.String("identifier", authz.IdentifierValue()), | ||||
| 					zap.Error(err), | ||||
| 				l := c.Logger | ||||
| 				if haveAuthz { | ||||
| 					l = l.With(zap.String("identifier", authz.IdentifierValue())) | ||||
| 				} | ||||
| 				l.Error("validating authorization", | ||||
| 					zap.Object("problem", problem), | ||||
| 					zap.String("order", order.Location), | ||||
| 					zap.Int("attempt", attempt), | ||||
| 					zap.Int("max_attempts", maxAttempts)) | ||||
| 			} | ||||
| 			err = fmt.Errorf("solving challenge: %s: %w", authz.IdentifierValue(), err) | ||||
| 			errStr := "solving challenge" | ||||
| 			if haveAuthz { | ||||
| 				errStr += ": " + authz.IdentifierValue() | ||||
| 			} | ||||
| 			err = fmt.Errorf("%s: %w", errStr, err) | ||||
| 			if errors.As(err, &retryableErr{}) { | ||||
| 				continue | ||||
| 			} | ||||
|  | @ -505,9 +512,7 @@ func (c *Client) pollAuthorization(ctx context.Context, account acme.Account, au | |||
| 				c.Logger.Error("challenge failed", | ||||
| 					zap.String("identifier", authz.IdentifierValue()), | ||||
| 					zap.String("challenge_type", authz.currentChallenge.Type), | ||||
| 					zap.Int("status_code", problem.Status), | ||||
| 					zap.String("problem_type", problem.Type), | ||||
| 					zap.String("error", problem.Detail)) | ||||
| 					zap.Object("problem", problem)) | ||||
| 			} | ||||
| 
 | ||||
| 			failedChallengeTypes.rememberFailedChallenge(authz) | ||||
|  |  | |||
|  | @ -197,7 +197,7 @@ github.com/boombuler/barcode/utils | |||
| # github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b | ||||
| ## explicit | ||||
| github.com/bradfitz/gomemcache/memcache | ||||
| # github.com/caddyserver/certmagic v0.14.1 | ||||
| # github.com/caddyserver/certmagic v0.15.2 | ||||
| ## explicit | ||||
| github.com/caddyserver/certmagic | ||||
| # github.com/cespare/xxhash/v2 v2.1.1 | ||||
|  | @ -606,7 +606,7 @@ github.com/mattn/go-runewidth | |||
| github.com/mattn/go-sqlite3 | ||||
| # github.com/matttproud/golang_protobuf_extensions v1.0.1 | ||||
| github.com/matttproud/golang_protobuf_extensions/pbutil | ||||
| # github.com/mholt/acmez v0.1.3 | ||||
| # github.com/mholt/acmez v1.0.1 | ||||
| github.com/mholt/acmez | ||||
| github.com/mholt/acmez/acme | ||||
| # github.com/mholt/archiver/v3 v3.5.0 | ||||
|  | @ -617,7 +617,6 @@ github.com/mholt/archiver/v3 | |||
| github.com/microcosm-cc/bluemonday | ||||
| github.com/microcosm-cc/bluemonday/css | ||||
| # github.com/miekg/dns v1.1.43 | ||||
| ## explicit | ||||
| github.com/miekg/dns | ||||
| # github.com/minio/md5-simd v1.1.2 | ||||
| ## explicit | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue