diff options
Diffstat (limited to 'core/https/maintain.go')
-rw-r--r-- | core/https/maintain.go | 211 |
1 files changed, 0 insertions, 211 deletions
diff --git a/core/https/maintain.go b/core/https/maintain.go deleted file mode 100644 index 46fd3d1f5..000000000 --- a/core/https/maintain.go +++ /dev/null @@ -1,211 +0,0 @@ -package https - -import ( - "log" - "time" - - "github.com/miekg/coredns/server" - - "golang.org/x/crypto/ocsp" -) - -const ( - // RenewInterval is how often to check certificates for renewal. - RenewInterval = 12 * time.Hour - - // OCSPInterval is how often to check if OCSP stapling needs updating. - OCSPInterval = 1 * time.Hour -) - -// maintainAssets is a permanently-blocking function -// that loops indefinitely and, on a regular schedule, checks -// certificates for expiration and initiates a renewal of certs -// that are expiring soon. It also updates OCSP stapling and -// performs other maintenance of assets. -// -// You must pass in the channel which you'll close when -// maintenance should stop, to allow this goroutine to clean up -// after itself and unblock. -func maintainAssets(stopChan chan struct{}) { - renewalTicker := time.NewTicker(RenewInterval) - ocspTicker := time.NewTicker(OCSPInterval) - - for { - select { - case <-renewalTicker.C: - log.Println("[INFO] Scanning for expiring certificates") - renewManagedCertificates(false) - log.Println("[INFO] Done checking certificates") - case <-ocspTicker.C: - log.Println("[INFO] Scanning for stale OCSP staples") - updateOCSPStaples() - log.Println("[INFO] Done checking OCSP staples") - case <-stopChan: - renewalTicker.Stop() - ocspTicker.Stop() - log.Println("[INFO] Stopped background maintenance routine") - return - } - } -} - -func renewManagedCertificates(allowPrompts bool) (err error) { - var renewed, deleted []Certificate - var client *ACMEClient - visitedNames := make(map[string]struct{}) - - certCacheMu.RLock() - for name, cert := range certCache { - if !cert.Managed { - continue - } - - // the list of names on this cert should never be empty... - if cert.Names == nil || len(cert.Names) == 0 { - log.Printf("[WARNING] Certificate keyed by '%s' has no names: %v", name, cert.Names) - deleted = append(deleted, cert) - continue - } - - // skip names whose certificate we've already renewed - if _, ok := visitedNames[name]; ok { - continue - } - for _, name := range cert.Names { - visitedNames[name] = struct{}{} - } - - timeLeft := cert.NotAfter.Sub(time.Now().UTC()) - if timeLeft < renewDurationBefore { - log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft) - - if client == nil { - client, err = NewACMEClientGetEmail(server.Config{}, allowPrompts) - if err != nil { - return err - } - client.Configure("") // TODO: Bind address of relevant listener, yuck - } - - err := client.Renew(cert.Names[0]) // managed certs better have only one name - if err != nil { - if client.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/miekg/coredns/issues/642 we decided to - // only stop startup if the certificate is expired. We still log the error - // otherwise. - certCacheMu.RUnlock() - return err - } - log.Printf("[ERROR] %v", err) - if cert.OnDemand { - deleted = append(deleted, cert) - } - } else { - renewed = append(renewed, cert) - } - } - } - certCacheMu.RUnlock() - - // Apply changes to the cache - for _, cert := range renewed { - _, err := cacheManagedCertificate(cert.Names[0], cert.OnDemand) - if err != nil { - if client.AllowPrompts { - return err // operator is present, so report error immediately - } - log.Printf("[ERROR] %v", err) - } - } - for _, cert := range deleted { - certCacheMu.Lock() - for _, name := range cert.Names { - delete(certCache, name) - } - certCacheMu.Unlock() - } - - return nil -} - -func updateOCSPStaples() { - // Create a temporary place to store updates - // until we release the potentially long-lived - // read lock and use a short-lived write lock. - type ocspUpdate struct { - rawBytes []byte - parsed *ocsp.Response - } - updated := make(map[string]ocspUpdate) - - // A single SAN certificate maps to multiple names, so we use this - // set to make sure we don't waste cycles checking OCSP for the same - // certificate multiple times. - visited := make(map[string]struct{}) - - certCacheMu.RLock() - for name, cert := range certCache { - // skip this certificate if we've already visited it, - // and if not, mark all the names as visited - if _, ok := visited[name]; ok { - continue - } - for _, n := range cert.Names { - visited[n] = struct{}{} - } - - // no point in updating OCSP for expired certificates - if time.Now().After(cert.NotAfter) { - continue - } - - var lastNextUpdate time.Time - if cert.OCSP != nil { - // start checking OCSP staple about halfway through validity period for good measure - lastNextUpdate = cert.OCSP.NextUpdate - refreshTime := cert.OCSP.ThisUpdate.Add(lastNextUpdate.Sub(cert.OCSP.ThisUpdate) / 2) - - // since OCSP is already stapled, we need only check if we're in that "refresh window" - if time.Now().Before(refreshTime) { - continue - } - } - - err := stapleOCSP(&cert, nil) - if err != nil { - if cert.OCSP != nil { - // if it was no staple before, that's fine, otherwise we should log the error - log.Printf("[ERROR] Checking OCSP for %s: %v", name, err) - } - continue - } - - // By this point, we've obtained the latest OCSP response. - // If there was no staple before, or if the response is updated, make - // sure we apply the update to all names on the certificate. - if lastNextUpdate.IsZero() || lastNextUpdate != cert.OCSP.NextUpdate { - log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s", - cert.Names, lastNextUpdate, cert.OCSP.NextUpdate) - for _, n := range cert.Names { - updated[n] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.OCSP} - } - } - } - certCacheMu.RUnlock() - - // This write lock should be brief since we have all the info we need now. - certCacheMu.Lock() - for name, update := range updated { - cert := certCache[name] - cert.OCSP = update.parsed - cert.Certificate.OCSPStaple = update.rawBytes - certCache[name] = cert - } - certCacheMu.Unlock() -} - -// renewDurationBefore is how long before expiration to renew certificates. -const renewDurationBefore = (24 * time.Hour) * 30 |