Fix the certificate rotation threshold and add jitter.
This commit is contained in:
		@@ -75,7 +75,6 @@ type manager struct {
 | 
				
			|||||||
	certStore                Store
 | 
						certStore                Store
 | 
				
			||||||
	certAccessLock           sync.RWMutex
 | 
						certAccessLock           sync.RWMutex
 | 
				
			||||||
	cert                     *tls.Certificate
 | 
						cert                     *tls.Certificate
 | 
				
			||||||
	shouldRotatePercent      uint
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewManager returns a new certificate manager. A certificate manager is
 | 
					// NewManager returns a new certificate manager. A certificate manager is
 | 
				
			||||||
@@ -85,25 +84,19 @@ func NewManager(
 | 
				
			|||||||
	certSigningRequestClient certificatesclient.CertificateSigningRequestInterface,
 | 
						certSigningRequestClient certificatesclient.CertificateSigningRequestInterface,
 | 
				
			||||||
	template *x509.CertificateRequest,
 | 
						template *x509.CertificateRequest,
 | 
				
			||||||
	usages []certificates.KeyUsage,
 | 
						usages []certificates.KeyUsage,
 | 
				
			||||||
	certificateStore Store,
 | 
						certificateStore Store) (Manager, error) {
 | 
				
			||||||
	certRotationPercent uint) (Manager, error) {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cert, err := certificateStore.Current()
 | 
						cert, err := certificateStore.Current()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if certRotationPercent > 100 {
 | 
					 | 
				
			||||||
		certRotationPercent = 100
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	m := manager{
 | 
						m := manager{
 | 
				
			||||||
		certSigningRequestClient: certSigningRequestClient,
 | 
							certSigningRequestClient: certSigningRequestClient,
 | 
				
			||||||
		template:                 template,
 | 
							template:                 template,
 | 
				
			||||||
		usages:                   usages,
 | 
							usages:                   usages,
 | 
				
			||||||
		certStore:                certificateStore,
 | 
							certStore:                certificateStore,
 | 
				
			||||||
		cert:                     cert,
 | 
							cert:                     cert,
 | 
				
			||||||
		shouldRotatePercent:      certRotationPercent,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &m, nil
 | 
						return &m, nil
 | 
				
			||||||
@@ -129,15 +122,10 @@ func (m *manager) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certifi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Start will start the background work of rotating the certificates.
 | 
					// Start will start the background work of rotating the certificates.
 | 
				
			||||||
func (m *manager) Start() {
 | 
					func (m *manager) Start() {
 | 
				
			||||||
	if m.shouldRotatePercent < 1 {
 | 
					 | 
				
			||||||
		glog.V(2).Infof("Certificate rotation is not enabled.")
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Certificate rotation depends on access to the API server certificate
 | 
						// Certificate rotation depends on access to the API server certificate
 | 
				
			||||||
	// signing API, so don't start the certificate manager if we don't have a
 | 
						// signing API, so don't start the certificate manager if we don't have a
 | 
				
			||||||
	// client. This will happen on the master, where the kubelet is responsible
 | 
						// client. This will happen on the cluster master, where the kubelet is
 | 
				
			||||||
	// for bootstrapping the pods of the master components.
 | 
						// responsible for bootstrapping the pods of the master components.
 | 
				
			||||||
	if m.certSigningRequestClient == nil {
 | 
						if m.certSigningRequestClient == nil {
 | 
				
			||||||
		glog.V(2).Infof("Certificate rotation is not enabled, no connection to the apiserver.")
 | 
							glog.V(2).Infof("Certificate rotation is not enabled, no connection to the apiserver.")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -160,9 +148,17 @@ func (m *manager) shouldRotate() bool {
 | 
				
			|||||||
	m.certAccessLock.RLock()
 | 
						m.certAccessLock.RLock()
 | 
				
			||||||
	defer m.certAccessLock.RUnlock()
 | 
						defer m.certAccessLock.RUnlock()
 | 
				
			||||||
	notAfter := m.cert.Leaf.NotAfter
 | 
						notAfter := m.cert.Leaf.NotAfter
 | 
				
			||||||
	total := notAfter.Sub(m.cert.Leaf.NotBefore)
 | 
						totalDuration := float64(notAfter.Sub(m.cert.Leaf.NotBefore))
 | 
				
			||||||
	remaining := notAfter.Sub(time.Now())
 | 
					
 | 
				
			||||||
	return remaining < 0 || uint(remaining*100/total) < m.shouldRotatePercent
 | 
						// Use some jitter to set the rotation threshold so each node will rotate
 | 
				
			||||||
 | 
						// at approximately 70-90% of the total lifetime of the certificate.  With
 | 
				
			||||||
 | 
						// jitter, if a number of nodes are added to a cluster at approximately the
 | 
				
			||||||
 | 
						// same time (such as cluster creation time), they won't all try to rotate
 | 
				
			||||||
 | 
						// certificates at the same time for the rest of the life of the cluster.
 | 
				
			||||||
 | 
						jitteryDuration := wait.Jitter(time.Duration(totalDuration), 0.2) - time.Duration(totalDuration*0.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rotationThreshold := m.cert.Leaf.NotBefore.Add(jitteryDuration)
 | 
				
			||||||
 | 
						return time.Now().After(rotationThreshold)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *manager) rotateCerts() error {
 | 
					func (m *manager) rotateCerts() error {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -78,18 +78,6 @@ O1eRCsCGPAnUCviFgNeH15ug+6N54DTTR6ZV/TTV64FDOcsox9nrhYcmH9sYuITi
 | 
				
			|||||||
-----END CERTIFICATE-----`
 | 
					-----END CERTIFICATE-----`
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestNewManagerNoRotation(t *testing.T) {
 | 
					 | 
				
			||||||
	cert, err := tls.X509KeyPair([]byte(certificateData), []byte(privateKeyData))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		t.Fatalf("Unable to initialize a certificate: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	store := &fakeStore{cert: &cert}
 | 
					 | 
				
			||||||
	if _, err := NewManager(nil, &x509.CertificateRequest{}, []certificates.KeyUsage{}, store, 0); err != nil {
 | 
					 | 
				
			||||||
		t.Fatalf("Failed to initialize the certificate manager: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestShouldRotate(t *testing.T) {
 | 
					func TestShouldRotate(t *testing.T) {
 | 
				
			||||||
	now := time.Now()
 | 
						now := time.Now()
 | 
				
			||||||
	tests := []struct {
 | 
						tests := []struct {
 | 
				
			||||||
@@ -98,32 +86,34 @@ func TestShouldRotate(t *testing.T) {
 | 
				
			|||||||
		notAfter     time.Time
 | 
							notAfter     time.Time
 | 
				
			||||||
		shouldRotate bool
 | 
							shouldRotate bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{"half way", now.Add(-24 * time.Hour), now.Add(24 * time.Hour), false},
 | 
							{"just issued, still good", now.Add(-1 * time.Hour), now.Add(99 * time.Hour), false},
 | 
				
			||||||
		{"nearly there", now.Add(-100 * time.Hour), now.Add(1 * time.Hour), true},
 | 
							{"half way expired, still good", now.Add(-24 * time.Hour), now.Add(24 * time.Hour), false},
 | 
				
			||||||
		{"just started", now.Add(-1 * time.Hour), now.Add(100 * time.Hour), false},
 | 
							{"mostly expired, still good", now.Add(-69 * time.Hour), now.Add(31 * time.Hour), false},
 | 
				
			||||||
 | 
							{"just about expired, should rotate", now.Add(-91 * time.Hour), now.Add(9 * time.Hour), true},
 | 
				
			||||||
 | 
							{"nearly expired, should rotate", now.Add(-99 * time.Hour), now.Add(1 * time.Hour), true},
 | 
				
			||||||
 | 
							{"already expired, should rotate", now.Add(-10 * time.Hour), now.Add(-1 * time.Hour), true},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, test := range tests {
 | 
						for _, test := range tests {
 | 
				
			||||||
		m := manager{
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
			cert: &tls.Certificate{
 | 
								m := manager{
 | 
				
			||||||
				Leaf: &x509.Certificate{
 | 
									cert: &tls.Certificate{
 | 
				
			||||||
					NotAfter:  test.notAfter,
 | 
										Leaf: &x509.Certificate{
 | 
				
			||||||
					NotBefore: test.notBefore,
 | 
											NotAfter:  test.notAfter,
 | 
				
			||||||
 | 
											NotBefore: test.notBefore,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
									template: &x509.CertificateRequest{},
 | 
				
			||||||
			template:            &x509.CertificateRequest{},
 | 
									usages:   []certificates.KeyUsage{},
 | 
				
			||||||
			usages:              []certificates.KeyUsage{},
 | 
								}
 | 
				
			||||||
			shouldRotatePercent: 10,
 | 
								if m.shouldRotate() != test.shouldRotate {
 | 
				
			||||||
		}
 | 
									t.Errorf("For time %v, a certificate issued for (%v, %v) should rotate should be %t.",
 | 
				
			||||||
 | 
										now,
 | 
				
			||||||
		if m.shouldRotate() != test.shouldRotate {
 | 
										m.cert.Leaf.NotBefore,
 | 
				
			||||||
			t.Errorf("For test case %s, time %v, a certificate issued for (%v, %v) should rotate should be %t.",
 | 
										m.cert.Leaf.NotAfter,
 | 
				
			||||||
				test.name,
 | 
										test.shouldRotate)
 | 
				
			||||||
				now,
 | 
								}
 | 
				
			||||||
				m.cert.Leaf.NotBefore,
 | 
							})
 | 
				
			||||||
				m.cert.Leaf.NotAfter,
 | 
					 | 
				
			||||||
				test.shouldRotate)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user