github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/ca/renewer.go (about)

     1  package ca
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/docker/go-events"
     9  	"github.com/docker/swarmkit/connectionbroker"
    10  	"github.com/docker/swarmkit/log"
    11  	"github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  // RenewTLSExponentialBackoff sets the exponential backoff when trying to renew TLS certificates that have expired
    16  var RenewTLSExponentialBackoff = events.ExponentialBackoffConfig{
    17  	Base:   time.Second * 5,
    18  	Factor: time.Second * 5,
    19  	Max:    1 * time.Hour,
    20  }
    21  
    22  // TLSRenewer handles renewing TLS certificates, either automatically or upon
    23  // request.
    24  type TLSRenewer struct {
    25  	mu           sync.Mutex
    26  	s            *SecurityConfig
    27  	connBroker   *connectionbroker.Broker
    28  	renew        chan struct{}
    29  	expectedRole string
    30  	rootPaths    CertPaths
    31  }
    32  
    33  // NewTLSRenewer creates a new TLS renewer. It must be started with Start.
    34  func NewTLSRenewer(s *SecurityConfig, connBroker *connectionbroker.Broker, rootPaths CertPaths) *TLSRenewer {
    35  	return &TLSRenewer{
    36  		s:          s,
    37  		connBroker: connBroker,
    38  		renew:      make(chan struct{}, 1),
    39  		rootPaths:  rootPaths,
    40  	}
    41  }
    42  
    43  // SetExpectedRole sets the expected role. If a renewal is forced, and the role
    44  // doesn't match this expectation, renewal will be retried with exponential
    45  // backoff until it does match.
    46  func (t *TLSRenewer) SetExpectedRole(role string) {
    47  	t.mu.Lock()
    48  	t.expectedRole = role
    49  	t.mu.Unlock()
    50  }
    51  
    52  // Renew causes the TLSRenewer to renew the certificate (nearly) right away,
    53  // instead of waiting for the next automatic renewal.
    54  func (t *TLSRenewer) Renew() {
    55  	select {
    56  	case t.renew <- struct{}{}:
    57  	default:
    58  	}
    59  }
    60  
    61  // Start will continuously monitor for the necessity of renewing the local certificates, either by
    62  // issuing them locally if key-material is available, or requesting them from a remote CA.
    63  func (t *TLSRenewer) Start(ctx context.Context) <-chan CertificateUpdate {
    64  	updates := make(chan CertificateUpdate)
    65  
    66  	go func() {
    67  		var (
    68  			retry      time.Duration
    69  			forceRetry bool
    70  		)
    71  		expBackoff := events.NewExponentialBackoff(RenewTLSExponentialBackoff)
    72  		defer close(updates)
    73  		for {
    74  			ctx = log.WithModule(ctx, "tls")
    75  			log := log.G(ctx).WithFields(logrus.Fields{
    76  				"node.id":   t.s.ClientTLSCreds.NodeID(),
    77  				"node.role": t.s.ClientTLSCreds.Role(),
    78  			})
    79  			// Our starting default will be 5 minutes
    80  			retry = 5 * time.Minute
    81  
    82  			// Since the expiration of the certificate is managed remotely we should update our
    83  			// retry timer on every iteration of this loop.
    84  			// Retrieve the current certificate expiration information.
    85  			validFrom, validUntil, err := readCertValidity(t.s.KeyReader())
    86  			if err != nil {
    87  				// We failed to read the expiration, let's stick with the starting default
    88  				log.Errorf("failed to read the expiration of the TLS certificate in: %s", t.s.KeyReader().Target())
    89  
    90  				select {
    91  				case updates <- CertificateUpdate{Err: errors.New("failed to read certificate expiration")}:
    92  				case <-ctx.Done():
    93  					log.Info("shutting down certificate renewal routine")
    94  					return
    95  				}
    96  			} else {
    97  				// If we have an expired certificate, try to renew immediately: the hope that this is a temporary clock skew, or
    98  				// we can issue our own TLS certs.
    99  				if validUntil.Before(time.Now()) {
   100  					log.Warn("the current TLS certificate is expired, so an attempt to renew it will be made immediately")
   101  					// retry immediately(ish) with exponential backoff
   102  					retry = expBackoff.Proceed(nil)
   103  				} else if forceRetry {
   104  					// A forced renewal was requested, but did not succeed yet.
   105  					// retry immediately(ish) with exponential backoff
   106  					retry = expBackoff.Proceed(nil)
   107  				} else {
   108  					// Random retry time between 50% and 80% of the total time to expiration
   109  					retry = calculateRandomExpiry(validFrom, validUntil)
   110  				}
   111  			}
   112  
   113  			log.WithFields(logrus.Fields{
   114  				"time": time.Now().Add(retry),
   115  			}).Debugf("next certificate renewal scheduled for %v from now", retry)
   116  
   117  			select {
   118  			case <-time.After(retry):
   119  				log.Info("renewing certificate")
   120  			case <-t.renew:
   121  				forceRetry = true
   122  				log.Info("forced certificate renewal")
   123  
   124  				// Pause briefly before attempting the renewal,
   125  				// to give the CA a chance to reconcile the
   126  				// desired role.
   127  				select {
   128  				case <-time.After(500 * time.Millisecond):
   129  				case <-ctx.Done():
   130  					log.Info("shutting down certificate renewal routine")
   131  					return
   132  				}
   133  			case <-ctx.Done():
   134  				log.Info("shutting down certificate renewal routine")
   135  				return
   136  			}
   137  
   138  			// ignore errors - it will just try again later
   139  			var certUpdate CertificateUpdate
   140  			if err := RenewTLSConfigNow(ctx, t.s, t.connBroker, t.rootPaths); err != nil {
   141  				certUpdate.Err = err
   142  				expBackoff.Failure(nil, nil)
   143  			} else {
   144  				newRole := t.s.ClientTLSCreds.Role()
   145  				t.mu.Lock()
   146  				expectedRole := t.expectedRole
   147  				t.mu.Unlock()
   148  				if expectedRole != "" && expectedRole != newRole {
   149  					expBackoff.Failure(nil, nil)
   150  					continue
   151  				}
   152  
   153  				certUpdate.Role = newRole
   154  				expBackoff.Success(nil)
   155  				forceRetry = false
   156  			}
   157  
   158  			select {
   159  			case updates <- certUpdate:
   160  			case <-ctx.Done():
   161  				log.Info("shutting down certificate renewal routine")
   162  				return
   163  			}
   164  		}
   165  	}()
   166  
   167  	return updates
   168  }