1
0
Fork 0
forked from forgejo/forgejo

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

This commit is contained in:
Lunny Xiao 2022-01-01 17:43:28 +08:00 committed by GitHub
parent 385dc6a992
commit e9c9a35a61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 294 additions and 142 deletions

View file

@ -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))
}