Derive kubelet serving certificate CSR template from node status addresses

Kubernetes-commit: db9d3c2d1057f97a4763987baee00b82718feaa0
This commit is contained in:
Jordan Liggitt 2018-06-28 14:44:25 -04:00 committed by Kubernetes Publisher
parent 0ec73abb06
commit 9936c2e1aa
2 changed files with 115 additions and 17 deletions

View File

@ -24,6 +24,7 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"reflect"
"sync"
"time"
@ -32,6 +33,7 @@ import (
certificates "k8s.io/api/certificates/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/client-go/util/cert"
@ -75,6 +77,13 @@ type Config struct {
// part of rotation. It follows the same rules as the template parameter of
// crypto.x509.CreateCertificateRequest in the Go standard libraries.
Template *x509.CertificateRequest
// GetTemplate returns the CertificateRequest that will be used as a template for
// generating certificate signing requests for all new keys generated as
// part of rotation. It follows the same rules as the template parameter of
// crypto.x509.CreateCertificateRequest in the Go standard libraries.
// If no template is available, nil may be returned, and no certificate will be requested.
// If specified, takes precedence over Template.
GetTemplate func() *x509.CertificateRequest
// Usages is the types of usages that certificates generated by the manager
// can be used for.
Usages []certificates.KeyUsage
@ -136,7 +145,10 @@ func (e *NoCertKeyError) Error() string { return string(*e) }
type manager struct {
certSigningRequestClient certificatesclient.CertificateSigningRequestInterface
template *x509.CertificateRequest
getTemplate func() *x509.CertificateRequest
lastRequestLock sync.Mutex
lastRequest *x509.CertificateRequest
dynamicTemplate bool
usages []certificates.KeyUsage
certStore Store
certAccessLock sync.RWMutex
@ -158,9 +170,15 @@ func NewManager(config *Config) (Manager, error) {
return nil, err
}
getTemplate := config.GetTemplate
if getTemplate == nil {
getTemplate = func() *x509.CertificateRequest { return config.Template }
}
m := manager{
certSigningRequestClient: config.CertificateSigningRequestClient,
template: config.Template,
getTemplate: getTemplate,
dynamicTemplate: config.GetTemplate != nil,
usages: config.Usages,
certStore: config.CertificateStore,
cert: cert,
@ -215,12 +233,32 @@ func (m *manager) Start() {
glog.V(2).Infof("Certificate rotation is enabled.")
templateChanged := make(chan struct{})
go wait.Forever(func() {
deadline := m.nextRotationDeadline()
if sleepInterval := deadline.Sub(time.Now()); sleepInterval > 0 {
glog.V(2).Infof("Waiting %v for next certificate rotation", sleepInterval)
time.Sleep(sleepInterval)
timer := time.NewTimer(sleepInterval)
defer timer.Stop()
select {
case <-timer.C:
// unblock when deadline expires
case <-templateChanged:
if reflect.DeepEqual(m.getLastRequest(), m.getTemplate()) {
// if the template now matches what we last requested, restart the rotation deadline loop
return
}
glog.V(2).Infof("Certificate template changed, rotating")
}
}
// Don't enter rotateCerts and trigger backoff if we don't even have a template to request yet
if m.getTemplate() == nil {
return
}
backoff := wait.Backoff{
Duration: 2 * time.Second,
Factor: 2,
@ -231,7 +269,18 @@ func (m *manager) Start() {
utilruntime.HandleError(fmt.Errorf("Reached backoff limit, still unable to rotate certs: %v", err))
wait.PollInfinite(32*time.Second, m.rotateCerts)
}
}, 0)
}, time.Second)
if m.dynamicTemplate {
go wait.Forever(func() {
// check if the current template matches what we last requested
if !reflect.DeepEqual(m.getLastRequest(), m.getTemplate()) {
// if the template is different, queue up an interrupt of the rotation deadline loop.
// if we've requested a CSR that matches the new template by the time the interrupt is handled, the interrupt is disregarded.
templateChanged <- struct{}{}
}
}, time.Second)
}
}
func getCurrentCertificateOrBootstrap(
@ -286,7 +335,7 @@ func getCurrentCertificateOrBootstrap(
func (m *manager) rotateCerts() (bool, error) {
glog.V(2).Infof("Rotating certificates")
csrPEM, keyPEM, privateKey, err := m.generateCSR()
template, csrPEM, keyPEM, privateKey, err := m.generateCSR()
if err != nil {
utilruntime.HandleError(fmt.Errorf("Unable to generate a certificate signing request: %v", err))
return false, nil
@ -300,6 +349,9 @@ func (m *manager) rotateCerts() (bool, error) {
return false, m.updateServerError(err)
}
// Once we've successfully submitted a CSR for this template, record that we did so
m.setLastRequest(template)
// Wait for the certificate to be signed. Instead of one long watch, we retry with slightly longer
// intervals each time in order to tolerate failures from the server AND to preserve the liveliness
// of the cert manager loop. This creates slightly more traffic against the API server in return
@ -353,6 +405,36 @@ func (m *manager) nextRotationDeadline() time.Time {
return time.Now()
}
// Ensure the currently held certificate satisfies the requested subject CN and SANs
if template := m.getTemplate(); template != nil {
if template.Subject.CommonName != m.cert.Leaf.Subject.CommonName {
glog.V(2).Infof("Current certificate CN (%s) does not match requested CN (%s), rotating now", m.cert.Leaf.Subject.CommonName, template.Subject.CommonName)
return time.Now()
}
currentDNSNames := sets.NewString(m.cert.Leaf.DNSNames...)
desiredDNSNames := sets.NewString(template.DNSNames...)
missingDNSNames := desiredDNSNames.Difference(currentDNSNames)
if len(missingDNSNames) > 0 {
glog.V(2).Infof("Current certificate is missing requested DNS names %v, rotating now", missingDNSNames.List())
return time.Now()
}
currentIPs := sets.NewString()
for _, ip := range m.cert.Leaf.IPAddresses {
currentIPs.Insert(ip.String())
}
desiredIPs := sets.NewString()
for _, ip := range template.IPAddresses {
desiredIPs.Insert(ip.String())
}
missingIPs := desiredIPs.Difference(currentIPs)
if len(missingIPs) > 0 {
glog.V(2).Infof("Current certificate is missing requested IP addresses %v, rotating now", missingIPs.List())
return time.Now()
}
}
notAfter := m.cert.Leaf.NotAfter
totalDuration := float64(notAfter.Sub(m.cert.Leaf.NotBefore))
deadline := m.cert.Leaf.NotBefore.Add(jitteryDuration(totalDuration))
@ -408,22 +490,38 @@ func (m *manager) updateServerError(err error) error {
return nil
}
func (m *manager) generateCSR() (csrPEM []byte, keyPEM []byte, key interface{}, err error) {
func (m *manager) generateCSR() (template *x509.CertificateRequest, csrPEM []byte, keyPEM []byte, key interface{}, err error) {
// Generate a new private key.
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to generate a new private key: %v", err)
return nil, nil, nil, nil, fmt.Errorf("unable to generate a new private key: %v", err)
}
der, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to marshal the new key to DER: %v", err)
return nil, nil, nil, nil, fmt.Errorf("unable to marshal the new key to DER: %v", err)
}
keyPEM = pem.EncodeToMemory(&pem.Block{Type: cert.ECPrivateKeyBlockType, Bytes: der})
csrPEM, err = cert.MakeCSRFromTemplate(privateKey, m.template)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to create a csr from the private key: %v", err)
template = m.getTemplate()
if template == nil {
return nil, nil, nil, nil, fmt.Errorf("unable to create a csr, no template available")
}
return csrPEM, keyPEM, privateKey, nil
csrPEM, err = cert.MakeCSRFromTemplate(privateKey, template)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("unable to create a csr from the private key: %v", err)
}
return template, csrPEM, keyPEM, privateKey, nil
}
func (m *manager) getLastRequest() *x509.CertificateRequest {
m.lastRequestLock.Lock()
defer m.lastRequestLock.Unlock()
return m.lastRequest
}
func (m *manager) setLastRequest(r *x509.CertificateRequest) {
m.lastRequestLock.Lock()
defer m.lastRequestLock.Unlock()
m.lastRequest = r
}

View File

@ -186,7 +186,7 @@ func TestSetRotationDeadline(t *testing.T) {
NotAfter: tc.notAfter,
},
},
template: &x509.CertificateRequest{},
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
usages: []certificates.KeyUsage{},
certificateExpiration: &g,
}
@ -221,8 +221,8 @@ func TestRotateCertCreateCSRError(t *testing.T) {
NotAfter: now.Add(-1 * time.Hour),
},
},
template: &x509.CertificateRequest{},
usages: []certificates.KeyUsage{},
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
usages: []certificates.KeyUsage{},
certSigningRequestClient: fakeClient{
failureType: createError,
},
@ -244,8 +244,8 @@ func TestRotateCertWaitingForResultError(t *testing.T) {
NotAfter: now.Add(-1 * time.Hour),
},
},
template: &x509.CertificateRequest{},
usages: []certificates.KeyUsage{},
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
usages: []certificates.KeyUsage{},
certSigningRequestClient: fakeClient{
failureType: watchError,
},