diff --git a/pkg/apis/certmanager/v1/types.go b/pkg/apis/certmanager/v1/types.go index a2ab4d8f7..1cbaf5713 100644 --- a/pkg/apis/certmanager/v1/types.go +++ b/pkg/apis/certmanager/v1/types.go @@ -59,6 +59,33 @@ const ( // Minimum value is 1. // If unset all CertificateRequests will be kept. RevisionHistoryLimitAnnotationKey = "cert-manager.io/revision-history-limit" + + // Annotation key used to set the PrivateKeyAlgorithm for a Certificate. + // If PrivateKeyAlgorithm is specified and `size` is not provided, + // key size of 256 will be used for `ECDSA` key algorithm and + // key size of 2048 will be used for `RSA` key algorithm. + // key size is ignored when using the `Ed25519` key algorithm. + // If unset an algorithm `RSA` will be used. + PrivateKeyAlgorithmAnnotationKey = "cert-manager.io/private-key-algorithm" + + // Annotation key used to set the PrivateKeyEncoding for a Certificate. + // If provided, allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 + // and PKCS#8, respectively. + // If unset an encoding `PKCS1` will be used. + PrivateKeyEncodingAnnotationKey = "cert-manager.io/private-key-encoding" + + // Annotation key used to set the size of the private key for a Certificate. + // If PrivateKeyAlgorithm is set to `RSA`, valid values are `2048`, `4096` or `8192`, + // and will default to `2048` if not specified. + // If PrivateKeyAlgorithm is set to `ECDSA`, valid values are `256`, `384` or `521`, + // and will default to `256` if not specified. + // If PrivateKeyAlgorithm is set to `Ed25519`, Size is ignored. + // No other values are allowed. + PrivateKeySizeAnnotationKey = "cert-manager.io/private-key-size" + + // Annotation key used to set the PrivateKeyRotationPolicy for a Certificate. + // If unset a policy `Never` will be used. + PrivateKeyRotationPolicyAnnotationKey = "cert-manager.io/private-key-rotation-policy" ) const ( diff --git a/pkg/controller/certificate-shim/helper.go b/pkg/controller/certificate-shim/helper.go index a2d4a9e60..5d9c8818b 100644 --- a/pkg/controller/certificate-shim/helper.go +++ b/pkg/controller/certificate-shim/helper.go @@ -110,5 +110,84 @@ func translateAnnotations(crt *cmapi.Certificate, ingLikeAnnotations map[string] crt.Spec.RevisionHistoryLimit = pointer.Int32(int32(limit)) } + if privateKeyAlgorithm, found := ingLikeAnnotations[cmapi.PrivateKeyAlgorithmAnnotationKey]; found { + algorithm := cmapi.PrivateKeyAlgorithm(privateKeyAlgorithm) + switch algorithm { + case cmapi.RSAKeyAlgorithm, + cmapi.ECDSAKeyAlgorithm, + cmapi.Ed25519KeyAlgorithm: + // ok + default: + return fmt.Errorf("%w %q: invalid private key algorithm %q", errInvalidIngressAnnotation, cmapi.PrivateKeyAlgorithmAnnotationKey, privateKeyAlgorithm) + } + + if crt.Spec.PrivateKey == nil { + crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{Algorithm: algorithm} + } else { + crt.Spec.PrivateKey.Algorithm = algorithm + } + } + + if privateKeyEncoding, found := ingLikeAnnotations[cmapi.PrivateKeyEncodingAnnotationKey]; found { + encoding := cmapi.PrivateKeyEncoding(privateKeyEncoding) + if encoding != cmapi.PKCS1 && + encoding != cmapi.PKCS8 { + return fmt.Errorf("%w %q: invalid private key encoding %q", errInvalidIngressAnnotation, cmapi.PrivateKeyEncodingAnnotationKey, privateKeyEncoding) + } + + if crt.Spec.PrivateKey == nil { + crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{Encoding: encoding} + } else { + crt.Spec.PrivateKey.Encoding = encoding + } + } + + if privateKeySize, found := ingLikeAnnotations[cmapi.PrivateKeySizeAnnotationKey]; found { + size, err := strconv.Atoi(privateKeySize) + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.PrivateKeySizeAnnotationKey, err) + } + + // default algorithm + algorithm := cmapi.RSAKeyAlgorithm + if crt.Spec.PrivateKey != nil && crt.Spec.PrivateKey.Algorithm != "" { + algorithm = crt.Spec.PrivateKey.Algorithm + } + + switch algorithm { + case cmapi.RSAKeyAlgorithm: + if size < 2048 || size > 8192 { + return fmt.Errorf("%w %q: invalid private key size for RSA algorithm %q", errInvalidIngressAnnotation, cmapi.PrivateKeySizeAnnotationKey, privateKeySize) + } + case cmapi.ECDSAKeyAlgorithm: + switch size { + case 256, 384, 521: + // ok + default: + return fmt.Errorf("%w %q: invalid private key size for ECDSA algorithm %q", errInvalidIngressAnnotation, cmapi.PrivateKeySizeAnnotationKey, privateKeySize) + } + } + + if crt.Spec.PrivateKey == nil { + crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{Size: size} + } else { + crt.Spec.PrivateKey.Size = size + } + } + + if privateKeyRotationPolicy, found := ingLikeAnnotations[cmapi.PrivateKeyRotationPolicyAnnotationKey]; found { + rotationPolicy := cmapi.PrivateKeyRotationPolicy(privateKeyRotationPolicy) + if rotationPolicy != cmapi.RotationPolicyNever && + rotationPolicy != cmapi.RotationPolicyAlways { + return fmt.Errorf("%w %q: invalid private key rotation policy %q", errInvalidIngressAnnotation, cmapi.PrivateKeyRotationPolicyAnnotationKey, privateKeyRotationPolicy) + } + + if crt.Spec.PrivateKey == nil { + crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{RotationPolicy: rotationPolicy} + } else { + crt.Spec.PrivateKey.RotationPolicy = rotationPolicy + } + } + return nil } diff --git a/pkg/controller/certificate-shim/helper_test.go b/pkg/controller/certificate-shim/helper_test.go index d1298ed05..fdf28bd0a 100644 --- a/pkg/controller/certificate-shim/helper_test.go +++ b/pkg/controller/certificate-shim/helper_test.go @@ -60,6 +60,73 @@ func Test_translateAnnotations(t *testing.T) { a.Equal(pointer.Int32(7), crt.Spec.RevisionHistoryLimit) }, }, + "success rsa private key algorithm": { + crt: gen.Certificate("example-cert"), + annotations: map[string]string{ + cmapi.CommonNameAnnotationKey: "www.example.com", + cmapi.DurationAnnotationKey: "168h", // 1 week + cmapi.RenewBeforeAnnotationKey: "24h", + cmapi.UsagesAnnotationKey: "server auth,signing", + cmapi.PrivateKeyAlgorithmAnnotationKey: "RSA", + cmapi.PrivateKeyEncodingAnnotationKey: "PKCS1", + cmapi.PrivateKeySizeAnnotationKey: "2048", + cmapi.PrivateKeyRotationPolicyAnnotationKey: "Always", + }, + check: func(a *assert.Assertions, crt *cmapi.Certificate) { + a.Equal("www.example.com", crt.Spec.CommonName) + a.Equal(&metav1.Duration{Duration: time.Hour * 24 * 7}, crt.Spec.Duration) + a.Equal(&metav1.Duration{Duration: time.Hour * 24}, crt.Spec.RenewBefore) + a.Equal([]cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageSigning}, crt.Spec.Usages) + a.Equal(cmapi.RSAKeyAlgorithm, crt.Spec.PrivateKey.Algorithm) + a.Equal(cmapi.PKCS1, crt.Spec.PrivateKey.Encoding) + a.Equal(2048, crt.Spec.PrivateKey.Size) + a.Equal(cmapi.RotationPolicyAlways, crt.Spec.PrivateKey.RotationPolicy) + }, + }, + "success ecdsa private key algorithm": { + crt: gen.Certificate("example-cert"), + annotations: map[string]string{ + cmapi.CommonNameAnnotationKey: "www.example.com", + cmapi.DurationAnnotationKey: "168h", // 1 week + cmapi.RenewBeforeAnnotationKey: "24h", + cmapi.UsagesAnnotationKey: "server auth,signing", + cmapi.PrivateKeyAlgorithmAnnotationKey: "ECDSA", + cmapi.PrivateKeyEncodingAnnotationKey: "PKCS1", + cmapi.PrivateKeySizeAnnotationKey: "256", + cmapi.PrivateKeyRotationPolicyAnnotationKey: "Always", + }, + check: func(a *assert.Assertions, crt *cmapi.Certificate) { + a.Equal("www.example.com", crt.Spec.CommonName) + a.Equal(&metav1.Duration{Duration: time.Hour * 24 * 7}, crt.Spec.Duration) + a.Equal(&metav1.Duration{Duration: time.Hour * 24}, crt.Spec.RenewBefore) + a.Equal([]cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageSigning}, crt.Spec.Usages) + a.Equal(cmapi.ECDSAKeyAlgorithm, crt.Spec.PrivateKey.Algorithm) + a.Equal(cmapi.PKCS1, crt.Spec.PrivateKey.Encoding) + a.Equal(256, crt.Spec.PrivateKey.Size) + a.Equal(cmapi.RotationPolicyAlways, crt.Spec.PrivateKey.RotationPolicy) + }, + }, + "success ed25519 private key algorithm": { + crt: gen.Certificate("example-cert"), + annotations: map[string]string{ + cmapi.CommonNameAnnotationKey: "www.example.com", + cmapi.DurationAnnotationKey: "168h", // 1 week + cmapi.RenewBeforeAnnotationKey: "24h", + cmapi.UsagesAnnotationKey: "server auth,signing", + cmapi.PrivateKeyAlgorithmAnnotationKey: "Ed25519", + cmapi.PrivateKeyEncodingAnnotationKey: "PKCS8", + cmapi.PrivateKeyRotationPolicyAnnotationKey: "Always", + }, + check: func(a *assert.Assertions, crt *cmapi.Certificate) { + a.Equal("www.example.com", crt.Spec.CommonName) + a.Equal(&metav1.Duration{Duration: time.Hour * 24 * 7}, crt.Spec.Duration) + a.Equal(&metav1.Duration{Duration: time.Hour * 24}, crt.Spec.RenewBefore) + a.Equal([]cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageSigning}, crt.Spec.Usages) + a.Equal(cmapi.Ed25519KeyAlgorithm, crt.Spec.PrivateKey.Algorithm) + a.Equal(cmapi.PKCS8, crt.Spec.PrivateKey.Encoding) + a.Equal(cmapi.RotationPolicyAlways, crt.Spec.PrivateKey.RotationPolicy) + }, + }, "nil annotations": { crt: gen.Certificate("example-cert"), annotations: nil, @@ -121,6 +188,64 @@ func Test_translateAnnotations(t *testing.T) { }, expectedError: errInvalidIngressAnnotation, }, + "bad private key algorithm": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.PrivateKeyAlgorithmAnnotationKey] = "invalid private key algorithm" + }, + expectedError: errInvalidIngressAnnotation, + }, + "bad private key encoding": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.PrivateKeyEncodingAnnotationKey] = "invalid private key encoding" + }, + expectedError: errInvalidIngressAnnotation, + }, + "bad private key rotation policy": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.PrivateKeyRotationPolicyAnnotationKey] = "invalid private key rotation policy" + }, + expectedError: errInvalidIngressAnnotation, + }, + "bad private key size": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.PrivateKeySizeAnnotationKey] = "invalid private key size" + }, + expectedError: errInvalidIngressAnnotation, + }, + "zero private key size": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.PrivateKeySizeAnnotationKey] = "0" + }, + expectedError: errInvalidIngressAnnotation, + }, + "bad private key size for rsa": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.PrivateKeyAlgorithmAnnotationKey] = "RSA" + tc.annotations[cmapi.PrivateKeySizeAnnotationKey] = "1024" + }, + expectedError: errInvalidIngressAnnotation, + }, + "bad private key size for ecdsa": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.PrivateKeyAlgorithmAnnotationKey] = "ECDSA" + tc.annotations[cmapi.PrivateKeySizeAnnotationKey] = "128" + }, + expectedError: errInvalidIngressAnnotation, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/pkg/controller/certificate-shim/sync.go b/pkg/controller/certificate-shim/sync.go index 9d038b0f3..7fadb0512 100644 --- a/pkg/controller/certificate-shim/sync.go +++ b/pkg/controller/certificate-shim/sync.go @@ -508,6 +508,65 @@ func certNeedsUpdate(a, b *cmapi.Certificate) bool { return true } + if a.Spec.RevisionHistoryLimit != b.Spec.RevisionHistoryLimit { + return true + } + + var aAlgorithm, bAlgorithm cmapi.PrivateKeyAlgorithm + if a.Spec.PrivateKey != nil && a.Spec.PrivateKey.Algorithm != "" { + aAlgorithm = a.Spec.PrivateKey.Algorithm + } + + if b.Spec.PrivateKey != nil && b.Spec.PrivateKey.Algorithm != "" { + bAlgorithm = b.Spec.PrivateKey.Algorithm + } + + if aAlgorithm != bAlgorithm { + return true + } + + var aEncoding, bEncoding cmapi.PrivateKeyEncoding + if a.Spec.PrivateKey != nil && a.Spec.PrivateKey.Encoding != "" { + aEncoding = a.Spec.PrivateKey.Encoding + } + + if b.Spec.PrivateKey != nil && b.Spec.PrivateKey.Encoding != "" { + bEncoding = b.Spec.PrivateKey.Encoding + } + + if aEncoding != bEncoding { + return true + } + + var aRotationPolicy, bRotationPolicy cmapi.PrivateKeyRotationPolicy + if a.Spec.PrivateKey != nil && a.Spec.PrivateKey.RotationPolicy != "" { + aRotationPolicy = a.Spec.PrivateKey.RotationPolicy + } + + if b.Spec.PrivateKey != nil && b.Spec.PrivateKey.RotationPolicy != "" { + bRotationPolicy = b.Spec.PrivateKey.RotationPolicy + } + + if aRotationPolicy != bRotationPolicy { + return true + } + + // for Ed25519 private key size is ignored + if aAlgorithm != cmapi.Ed25519KeyAlgorithm { + var aSize, bSize int + if a.Spec.PrivateKey != nil && a.Spec.PrivateKey.Size != 0 { + aSize = a.Spec.PrivateKey.Size + } + + if b.Spec.PrivateKey != nil && b.Spec.PrivateKey.Size != 0 { + bSize = b.Spec.PrivateKey.Size + } + + if aSize != bSize { + return true + } + } + return false } diff --git a/pkg/controller/certificate-shim/sync_test.go b/pkg/controller/certificate-shim/sync_test.go index cde2045a8..757874c46 100644 --- a/pkg/controller/certificate-shim/sync_test.go +++ b/pkg/controller/certificate-shim/sync_test.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" coretesting "k8s.io/client-go/testing" + "k8s.io/utils/pointer" gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" @@ -787,6 +788,360 @@ func TestSync(t *testing.T) { }, }, }, + { + Name: "should update an existing Certificate resource with different revision limit if it does not match specified on the IngressLike", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuerNewFormat}, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.RevisionHistoryLimitAnnotationKey: "1", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com"}, + SecretName: "cert-secret-name", + }, + }, + }, + }, + DefaultIssuerKind: "Issuer", + CertificateLister: []runtime.Object{ + &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + RevisionHistoryLimit: pointer.Int32(7), + }, + }, + }, + ExpectedEvents: []string{`Normal UpdateCertificate Successfully updated Certificate "cert-secret-name"`}, + ExpectedUpdate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + RevisionHistoryLimit: pointer.Int32(1), + }, + }, + }, + }, + { + Name: "should update an existing Certificate resource with different rsa private key size if it does not match specified on the IngressLike", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuerNewFormat}, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.PrivateKeyAlgorithmAnnotationKey: "RSA", + cmapi.PrivateKeySizeAnnotationKey: "4096", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com"}, + SecretName: "cert-secret-name", + }, + }, + }, + }, + DefaultIssuerKind: "Issuer", + CertificateLister: []runtime.Object{ + &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.RSAKeyAlgorithm, + Size: 2048, + }, + }, + }, + }, + ExpectedEvents: []string{`Normal UpdateCertificate Successfully updated Certificate "cert-secret-name"`}, + ExpectedUpdate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.RSAKeyAlgorithm, + Size: 4096, + }, + }, + }, + }, + }, + { + Name: "should update an existing Certificate resource with different ecdsa private key size if it does not match specified on the IngressLike", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuerNewFormat}, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.PrivateKeyAlgorithmAnnotationKey: "ECDSA", + cmapi.PrivateKeySizeAnnotationKey: "384", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com"}, + SecretName: "cert-secret-name", + }, + }, + }, + }, + DefaultIssuerKind: "Issuer", + CertificateLister: []runtime.Object{ + &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + Size: 256, + }, + }, + }, + }, + ExpectedEvents: []string{`Normal UpdateCertificate Successfully updated Certificate "cert-secret-name"`}, + ExpectedUpdate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + Size: 384, + }, + }, + }, + }, + }, + { + Name: "should update an existing Certificate resource with different private key encoding if it does not match specified on the IngressLike", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuerNewFormat}, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.PrivateKeyAlgorithmAnnotationKey: "ECDSA", + cmapi.PrivateKeyEncodingAnnotationKey: "PKCS8", + cmapi.PrivateKeySizeAnnotationKey: "384", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com"}, + SecretName: "cert-secret-name", + }, + }, + }, + }, + DefaultIssuerKind: "Issuer", + CertificateLister: []runtime.Object{ + &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + Size: 384, + }, + }, + }, + }, + ExpectedEvents: []string{`Normal UpdateCertificate Successfully updated Certificate "cert-secret-name"`}, + ExpectedUpdate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + Encoding: cmapi.PKCS8, + Size: 384, + }, + }, + }, + }, + }, + { + Name: "should update an existing Certificate resource with different private key rotation policy if it does not match specified on the IngressLike", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuerNewFormat}, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.PrivateKeyAlgorithmAnnotationKey: "ECDSA", + cmapi.PrivateKeyEncodingAnnotationKey: "PKCS1", + cmapi.PrivateKeySizeAnnotationKey: "384", + cmapi.PrivateKeyRotationPolicyAnnotationKey: "Always", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com"}, + SecretName: "cert-secret-name", + }, + }, + }, + }, + DefaultIssuerKind: "Issuer", + CertificateLister: []runtime.Object{ + &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + Size: 384, + }, + }, + }, + }, + ExpectedEvents: []string{`Normal UpdateCertificate Successfully updated Certificate "cert-secret-name"`}, + ExpectedUpdate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-secret-name", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "cert-secret-name", + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + Usages: cmapi.DefaultKeyUsages(), + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + Encoding: cmapi.PKCS1, + Size: 384, + RotationPolicy: cmapi.RotationPolicyAlways, + }, + }, + }, + }, + }, { Name: "should not update certificate if it does not belong to any ingress", Issuer: acmeIssuer, @@ -1027,11 +1382,15 @@ func TestSync(t *testing.T) { Name: "ingress-name", Namespace: gen.DefaultTestNamespace, Annotations: map[string]string{ - cmapi.IngressIssuerNameAnnotationKey: "issuer-name", - cmapi.IssuerKindAnnotationKey: "Issuer", - cmapi.IssuerGroupAnnotationKey: "cert-manager.io", - cmapi.RenewBeforeAnnotationKey: "invalid renew before value", - cmapi.RevisionHistoryLimitAnnotationKey: "invalid revision history limit value", + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.IssuerKindAnnotationKey: "Issuer", + cmapi.IssuerGroupAnnotationKey: "cert-manager.io", + cmapi.RenewBeforeAnnotationKey: "invalid renew before value", + cmapi.RevisionHistoryLimitAnnotationKey: "invalid revision history limit value", + cmapi.PrivateKeyAlgorithmAnnotationKey: "invalid private key algorithm value", + cmapi.PrivateKeyEncodingAnnotationKey: "invalid private key encoding value", + cmapi.PrivateKeySizeAnnotationKey: "invalid private key size value", + cmapi.PrivateKeyRotationPolicyAnnotationKey: "invalid private key rotation policy value", }, UID: types.UID("ingress-name"), }, diff --git a/test/e2e/suite/conformance/certificates/tests.go b/test/e2e/suite/conformance/certificates/tests.go index f46fa2f13..307e9da61 100644 --- a/test/e2e/suite/conformance/certificates/tests.go +++ b/test/e2e/suite/conformance/certificates/tests.go @@ -786,7 +786,11 @@ func (s *Suite) Define() { domain := e2eutil.RandomSubdomain(s.DomainSuffix) duration := time.Hour * 999 renewBefore := time.Hour * 111 - revisitionHistoryLimit := pointer.Int32(7) + revisionHistoryLimit := pointer.Int32(7) + privateKeyAlgorithm := cmapi.RSAKeyAlgorithm + privateKeyEncoding := cmapi.PKCS1 + privateKeySize := 4096 + privateKeyRotationPolicy := cmapi.RotationPolicyAlways switch { case e2eutil.HasIngresses(f.KubeClientSet.Discovery(), networkingv1.SchemeGroupVersion.String()): @@ -797,13 +801,17 @@ func (s *Suite) Define() { By("Creating an Ingress with annotations for issuerRef and other Certificate fields") ingress, err := ingClient.Create(context.TODO(), e2eutil.NewIngress(name, secretName, map[string]string{ - "cert-manager.io/issuer": issuerRef.Name, - "cert-manager.io/issuer-kind": issuerRef.Kind, - "cert-manager.io/issuer-group": issuerRef.Group, - "cert-manager.io/common-name": domain, - "cert-manager.io/duration": duration.String(), - "cert-manager.io/renew-before": renewBefore.String(), - "cert-manager.io/revision-history-limit": strconv.FormatInt(int64(*revisitionHistoryLimit), 10), + "cert-manager.io/issuer": issuerRef.Name, + "cert-manager.io/issuer-kind": issuerRef.Kind, + "cert-manager.io/issuer-group": issuerRef.Group, + "cert-manager.io/common-name": domain, + "cert-manager.io/duration": duration.String(), + "cert-manager.io/renew-before": renewBefore.String(), + "cert-manager.io/revision-history-limit": strconv.FormatInt(int64(*revisionHistoryLimit), 10), + "cert-manager.io/private-key-algorithm": string(privateKeyAlgorithm), + "cert-manager.io/private-key-encoding": string(privateKeyEncoding), + "cert-manager.io/private-key-size": strconv.Itoa(privateKeySize), + "cert-manager.io/private-key-rotation-policy": string(privateKeyRotationPolicy), }, domain), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -816,13 +824,17 @@ func (s *Suite) Define() { By("Creating an Ingress with annotations for issuerRef and other Certificate fields") ingress, err := ingClient.Create(context.TODO(), e2eutil.NewV1Beta1Ingress(name, secretName, map[string]string{ - "cert-manager.io/issuer": issuerRef.Name, - "cert-manager.io/issuer-kind": issuerRef.Kind, - "cert-manager.io/issuer-group": issuerRef.Group, - "cert-manager.io/common-name": domain, - "cert-manager.io/duration": duration.String(), - "cert-manager.io/renew-before": renewBefore.String(), - "cert-manager.io/revision-history-limit": strconv.FormatInt(int64(*revisitionHistoryLimit), 10), + "cert-manager.io/issuer": issuerRef.Name, + "cert-manager.io/issuer-kind": issuerRef.Kind, + "cert-manager.io/issuer-group": issuerRef.Group, + "cert-manager.io/common-name": domain, + "cert-manager.io/duration": duration.String(), + "cert-manager.io/renew-before": renewBefore.String(), + "cert-manager.io/revision-history-limit": strconv.FormatInt(int64(*revisionHistoryLimit), 10), + "cert-manager.io/private-key-algorithm": string(privateKeyAlgorithm), + "cert-manager.io/private-key-encoding": string(privateKeyEncoding), + "cert-manager.io/private-key-size": strconv.Itoa(privateKeySize), + "cert-manager.io/private-key-rotation-policy": string(privateKeyRotationPolicy), }, domain), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -849,7 +861,11 @@ func (s *Suite) Define() { Expect(certificate.Spec.CommonName).To(Equal(domain)) Expect(certificate.Spec.Duration.Duration).To(Equal(duration)) Expect(certificate.Spec.RenewBefore.Duration).To(Equal(renewBefore)) - Expect(certificate.Spec.RevisionHistoryLimit).To(Equal(revisitionHistoryLimit)) + Expect(certificate.Spec.RevisionHistoryLimit).To(Equal(revisionHistoryLimit)) + Expect(certificate.Spec.PrivateKey.Algorithm).To(Equal(privateKeyAlgorithm)) + Expect(certificate.Spec.PrivateKey.Encoding).To(Equal(privateKeyEncoding)) + Expect(certificate.Spec.PrivateKey.Size).To(Equal(privateKeySize)) + Expect(certificate.Spec.PrivateKey.RotationPolicy).To(Equal(privateKeyRotationPolicy)) return nil }, )