mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-18 16:55:37 +03:00
tls: Remove expiring certificates from cache and load renewed ones
Renewed certificates would not be reloaded into the cache because their names conflict with names of certificates already in the cache; this was intentional when loading new certs to avoid confusion, but is problematic when renewing, since the old certificate doesn't get evicted from the cache. (Oops.) Here, I remedy this situation by explicitly deleting the old cert from the cache before adding the renewed one back in.
This commit is contained in:
parent
3b144c21d0
commit
46ae4a6652
3 changed files with 49 additions and 18 deletions
|
@ -227,8 +227,10 @@ func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
|
||||||
// full, random entries are deleted until there is room to map all the
|
// full, random entries are deleted until there is room to map all the
|
||||||
// names on the certificate.
|
// names on the certificate.
|
||||||
//
|
//
|
||||||
// This certificate will be keyed to the names in cert.Names. Any name
|
// This certificate will be keyed to the names in cert.Names. Any names
|
||||||
// that is already a key in the cache will be replaced with this cert.
|
// already used as a cache key will NOT be replaced by this cert; in
|
||||||
|
// other words, no overlap is allowed, and this certificate will not
|
||||||
|
// service those pre-existing names.
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent use.
|
// This function is safe for concurrent use.
|
||||||
func cacheCertificate(cert Certificate) {
|
func cacheCertificate(cert Certificate) {
|
||||||
|
|
|
@ -246,7 +246,7 @@ func (cfg *Config) handshakeMaintenance(name string, cert Certificate) (Certific
|
||||||
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||||
if timeLeft < RenewDurationBefore {
|
if timeLeft < RenewDurationBefore {
|
||||||
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft)
|
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft)
|
||||||
return cfg.renewDynamicCertificate(name)
|
return cfg.renewDynamicCertificate(name, cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check OCSP staple validity
|
// Check OCSP staple validity
|
||||||
|
@ -269,12 +269,12 @@ func (cfg *Config) handshakeMaintenance(name string, cert Certificate) (Certific
|
||||||
}
|
}
|
||||||
|
|
||||||
// renewDynamicCertificate renews the certificate for name using cfg. It returns the
|
// renewDynamicCertificate renews the certificate for name using cfg. It returns the
|
||||||
// certificate to use and an error, if any. currentCert may be returned even if an
|
// certificate to use and an error, if any. name should already be lower-cased before
|
||||||
// error occurs, since we perform renewals before they expire and it may still be
|
// calling this function. name is the name obtained directly from the handshake's
|
||||||
// usable. name should already be lower-cased before calling this function.
|
// ClientHello.
|
||||||
//
|
//
|
||||||
// This function is safe for use by multiple concurrent goroutines.
|
// This function is safe for use by multiple concurrent goroutines.
|
||||||
func (cfg *Config) renewDynamicCertificate(name string) (Certificate, error) {
|
func (cfg *Config) renewDynamicCertificate(name string, currentCert Certificate) (Certificate, error) {
|
||||||
obtainCertWaitChansMu.Lock()
|
obtainCertWaitChansMu.Lock()
|
||||||
wait, ok := obtainCertWaitChans[name]
|
wait, ok := obtainCertWaitChans[name]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -290,9 +290,31 @@ func (cfg *Config) renewDynamicCertificate(name string) (Certificate, error) {
|
||||||
obtainCertWaitChans[name] = wait
|
obtainCertWaitChans[name] = wait
|
||||||
obtainCertWaitChansMu.Unlock()
|
obtainCertWaitChansMu.Unlock()
|
||||||
|
|
||||||
// do the renew
|
// do the renew and reload the certificate
|
||||||
log.Printf("[INFO] Renewing certificate for %s", name)
|
log.Printf("[INFO] Renewing certificate for %s", name)
|
||||||
err := cfg.RenewCert(name, false)
|
err := cfg.RenewCert(name, false)
|
||||||
|
if err == nil {
|
||||||
|
// immediately flush this certificate from the cache so
|
||||||
|
// the name doesn't overlap when we try to replace it,
|
||||||
|
// which would fail, because overlapping existing cert
|
||||||
|
// names isn't allowed
|
||||||
|
certCacheMu.Lock()
|
||||||
|
for _, certName := range currentCert.Names {
|
||||||
|
delete(certCache, certName)
|
||||||
|
}
|
||||||
|
certCacheMu.Unlock()
|
||||||
|
|
||||||
|
// even though the recursive nature of the dynamic cert loading
|
||||||
|
// would just call this function anyway, we do it here to
|
||||||
|
// make the replacement as atomic as possible. (TODO: similar
|
||||||
|
// to the note in maintain.go, it'd be nice if the clearing of
|
||||||
|
// the cache entries above and this load function were truly
|
||||||
|
// atomic...)
|
||||||
|
_, err := currentCert.Config.CacheManagedCertificate(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] loading renewed certificate: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// immediately unblock anyone waiting for it; doing this in
|
// immediately unblock anyone waiting for it; doing this in
|
||||||
// a defer would risk deadlock because of the recursive call
|
// a defer would risk deadlock because of the recursive call
|
||||||
|
|
|
@ -70,7 +70,8 @@ func maintainAssets(stopChan chan struct{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenewManagedCertificates renews managed certificates.
|
// RenewManagedCertificates renews managed certificates,
|
||||||
|
// including ones loaded on-demand.
|
||||||
func RenewManagedCertificates(allowPrompts bool) (err error) {
|
func RenewManagedCertificates(allowPrompts bool) (err error) {
|
||||||
var renewQueue, deleteQueue []Certificate
|
var renewQueue, deleteQueue []Certificate
|
||||||
visitedNames := make(map[string]struct{})
|
visitedNames := make(map[string]struct{})
|
||||||
|
@ -147,21 +148,27 @@ func RenewManagedCertificates(allowPrompts bool) (err error) {
|
||||||
}
|
}
|
||||||
log.Printf("[ERROR] %v", err)
|
log.Printf("[ERROR] %v", err)
|
||||||
if cert.Config.OnDemand {
|
if cert.Config.OnDemand {
|
||||||
|
// loaded dynamically, removed dynamically
|
||||||
deleteQueue = append(deleteQueue, cert)
|
deleteQueue = append(deleteQueue, cert)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// successful renewal, so update in-memory cache by loading
|
// successful renewal, so update in-memory cache by loading
|
||||||
// renewed certificate so it will be used with handshakes
|
// renewed certificate so it will be used with handshakes
|
||||||
if cert.Names[len(cert.Names)-1] == "" {
|
|
||||||
// Special case: This is the default certificate. We must
|
// we must delete all the names this cert services from the cache
|
||||||
// flush it out of the cache so that we no longer point to
|
// so that we can replace the certificate, because replacing names
|
||||||
// the old, un-renewed certificate. Otherwise it will be
|
// already in the cache is not allowed, to avoid later conflicts
|
||||||
// renewed on every scan, which is too often. The next cert
|
// with renewals.
|
||||||
// to be cached (probably this one) will become the default.
|
// TODO: It would be nice if this whole operation were idempotent;
|
||||||
certCacheMu.Lock()
|
// i.e. a thread-safe function to replace a certificate in the cache,
|
||||||
delete(certCache, "")
|
// see also handshake.go for on-demand maintenance.
|
||||||
certCacheMu.Unlock()
|
certCacheMu.Lock()
|
||||||
|
for _, name := range cert.Names {
|
||||||
|
delete(certCache, name)
|
||||||
}
|
}
|
||||||
|
certCacheMu.Unlock()
|
||||||
|
|
||||||
|
// put the certificate in the cache
|
||||||
_, err := cert.Config.CacheManagedCertificate(cert.Names[0])
|
_, err := cert.Config.CacheManagedCertificate(cert.Names[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if allowPrompts {
|
if allowPrompts {
|
||||||
|
|
Loading…
Reference in a new issue