diff --git a/util/certificate/certificate_manager.go b/util/certificate/certificate_manager.go index 26a01a1d..dee21cf3 100644 --- a/util/certificate/certificate_manager.go +++ b/util/certificate/certificate_manager.go @@ -88,6 +88,11 @@ type Config struct { // SignerName is the name of the certificate signer that should sign certificates // generated by the manager. SignerName string + // RequestedCertificateLifetime is the requested lifetime length for certificates generated by the manager. + // Optional. + // This will set the spec.expirationSeconds field on the CSR. Controlling the lifetime of + // 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. Usages []certificates.KeyUsage @@ -184,10 +189,11 @@ type manager struct { lastRequestCancel context.CancelFunc lastRequest *x509.CertificateRequest - dynamicTemplate bool - signerName string - usages []certificates.KeyUsage - forceRotation bool + dynamicTemplate bool + signerName string + requestedCertificateLifetime *time.Duration + usages []certificates.KeyUsage + forceRotation bool certStore Store @@ -230,18 +236,19 @@ func NewManager(config *Config) (Manager, error) { } m := manager{ - stopCh: make(chan struct{}), - clientsetFn: config.ClientsetFn, - getTemplate: getTemplate, - dynamicTemplate: config.GetTemplate != nil, - signerName: config.SignerName, - usages: config.Usages, - certStore: config.CertificateStore, - cert: cert, - forceRotation: forceRotation, - certificateRotation: config.CertificateRotation, - certificateRenewFailure: config.CertificateRenewFailure, - now: time.Now, + stopCh: make(chan struct{}), + clientsetFn: config.ClientsetFn, + getTemplate: getTemplate, + dynamicTemplate: config.GetTemplate != nil, + signerName: config.SignerName, + requestedCertificateLifetime: config.RequestedCertificateLifetime, + usages: config.Usages, + certStore: config.CertificateStore, + cert: cert, + forceRotation: forceRotation, + certificateRotation: config.CertificateRotation, + certificateRenewFailure: config.CertificateRenewFailure, + now: time.Now, } name := config.Name @@ -459,7 +466,7 @@ func (m *manager) rotateCerts() (bool, error) { // 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.usages, privateKey) + reqName, reqUID, err := csr.RequestCertificate(clientSet, csrPEM, "", m.signerName, m.requestedCertificateLifetime, m.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 { diff --git a/util/certificate/csr/csr.go b/util/certificate/csr/csr.go index ec117663..0017007a 100644 --- a/util/certificate/csr/csr.go +++ b/util/certificate/csr/csr.go @@ -25,8 +25,6 @@ import ( "reflect" "time" - "k8s.io/klog/v2" - certificatesv1 "k8s.io/api/certificates/v1" certificatesv1beta1 "k8s.io/api/certificates/v1beta1" "k8s.io/apimachinery/pkg/api/errors" @@ -41,12 +39,16 @@ import ( "k8s.io/client-go/tools/cache" watchtools "k8s.io/client-go/tools/watch" certutil "k8s.io/client-go/util/cert" + "k8s.io/klog/v2" + "k8s.io/utils/pointer" ) // RequestCertificate will either use an existing (if this process has run // before but not to completion) or create a certificate signing request using the -// PEM encoded CSR and send it to API server. -func RequestCertificate(client clientset.Interface, csrData []byte, name string, signerName string, usages []certificatesv1.KeyUsage, privateKey interface{}) (reqName string, reqUID types.UID, err error) { +// PEM encoded CSR and send it to API server. An optional requestedDuration may be passed +// to set the spec.expirationSeconds field on the CSR to control the lifetime of the issued +// certificate. This is not guaranteed as the signer may choose to ignore the request. +func RequestCertificate(client clientset.Interface, csrData []byte, name, signerName string, requestedDuration *time.Duration, usages []certificatesv1.KeyUsage, privateKey interface{}) (reqName string, reqUID types.UID, err error) { csr := &certificatesv1.CertificateSigningRequest{ // Username, UID, Groups will be injected by API server. TypeMeta: metav1.TypeMeta{Kind: "CertificateSigningRequest"}, @@ -62,6 +64,9 @@ func RequestCertificate(client clientset.Interface, csrData []byte, name string, if len(csr.Name) == 0 { csr.GenerateName = "csr-" } + if requestedDuration != nil { + csr.Spec.ExpirationSeconds = DurationToExpirationSeconds(*requestedDuration) + } reqName, reqUID, err = create(client, csr) switch { @@ -85,6 +90,14 @@ func RequestCertificate(client clientset.Interface, csrData []byte, name string, } } +func DurationToExpirationSeconds(duration time.Duration) *int32 { + return pointer.Int32(int32(duration / time.Second)) +} + +func ExpirationSecondsToDuration(expirationSeconds int32) time.Duration { + return time.Duration(expirationSeconds) * time.Second +} + func get(client clientset.Interface, name string) (*certificatesv1.CertificateSigningRequest, error) { v1req, v1err := client.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) if v1err == nil || !apierrors.IsNotFound(v1err) {