From 80bc253d745095236722ee7681fbd61918eae226 Mon Sep 17 00:00:00 2001 From: James Munnelly Date: Tue, 3 Dec 2019 17:01:37 +0000 Subject: [PATCH] acme: Add API fields for ExternalAccountBinding Signed-off-by: James Munnelly --- .../cert-manager/crds/clusterissuers.yaml | 38 ++++++++++ deploy/charts/cert-manager/crds/issuers.yaml | 38 ++++++++++ deploy/manifests/00-crds.yaml | 76 +++++++++++++++++++ pkg/apis/acme/v1alpha2/types_issuer.go | 25 ++++++ .../acme/v1alpha2/zz_generated.deepcopy.go | 22 ++++++ pkg/internal/apis/acme/types_issuer.go | 23 ++++++ .../acme/v1alpha2/zz_generated.conversion.go | 42 ++++++++++ .../apis/acme/zz_generated.deepcopy.go | 22 ++++++ .../apis/certmanager/validation/issuer.go | 14 ++++ .../certmanager/validation/issuer_test.go | 21 +++++ 10 files changed, 321 insertions(+) diff --git a/deploy/charts/cert-manager/crds/clusterissuers.yaml b/deploy/charts/cert-manager/crds/clusterissuers.yaml index 563a62572..8b7ef249e 100644 --- a/deploy/charts/cert-manager/crds/clusterissuers.yaml +++ b/deploy/charts/cert-manager/crds/clusterissuers.yaml @@ -59,6 +59,44 @@ spec: email: description: Email is the email for this account type: string + externalAccountBinding: + description: ExternalAcccountBinding is a reference to a CA external + account of the ACME server. + type: object + required: + - keyAlgorithm + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: keyAlgorithm is the MAC key algorithm that the + key is used for. Valid values are "HS256", "HS384" and "HS512". + type: string + keyID: + description: keyID is the ID of the CA key that the External + Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing + a data item in a Kubernetes Secret which holds the symmetric + MAC key of the External Account Binding. The `key` is the + index string that is paired with the key data in the Secret + and should not be confused with the key data itself, or indeed + with the External Account Binding keyID above. The secret + key stored in the Secret **must** be un-padded, base64 URL + encoded data. + type: object + required: + - name + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string privateKeySecretRef: description: PrivateKey is the name of a secret containing the private key for this user account. diff --git a/deploy/charts/cert-manager/crds/issuers.yaml b/deploy/charts/cert-manager/crds/issuers.yaml index d101481c3..9edad7b8e 100644 --- a/deploy/charts/cert-manager/crds/issuers.yaml +++ b/deploy/charts/cert-manager/crds/issuers.yaml @@ -59,6 +59,44 @@ spec: email: description: Email is the email for this account type: string + externalAccountBinding: + description: ExternalAcccountBinding is a reference to a CA external + account of the ACME server. + type: object + required: + - keyAlgorithm + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: keyAlgorithm is the MAC key algorithm that the + key is used for. Valid values are "HS256", "HS384" and "HS512". + type: string + keyID: + description: keyID is the ID of the CA key that the External + Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing + a data item in a Kubernetes Secret which holds the symmetric + MAC key of the External Account Binding. The `key` is the + index string that is paired with the key data in the Secret + and should not be confused with the key data itself, or indeed + with the External Account Binding keyID above. The secret + key stored in the Secret **must** be un-padded, base64 URL + encoded data. + type: object + required: + - name + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string privateKeySecretRef: description: PrivateKey is the name of a secret containing the private key for this user account. diff --git a/deploy/manifests/00-crds.yaml b/deploy/manifests/00-crds.yaml index 500a68bfe..686f2eeee 100644 --- a/deploy/manifests/00-crds.yaml +++ b/deploy/manifests/00-crds.yaml @@ -1886,6 +1886,44 @@ spec: email: description: Email is the email for this account type: string + externalAccountBinding: + description: ExternalAcccountBinding is a reference to a CA external + account of the ACME server. + type: object + required: + - keyAlgorithm + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: keyAlgorithm is the MAC key algorithm that the + key is used for. Valid values are "HS256", "HS384" and "HS512". + type: string + keyID: + description: keyID is the ID of the CA key that the External + Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing + a data item in a Kubernetes Secret which holds the symmetric + MAC key of the External Account Binding. The `key` is the + index string that is paired with the key data in the Secret + and should not be confused with the key data itself, or indeed + with the External Account Binding keyID above. The secret + key stored in the Secret **must** be un-padded, base64 URL + encoded data. + type: object + required: + - name + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string privateKeySecretRef: description: PrivateKey is the name of a secret containing the private key for this user account. @@ -3584,6 +3622,44 @@ spec: email: description: Email is the email for this account type: string + externalAccountBinding: + description: ExternalAcccountBinding is a reference to a CA external + account of the ACME server. + type: object + required: + - keyAlgorithm + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: keyAlgorithm is the MAC key algorithm that the + key is used for. Valid values are "HS256", "HS384" and "HS512". + type: string + keyID: + description: keyID is the ID of the CA key that the External + Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing + a data item in a Kubernetes Secret which holds the symmetric + MAC key of the External Account Binding. The `key` is the + index string that is paired with the key data in the Secret + and should not be confused with the key data itself, or indeed + with the External Account Binding keyID above. The secret + key stored in the Secret **must** be un-padded, base64 URL + encoded data. + type: object + required: + - name + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string privateKeySecretRef: description: PrivateKey is the name of a secret containing the private key for this user account. diff --git a/pkg/apis/acme/v1alpha2/types_issuer.go b/pkg/apis/acme/v1alpha2/types_issuer.go index 1f0b43b9a..4d7a28d55 100644 --- a/pkg/apis/acme/v1alpha2/types_issuer.go +++ b/pkg/apis/acme/v1alpha2/types_issuer.go @@ -36,6 +36,11 @@ type ACMEIssuer struct { // +optional SkipTLSVerify bool `json:"skipTLSVerify,omitempty"` + // ExternalAcccountBinding is a reference to a CA external account of the ACME + // server. + // +optional + ExternalAccountBinding *ACMEExternalAccountBinding `json:"externalAccountBinding,omitempty"` + // PrivateKey is the name of a secret containing the private key for this // user account. PrivateKey cmmeta.SecretKeySelector `json:"privateKeySecretRef"` @@ -46,6 +51,26 @@ type ACMEIssuer struct { Solvers []ACMEChallengeSolver `json:"solvers,omitempty"` } +// ACMEExternalAcccountBinding is a reference to a CA external account of the ACME +// server. +type ACMEExternalAccountBinding struct { + // keyID is the ID of the CA key that the External Account is bound to. + KeyID string `json:"keyID"` + + // keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes + // Secret which holds the symmetric MAC key of the External Account Binding. + // The `key` is the index string that is paired with the key data in the + // Secret and should not be confused with the key data itself, or indeed with + // the External Account Binding keyID above. + // The secret key stored in the Secret **must** be un-padded, base64 URL + // encoded data. + Key cmmeta.SecretKeySelector `json:"keySecretRef"` + + // keyAlgorithm is the MAC key algorithm that the key is used for. Valid + // values are "HS256", "HS384" and "HS512". + KeyAlgorithm string `json:"keyAlgorithm"` +} + type ACMEChallengeSolver struct { // Selector selects a set of DNSNames on the Certificate resource that // should be solved using this challenge solver. diff --git a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go index 119f43fe1..67c5f87d5 100644 --- a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go @@ -291,9 +291,31 @@ func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopy() *ACMEChallenge return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEExternalAccountBinding) DeepCopyInto(out *ACMEExternalAccountBinding) { + *out = *in + out.Key = in.Key + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEExternalAccountBinding. +func (in *ACMEExternalAccountBinding) DeepCopy() *ACMEExternalAccountBinding { + if in == nil { + return nil + } + out := new(ACMEExternalAccountBinding) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) { *out = *in + if in.ExternalAccountBinding != nil { + in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding + *out = new(ACMEExternalAccountBinding) + **out = **in + } out.PrivateKey = in.PrivateKey if in.Solvers != nil { in, out := &in.Solvers, &out.Solvers diff --git a/pkg/internal/apis/acme/types_issuer.go b/pkg/internal/apis/acme/types_issuer.go index aba38c18c..26114521a 100644 --- a/pkg/internal/apis/acme/types_issuer.go +++ b/pkg/internal/apis/acme/types_issuer.go @@ -36,6 +36,11 @@ type ACMEIssuer struct { // +optional SkipTLSVerify bool `json:"skipTLSVerify,omitempty"` + // ExternalAcccountBinding is a reference to a CA external account of the ACME + // server. + // +optional + ExternalAccountBinding *ACMEExternalAccountBinding `json:"externalAccountBinding,omitempty"` + // PrivateKey is the name of a secret containing the private key for this // user account. PrivateKey cmmeta.SecretKeySelector `json:"privateKeySecretRef"` @@ -46,6 +51,24 @@ type ACMEIssuer struct { Solvers []ACMEChallengeSolver `json:"solvers,omitempty"` } +// ACMEExternalAcccountBinding is a reference to a CA external account of the ACME +// server. +type ACMEExternalAccountBinding struct { + // keyID is the ID of the CA key that the External Account is bound to. + KeyID string `json:"keyID"` + + // keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes + // Secret which holds the symmetric MAC key of the External Account Binding. + // The `key` is the index string that is paired with the key data in the + // Secret and should not be confused with the key data itself, or indeed with + // the External Account Binding keyID above. + Key cmmeta.SecretKeySelector `json:"keySecretRef"` + + // keyAlgorithm is the MAC key algorithm that the key is used for. Valid + // values are "HS256", "HS384" and "HS512". + KeyAlgorithm string `json:"keyAlgorithm"` +} + type ACMEChallengeSolver struct { // Selector selects a set of DNSNames on the Certificate resource that // should be solved using this challenge solver. diff --git a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go index 2666b8c4c..efdf25732 100644 --- a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go @@ -131,6 +131,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*v1alpha2.ACMEExternalAccountBinding)(nil), (*acme.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(a.(*v1alpha2.ACMEExternalAccountBinding), b.(*acme.ACMEExternalAccountBinding), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.ACMEExternalAccountBinding)(nil), (*v1alpha2.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(a.(*acme.ACMEExternalAccountBinding), b.(*v1alpha2.ACMEExternalAccountBinding), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*v1alpha2.ACMEIssuer)(nil), (*acme.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer(a.(*v1alpha2.ACMEIssuer), b.(*acme.ACMEIssuer), scope) }); err != nil { @@ -568,10 +578,41 @@ func Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha2_ACMECh return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) } +func autoConvert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *v1alpha2.ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { + out.KeyID = in.KeyID + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.Key, &out.Key, 0); err != nil { + return err + } + out.KeyAlgorithm = in.KeyAlgorithm + return nil +} + +// Convert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding is an autogenerated conversion function. +func Convert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *v1alpha2.ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { + return autoConvert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in, out, s) +} + +func autoConvert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *v1alpha2.ACMEExternalAccountBinding, s conversion.Scope) error { + out.KeyID = in.KeyID + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.Key, &out.Key, 0); err != nil { + return err + } + out.KeyAlgorithm = in.KeyAlgorithm + return nil +} + +// Convert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding is an autogenerated conversion function. +func Convert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *v1alpha2.ACMEExternalAccountBinding, s conversion.Scope) error { + return autoConvert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(in, out, s) +} + func autoConvert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer(in *v1alpha2.ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { out.Email = in.Email out.Server = in.Server out.SkipTLSVerify = in.SkipTLSVerify + out.ExternalAccountBinding = (*acme.ACMEExternalAccountBinding)(unsafe.Pointer(in.ExternalAccountBinding)) // TODO: Inefficient conversion - can we improve it? if err := s.Convert(&in.PrivateKey, &out.PrivateKey, 0); err != nil { return err @@ -589,6 +630,7 @@ func autoConvert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer(in *acme.ACMEIssuer, out out.Email = in.Email out.Server = in.Server out.SkipTLSVerify = in.SkipTLSVerify + out.ExternalAccountBinding = (*v1alpha2.ACMEExternalAccountBinding)(unsafe.Pointer(in.ExternalAccountBinding)) // TODO: Inefficient conversion - can we improve it? if err := s.Convert(&in.PrivateKey, &out.PrivateKey, 0); err != nil { return err diff --git a/pkg/internal/apis/acme/zz_generated.deepcopy.go b/pkg/internal/apis/acme/zz_generated.deepcopy.go index 5d0ddf338..c64676035 100644 --- a/pkg/internal/apis/acme/zz_generated.deepcopy.go +++ b/pkg/internal/apis/acme/zz_generated.deepcopy.go @@ -291,9 +291,31 @@ func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopy() *ACMEChallenge return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEExternalAccountBinding) DeepCopyInto(out *ACMEExternalAccountBinding) { + *out = *in + out.Key = in.Key + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEExternalAccountBinding. +func (in *ACMEExternalAccountBinding) DeepCopy() *ACMEExternalAccountBinding { + if in == nil { + return nil + } + out := new(ACMEExternalAccountBinding) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) { *out = *in + if in.ExternalAccountBinding != nil { + in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding + *out = new(ACMEExternalAccountBinding) + **out = **in + } out.PrivateKey = in.PrivateKey if in.Solvers != nil { in, out := &in.Solvers, &out.Solvers diff --git a/pkg/internal/apis/certmanager/validation/issuer.go b/pkg/internal/apis/certmanager/validation/issuer.go index 5fb8800f8..c76347d16 100644 --- a/pkg/internal/apis/certmanager/validation/issuer.go +++ b/pkg/internal/apis/certmanager/validation/issuer.go @@ -103,6 +103,20 @@ func ValidateACMEIssuerConfig(iss *cmacme.ACMEIssuer, fldPath *field.Path) field if len(iss.Server) == 0 { el = append(el, field.Required(fldPath.Child("server"), "acme server URL is a required field")) } + + if eab := iss.ExternalAccountBinding; eab != nil { + eabFldPath := fldPath.Child("externalAccountBinding") + if len(eab.KeyID) == 0 { + el = append(el, field.Required(eabFldPath.Child("keyID"), "the keyID field is required when using externalAccountBinding")) + } + + el = append(el, ValidateSecretKeySelector(&eab.Key, eabFldPath.Child("keySecretRef"))...) + + if len(eab.KeyAlgorithm) == 0 { + el = append(el, field.Required(eabFldPath.Child("keyAlgorithm"), "the keyAlgorithm field is required when using externalAccountBinding")) + } + } + for i, sol := range iss.Solvers { el = append(el, ValidateACMEIssuerChallengeSolverConfig(&sol, fldPath.Child("solvers").Index(i))...) } diff --git a/pkg/internal/apis/certmanager/validation/issuer_test.go b/pkg/internal/apis/certmanager/validation/issuer_test.go index 381347699..2d65cc6d9 100644 --- a/pkg/internal/apis/certmanager/validation/issuer_test.go +++ b/pkg/internal/apis/certmanager/validation/issuer_test.go @@ -144,6 +144,27 @@ func TestValidateACMEIssuerConfig(t *testing.T) { }, }, }, + "acme solver with empty external account binding fields": { + spec: &cmacme.ACMEIssuer{ + Email: "valid-email", + Server: "valid-server", + PrivateKey: validSecretKeyRef, + ExternalAccountBinding: &cmacme.ACMEExternalAccountBinding{}, + Solvers: []cmacme.ACMEChallengeSolver{ + { + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + CloudDNS: &validCloudDNSProvider, + }, + }, + }, + }, + errs: []*field.Error{ + field.Required(fldPath.Child("externalAccountBinding.keyID"), "the keyID field is required when using externalAccountBinding"), + field.Required(fldPath.Child("externalAccountBinding.keySecretRef.name"), "secret name is required"), + field.Required(fldPath.Child("externalAccountBinding.keySecretRef.key"), "secret key is required"), + field.Required(fldPath.Child("externalAccountBinding.keyAlgorithm"), "the keyAlgorithm field is required when using externalAccountBinding"), + }, + }, "acme solver with missing http01 config type": { spec: &cmacme.ACMEIssuer{ Email: "valid-email",