mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
4f3616fb1c
Add forks for changes which only make logrus change without functional change. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
168 lines
5.1 KiB
Go
168 lines
5.1 KiB
Go
package ca
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/docker/go-events"
|
|
"github.com/docker/swarmkit/connectionbroker"
|
|
"github.com/docker/swarmkit/log"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// RenewTLSExponentialBackoff sets the exponential backoff when trying to renew TLS certificates that have expired
|
|
var RenewTLSExponentialBackoff = events.ExponentialBackoffConfig{
|
|
Base: time.Second * 5,
|
|
Factor: time.Second * 5,
|
|
Max: 1 * time.Hour,
|
|
}
|
|
|
|
// TLSRenewer handles renewing TLS certificates, either automatically or upon
|
|
// request.
|
|
type TLSRenewer struct {
|
|
mu sync.Mutex
|
|
s *SecurityConfig
|
|
connBroker *connectionbroker.Broker
|
|
renew chan struct{}
|
|
expectedRole string
|
|
rootPaths CertPaths
|
|
}
|
|
|
|
// NewTLSRenewer creates a new TLS renewer. It must be started with Start.
|
|
func NewTLSRenewer(s *SecurityConfig, connBroker *connectionbroker.Broker, rootPaths CertPaths) *TLSRenewer {
|
|
return &TLSRenewer{
|
|
s: s,
|
|
connBroker: connBroker,
|
|
renew: make(chan struct{}, 1),
|
|
rootPaths: rootPaths,
|
|
}
|
|
}
|
|
|
|
// SetExpectedRole sets the expected role. If a renewal is forced, and the role
|
|
// doesn't match this expectation, renewal will be retried with exponential
|
|
// backoff until it does match.
|
|
func (t *TLSRenewer) SetExpectedRole(role string) {
|
|
t.mu.Lock()
|
|
t.expectedRole = role
|
|
t.mu.Unlock()
|
|
}
|
|
|
|
// Renew causes the TLSRenewer to renew the certificate (nearly) right away,
|
|
// instead of waiting for the next automatic renewal.
|
|
func (t *TLSRenewer) Renew() {
|
|
select {
|
|
case t.renew <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// Start will continuously monitor for the necessity of renewing the local certificates, either by
|
|
// issuing them locally if key-material is available, or requesting them from a remote CA.
|
|
func (t *TLSRenewer) Start(ctx context.Context) <-chan CertificateUpdate {
|
|
updates := make(chan CertificateUpdate)
|
|
|
|
go func() {
|
|
var (
|
|
retry time.Duration
|
|
forceRetry bool
|
|
)
|
|
expBackoff := events.NewExponentialBackoff(RenewTLSExponentialBackoff)
|
|
defer close(updates)
|
|
for {
|
|
ctx = log.WithModule(ctx, "tls")
|
|
log := log.G(ctx).WithFields(logrus.Fields{
|
|
"node.id": t.s.ClientTLSCreds.NodeID(),
|
|
"node.role": t.s.ClientTLSCreds.Role(),
|
|
})
|
|
// Our starting default will be 5 minutes
|
|
retry = 5 * time.Minute
|
|
|
|
// Since the expiration of the certificate is managed remotely we should update our
|
|
// retry timer on every iteration of this loop.
|
|
// Retrieve the current certificate expiration information.
|
|
validFrom, validUntil, err := readCertValidity(t.s.KeyReader())
|
|
if err != nil {
|
|
// We failed to read the expiration, let's stick with the starting default
|
|
log.Errorf("failed to read the expiration of the TLS certificate in: %s", t.s.KeyReader().Target())
|
|
|
|
select {
|
|
case updates <- CertificateUpdate{Err: errors.New("failed to read certificate expiration")}:
|
|
case <-ctx.Done():
|
|
log.Info("shutting down certificate renewal routine")
|
|
return
|
|
}
|
|
} else {
|
|
// If we have an expired certificate, try to renew immediately: the hope that this is a temporary clock skew, or
|
|
// we can issue our own TLS certs.
|
|
if validUntil.Before(time.Now()) {
|
|
log.Warn("the current TLS certificate is expired, so an attempt to renew it will be made immediately")
|
|
// retry immediately(ish) with exponential backoff
|
|
retry = expBackoff.Proceed(nil)
|
|
} else if forceRetry {
|
|
// A forced renewal was requested, but did not succeed yet.
|
|
// retry immediately(ish) with exponential backoff
|
|
retry = expBackoff.Proceed(nil)
|
|
} else {
|
|
// Random retry time between 50% and 80% of the total time to expiration
|
|
retry = calculateRandomExpiry(validFrom, validUntil)
|
|
}
|
|
}
|
|
|
|
log.WithFields(logrus.Fields{
|
|
"time": time.Now().Add(retry),
|
|
}).Debugf("next certificate renewal scheduled for %v from now", retry)
|
|
|
|
select {
|
|
case <-time.After(retry):
|
|
log.Info("renewing certificate")
|
|
case <-t.renew:
|
|
forceRetry = true
|
|
log.Info("forced certificate renewal")
|
|
|
|
// Pause briefly before attempting the renewal,
|
|
// to give the CA a chance to reconcile the
|
|
// desired role.
|
|
select {
|
|
case <-time.After(500 * time.Millisecond):
|
|
case <-ctx.Done():
|
|
log.Info("shutting down certificate renewal routine")
|
|
return
|
|
}
|
|
case <-ctx.Done():
|
|
log.Info("shutting down certificate renewal routine")
|
|
return
|
|
}
|
|
|
|
// ignore errors - it will just try again later
|
|
var certUpdate CertificateUpdate
|
|
if err := RenewTLSConfigNow(ctx, t.s, t.connBroker, t.rootPaths); err != nil {
|
|
certUpdate.Err = err
|
|
expBackoff.Failure(nil, nil)
|
|
} else {
|
|
newRole := t.s.ClientTLSCreds.Role()
|
|
t.mu.Lock()
|
|
expectedRole := t.expectedRole
|
|
t.mu.Unlock()
|
|
if expectedRole != "" && expectedRole != newRole {
|
|
expBackoff.Failure(nil, nil)
|
|
continue
|
|
}
|
|
|
|
certUpdate.Role = newRole
|
|
expBackoff.Success(nil)
|
|
forceRetry = false
|
|
}
|
|
|
|
select {
|
|
case updates <- certUpdate:
|
|
case <-ctx.Done():
|
|
log.Info("shutting down certificate renewal routine")
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return updates
|
|
}
|