mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-07 11:28:48 +03:00
Merge pull request #1366 from mholt/tls-sni-renew-fix
tls: Fix background certificate renewals that use TLS-SNI challenge
This commit is contained in:
commit
9369b81498
2 changed files with 59 additions and 51 deletions
|
@ -229,7 +229,7 @@ func cacheCertificate(cert Certificate) {
|
||||||
}
|
}
|
||||||
certCacheMu.Lock()
|
certCacheMu.Lock()
|
||||||
if _, ok := certCache[""]; !ok {
|
if _, ok := certCache[""]; !ok {
|
||||||
// use as default - must be *appended* to list, or bad things happen!
|
// use as default - must be *appended* to end of list, or bad things happen!
|
||||||
cert.Names = append(cert.Names, "")
|
cert.Names = append(cert.Names, "")
|
||||||
certCache[""] = cert
|
certCache[""] = cert
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ func maintainAssets(stopChan chan struct{}) {
|
||||||
|
|
||||||
// RenewManagedCertificates renews managed certificates.
|
// RenewManagedCertificates renews managed certificates.
|
||||||
func RenewManagedCertificates(allowPrompts bool) (err error) {
|
func RenewManagedCertificates(allowPrompts bool) (err error) {
|
||||||
var renewed, deleted []Certificate
|
var renewQueue, deleteQueue []Certificate
|
||||||
visitedNames := make(map[string]struct{})
|
visitedNames := make(map[string]struct{})
|
||||||
|
|
||||||
certCacheMu.RLock()
|
certCacheMu.RLock()
|
||||||
|
@ -77,7 +77,7 @@ func RenewManagedCertificates(allowPrompts bool) (err error) {
|
||||||
// the list of names on this cert should never be empty...
|
// the list of names on this cert should never be empty...
|
||||||
if cert.Names == nil || len(cert.Names) == 0 {
|
if cert.Names == nil || len(cert.Names) == 0 {
|
||||||
log.Printf("[WARNING] Certificate keyed by '%s' has no names: %v - removing from cache", name, cert.Names)
|
log.Printf("[WARNING] Certificate keyed by '%s' has no names: %v - removing from cache", name, cert.Names)
|
||||||
deleted = append(deleted, cert)
|
deleteQueue = append(deleteQueue, cert)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,64 +99,72 @@ func RenewManagedCertificates(allowPrompts bool) (err error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the name which we should use to renew this certificate;
|
// queue for renewal when we aren't in a read lock anymore
|
||||||
// we only support managing certificates with one name per cert,
|
// (the TLS-SNI challenge will need a write lock in order to
|
||||||
// so this should be easy. We can't rely on cert.Config.Hostname
|
// present the certificate, so we renew outside of read lock)
|
||||||
// because it may be a wildcard value from the Caddyfile (e.g.
|
renewQueue = append(renewQueue, cert)
|
||||||
// *.something.com) which, as of 2016, is not supported by ACME.
|
|
||||||
var renewName string
|
|
||||||
for _, name := range cert.Names {
|
|
||||||
if name != "" {
|
|
||||||
renewName = name
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := cert.Config.RenewCert(renewName, allowPrompts)
|
|
||||||
if err != nil {
|
|
||||||
if allowPrompts && timeLeft < 0 {
|
|
||||||
// Certificate renewal failed, the operator is present, and the certificate
|
|
||||||
// is already expired; we should stop immediately and return the error. Note
|
|
||||||
// that we used to do this any time a renewal failed at startup. However,
|
|
||||||
// after discussion in https://github.com/mholt/caddy/issues/642 we decided to
|
|
||||||
// only stop startup if the certificate is expired. We still log the error
|
|
||||||
// otherwise. I'm not sure how permanent the change in #642 will be...
|
|
||||||
certCacheMu.RUnlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("[ERROR] %v", err)
|
|
||||||
if cert.Config.OnDemand {
|
|
||||||
deleted = append(deleted, cert)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
renewed = append(renewed, cert)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
certCacheMu.RUnlock()
|
certCacheMu.RUnlock()
|
||||||
|
|
||||||
// Apply changes to the cache
|
// Perform renewals that are queued
|
||||||
for _, cert := range renewed {
|
for _, cert := range renewQueue {
|
||||||
// TODO: Don't do these until we have valid OCSP for the new cert
|
// Get the name which we should use to renew this certificate;
|
||||||
if cert.Names[len(cert.Names)-1] == "" {
|
// we only support managing certificates with one name per cert,
|
||||||
// Special case: This is the default certificate. We must
|
// so this should be easy. We can't rely on cert.Config.Hostname
|
||||||
// flush it out of the cache so that we no longer point to
|
// because it may be a wildcard value from the Caddyfile (e.g.
|
||||||
// the old, un-renewed certificate. Otherwise it will be
|
// *.something.com) which, as of Jan. 2017, is not supported by ACME.
|
||||||
// renewed on every scan, which is too often. When we cache
|
var renewName string
|
||||||
// this certificate in a moment, it will be the default again.
|
for _, name := range cert.Names {
|
||||||
certCacheMu.Lock()
|
if name != "" {
|
||||||
delete(certCache, "")
|
renewName = name
|
||||||
certCacheMu.Unlock()
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, err := CacheManagedCertificate(cert.Names[0], cert.Config)
|
|
||||||
|
// perform renewal
|
||||||
|
err := cert.Config.RenewCert(renewName, allowPrompts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if allowPrompts {
|
if allowPrompts && cert.NotAfter.Sub(time.Now().UTC()) < 0 {
|
||||||
return err // operator is present, so report error immediately
|
// Certificate renewal failed, the operator is present, and the certificate
|
||||||
|
// is already expired; we should stop immediately and return the error. Note
|
||||||
|
// that we used to do this any time a renewal failed at startup. However,
|
||||||
|
// after discussion in https://github.com/mholt/caddy/issues/642 we decided to
|
||||||
|
// only stop startup if the certificate is expired. We still log the error
|
||||||
|
// otherwise. I'm not sure how permanent the change in #642 will be...
|
||||||
|
// TODO: Get rid of the expiration check... always break on error.
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("[ERROR] %v", err)
|
log.Printf("[ERROR] %v", err)
|
||||||
|
if cert.Config.OnDemand {
|
||||||
|
deleteQueue = append(deleteQueue, cert)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// successful renewal, so update in-memory cache by loading
|
||||||
|
// renewed certificate so it will be used with handshakes
|
||||||
|
// TODO: Not until CA has valid OCSP response ready for the new cert... sigh.
|
||||||
|
if cert.Names[len(cert.Names)-1] == "" {
|
||||||
|
// Special case: This is the default certificate. We must
|
||||||
|
// flush it out of the cache so that we no longer point to
|
||||||
|
// the old, un-renewed certificate. Otherwise it will be
|
||||||
|
// renewed on every scan, which is too often. The next cert
|
||||||
|
// to be cached (probably this one) will become the default.
|
||||||
|
certCacheMu.Lock()
|
||||||
|
delete(certCache, "")
|
||||||
|
certCacheMu.Unlock()
|
||||||
|
}
|
||||||
|
_, err := CacheManagedCertificate(cert.Names[0], cert.Config)
|
||||||
|
if err != nil {
|
||||||
|
if allowPrompts {
|
||||||
|
return err // operator is present, so report error immediately
|
||||||
|
}
|
||||||
|
log.Printf("[ERROR] %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, cert := range deleted {
|
|
||||||
|
// Apply queued deletion changes to the cache
|
||||||
|
for _, cert := range deleteQueue {
|
||||||
certCacheMu.Lock()
|
certCacheMu.Lock()
|
||||||
for _, name := range cert.Names {
|
for _, name := range cert.Names {
|
||||||
delete(certCache, name)
|
delete(certCache, name)
|
||||||
|
|
Loading…
Reference in a new issue