From e9c9a35a61385894754dd22bcf4e1bed4efe7744 Mon Sep 17 00:00:00 2001
From: Lunny Xiao
-
-
## 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,
diff --git a/vendor/github.com/caddyserver/certmagic/account.go b/vendor/github.com/caddyserver/certmagic/account.go
index 8633f92fcb..1cc6723429 100644
--- a/vendor/github.com/caddyserver/certmagic/account.go
+++ b/vendor/github.com/caddyserver/certmagic/account.go
@@ -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)
diff --git a/vendor/github.com/caddyserver/certmagic/acmeclient.go b/vendor/github.com/caddyserver/certmagic/acmeclient.go
index cc876b9252..a22dc19a7a 100644
--- a/vendor/github.com/caddyserver/certmagic/acmeclient.go
+++ b/vendor/github.com/caddyserver/certmagic/acmeclient.go
@@ -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.
diff --git a/vendor/github.com/caddyserver/certmagic/cache.go b/vendor/github.com/caddyserver/certmagic/cache.go
index 30bfc2a3f7..4e986303a9 100644
--- a/vendor/github.com/caddyserver/certmagic/cache.go
+++ b/vendor/github.com/caddyserver/certmagic/cache.go
@@ -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))
}
}
diff --git a/vendor/github.com/caddyserver/certmagic/certificates.go b/vendor/github.com/caddyserver/certmagic/certificates.go
index e6dab5e0ff..067bfc508b 100644
--- a/vendor/github.com/caddyserver/certmagic/certificates.go
+++ b/vendor/github.com/caddyserver/certmagic/certificates.go
@@ -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
diff --git a/vendor/github.com/caddyserver/certmagic/certmagic.go b/vendor/github.com/caddyserver/certmagic/certmagic.go
index bb33b90c55..88080a8036 100644
--- a/vendor/github.com/caddyserver/certmagic/certmagic.go
+++ b/vendor/github.com/caddyserver/certmagic/certmagic.go
@@ -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
diff --git a/vendor/github.com/caddyserver/certmagic/config.go b/vendor/github.com/caddyserver/certmagic/config.go
index d408418f65..3d031fa627 100644
--- a/vendor/github.com/caddyserver/certmagic/config.go
+++ b/vendor/github.com/caddyserver/certmagic/config.go
@@ -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
diff --git a/vendor/github.com/caddyserver/certmagic/crypto.go b/vendor/github.com/caddyserver/certmagic/crypto.go
index a705cdde2c..ee4671f5f5 100644
--- a/vendor/github.com/caddyserver/certmagic/crypto.go
+++ b/vendor/github.com/caddyserver/certmagic/crypto.go
@@ -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,
diff --git a/vendor/github.com/caddyserver/certmagic/go.mod b/vendor/github.com/caddyserver/certmagic/go.mod
index 42665bf1b3..a9267715b6 100644
--- a/vendor/github.com/caddyserver/certmagic/go.mod
+++ b/vendor/github.com/caddyserver/certmagic/go.mod
@@ -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
diff --git a/vendor/github.com/caddyserver/certmagic/go.sum b/vendor/github.com/caddyserver/certmagic/go.sum
index 1251ac84f0..d9512a8062 100644
--- a/vendor/github.com/caddyserver/certmagic/go.sum
+++ b/vendor/github.com/caddyserver/certmagic/go.sum
@@ -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=
diff --git a/vendor/github.com/caddyserver/certmagic/handshake.go b/vendor/github.com/caddyserver/certmagic/handshake.go
index 5506719c2b..e57a3d1683 100644
--- a/vendor/github.com/caddyserver/certmagic/handshake.go
+++ b/vendor/github.com/caddyserver/certmagic/handshake.go
@@ -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))
}
diff --git a/vendor/github.com/caddyserver/certmagic/solvers.go b/vendor/github.com/caddyserver/certmagic/solvers.go
index 8c71995814..8cdaeaf839 100644
--- a/vendor/github.com/caddyserver/certmagic/solvers.go
+++ b/vendor/github.com/caddyserver/certmagic/solvers.go
@@ -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)
}
diff --git a/vendor/github.com/mholt/acmez/acme/certificate.go b/vendor/github.com/mholt/acmez/acme/certificate.go
index a778280802..42bbba07e3 100644
--- a/vendor/github.com/mholt/acmez/acme/certificate.go
+++ b/vendor/github.com/mholt/acmez/acme/certificate.go
@@ -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)
}
diff --git a/vendor/github.com/mholt/acmez/acme/http.go b/vendor/github.com/mholt/acmez/acme/http.go
index a910d57e56..63688013e9 100644
--- a/vendor/github.com/mholt/acmez/acme/http.go
+++ b/vendor/github.com/mholt/acmez/acme/http.go
@@ -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
diff --git a/vendor/github.com/mholt/acmez/acme/problem.go b/vendor/github.com/mholt/acmez/acme/problem.go
index 98fdb00958..c5f1124649 100644
--- a/vendor/github.com/mholt/acmez/acme/problem.go
+++ b/vendor/github.com/mholt/acmez/acme/problem.go
@@ -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
//
diff --git a/vendor/github.com/mholt/acmez/client.go b/vendor/github.com/mholt/acmez/client.go
index a4d0446bb8..3add982cac 100644
--- a/vendor/github.com/mholt/acmez/client.go
+++ b/vendor/github.com/mholt/acmez/client.go
@@ -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)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index dfc355e904..23a59c18ac 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -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