diff --git a/deploy/charts/cert-manager/crds/challenges.yaml b/deploy/charts/cert-manager/crds/challenges.yaml index 1902a7b06..740b9aed2 100644 --- a/deploy/charts/cert-manager/crds/challenges.yaml +++ b/deploy/charts/cert-manager/crds/challenges.yaml @@ -240,7 +240,6 @@ spec: containing the DNS configuration for Cloudflare type: object required: - - apiKeySecretRef - email properties: apiKeySecretRef: @@ -256,6 +255,19 @@ spec: 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 + apiTokenSecretRef: + 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 email: type: string cnameStrategy: diff --git a/deploy/charts/cert-manager/crds/clusterissuers.yaml b/deploy/charts/cert-manager/crds/clusterissuers.yaml index 8bc21acf6..563a62572 100644 --- a/deploy/charts/cert-manager/crds/clusterissuers.yaml +++ b/deploy/charts/cert-manager/crds/clusterissuers.yaml @@ -245,7 +245,6 @@ spec: containing the DNS configuration for Cloudflare type: object required: - - apiKeySecretRef - email properties: apiKeySecretRef: @@ -263,6 +262,21 @@ spec: TODO: Add other useful fields. apiVersion, kind, uid?' type: string + apiTokenSecretRef: + 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 email: type: string cnameStrategy: diff --git a/deploy/charts/cert-manager/crds/issuers.yaml b/deploy/charts/cert-manager/crds/issuers.yaml index 7f362efb0..d101481c3 100644 --- a/deploy/charts/cert-manager/crds/issuers.yaml +++ b/deploy/charts/cert-manager/crds/issuers.yaml @@ -245,7 +245,6 @@ spec: containing the DNS configuration for Cloudflare type: object required: - - apiKeySecretRef - email properties: apiKeySecretRef: @@ -263,6 +262,21 @@ spec: TODO: Add other useful fields. apiVersion, kind, uid?' type: string + apiTokenSecretRef: + 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 email: type: string cnameStrategy: diff --git a/deploy/manifests/00-crds.yaml b/deploy/manifests/00-crds.yaml index 13d9a828f..500a68bfe 100644 --- a/deploy/manifests/00-crds.yaml +++ b/deploy/manifests/00-crds.yaml @@ -675,7 +675,6 @@ spec: containing the DNS configuration for Cloudflare type: object required: - - apiKeySecretRef - email properties: apiKeySecretRef: @@ -691,6 +690,19 @@ spec: 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 + apiTokenSecretRef: + 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 email: type: string cnameStrategy: @@ -2060,7 +2072,6 @@ spec: containing the DNS configuration for Cloudflare type: object required: - - apiKeySecretRef - email properties: apiKeySecretRef: @@ -2078,6 +2089,21 @@ spec: TODO: Add other useful fields. apiVersion, kind, uid?' type: string + apiTokenSecretRef: + 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 email: type: string cnameStrategy: @@ -3744,7 +3770,6 @@ spec: containing the DNS configuration for Cloudflare type: object required: - - apiKeySecretRef - email properties: apiKeySecretRef: @@ -3762,6 +3787,21 @@ spec: TODO: Add other useful fields. apiVersion, kind, uid?' type: string + apiTokenSecretRef: + 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 email: type: string cnameStrategy: diff --git a/pkg/apis/acme/v1alpha2/types_issuer.go b/pkg/apis/acme/v1alpha2/types_issuer.go index df2836424..1f0b43b9a 100644 --- a/pkg/apis/acme/v1alpha2/types_issuer.go +++ b/pkg/apis/acme/v1alpha2/types_issuer.go @@ -242,8 +242,9 @@ type ACMEIssuerDNS01ProviderCloudDNS struct { // ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS // configuration for Cloudflare type ACMEIssuerDNS01ProviderCloudflare struct { - Email string `json:"email"` - APIKey cmmeta.SecretKeySelector `json:"apiKeySecretRef"` + Email string `json:"email"` + APIKey *cmmeta.SecretKeySelector `json:"apiKeySecretRef,omitempty"` + APIToken *cmmeta.SecretKeySelector `json:"apiTokenSecretRef,omitempty"` } // ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS diff --git a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go index 31b10dc1c..119f43fe1 100644 --- a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go @@ -116,7 +116,7 @@ func (in *ACMEChallengeSolverDNS01) DeepCopyInto(out *ACMEChallengeSolverDNS01) if in.Cloudflare != nil { in, out := &in.Cloudflare, &out.Cloudflare *out = new(ACMEIssuerDNS01ProviderCloudflare) - **out = **in + (*in).DeepCopyInto(*out) } if in.Route53 != nil { in, out := &in.Route53, &out.Route53 @@ -392,7 +392,16 @@ func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopy() *ACMEIssuerDNS01ProviderCl // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudflare) { *out = *in - out.APIKey = in.APIKey + if in.APIKey != nil { + in, out := &in.APIKey, &out.APIKey + *out = new(metav1.SecretKeySelector) + **out = **in + } + if in.APIToken != nil { + in, out := &in.APIToken, &out.APIToken + *out = new(metav1.SecretKeySelector) + **out = **in + } return } diff --git a/pkg/internal/apis/acme/types_issuer.go b/pkg/internal/apis/acme/types_issuer.go index fef4c8644..aba38c18c 100644 --- a/pkg/internal/apis/acme/types_issuer.go +++ b/pkg/internal/apis/acme/types_issuer.go @@ -242,8 +242,9 @@ type ACMEIssuerDNS01ProviderCloudDNS struct { // ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS // configuration for Cloudflare type ACMEIssuerDNS01ProviderCloudflare struct { - Email string `json:"email"` - APIKey cmmeta.SecretKeySelector `json:"apiKeySecretRef"` + Email string `json:"email"` + APIKey *cmmeta.SecretKeySelector `json:"apiKeySecretRef,omitempty"` + APIToken *cmmeta.SecretKeySelector `json:"apiTokenSecretRef,omitempty"` } // ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS diff --git a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go index 62c10075e..2666b8c4c 100644 --- a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go @@ -736,10 +736,8 @@ func Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01Pro func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *v1alpha2.ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { out.Email = in.Email - // TODO: Inefficient conversion - can we improve it? - if err := s.Convert(&in.APIKey, &out.APIKey, 0); err != nil { - return err - } + out.APIKey = (*meta.SecretKeySelector)(unsafe.Pointer(in.APIKey)) + out.APIToken = (*meta.SecretKeySelector)(unsafe.Pointer(in.APIToken)) return nil } @@ -750,10 +748,8 @@ func Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01P func autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha2_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *v1alpha2.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { out.Email = in.Email - // TODO: Inefficient conversion - can we improve it? - if err := s.Convert(&in.APIKey, &out.APIKey, 0); err != nil { - return err - } + out.APIKey = (*metav1.SecretKeySelector)(unsafe.Pointer(in.APIKey)) + out.APIToken = (*metav1.SecretKeySelector)(unsafe.Pointer(in.APIToken)) return nil } diff --git a/pkg/internal/apis/acme/zz_generated.deepcopy.go b/pkg/internal/apis/acme/zz_generated.deepcopy.go index 4519cc6f1..5d0ddf338 100644 --- a/pkg/internal/apis/acme/zz_generated.deepcopy.go +++ b/pkg/internal/apis/acme/zz_generated.deepcopy.go @@ -116,7 +116,7 @@ func (in *ACMEChallengeSolverDNS01) DeepCopyInto(out *ACMEChallengeSolverDNS01) if in.Cloudflare != nil { in, out := &in.Cloudflare, &out.Cloudflare *out = new(ACMEIssuerDNS01ProviderCloudflare) - **out = **in + (*in).DeepCopyInto(*out) } if in.Route53 != nil { in, out := &in.Route53, &out.Route53 @@ -392,7 +392,16 @@ func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopy() *ACMEIssuerDNS01ProviderCl // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudflare) { *out = *in - out.APIKey = in.APIKey + if in.APIKey != nil { + in, out := &in.APIKey, &out.APIKey + *out = new(meta.SecretKeySelector) + **out = **in + } + if in.APIToken != nil { + in, out := &in.APIToken, &out.APIToken + *out = new(meta.SecretKeySelector) + **out = **in + } return } diff --git a/pkg/internal/apis/certmanager/validation/issuer.go b/pkg/internal/apis/certmanager/validation/issuer.go index 61265d5ba..5fb8800f8 100644 --- a/pkg/internal/apis/certmanager/validation/issuer.go +++ b/pkg/internal/apis/certmanager/validation/issuer.go @@ -280,7 +280,18 @@ func ValidateACMEChallengeSolverDNS01(p *cmacme.ACMEChallengeSolverDNS01, fldPat el = append(el, field.Forbidden(fldPath.Child("cloudflare"), "may not specify more than one provider type")) } else { numProviders++ - el = append(el, ValidateSecretKeySelector(&p.Cloudflare.APIKey, fldPath.Child("cloudflare", "apiKeySecretRef"))...) + if p.Cloudflare.APIKey != nil { + el = append(el, ValidateSecretKeySelector(p.Cloudflare.APIKey, fldPath.Child("cloudflare", "apiKeySecretRef"))...) + } + if p.Cloudflare.APIToken != nil { + el = append(el, ValidateSecretKeySelector(p.Cloudflare.APIToken, fldPath.Child("cloudflare", "apiTokenSecretRef"))...) + } + if p.Cloudflare.APIKey != nil && p.Cloudflare.APIToken != nil { + el = append(el, field.Forbidden(fldPath.Child("cloudflare"), "apiKeySecretRef and apiTokenSecretRef cannot both be specified")) + } + if p.Cloudflare.APIKey == nil && p.Cloudflare.APIToken == nil { + el = append(el, field.Required(fldPath.Child("cloudflare"), "apiKeySecretRef or apiTokenSecretRef is required")) + } if len(p.Cloudflare.Email) == 0 { el = append(el, field.Required(fldPath.Child("cloudflare", "email"), "")) } diff --git a/pkg/internal/apis/certmanager/validation/issuer_test.go b/pkg/internal/apis/certmanager/validation/issuer_test.go index ef0596c90..381347699 100644 --- a/pkg/internal/apis/certmanager/validation/issuer_test.go +++ b/pkg/internal/apis/certmanager/validation/issuer_test.go @@ -40,7 +40,7 @@ var ( Key: "validkey", } validCloudflareProvider = cmacme.ACMEIssuerDNS01ProviderCloudflare{ - APIKey: validSecretKeyRef, + APIKey: &validSecretKeyRef, Email: "valid", } validACMEIssuer = cmacme.ACMEIssuer{ @@ -494,10 +494,11 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { }, }, }, - "missing cloudflare token": { + "missing cloudflare api key fields": { cfg: &cmacme.ACMEChallengeSolverDNS01{ Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "valid", + Email: "valid", + APIKey: &cmmeta.SecretKeySelector{}, }, }, errs: []*field.Error{ @@ -505,10 +506,44 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { field.Required(fldPath.Child("cloudflare", "apiKeySecretRef", "key"), "secret key is required"), }, }, + "missing cloudflare api token fields": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "valid", + APIToken: &cmmeta.SecretKeySelector{}, + }, + }, + errs: []*field.Error{ + field.Required(fldPath.Child("cloudflare", "apiTokenSecretRef", "name"), "secret name is required"), + field.Required(fldPath.Child("cloudflare", "apiTokenSecretRef", "key"), "secret key is required"), + }, + }, + "missing cloudflare api token or key": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "valid", + }, + }, + errs: []*field.Error{ + field.Required(fldPath.Child("cloudflare"), "apiKeySecretRef or apiTokenSecretRef is required"), + }, + }, + "both cloudflare api token and key specified": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "valid", + APIToken: &validSecretKeyRef, + APIKey: &validSecretKeyRef, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("cloudflare"), "apiKeySecretRef and apiTokenSecretRef cannot both be specified"), + }, + }, "missing cloudflare email": { cfg: &cmacme.ACMEChallengeSolverDNS01{ Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - APIKey: validSecretKeyRef, + APIKey: &validSecretKeyRef, }, }, errs: []*field.Error{ diff --git a/pkg/issuer/acme/dns/cloudflare/cloudflare.go b/pkg/issuer/acme/dns/cloudflare/cloudflare.go index b26e37c41..7c1945bc3 100644 --- a/pkg/issuer/acme/dns/cloudflare/cloudflare.go +++ b/pkg/issuer/acme/dns/cloudflare/cloudflare.go @@ -33,6 +33,7 @@ type DNSProvider struct { dns01Nameservers []string authEmail string authKey string + authToken string } // NewDNSProvider returns a DNSProvider instance configured for cloudflare. @@ -41,15 +42,18 @@ type DNSProvider struct { func NewDNSProvider(dns01Nameservers []string) (*DNSProvider, error) { email := os.Getenv("CLOUDFLARE_EMAIL") key := os.Getenv("CLOUDFLARE_API_KEY") - return NewDNSProviderCredentials(email, key, dns01Nameservers) + return NewDNSProviderCredentials(email, key, "", dns01Nameservers) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for cloudflare. -func NewDNSProviderCredentials(email, key string, dns01Nameservers []string) (*DNSProvider, error) { - if email == "" || key == "" { +func NewDNSProviderCredentials(email, key, token string, dns01Nameservers []string) (*DNSProvider, error) { + if email == "" || (key == "" && token == "") { return nil, fmt.Errorf("CloudFlare credentials missing") } + if key != "" && token != "" { + return nil, fmt.Errorf("CloudFlare key and token are both present") + } // cloudflare uses X-Auth-Key as a header for its // authentication. However, if it's an invalid value, the go // http library will "helpfully" print out the value to help with @@ -60,9 +64,14 @@ func NewDNSProviderCredentials(email, key string, dns01Nameservers []string) (*D return nil, fmt.Errorf("Cloudflare key invalid (does the key contain a newline?)") } + if !validHeaderFieldValue(token) { + return nil, fmt.Errorf("Cloudflare token invalid (does the token contain a newline?)") + } + return &DNSProvider{ authEmail: email, authKey: key, + authToken: token, dns01Nameservers: dns01Nameservers, }, nil } @@ -213,7 +222,11 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM } req.Header.Set("X-Auth-Email", c.authEmail) - req.Header.Set("X-Auth-Key", c.authKey) + if c.authToken != "" { + req.Header.Set("Authorization", "Bearer "+c.authToken) + } else { + req.Header.Set("X-Auth-Key", c.authKey) + } req.Header.Set("User-Agent", pkgutil.CertManagerUserAgent) client := http.Client{ diff --git a/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go b/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go index ac0a0c35e..ba14dcaf3 100644 --- a/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go +++ b/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go @@ -21,6 +21,7 @@ var ( cflareLiveTest bool cflareEmail string cflareAPIKey string + cflareAPIToken string cflareDomain string ) @@ -38,15 +39,31 @@ func restoreCloudFlareEnv() { os.Setenv("CLOUDFLARE_API_KEY", cflareAPIKey) } -func TestNewDNSProviderValid(t *testing.T) { +func TestNewDNSProviderValidAPIKey(t *testing.T) { os.Setenv("CLOUDFLARE_EMAIL", "") os.Setenv("CLOUDFLARE_API_KEY", "") - _, err := NewDNSProviderCredentials("123", "123", util.RecursiveNameservers) + _, err := NewDNSProviderCredentials("123", "123", "", util.RecursiveNameservers) assert.NoError(t, err) restoreCloudFlareEnv() } -func TestNewDNSProviderValidEnv(t *testing.T) { +func TestNewDNSProviderValidAPIToken(t *testing.T) { + os.Setenv("CLOUDFLARE_EMAIL", "") + os.Setenv("CLOUDFLARE_API_KEY", "") + _, err := NewDNSProviderCredentials("123", "", "123", util.RecursiveNameservers) + assert.NoError(t, err) + restoreCloudFlareEnv() +} + +func TestNewDNSProviderKeyAndTokenProvided(t *testing.T) { + os.Setenv("CLOUDFLARE_EMAIL", "") + os.Setenv("CLOUDFLARE_API_KEY", "") + _, err := NewDNSProviderCredentials("123", "123", "123", util.RecursiveNameservers) + assert.EqualError(t, err, "CloudFlare key and token are both present") + restoreCloudFlareEnv() +} + +func TestNewDNSProviderValidApiKeyEnv(t *testing.T) { os.Setenv("CLOUDFLARE_EMAIL", "test@example.com") os.Setenv("CLOUDFLARE_API_KEY", "123") _, err := NewDNSProvider(util.RecursiveNameservers) @@ -67,7 +84,7 @@ func TestCloudFlarePresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey, util.RecursiveNameservers) + provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey, cflareAPIToken, util.RecursiveNameservers) assert.NoError(t, err) err = provider.Present(cflareDomain, "_acme-challenge."+cflareDomain+".", "123d==") @@ -81,7 +98,7 @@ func TestCloudFlareCleanUp(t *testing.T) { time.Sleep(time.Second * 2) - provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey, util.RecursiveNameservers) + provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey, cflareAPIToken, util.RecursiveNameservers) assert.NoError(t, err) err = provider.CleanUp(cflareDomain, "_acme-challenge."+cflareDomain+".", "123d==") diff --git a/pkg/issuer/acme/dns/dns.go b/pkg/issuer/acme/dns/dns.go index 82fe536f6..733cd7fd9 100644 --- a/pkg/issuer/acme/dns/dns.go +++ b/pkg/issuer/acme/dns/dns.go @@ -62,7 +62,7 @@ type solver interface { // constructors may be set. type dnsProviderConstructors struct { cloudDNS func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool) (*clouddns.DNSProvider, error) - cloudFlare func(email, apikey string, dns01Nameservers []string) (*cloudflare.DNSProvider, error) + cloudFlare func(email, apikey, apiToken string, dns01Nameservers []string) (*cloudflare.DNSProvider, error) route53 func(accessKey, secretKey, hostedZoneID, region, role string, ambient bool, dns01Nameservers []string) (*route53.DNSProvider, error) azureDNS func(environment, clientID, clientSecret, subscriptionID, tenantID, resourceGroupName, hostedZoneName string, dns01Nameservers []string) (*azuredns.DNSProvider, error) acmeDNS func(host string, accountJson []byte, dns01Nameservers []string) (*acmedns.DNSProvider, error) @@ -252,15 +252,38 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1alpha2.Generic } case providerConfig.Cloudflare != nil: dbg.Info("preparing to create Cloudflare provider") - apiKeySecret, err := s.secretLister.Secrets(resourceNamespace).Get(providerConfig.Cloudflare.APIKey.Name) + if providerConfig.Cloudflare.APIKey != nil && providerConfig.Cloudflare.APIToken != nil { + return nil, nil, fmt.Errorf("API key and API token secret references are both present") + } + + var saSecretName, saSecretKey string + if providerConfig.Cloudflare.APIKey != nil { + saSecretName = providerConfig.Cloudflare.APIKey.Name + saSecretKey = providerConfig.Cloudflare.APIKey.Key + } else { + saSecretName = providerConfig.Cloudflare.APIToken.Name + saSecretKey = providerConfig.Cloudflare.APIToken.Key + } + + saSecret, err := s.secretLister.Secrets(resourceNamespace).Get(saSecretName) if err != nil { - return nil, nil, fmt.Errorf("error getting cloudflare service account: %s", err) + return nil, nil, fmt.Errorf("error getting cloudflare secret: %s", err) + } + + keyData, ok := saSecret.Data[saSecretKey] + if !ok { + return nil, nil, fmt.Errorf("specfied key %q not found in secret %s/%s", saSecretKey, saSecret.Namespace, saSecret.Name) + } + + var apiKey, apiToken string + if providerConfig.Cloudflare.APIKey != nil { + apiKey = string(keyData) + } else { + apiToken = string(keyData) } email := providerConfig.Cloudflare.Email - apiKey := string(apiKeySecret.Data[providerConfig.Cloudflare.APIKey.Key]) - - impl, err = s.dnsProviderConstructors.cloudFlare(email, apiKey, s.DNS01Nameservers) + impl, err = s.dnsProviderConstructors.cloudFlare(email, apiKey, apiToken, s.DNS01Nameservers) if err != nil { return nil, nil, fmt.Errorf("error instantiating cloudflare challenge solver: %s", err) } diff --git a/pkg/issuer/acme/dns/dns_test.go b/pkg/issuer/acme/dns/dns_test.go index 42bed011b..b4fcbe66e 100644 --- a/pkg/issuer/acme/dns/dns_test.go +++ b/pkg/issuer/acme/dns/dns_test.go @@ -66,7 +66,7 @@ func TestSolverFor(t *testing.T) { expectedSolverType reflect.Type } tests := map[string]testT{ - "loads secret for cloudflare provider": { + "loads api key for cloudflare provider": { solverFixture: &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ @@ -82,7 +82,7 @@ func TestSolverFor(t *testing.T) { DNS01: &cmacme.ACMEChallengeSolverDNS01{ Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ Email: "test", - APIKey: cmmeta.SecretKeySelector{ + APIKey: &cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ Name: "cloudflare-key", }, @@ -97,6 +97,37 @@ func TestSolverFor(t *testing.T) { domain: "example.com", expectedSolverType: reflect.TypeOf(&cloudflare.DNSProvider{}), }, + "loads api token for cloudflare provider": { + solverFixture: &solverFixture{ + Builder: &test.Builder{ + KubeObjects: []runtime.Object{ + newSecret("cloudflare-token", "default", map[string][]byte{ + "api-token": []byte("a-cloudflare-api-token"), + }), + }, + }, + Issuer: newIssuer("test", "default"), + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + Solver: &cmacme.ACMEChallengeSolver{ + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "test", + APIToken: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cloudflare-token", + }, + Key: "api-token", + }, + }, + }, + }, + }, + }, + }, + domain: "example.com", + expectedSolverType: reflect.TypeOf(&cloudflare.DNSProvider{}), + }, "fails to load a cloudflare provider with a missing secret": { solverFixture: &solverFixture{ Issuer: newIssuer("test", "default"), @@ -107,7 +138,38 @@ func TestSolverFor(t *testing.T) { DNS01: &cmacme.ACMEChallengeSolverDNS01{ Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ Email: "test", - APIKey: cmmeta.SecretKeySelector{ + APIToken: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cloudflare-token", + }, + Key: "api-token", + }, + }, + }, + }, + }, + }, + }, + domain: "example.com", + expectErr: true, + }, + "fails to load a cloudflare provider when key and token are provided": { + solverFixture: &solverFixture{ + Issuer: newIssuer("test", "default"), + // don't include any secrets in the lister + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + Solver: &cmacme.ACMEChallengeSolver{ + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "test", + APIToken: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cloudflare-token", + }, + Key: "api-token", + }, + APIKey: &cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ Name: "cloudflare-key", }, @@ -122,7 +184,7 @@ func TestSolverFor(t *testing.T) { domain: "example.com", expectErr: true, }, - "fails to load a cloudflare provider with an invalid secret": { + "fails to load a cloudflare provider with an invalid key secret": { solverFixture: &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ @@ -138,7 +200,7 @@ func TestSolverFor(t *testing.T) { DNS01: &cmacme.ACMEChallengeSolverDNS01{ Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ Email: "test", - APIKey: cmmeta.SecretKeySelector{ + APIKey: &cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ Name: "cloudflare-key", }, @@ -153,6 +215,37 @@ func TestSolverFor(t *testing.T) { domain: "example.com", expectErr: true, }, + "fails to load a cloudflare provider with an invalid token secret": { + solverFixture: &solverFixture{ + Builder: &test.Builder{ + KubeObjects: []runtime.Object{ + newSecret("cloudflare-token", "default", map[string][]byte{ + "api-key-oops": []byte("a-cloudflare-api-token"), + }), + }, + }, + Issuer: newIssuer("test", "default"), + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + Solver: &cmacme.ACMEChallengeSolver{ + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "test", + APIToken: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cloudflare-token", + }, + Key: "api-token", + }, + }, + }, + }, + }, + }, + }, + domain: "example.com", + expectErr: true, + }, "loads json for acmedns provider": { solverFixture: &solverFixture{ Builder: &test.Builder{ diff --git a/pkg/issuer/acme/dns/util_test.go b/pkg/issuer/acme/dns/util_test.go index 5c262c121..cbbb63868 100644 --- a/pkg/issuer/acme/dns/util_test.go +++ b/pkg/issuer/acme/dns/util_test.go @@ -139,10 +139,10 @@ func newFakeDNSProviders() *fakeDNSProviders { f.call("clouddns", project, serviceAccount, util.RecursiveNameservers, ambient) return nil, nil }, - cloudFlare: func(email, apikey string, dns01Nameservers []string) (*cloudflare.DNSProvider, error) { - f.call("cloudflare", email, apikey, util.RecursiveNameservers) - if email == "" || apikey == "" { - return nil, errors.New("invalid email or apikey") + cloudFlare: func(email, apikey, apiToken string, dns01Nameservers []string) (*cloudflare.DNSProvider, error) { + f.call("cloudflare", email, apikey, apiToken, util.RecursiveNameservers) + if email == "" || (apikey == "" && apiToken == "") { + return nil, errors.New("invalid email or apikey or apitoken") } return nil, nil }, diff --git a/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go b/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go index 4f034c92b..4594218b0 100644 --- a/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go +++ b/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go @@ -86,7 +86,7 @@ func (b *Cloudflare) Provision() error { b.details.ProviderConfig = cmacme.ACMEChallengeSolverDNS01{ Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ Email: b.cf.Email, - APIKey: cmmeta.SecretKeySelector{ + APIKey: &cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ Name: b.createdSecret.Name, },