diff --git a/util/certificate/certificate_manager.go b/util/certificate/certificate_manager.go index dee21cf3..b4dcb0b8 100644 --- a/util/certificate/certificate_manager.go +++ b/util/certificate/certificate_manager.go @@ -21,9 +21,11 @@ import ( "crypto/ecdsa" "crypto/elliptic" cryptorand "crypto/rand" + "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" + "errors" "fmt" "reflect" "sync" @@ -32,7 +34,7 @@ import ( "k8s.io/klog/v2" certificates "k8s.io/api/certificates/v1" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "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" @@ -42,9 +44,76 @@ import ( "k8s.io/client-go/util/keyutil" ) -// certificateWaitTimeout controls the amount of time we wait for certificate -// approval in one iteration. -var certificateWaitTimeout = 15 * time.Minute +var ( + // certificateWaitTimeout controls the amount of time we wait for certificate + // approval in one iteration. + certificateWaitTimeout = 15 * time.Minute + + kubeletServingUsagesWithEncipherment = []certificates.KeyUsage{ + // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + // + // Digital signature allows the certificate to be used to verify + // digital signatures used during TLS negotiation. + certificates.UsageDigitalSignature, + // KeyEncipherment allows the cert/key pair to be used to encrypt + // keys, including the symmetric keys negotiated during TLS setup + // and used for data transfer. + certificates.UsageKeyEncipherment, + // ServerAuth allows the cert to be used by a TLS server to + // authenticate itself to a TLS client. + certificates.UsageServerAuth, + } + kubeletServingUsagesNoEncipherment = []certificates.KeyUsage{ + // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + // + // Digital signature allows the certificate to be used to verify + // digital signatures used during TLS negotiation. + certificates.UsageDigitalSignature, + // ServerAuth allows the cert to be used by a TLS server to + // authenticate itself to a TLS client. + certificates.UsageServerAuth, + } + DefaultKubeletServingGetUsages = func(privateKey interface{}) []certificates.KeyUsage { + switch privateKey.(type) { + case *rsa.PrivateKey: + return kubeletServingUsagesWithEncipherment + default: + return kubeletServingUsagesNoEncipherment + } + } + kubeletClientUsagesWithEncipherment = []certificates.KeyUsage{ + // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + // + // Digital signature allows the certificate to be used to verify + // digital signatures used during TLS negotiation. + certificates.UsageDigitalSignature, + // KeyEncipherment allows the cert/key pair to be used to encrypt + // keys, including the symmetric keys negotiated during TLS setup + // and used for data transfer. + certificates.UsageKeyEncipherment, + // ClientAuth allows the cert to be used by a TLS client to + // authenticate itself to the TLS server. + certificates.UsageClientAuth, + } + kubeletClientUsagesNoEncipherment = []certificates.KeyUsage{ + // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 + // + // Digital signature allows the certificate to be used to verify + // digital signatures used during TLS negotiation. + certificates.UsageDigitalSignature, + // ClientAuth allows the cert to be used by a TLS client to + // authenticate itself to the TLS server. + certificates.UsageClientAuth, + } + DefaultKubeletClientGetUsages = func(privateKey interface{}) []certificates.KeyUsage { + switch privateKey.(type) { + case *rsa.PrivateKey: + return kubeletClientUsagesWithEncipherment + default: + return kubeletClientUsagesNoEncipherment + } + } +) // Manager maintains and updates the certificates in use by this certificate // manager. In the background it communicates with the API server to get new @@ -94,8 +163,12 @@ type Config struct { // the issued certificate is not guaranteed as the signer may choose to ignore the request. RequestedCertificateLifetime *time.Duration // Usages is the types of usages that certificates generated by the manager - // can be used for. + // can be used for. It is mutually exclusive with GetUsages. Usages []certificates.KeyUsage + // GetUsages is dynamic way to get the types of usages that certificates generated by the manager + // can be used for. If Usages is not nil, GetUsages has to be nil, vice versa. + // It is mutually exclusive with Usages. + GetUsages func(privateKey interface{}) []certificates.KeyUsage // CertificateStore is a persistent store where the current cert/key is // kept and future cert/key pairs will be persisted after they are // generated. @@ -192,7 +265,7 @@ type manager struct { dynamicTemplate bool signerName string requestedCertificateLifetime *time.Duration - usages []certificates.KeyUsage + getUsages func(privateKey interface{}) []certificates.KeyUsage forceRotation bool certStore Store @@ -235,6 +308,18 @@ func NewManager(config *Config) (Manager, error) { getTemplate = func() *x509.CertificateRequest { return config.Template } } + if config.GetUsages != nil && config.Usages != nil { + return nil, errors.New("cannot specify both GetUsages and Usages") + } + if config.GetUsages == nil && config.Usages == nil { + return nil, errors.New("either GetUsages or Usages should be specified") + } + var getUsages func(interface{}) []certificates.KeyUsage + if config.GetUsages != nil { + getUsages = config.GetUsages + } else { + getUsages = func(interface{}) []certificates.KeyUsage { return config.Usages } + } m := manager{ stopCh: make(chan struct{}), clientsetFn: config.ClientsetFn, @@ -242,7 +327,7 @@ func NewManager(config *Config) (Manager, error) { dynamicTemplate: config.GetTemplate != nil, signerName: config.SignerName, requestedCertificateLifetime: config.RequestedCertificateLifetime, - usages: config.Usages, + getUsages: getUsages, certStore: config.CertificateStore, cert: cert, forceRotation: forceRotation, @@ -256,8 +341,9 @@ func NewManager(config *Config) (Manager, error) { name = m.signerName } if len(name) == 0 { + usages := getUsages(nil) switch { - case hasKeyUsage(config.Usages, certificates.UsageClientAuth): + case hasKeyUsage(usages, certificates.UsageClientAuth): name = string(certificates.UsageClientAuth) default: name = "certificate" @@ -416,7 +502,7 @@ func getCurrentCertificateOrBootstrap( bootstrapCert.Leaf = certs[0] if _, err := store.Update(bootstrapCertificatePEM, bootstrapKeyPEM); err != nil { - utilruntime.HandleError(fmt.Errorf("Unable to set the cert/key pair to the bootstrap certificate: %v", err)) + utilruntime.HandleError(fmt.Errorf("unable to set the cert/key pair to the bootstrap certificate: %v", err)) } return &bootstrapCert, true, nil @@ -464,9 +550,14 @@ func (m *manager) rotateCerts() (bool, error) { return false, nil } + getUsages := m.getUsages + if m.getUsages == nil { + getUsages = DefaultKubeletClientGetUsages + } + usages := getUsages(privateKey) // Call the Certificate Signing Request API to get a certificate for the - // new private key. - reqName, reqUID, err := csr.RequestCertificate(clientSet, csrPEM, "", m.signerName, m.requestedCertificateLifetime, m.usages, privateKey) + // new private key + reqName, reqUID, err := csr.RequestCertificate(clientSet, csrPEM, "", m.signerName, m.requestedCertificateLifetime, usages, privateKey) if err != nil { utilruntime.HandleError(fmt.Errorf("%s: Failed while requesting a signed certificate from the control plane: %v", m.name, err)) if m.certificateRenewFailure != nil { @@ -622,17 +713,17 @@ func (m *manager) updateServerError(err error) error { m.certAccessLock.Lock() defer m.certAccessLock.Unlock() switch { - case errors.IsUnauthorized(err): + case apierrors.IsUnauthorized(err): // SSL terminating proxies may report this error instead of the master m.serverHealth = true - case errors.IsUnexpectedServerError(err): + case apierrors.IsUnexpectedServerError(err): // generally indicates a proxy or other load balancer problem, rather than a problem coming // from the master m.serverHealth = false default: // Identify known errors that could be expected for a cert request that // indicate everything is working normally - m.serverHealth = errors.IsNotFound(err) || errors.IsForbidden(err) + m.serverHealth = apierrors.IsNotFound(err) || apierrors.IsForbidden(err) } return nil } diff --git a/util/certificate/certificate_manager_test.go b/util/certificate/certificate_manager_test.go index 291c7245..df1fd1d5 100644 --- a/util/certificate/certificate_manager_test.go +++ b/util/certificate/certificate_manager_test.go @@ -276,7 +276,6 @@ func TestSetRotationDeadline(t *testing.T) { }, }, getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} }, - usages: []certificatesv1.KeyUsage{}, now: func() time.Time { return now }, logf: t.Logf, } @@ -472,7 +471,6 @@ func TestRotateCertCreateCSRError(t *testing.T) { }, }, getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} }, - usages: []certificatesv1.KeyUsage{}, clientsetFn: func(_ *tls.Certificate) (clientset.Interface, error) { return newClientset(fakeClient{failureType: createError}), nil }, @@ -497,7 +495,6 @@ func TestRotateCertWaitingForResultError(t *testing.T) { }, }, getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} }, - usages: []certificatesv1.KeyUsage{}, clientsetFn: func(_ *tls.Certificate) (clientset.Interface, error) { return newClientset(fakeClient{failureType: watchError}), nil },