Refreshing secrets when the keystore fields change

Signed-off-by: Sathyanarayanan Saravanamuthu <sathyanarays@vmware.com>
This commit is contained in:
Sathyanarayanan Saravanamuthu 2022-12-04 10:25:41 +05:30
parent 37ae8b2773
commit 42ae76ae30
4 changed files with 502 additions and 0 deletions

View File

@ -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]

View File

@ -100,6 +100,7 @@ func NewSecretPostIssuancePolicyChain(ownerRefEnabled bool, fieldManager string)
SecretAdditionalOutputFormatsOwnerMismatch(fieldManager),
SecretOwnerReferenceManagedFieldMismatch(ownerRefEnabled, fieldManager),
SecretOwnerReferenceValueMismatch(ownerRefEnabled),
SecretKeystoreFormatMatchesSpec,
}
}

View File

@ -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

View File

@ -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 {