diff --git a/internal/controller/certificates/policies/checks.go b/internal/controller/certificates/policies/checks.go index 52d47d1ab..9732c662f 100644 --- a/internal/controller/certificates/policies/checks.go +++ b/internal/controller/certificates/policies/checks.go @@ -94,6 +94,64 @@ func SecretPrivateKeyMatchesSpec(input Input) (string, string, bool) { return "", "", false } +// SecretKeystoreFormatMatchesSpec - When the keystore is not defined, the keystore +// related fields are removed from the secret. +// When one or more key stores are defined, re-issuance ensure that the +// corresponding secrets are generated. +// If the private key rotation is set to "Never", the key store related values are re-encoded +// as per the certificate specification +func SecretKeystoreFormatMatchesSpec(input Input) (string, string, bool) { + if input.Certificate.Spec.Keystores == nil { + if len(input.Secret.Data[cmapi.Pkcs12SecretKey]) != 0 || + len(input.Secret.Data[cmapi.Pkcs12TruststoreKey]) != 0 || + len(input.Secret.Data[cmapi.JksSecretKey]) != 0 || + len(input.Secret.Data[cmapi.JksTruststoreKey]) != 0 { + return SecretMismatch, "Keystore is not defined", true + } + return "", "", false + } + + if input.Certificate.Spec.Keystores.JKS != nil { + if input.Certificate.Spec.Keystores.JKS.Create { + if len(input.Secret.Data[cmapi.JksSecretKey]) == 0 || + len(input.Secret.Data[cmapi.JksTruststoreKey]) == 0 { + return SecretMismatch, "JKS Keystore keys does not contain data", true + } + } else { + if len(input.Secret.Data[cmapi.JksSecretKey]) != 0 || + len(input.Secret.Data[cmapi.JksTruststoreKey]) != 0 { + return SecretMismatch, "JKS Keystore create disabled", true + } + } + } else { + if len(input.Secret.Data[cmapi.JksSecretKey]) != 0 || + len(input.Secret.Data[cmapi.JksTruststoreKey]) != 0 { + return SecretMismatch, "JKS Keystore not defined", true + } + } + + if input.Certificate.Spec.Keystores.PKCS12 != nil { + if input.Certificate.Spec.Keystores.PKCS12.Create { + if len(input.Secret.Data[cmapi.Pkcs12SecretKey]) == 0 || + len(input.Secret.Data[cmapi.Pkcs12TruststoreKey]) == 0 { + return SecretMismatch, "PKCS12 Keystore keys does not contain data", true + } + } else { + if len(input.Secret.Data[cmapi.Pkcs12SecretKey]) != 0 || + len(input.Secret.Data[cmapi.Pkcs12TruststoreKey]) != 0 { + return SecretMismatch, "PKCS12 Keystore create disabled", true + } + } + } else { + if len(input.Secret.Data[cmapi.Pkcs12SecretKey]) != 0 || + len(input.Secret.Data[cmapi.Pkcs12TruststoreKey]) != 0 { + return SecretMismatch, "PKCS12 Keystore not defined", true + } + } + + return "", "", false +} + func SecretIssuerAnnotationsNotUpToDate(input Input) (string, string, bool) { name := input.Secret.Annotations[cmapi.IssuerNameAnnotationKey] kind := input.Secret.Annotations[cmapi.IssuerKindAnnotationKey] diff --git a/internal/controller/certificates/policies/policies.go b/internal/controller/certificates/policies/policies.go index 20e5893f2..d5bb6c75a 100644 --- a/internal/controller/certificates/policies/policies.go +++ b/internal/controller/certificates/policies/policies.go @@ -100,6 +100,7 @@ func NewSecretPostIssuancePolicyChain(ownerRefEnabled bool, fieldManager string) SecretAdditionalOutputFormatsOwnerMismatch(fieldManager), SecretOwnerReferenceManagedFieldMismatch(ownerRefEnabled, fieldManager), SecretOwnerReferenceValueMismatch(ownerRefEnabled), + SecretKeystoreFormatMatchesSpec, } } diff --git a/pkg/apis/certmanager/v1/types.go b/pkg/apis/certmanager/v1/types.go index a3fa3ae35..2561bd21e 100644 --- a/pkg/apis/certmanager/v1/types.go +++ b/pkg/apis/certmanager/v1/types.go @@ -233,6 +233,21 @@ const ( UsageNetscapeSGC KeyUsage = "netscape sgc" ) +// Keystore specific secret keys +const ( + // Pkcs12SecretKey is the name of the data entry in the Secret resource + // used to store the p12 file. + Pkcs12SecretKey = "keystore.p12" + // Data Entry Name in the Secret resource for PKCS12 containing Certificate Authority + Pkcs12TruststoreKey = "truststore.p12" + + // JksSecretKey is the name of the data entry in the Secret resource + // used to store the jks file. + JksSecretKey = "keystore.jks" + // Data Entry Name in the Secret resource for JKS containing Certificate Authority + JksTruststoreKey = "truststore.jks" +) + // DefaultKeyUsages contains the default list of key usages func DefaultKeyUsages() []KeyUsage { // The serverAuth EKU is required as of Mac OS Catalina: https://support.apple.com/en-us/HT210176 diff --git a/pkg/controller/certificates/issuing/secret_manager_test.go b/pkg/controller/certificates/issuing/secret_manager_test.go index 449c3d298..17f2abf2b 100644 --- a/pkg/controller/certificates/issuing/secret_manager_test.go +++ b/pkg/controller/certificates/issuing/secret_manager_test.go @@ -492,6 +492,434 @@ func Test_ensureSecretData(t *testing.T) { }, expectedAction: false, }, + "refresh secrets when keystore is not defined and the secret has keystore/truststore fields": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-234")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "test-secret", + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-234"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-234\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.Pkcs12TruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "refresh secrets when JKS keystore is defined and the secret does not have keystore/truststore fields": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: &cmapi.JKSKeystore{ + Create: true, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: true, + }, + "refresh secrets when JKS keystore is defined, create is disabled and the secret has keystore/truststore fields": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: &cmapi.JKSKeystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.JksTruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "refresh secrets when JKS keystore is null and the secret has keystore/truststore fields": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: nil, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.JksTruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "do nothing when JKS keystore is defined and create field is set to false": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: &cmapi.JKSKeystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: false, + }, + "refresh secret when PKCS12 keystore is defined and the secret does not have keystore/truststore fields": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: &cmapi.PKCS12Keystore{ + Create: true, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: true, + }, + "refresh secret when PKCS12 keystore is defined, create is disabled and the secret has keystore/truststore fields": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: &cmapi.PKCS12Keystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.Pkcs12TruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "refresh secret when PKCS12 keystore is null and the secret has keystore/truststore fields": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: nil, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.Pkcs12TruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "do nothing when PKCS12 keystore is defined and the create is set to false": { + key: "test-namespace/test-name", + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.ObjectReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: &cmapi.PKCS12Keystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + }}}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: false, + }, } for name, test := range tests {