mirror of https://github.com/k3s-io/k3s
Fix the certificate rotation threshold and add jitter.
parent
6d8aa2b15f
commit
e7666648bf
|
@ -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,12 +86,16 @@ 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 {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
m := manager{
|
m := manager{
|
||||||
cert: &tls.Certificate{
|
cert: &tls.Certificate{
|
||||||
Leaf: &x509.Certificate{
|
Leaf: &x509.Certificate{
|
||||||
|
@ -113,17 +105,15 @@ func TestShouldRotate(t *testing.T) {
|
||||||
},
|
},
|
||||||
template: &x509.CertificateRequest{},
|
template: &x509.CertificateRequest{},
|
||||||
usages: []certificates.KeyUsage{},
|
usages: []certificates.KeyUsage{},
|
||||||
shouldRotatePercent: 10,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.shouldRotate() != test.shouldRotate {
|
if m.shouldRotate() != test.shouldRotate {
|
||||||
t.Errorf("For test case %s, time %v, a certificate issued for (%v, %v) should rotate should be %t.",
|
t.Errorf("For time %v, a certificate issued for (%v, %v) should rotate should be %t.",
|
||||||
test.name,
|
|
||||||
now,
|
now,
|
||||||
m.cert.Leaf.NotBefore,
|
m.cert.Leaf.NotBefore,
|
||||||
m.cert.Leaf.NotAfter,
|
m.cert.Leaf.NotAfter,
|
||||||
test.shouldRotate)
|
test.shouldRotate)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue