Upgrade certmagic from v0.14.1 to v0.15.2 (#18138)

pull/17403/head^2
Lunny Xiao 3 years ago committed by GitHub
parent 385dc6a992
commit e9c9a35a61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      cmd/web_letsencrypt.go
  2. 3
      go.mod
  3. 10
      go.sum
  4. 16
      vendor/github.com/caddyserver/certmagic/README.md
  5. 4
      vendor/github.com/caddyserver/certmagic/account.go
  6. 4
      vendor/github.com/caddyserver/certmagic/acmeclient.go
  7. 44
      vendor/github.com/caddyserver/certmagic/cache.go
  8. 2
      vendor/github.com/caddyserver/certmagic/certificates.go
  9. 10
      vendor/github.com/caddyserver/certmagic/certmagic.go
  10. 76
      vendor/github.com/caddyserver/certmagic/config.go
  11. 12
      vendor/github.com/caddyserver/certmagic/crypto.go
  12. 6
      vendor/github.com/caddyserver/certmagic/go.mod
  13. 12
      vendor/github.com/caddyserver/certmagic/go.sum
  14. 120
      vendor/github.com/caddyserver/certmagic/handshake.go
  15. 39
      vendor/github.com/caddyserver/certmagic/solvers.go
  16. 2
      vendor/github.com/mholt/acmez/acme/certificate.go
  17. 7
      vendor/github.com/mholt/acmez/acme/http.go
  18. 40
      vendor/github.com/mholt/acmez/acme/problem.go
  19. 21
      vendor/github.com/mholt/acmez/client.go
  20. 5
      vendor/modules.txt

@ -9,6 +9,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "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} magic.Issuers = []certmagic.Issuer{myACME}
// this obtains certificates or renews them if necessary // this obtains certificates or renews them if necessary
err := magic.ManageSync([]string{domain}) err := magic.ManageSync(graceful.GetManager().HammerContext(), []string{domain})
if err != nil { if err != nil {
return err return err
} }

@ -22,7 +22,7 @@ require (
github.com/blevesearch/bleve/v2 v2.3.0 github.com/blevesearch/bleve/v2 v2.3.0
github.com/boombuler/barcode v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // 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/chi-middleware/proxy v1.1.1
github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect
github.com/couchbase/gomemcached v0.1.2 // indirect github.com/couchbase/gomemcached v0.1.2 // indirect
@ -78,7 +78,6 @@ require (
github.com/mattn/go-sqlite3 v1.14.8 github.com/mattn/go-sqlite3 v1.14.8
github.com/mholt/archiver/v3 v3.5.0 github.com/mholt/archiver/v3 v3.5.0
github.com/microcosm-cc/bluemonday v1.0.16 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/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.12 github.com/minio/minio-go/v7 v7.0.12
github.com/minio/sha256-simd v1.0.0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect

@ -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-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 h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= 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.15.2 h1:OMTakTsLM1ZfzMDjwvYprfUgFzpVPh3u87oxMPwmeBc=
github.com/caddyserver/certmagic v0.14.1/go.mod h1:oRQOZmUVKwlpgNidslysHt05osM9uMrJ4YMk+Ot4P4Q= 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/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/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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 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.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.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 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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= 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/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 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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 v1.0.1 h1:J7uquHOKEmo71UDnVApy1sSLA0oF/r+NtVrNzMKKA9I=
github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= 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 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= 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 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc=
github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= 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.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 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= 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= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=

@ -10,9 +10,9 @@
</p> </p>
Caddy's automagic TLS features&mdash;now for your own Go programs&mdash;in one powerful and easy-to-use library! Caddy's [automagic TLS features](https://caddyserver.com/docs/automatic-https)&mdash;now for your own Go programs&mdash;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. 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 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 ## Menu
- [Features](#features) - [Features](#features)
@ -116,6 +111,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
## Requirements ## Requirements
0. ACME server (can be a publicly-trusted CA, or your own)
1. Public DNS name(s) you control 1. Public DNS name(s) you control
2. Server reachable from public Internet 2. Server reachable from public Internet
- Or use the DNS challenge to waive this requirement - Or use the DNS challenge to waive this requirement
@ -270,7 +266,7 @@ myACME := certmagic.NewACMEManager(magic, certmagic.ACMEManager{
magic.Issuer = myACME magic.Issuer = myACME
// this obtains certificates or renews them if necessary // 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 { if err != nil {
return err return err
} }
@ -279,6 +275,10 @@ if err != nil {
// you can get a TLS config to use in a TLS listener! // you can get a TLS config to use in a TLS listener!
tlsConfig := magic.TLSConfig() 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 //// //// OR ////
// if you already have a TLS config you don't want to replace, // if you already have a TLS config you don't want to replace,

@ -405,10 +405,6 @@ var (
discoveredEmailMu sync.Mutex 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; // stdin is used to read the user's input if prompted;
// this is changed by tests during tests. // this is changed by tests during tests.
var stdin = io.ReadWriter(os.Stdin) var stdin = io.ReadWriter(os.Stdin)

@ -370,11 +370,11 @@ var (
// RateLimitEvents is how many new events can be allowed // RateLimitEvents is how many new events can be allowed
// in RateLimitEventsWindow. // in RateLimitEventsWindow.
RateLimitEvents = 20 RateLimitEvents = 10
// RateLimitEventsWindow is the size of the sliding // RateLimitEventsWindow is the size of the sliding
// window that throttles events. // window that throttles events.
RateLimitEventsWindow = 1 * time.Minute RateLimitEventsWindow = 10 * time.Second
) )
// Some default values passed down to the underlying ACME client. // 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) { func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
// no-op if this certificate already exists in the cache // no-op if this certificate already exists in the cache
if _, ok := certCache.cache[cert.hash]; ok { 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 return
} }
@ -209,6 +217,13 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
i := 0 i := 0
for _, randomCert := range certCache.cache { for _, randomCert := range certCache.cache {
if i == rnd { 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) certCache.removeCertificate(randomCert)
break break
} }
@ -223,6 +238,17 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
for _, name := range cert.Names { for _, name := range cert.Names {
certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.hash) 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. // 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 // delete all mentions of this cert from the name index
for _, name := range cert.Names { for _, name := range cert.Names {
keyList := certCache.cacheIndex[name] keyList := certCache.cacheIndex[name]
for i, cacheKey := range keyList { for i := 0; i < len(keyList); i++ {
if cacheKey == cert.hash { if keyList[i] == cert.hash {
keyList = append(keyList[:i], keyList[i+1:]...) keyList = append(keyList[:i], keyList[i+1:]...)
i--
} }
} }
if len(keyList) == 0 { if len(keyList) == 0 {
@ -247,6 +274,17 @@ func (certCache *Cache) removeCertificate(cert Certificate) {
// delete the actual cert from the cache // delete the actual cert from the cache
delete(certCache.cache, cert.hash) 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 // replaceCertificate atomically replaces oldCert with newCert in
@ -260,7 +298,7 @@ func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
certCache.mu.Unlock() certCache.mu.Unlock()
if certCache.logger != nil { if certCache.logger != nil {
certCache.logger.Info("replaced certificate in cache", certCache.logger.Info("replaced certificate in cache",
zap.Strings("identifiers", newCert.Names), zap.Strings("subjects", newCert.Names),
zap.Time("new_expiration", newCert.Leaf.NotAfter)) 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") 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) cert.hash = hashCertificateChain(cert.Certificate.Certificate)
return nil return nil

@ -73,7 +73,7 @@ func HTTPS(domainNames []string, mux http.Handler) error {
DefaultACME.Agreed = true DefaultACME.Agreed = true
cfg := NewDefault() cfg := NewDefault()
err := cfg.ManageSync(domainNames) err := cfg.ManageSync(context.Background(), domainNames)
if err != nil { if err != nil {
return err return err
} }
@ -178,7 +178,7 @@ func TLS(domainNames []string) (*tls.Config, error) {
DefaultACME.Agreed = true DefaultACME.Agreed = true
DefaultACME.DisableHTTPChallenge = true DefaultACME.DisableHTTPChallenge = true
cfg := NewDefault() cfg := NewDefault()
return cfg.TLSConfig(), cfg.ManageSync(domainNames) return cfg.TLSConfig(), cfg.ManageSync(context.Background(), domainNames)
} }
// Listen manages certificates for domainName and returns a // Listen manages certificates for domainName and returns a
@ -195,7 +195,7 @@ func Listen(domainNames []string) (net.Listener, error) {
DefaultACME.Agreed = true DefaultACME.Agreed = true
DefaultACME.DisableHTTPChallenge = true DefaultACME.DisableHTTPChallenge = true
cfg := NewDefault() cfg := NewDefault()
err := cfg.ManageSync(domainNames) err := cfg.ManageSync(context.Background(), domainNames)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -223,9 +223,9 @@ func Listen(domainNames []string) (net.Listener, error) {
// //
// Calling this function signifies your acceptance to // Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service. // 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 DefaultACME.Agreed = true
return NewDefault().ManageSync(domainNames) return NewDefault().ManageSync(ctx, domainNames)
} }
// ManageAsync is the same as ManageSync, except that // 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 // of the given domainNames. This behavior is recommended for
// interactive use (i.e. when an administrator is present) so // interactive use (i.e. when an administrator is present) so
// that errors can be reported and fixed immediately. // that errors can be reported and fixed immediately.
func (cfg *Config) ManageSync(domainNames []string) error { func (cfg *Config) ManageSync(ctx context.Context, domainNames []string) error {
return cfg.manageAll(context.Background(), domainNames, false) 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. // 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 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 { func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bool) error {
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
@ -863,20 +863,28 @@ func (cfg *Config) RevokeCert(ctx context.Context, domain string, reason int, in
return nil return nil
} }
// TLSConfig is an opinionated method that returns a // TLSConfig is an opinionated method that returns a recommended, modern
// recommended, modern TLS configuration that can be // TLS configuration that can be used to configure TLS listeners. Aside
// used to configure TLS listeners, which also supports // from safe, modern defaults, this method sets two critical fields on the
// the TLS-ALPN challenge and serves up certificates // TLS config which are required to enable automatic certificate
// managed by cfg. // management: GetCertificate and NextProtos.
//
// 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.
// //
// Unlike the package TLS() function, this method does // The NextProtos field is pre-populated with a special value to enable
// not, by itself, enable certificate management for // solving the TLS-ALPN ACME challenge. Because this method does not
// any domain names. // 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).
// //
// Feel free to further customize the returned tls.Config, // Unlike the package TLS() function, this method does not, by itself,
// but do not mess with the GetCertificate or NextProtos // enable certificate management for any domain names.
// fields unless you know what you're doing, as they're
// necessary to solve the TLS-ALPN challenge.
func (cfg *Config) TLSConfig() *tls.Config { func (cfg *Config) TLSConfig() *tls.Config {
return &tls.Config{ return &tls.Config{
// these two fields necessary for TLS-ALPN challenge // 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) { func decodePrivateKey(keyPEMBytes []byte) (crypto.Signer, error) {
keyBlockDER, _ := pem.Decode(keyPEMBytes) 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") { if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type) 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() certKey := cert.NamesKey()
all := []keyValue{ all := []keyValue{
{
key: StorageKeys.SiteCert(issuerKey, certKey),
value: cert.CertificatePEM,
},
{ {
key: StorageKeys.SitePrivateKey(issuerKey, certKey), key: StorageKeys.SitePrivateKey(issuerKey, certKey),
value: cert.PrivateKeyPEM, value: cert.PrivateKeyPEM,
}, },
{
key: StorageKeys.SiteCert(issuerKey, certKey),
value: cert.CertificatePEM,
},
{ {
key: StorageKeys.SiteMeta(issuerKey, certKey), key: StorageKeys.SiteMeta(issuerKey, certKey),
value: metaBytes, value: metaBytes,

@ -3,10 +3,10 @@ module github.com/caddyserver/certmagic
go 1.14 go 1.14
require ( 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/libdns/libdns v0.2.1
github.com/mholt/acmez v0.1.3 github.com/mholt/acmez v1.0.1
github.com/miekg/dns v1.1.42 github.com/miekg/dns v1.1.43
go.uber.org/zap v1.17.0 go.uber.org/zap v1.17.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 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/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/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/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.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/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 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk= github.com/mholt/acmez v1.0.1 h1:J7uquHOKEmo71UDnVApy1sSLA0oF/r+NtVrNzMKKA9I=
github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= github.com/mholt/acmez v1.0.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= 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 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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 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 // 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 // then all certificates in the cache will be passed in
// for the cfg.CertSelection to make the final decision. // for the cfg.CertSelection to make the final decision.
func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certificate, bool) { func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certificate, bool) {
logger := loggerNamed(cfg.Logger, "handshake")
choices := cfg.certCache.getAllMatchingCerts(name) choices := cfg.certCache.getAllMatchingCerts(name)
if len(choices) == 0 { if len(choices) == 0 {
if cfg.CertSelection == nil { if cfg.CertSelection == nil {
if logger != nil {
logger.Debug("no matching certificates and no custom selection logic", zap.String("identifier", name))
}
return Certificate{}, false return Certificate{}, false
} }
if logger != nil {
logger.Debug("no matching certificate; will choose from all certificates", zap.String("identifier", name))
}
choices = cfg.certCache.getAllCerts() 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 { if cfg.CertSelection == nil {
cert, err := DefaultCertificateSelector(hello, choices) 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 return cert, err == nil
} }
cert, err := cfg.CertSelection.SelectCertificate(hello, choices) 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 return cert, err == nil
} }
@ -213,28 +226,54 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
// //
// This function is safe for concurrent use. // This function is safe for concurrent use.
func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) { 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 // First check our in-memory cache to see if we've already loaded it
cert, matched, defaulted := cfg.getCertificate(hello) cert, matched, defaulted := cfg.getCertificate(hello)
if matched { 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 { if cert.managed && cfg.OnDemand != nil && obtainIfNecessary {
// It's been reported before that if the machine goes to sleep (or // It's been reported before that if the machine goes to sleep (or
// suspends the process) that certs which are already loaded into // suspends the process) that certs which are already loaded into
// memory won't get renewed in the background, so we need to check // memory won't get renewed in the background, so we need to check
// expiry on each handshake too, sigh: // expiry on each handshake too, sigh:
// https://caddy.community/t/local-certificates-not-renewing-on-demand/9482 // 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 return cert, nil
} }
name := cfg.getNameFromClientHello(hello) name := cfg.getNameFromClientHello(hello)
// If OnDemand is enabled, then we might be able to load or // We might be able to load or obtain a needed certificate. Load from
// obtain a needed certificate // storage if OnDemand is enabled, or if there is the possibility that
if cfg.OnDemand != nil && loadIfNecessary { // 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 // 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) loadedCert, err := cfg.CacheManagedCertificate(name)
if _, ok := err.(ErrNotExist); ok { if _, ok := err.(ErrNotExist); ok {
// If no exact match, try a wildcard variant, which is something we can still use // 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, ".")) loadedCert, err = cfg.CacheManagedCertificate(strings.Join(labels, "."))
} }
if err == nil { 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) loadedCert, err = cfg.handshakeMaintenance(hello, loadedCert)
if err != nil { if err != nil {
if log != nil { if log != nil {
@ -253,7 +299,7 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
} }
return loadedCert, nil return loadedCert, nil
} }
if obtainIfNecessary { if cfg.OnDemand != nil && obtainIfNecessary {
// By this point, we need to ask the CA for a certificate // By this point, we need to ask the CA for a certificate
return cfg.obtainOnDemandCertificate(hello) 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 // Fall back to the default certificate if there is one
if defaulted { 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 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) 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 // 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() defer cancel()
// Obtain the certificate // 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 // renewing it, so we might as well serve what we have without blocking
if log != nil { if log != nil {
log.Debug("certificate expires soon but is already being renewed; serving current certificate", 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)) zap.Duration("remaining", timeLeft))
} }
return currentCert, nil return currentCert, nil
@ -470,7 +536,7 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
if log != nil { if log != nil {
log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete", 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)) zap.Time("expired", currentCert.Leaf.NotAfter))
} }
@ -501,7 +567,7 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
if log != nil { if log != nil {
log.Info("attempting certificate renewal", log.Info("attempting certificate renewal",
zap.String("server_name", name), zap.String("server_name", name),
zap.Strings("identifiers", currentCert.Names), zap.Strings("subjects", currentCert.Names),
zap.Time("expiration", currentCert.Leaf.NotAfter), zap.Time("expiration", currentCert.Leaf.NotAfter),
zap.Duration("remaining", timeLeft)) zap.Duration("remaining", timeLeft))
} }

@ -32,6 +32,7 @@ import (
"github.com/libdns/libdns" "github.com/libdns/libdns"
"github.com/mholt/acmez" "github.com/mholt/acmez"
"github.com/mholt/acmez/acme" "github.com/mholt/acmez/acme"
"github.com/miekg/dns"
) )
// httpSolver solves the HTTP challenge. It must be // 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 { if err != nil {
return err return err
} }
key := challengeKey(chal)
activeChallengesMu.Lock() activeChallengesMu.Lock()
chalData := activeChallenges[chal.Identifier.Value] chalData := activeChallenges[key]
chalData.data = cert chalData.data = cert
activeChallenges[chal.Identifier.Value] = chalData activeChallenges[key] = chalData
activeChallengesMu.Unlock() activeChallengesMu.Unlock()
// the rest of this function increments the // 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 // CleanUp removes the challenge certificate from the cache, and if
// it is the last one to finish, stops the TLS server. // it is the last one to finish, stops the TLS server.
func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error { 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() solversMu.Lock()
defer solversMu.Unlock() defer solversMu.Unlock()
si := getSolverInfo(s.address) si := getSolverInfo(s.address)
@ -236,14 +235,6 @@ func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error
return nil 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 // DNS01Solver is a type that makes libdns providers usable
// as ACME dns-01 challenge solvers. // as ACME dns-01 challenge solvers.
// See https://github.com/libdns/libdns // See https://github.com/libdns/libdns
@ -478,7 +469,7 @@ func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) e
return err return err
} }
err = dhs.storage.Store(dhs.challengeTokensKey(chal.Identifier.Value), infoBytes) err = dhs.storage.Store(dhs.challengeTokensKey(challengeKey(chal)), infoBytes)
if err != nil { if err != nil {
return err return err
} }
@ -501,7 +492,7 @@ func (dhs distributedSolver) Wait(ctx context.Context, challenge acme.Challenge)
// CleanUp invokes the underlying solver's CleanUp method // CleanUp invokes the underlying solver's CleanUp method
// and also cleans up any assets saved to storage. // and also cleans up any assets saved to storage.
func (dhs distributedSolver) CleanUp(ctx context.Context, chal acme.Challenge) error { 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 { if err != nil {
return err return err
} }
@ -648,6 +639,18 @@ type Challenge struct {
data interface{} 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 // solverWrapper should be used to wrap all challenge solvers so that
// we can add the challenge info to memory; this makes challenges globally // 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 // 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 { func (sw solverWrapper) Present(ctx context.Context, chal acme.Challenge) error {
activeChallengesMu.Lock() activeChallengesMu.Lock()
activeChallenges[chal.Identifier.Value] = Challenge{Challenge: chal} activeChallenges[challengeKey(chal)] = Challenge{Challenge: chal}
activeChallengesMu.Unlock() activeChallengesMu.Unlock()
return sw.Solver.Present(ctx, chal) 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 { func (sw solverWrapper) CleanUp(ctx context.Context, chal acme.Challenge) error {
activeChallengesMu.Lock() activeChallengesMu.Lock()
delete(activeChallenges, chal.Identifier.Value) delete(activeChallenges, challengeKey(chal))
activeChallengesMu.Unlock() activeChallengesMu.Unlock()
return sw.Solver.CleanUp(ctx, chal) 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 // heuristics to decide which is optimal." §7.4.2
alternates := extractLinks(resp, "alternate") alternates := extractLinks(resp, "alternate")
for _, altURL := range alternates { for _, altURL := range alternates {
resp, err = addChain(altURL) _, err = addChain(altURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("retrieving alternate certificate chain at %s: %w", altURL, err) 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 break
} }
return resp, fmt.Errorf("request to %s failed after %d attempts: %w", return resp, fmt.Errorf("attempt %d: %s: %w", attempts, endpoint, err)
endpoint, attempts, err)
} }
// httpReq robustly performs an HTTP request using the given method to the given endpoint, honoring // 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("method", req.Method),
zap.String("url", req.URL.String()), zap.String("url", req.URL.String()),
zap.Reflect("headers", req.Header), 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 // "The server MUST include a Replay-Nonce header field

@ -14,7 +14,11 @@
package acme package acme
import "fmt" import (
"fmt"
"go.uber.org/zap/zapcore"
)
// Problem carries the details of an error from HTTP APIs as // Problem carries the details of an error from HTTP APIs as
// defined in RFC 7807: https://tools.ietf.org/html/rfc7807 // defined in RFC 7807: https://tools.ietf.org/html/rfc7807
@ -77,6 +81,9 @@ func (p Problem) Error() string {
if len(p.Subproblems) > 0 { if len(p.Subproblems) > 0 {
for _, v := range p.Subproblems { for _, v := range p.Subproblems {
s += fmt.Sprintf(", problem %q: %s", v.Type, v.Detail) 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 != "" { if p.Instance != "" {
@ -85,6 +92,17 @@ func (p Problem) Error() string {
return s 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 // Subproblem describes a more specific error in a problem according to
// RFC 8555 §6.7.1: "An ACME problem document MAY contain the // RFC 8555 §6.7.1: "An ACME problem document MAY contain the
// 'subproblems' field, containing a JSON array of problem documents, // 'subproblems' field, containing a JSON array of problem documents,
@ -97,6 +115,26 @@ type Subproblem struct {
Identifier Identifier `json:"identifier,omitempty"` 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 // 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 // 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 // for some errors, we can retry with different challenge types
var problem acme.Problem var problem acme.Problem
if errors.As(err, &problem) { if errors.As(err, &problem) {
authz := problem.Resource.(acme.Authorization) authz, haveAuthz := problem.Resource.(acme.Authorization)
if c.Logger != nil { if c.Logger != nil {
c.Logger.Error("validating authorization", l := c.Logger
zap.String("identifier", authz.IdentifierValue()), if haveAuthz {
zap.Error(err), l = l.With(zap.String("identifier", authz.IdentifierValue()))
}
l.Error("validating authorization",
zap.Object("problem", problem),
zap.String("order", order.Location), zap.String("order", order.Location),
zap.Int("attempt", attempt), zap.Int("attempt", attempt),
zap.Int("max_attempts", maxAttempts)) 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{}) { if errors.As(err, &retryableErr{}) {
continue continue
} }
@ -505,9 +512,7 @@ func (c *Client) pollAuthorization(ctx context.Context, account acme.Account, au
c.Logger.Error("challenge failed", c.Logger.Error("challenge failed",
zap.String("identifier", authz.IdentifierValue()), zap.String("identifier", authz.IdentifierValue()),
zap.String("challenge_type", authz.currentChallenge.Type), zap.String("challenge_type", authz.currentChallenge.Type),
zap.Int("status_code", problem.Status), zap.Object("problem", problem))
zap.String("problem_type", problem.Type),
zap.String("error", problem.Detail))
} }
failedChallengeTypes.rememberFailedChallenge(authz) failedChallengeTypes.rememberFailedChallenge(authz)

@ -197,7 +197,7 @@ github.com/boombuler/barcode/utils
# github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b # github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
## explicit ## explicit
github.com/bradfitz/gomemcache/memcache github.com/bradfitz/gomemcache/memcache
# github.com/caddyserver/certmagic v0.14.1 # github.com/caddyserver/certmagic v0.15.2
## explicit ## explicit
github.com/caddyserver/certmagic github.com/caddyserver/certmagic
# github.com/cespare/xxhash/v2 v2.1.1 # github.com/cespare/xxhash/v2 v2.1.1
@ -606,7 +606,7 @@ github.com/mattn/go-runewidth
github.com/mattn/go-sqlite3 github.com/mattn/go-sqlite3
# github.com/matttproud/golang_protobuf_extensions v1.0.1 # github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/matttproud/golang_protobuf_extensions/pbutil 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
github.com/mholt/acmez/acme github.com/mholt/acmez/acme
# github.com/mholt/archiver/v3 v3.5.0 # 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
github.com/microcosm-cc/bluemonday/css github.com/microcosm-cc/bluemonday/css
# github.com/miekg/dns v1.1.43 # github.com/miekg/dns v1.1.43
## explicit
github.com/miekg/dns github.com/miekg/dns
# github.com/minio/md5-simd v1.1.2 # github.com/minio/md5-simd v1.1.2
## explicit ## explicit

Loading…
Cancel
Save