Skip to content

Commit ccdcf64

Browse files
Merge pull request #1889 from vrutkovs/cert-annotations-not-before-not-after
OCPBUGS-44842: certrotation: set not-before/not-after annotations
2 parents 1aaedc9 + f42dc03 commit ccdcf64

File tree

6 files changed

+372
-25
lines changed

6 files changed

+372
-25
lines changed

pkg/operator/certrotation/annotations.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import (
88
)
99

1010
const (
11+
// CertificateNotBeforeAnnotation contains the certificate expiration date in RFC3339 format.
12+
CertificateNotBeforeAnnotation = "auth.openshift.io/certificate-not-before"
13+
// CertificateNotAfterAnnotation contains the certificate expiration date in RFC3339 format.
14+
CertificateNotAfterAnnotation = "auth.openshift.io/certificate-not-after"
15+
// CertificateIssuer contains the common name of the certificate that signed another certificate.
16+
CertificateIssuer = "auth.openshift.io/certificate-issuer"
17+
// CertificateHostnames contains the hostnames used by a signer.
18+
CertificateHostnames = "auth.openshift.io/certificate-hostnames"
19+
// AutoRegenerateAfterOfflineExpiryAnnotation contains a link to PR and an e2e test name which verifies
20+
// that TLS artifact is correctly regenerated after it has expired
1121
AutoRegenerateAfterOfflineExpiryAnnotation string = "certificates.openshift.io/auto-regenerate-after-offline-expiry"
1222
)
1323

@@ -19,6 +29,10 @@ type AdditionalAnnotations struct {
1929
// AutoRegenerateAfterOfflineExpiry contains a link to PR and an e2e test name which verifies
2030
// that TLS artifact is correctly regenerated after it has expired
2131
AutoRegenerateAfterOfflineExpiry string
32+
// NotBefore contains certificate the certificate creation date in RFC3339 format.
33+
NotBefore string
34+
// NotAfter contains certificate the certificate validity date in RFC3339 format.
35+
NotAfter string
2236
}
2337

2438
func (a AdditionalAnnotations) EnsureTLSMetadataUpdate(meta *metav1.ObjectMeta) bool {
@@ -44,6 +58,14 @@ func (a AdditionalAnnotations) EnsureTLSMetadataUpdate(meta *metav1.ObjectMeta)
4458
meta.Annotations[AutoRegenerateAfterOfflineExpiryAnnotation] = a.AutoRegenerateAfterOfflineExpiry
4559
modified = true
4660
}
61+
if len(a.NotBefore) > 0 && meta.Annotations[CertificateNotBeforeAnnotation] != a.NotBefore {
62+
meta.Annotations[CertificateNotBeforeAnnotation] = a.NotBefore
63+
modified = true
64+
}
65+
if len(a.NotAfter) > 0 && meta.Annotations[CertificateNotAfterAnnotation] != a.NotAfter {
66+
meta.Annotations[CertificateNotAfterAnnotation] = a.NotAfter
67+
modified = true
68+
}
4769
return modified
4870
}
4971

pkg/operator/certrotation/client_cert_rotation_controller.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@ import (
1515
)
1616

1717
const (
18-
// CertificateNotBeforeAnnotation contains the certificate expiration date in RFC3339 format.
19-
CertificateNotBeforeAnnotation = "auth.openshift.io/certificate-not-before"
20-
// CertificateNotAfterAnnotation contains the certificate expiration date in RFC3339 format.
21-
CertificateNotAfterAnnotation = "auth.openshift.io/certificate-not-after"
22-
// CertificateIssuer contains the common name of the certificate that signed another certificate.
23-
CertificateIssuer = "auth.openshift.io/certificate-issuer"
24-
// CertificateHostnames contains the hostnames used by a signer.
25-
CertificateHostnames = "auth.openshift.io/certificate-hostnames"
2618
// RunOnceContextKey is a context value key that can be used to call the controller Sync() and make it only run the syncWorker once and report error.
2719
RunOnceContextKey = "cert-rotation-controller.openshift.io/run-once"
2820
)

pkg/operator/certrotation/signer.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (c RotatedSigningCASecret) EnsureSigningCertKeyPair(ctx context.Context) (*
9292
reason = "secret doesn't exist"
9393
}
9494
c.EventRecorder.Eventf("SignerUpdateRequired", "%q in %q requires a new signing cert/key pair: %v", c.Name, c.Namespace, reason)
95-
if err := setSigningCertKeyPairSecret(signingCertKeyPairSecret, c.Validity); err != nil {
95+
if err := setSigningCertKeyPairSecret(signingCertKeyPairSecret, c.Validity, c.AdditionalAnnotations); err != nil {
9696
return nil, false, err
9797
}
9898

@@ -200,7 +200,7 @@ func getValidityFromAnnotations(annotations map[string]string) (notBefore time.T
200200
}
201201

202202
// setSigningCertKeyPairSecret creates a new signing cert/key pair and sets them in the secret
203-
func setSigningCertKeyPairSecret(signingCertKeyPairSecret *corev1.Secret, validity time.Duration) error {
203+
func setSigningCertKeyPairSecret(signingCertKeyPairSecret *corev1.Secret, validity time.Duration, annotations AdditionalAnnotations) error {
204204
signerName := fmt.Sprintf("%s_%s@%d", signingCertKeyPairSecret.Namespace, signingCertKeyPairSecret.Name, time.Now().Unix())
205205
ca, err := crypto.MakeSelfSignedCAConfigForDuration(signerName, validity)
206206
if err != nil {
@@ -221,9 +221,11 @@ func setSigningCertKeyPairSecret(signingCertKeyPairSecret *corev1.Secret, validi
221221
}
222222
signingCertKeyPairSecret.Data["tls.crt"] = certBytes.Bytes()
223223
signingCertKeyPairSecret.Data["tls.key"] = keyBytes.Bytes()
224-
signingCertKeyPairSecret.Annotations[CertificateNotAfterAnnotation] = ca.Certs[0].NotAfter.Format(time.RFC3339)
225-
signingCertKeyPairSecret.Annotations[CertificateNotBeforeAnnotation] = ca.Certs[0].NotBefore.Format(time.RFC3339)
224+
annotations.NotBefore = ca.Certs[0].NotBefore.Format(time.RFC3339)
225+
annotations.NotAfter = ca.Certs[0].NotAfter.Format(time.RFC3339)
226226
signingCertKeyPairSecret.Annotations[CertificateIssuer] = ca.Certs[0].Issuer.CommonName
227227

228+
_ = annotations.EnsureTLSMetadataUpdate(&signingCertKeyPairSecret.ObjectMeta)
229+
228230
return nil
229231
}

pkg/operator/certrotation/target.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ func setTargetCertKeyPairSecret(targetCertKeyPairSecret *corev1.Secret, validity
258258
if err != nil {
259259
return err
260260
}
261-
targetCertKeyPairSecret.Annotations[CertificateNotAfterAnnotation] = certKeyPair.Certs[0].NotAfter.Format(time.RFC3339)
262-
targetCertKeyPairSecret.Annotations[CertificateNotBeforeAnnotation] = certKeyPair.Certs[0].NotBefore.Format(time.RFC3339)
261+
annotations.NotBefore = certKeyPair.Certs[0].NotBefore.Format(time.RFC3339)
262+
annotations.NotAfter = certKeyPair.Certs[0].NotAfter.Format(time.RFC3339)
263263
targetCertKeyPairSecret.Annotations[CertificateIssuer] = certKeyPair.Certs[0].Issuer.CommonName
264264

265265
_ = annotations.EnsureTLSMetadataUpdate(&targetCertKeyPairSecret.ObjectMeta)

pkg/operator/csr/cert_controller.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package csr
33
import (
44
"context"
55
"crypto/tls"
6+
"crypto/x509"
67
"crypto/x509/pkix"
8+
"encoding/pem"
79
"fmt"
810
"math/rand"
911
"time"
@@ -166,7 +168,7 @@ func (c *clientCertificateController) sync(ctx context.Context, syncCtx factory.
166168

167169
// reconcile pending csr if exists
168170
if len(c.csrName) > 0 {
169-
newSecretConfig, err := c.syncCSR(secret)
171+
newSecretConfig, leaf, err := c.syncCSR(secret)
170172
if err != nil {
171173
c.reset()
172174
return err
@@ -179,6 +181,12 @@ func (c *clientCertificateController) sync(ctx context.Context, syncCtx factory.
179181
newSecretConfig[k] = v
180182
}
181183
secret.Data = newSecretConfig
184+
185+
// Update not-before/not-after annotations
186+
c.AdditionalAnnotations.NotBefore = leaf.NotBefore.Format(time.RFC3339)
187+
c.AdditionalAnnotations.NotAfter = leaf.NotAfter.Format(time.RFC3339)
188+
_ = c.AdditionalAnnotations.EnsureTLSMetadataUpdate(&secret.ObjectMeta)
189+
182190
// save the changes into secret
183191
if err := c.saveSecret(secret); err != nil {
184192
return err
@@ -231,10 +239,10 @@ func (c *clientCertificateController) sync(ctx context.Context, syncCtx factory.
231239
return nil
232240
}
233241

234-
func (c *clientCertificateController) syncCSR(secret *corev1.Secret) (map[string][]byte, error) {
242+
func (c *clientCertificateController) syncCSR(secret *corev1.Secret) (map[string][]byte, *x509.Certificate, error) {
235243
// skip if there is no ongoing csr
236244
if len(c.csrName) == 0 {
237-
return nil, fmt.Errorf("no ongoing csr")
245+
return nil, nil, fmt.Errorf("no ongoing csr")
238246
}
239247

240248
// skip if csr no longer exists
@@ -244,38 +252,48 @@ func (c *clientCertificateController) syncCSR(secret *corev1.Secret) (map[string
244252
// fallback to fetching csr from hub apiserver in case it is not cached by informer yet
245253
csr, err = c.hubCSRClient.Get(context.Background(), c.csrName, metav1.GetOptions{})
246254
if errors.IsNotFound(err) {
247-
return nil, fmt.Errorf("unable to get csr %q. It might have already been deleted.", c.csrName)
255+
return nil, nil, fmt.Errorf("unable to get csr %q. It might have already been deleted.", c.csrName)
248256
}
249257
case err != nil:
250-
return nil, err
258+
return nil, nil, err
251259
}
252260

253261
// skip if csr is not approved yet
254262
if !isCSRApproved(csr) {
255-
return nil, nil
263+
return nil, nil, nil
256264
}
257265

258266
// skip if csr has no certificate in its status yet
259267
if len(csr.Status.Certificate) == 0 {
260-
return nil, nil
268+
return nil, nil, nil
261269
}
262270

263271
klog.V(4).Infof("Sync csr %v", c.csrName)
264272
// check if cert in csr status matches with the corresponding private key
265273
if c.keyData == nil {
266-
return nil, fmt.Errorf("No private key found for certificate in csr: %s", c.csrName)
274+
return nil, nil, fmt.Errorf("No private key found for certificate in csr: %s", c.csrName)
267275
}
268276
_, err = tls.X509KeyPair(csr.Status.Certificate, c.keyData)
269277
if err != nil {
270-
return nil, fmt.Errorf("Private key does not match with the certificate in csr: %s", c.csrName)
278+
return nil, nil, fmt.Errorf("Private key does not match with the certificate in csr: %s", c.csrName)
279+
}
280+
// verify that the recieved data is a valid x509 certificate
281+
var block *pem.Block
282+
block, _ = pem.Decode(csr.Status.Certificate)
283+
if block == nil || block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
284+
return nil, nil, fmt.Errorf("invalid first block found for certificate in csr: %s", c.csrName)
271285
}
286+
certBytes := block.Bytes
287+
parsedCert, err := x509.ParseCertificate(certBytes)
272288

289+
if err != nil {
290+
return nil, nil, fmt.Errorf("failed to parse the certificate in csr %s: %v", c.csrName, err)
291+
}
273292
data := map[string][]byte{
274293
TLSCertFile: csr.Status.Certificate,
275294
TLSKeyFile: c.keyData,
276295
}
277-
278-
return data, nil
296+
return data, parsedCert, nil
279297
}
280298

281299
func (c *clientCertificateController) createCSR(ctx context.Context) (string, error) {

0 commit comments

Comments
 (0)