diff --git a/deploy/crds/crd-challenges.yaml b/deploy/crds/crd-challenges.yaml index 70e7ff0f1..6bb0bbd3d 100644 --- a/deploy/crds/crd-challenges.yaml +++ b/deploy/crds/crd-challenges.yaml @@ -274,6 +274,12 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field that + tells cert-manager in which Cloud DNS zone the challenge + record has to be created. If left empty cert-manager + will automatically choose a zone. + type: string project: type: string serviceAccountSecretRef: @@ -1738,6 +1744,12 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field that + tells cert-manager in which Cloud DNS zone the challenge + record has to be created. If left empty cert-manager + will automatically choose a zone. + type: string project: type: string serviceAccountSecretRef: @@ -3203,6 +3215,12 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field that + tells cert-manager in which Cloud DNS zone the challenge + record has to be created. If left empty cert-manager + will automatically choose a zone. + type: string project: type: string serviceAccountSecretRef: diff --git a/deploy/crds/crd-clusterissuers.yaml b/deploy/crds/crd-clusterissuers.yaml index 172eeb2b3..28b1d9acf 100644 --- a/deploy/crds/crd-clusterissuers.yaml +++ b/deploy/crds/crd-clusterissuers.yaml @@ -332,6 +332,13 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field + that tells cert-manager in which Cloud DNS zone + the challenge record has to be created. If left + empty cert-manager will automatically choose a + zone. + type: string project: type: string serviceAccountSecretRef: @@ -2249,6 +2256,13 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field + that tells cert-manager in which Cloud DNS zone + the challenge record has to be created. If left + empty cert-manager will automatically choose a + zone. + type: string project: type: string serviceAccountSecretRef: @@ -4168,6 +4182,13 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field + that tells cert-manager in which Cloud DNS zone + the challenge record has to be created. If left + empty cert-manager will automatically choose a + zone. + type: string project: type: string serviceAccountSecretRef: diff --git a/deploy/crds/crd-issuers.yaml b/deploy/crds/crd-issuers.yaml index c14a7c8d6..96d553d26 100644 --- a/deploy/crds/crd-issuers.yaml +++ b/deploy/crds/crd-issuers.yaml @@ -331,6 +331,13 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field + that tells cert-manager in which Cloud DNS zone + the challenge record has to be created. If left + empty cert-manager will automatically choose a + zone. + type: string project: type: string serviceAccountSecretRef: @@ -2247,6 +2254,13 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field + that tells cert-manager in which Cloud DNS zone + the challenge record has to be created. If left + empty cert-manager will automatically choose a + zone. + type: string project: type: string serviceAccountSecretRef: @@ -4165,6 +4179,13 @@ spec: required: - project properties: + hostedZoneName: + description: HostedZoneName is an optional field + that tells cert-manager in which Cloud DNS zone + the challenge record has to be created. If left + empty cert-manager will automatically choose a + zone. + type: string project: type: string serviceAccountSecretRef: diff --git a/pkg/apis/acme/v1alpha2/types_issuer.go b/pkg/apis/acme/v1alpha2/types_issuer.go index 583b2e933..5608d963a 100644 --- a/pkg/apis/acme/v1alpha2/types_issuer.go +++ b/pkg/apis/acme/v1alpha2/types_issuer.go @@ -349,6 +349,12 @@ type ACMEIssuerDNS01ProviderCloudDNS struct { // +optional ServiceAccount *cmmeta.SecretKeySelector `json:"serviceAccountSecretRef,omitempty"` Project string `json:"project"` + + // HostedZoneName is an optional field that tells cert-manager in which + // Cloud DNS zone the challenge record has to be created. + // If left empty cert-manager will automatically choose a zone. + // +optional + HostedZoneName string `json:"hostedZoneName,omitempty"` } // ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS diff --git a/pkg/apis/acme/v1alpha3/types_issuer.go b/pkg/apis/acme/v1alpha3/types_issuer.go index 0c830f5b8..6d555be57 100644 --- a/pkg/apis/acme/v1alpha3/types_issuer.go +++ b/pkg/apis/acme/v1alpha3/types_issuer.go @@ -349,6 +349,12 @@ type ACMEIssuerDNS01ProviderCloudDNS struct { // +optional ServiceAccount *cmmeta.SecretKeySelector `json:"serviceAccountSecretRef,omitempty"` Project string `json:"project"` + + // HostedZoneName is an optional field that tells cert-manager in which + // Cloud DNS zone the challenge record has to be created. + // If left empty cert-manager will automatically choose a zone. + // +optional + HostedZoneName string `json:"hostedZoneName,omitempty"` } // ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS diff --git a/pkg/apis/acme/v1beta1/types_issuer.go b/pkg/apis/acme/v1beta1/types_issuer.go index 1b42677b0..80bd5976a 100644 --- a/pkg/apis/acme/v1beta1/types_issuer.go +++ b/pkg/apis/acme/v1beta1/types_issuer.go @@ -349,6 +349,12 @@ type ACMEIssuerDNS01ProviderCloudDNS struct { // +optional ServiceAccount *cmmeta.SecretKeySelector `json:"serviceAccountSecretRef,omitempty"` Project string `json:"project"` + + // HostedZoneName is an optional field that tells cert-manager in which + // Cloud DNS zone the challenge record has to be created. + // If left empty cert-manager will automatically choose a zone. + // +optional + HostedZoneName string `json:"hostedZoneName,omitempty"` } // ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS diff --git a/pkg/internal/apis/acme/types_issuer.go b/pkg/internal/apis/acme/types_issuer.go index 8515d1ba6..ef8c4b5b1 100644 --- a/pkg/internal/apis/acme/types_issuer.go +++ b/pkg/internal/apis/acme/types_issuer.go @@ -310,6 +310,7 @@ type ACMEIssuerDNS01ProviderAkamai struct { type ACMEIssuerDNS01ProviderCloudDNS struct { ServiceAccount *cmmeta.SecretKeySelector Project string + HostedZoneName string } // ACMEIssuerDNS01ProviderCloudflare 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 9ada8ad79..bea31f1fc 100644 --- a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go @@ -821,6 +821,7 @@ func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha2_ACMEIssuerDNS01Pro func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *v1alpha2.ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { out.ServiceAccount = (*meta.SecretKeySelector)(unsafe.Pointer(in.ServiceAccount)) out.Project = in.Project + out.HostedZoneName = in.HostedZoneName return nil } @@ -832,6 +833,7 @@ func Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01Pro func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *v1alpha2.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { out.ServiceAccount = (*metav1.SecretKeySelector)(unsafe.Pointer(in.ServiceAccount)) out.Project = in.Project + out.HostedZoneName = in.HostedZoneName return nil } diff --git a/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go b/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go index 2e1ea2c61..62d5dfafd 100644 --- a/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go @@ -821,6 +821,7 @@ func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha3_ACMEIssuerDNS01Pro func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *v1alpha3.ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { out.ServiceAccount = (*meta.SecretKeySelector)(unsafe.Pointer(in.ServiceAccount)) out.Project = in.Project + out.HostedZoneName = in.HostedZoneName return nil } @@ -832,6 +833,7 @@ func Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01Pro func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *v1alpha3.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { out.ServiceAccount = (*metav1.SecretKeySelector)(unsafe.Pointer(in.ServiceAccount)) out.Project = in.Project + out.HostedZoneName = in.HostedZoneName return nil } diff --git a/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go b/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go index 8e1373a2b..d38e28a1b 100644 --- a/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go +++ b/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go @@ -821,6 +821,7 @@ func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1beta1_ACMEIssuerDNS01Prov func autoConvert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *v1beta1.ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { out.ServiceAccount = (*meta.SecretKeySelector)(unsafe.Pointer(in.ServiceAccount)) out.Project = in.Project + out.HostedZoneName = in.HostedZoneName return nil } @@ -832,6 +833,7 @@ func Convert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01Prov func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1beta1_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *v1beta1.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { out.ServiceAccount = (*metav1.SecretKeySelector)(unsafe.Pointer(in.ServiceAccount)) out.Project = in.Project + out.HostedZoneName = in.HostedZoneName return nil } diff --git a/pkg/issuer/acme/dns/clouddns/clouddns.go b/pkg/issuer/acme/dns/clouddns/clouddns.go index d3a94bc0d..87e49656e 100644 --- a/pkg/issuer/acme/dns/clouddns/clouddns.go +++ b/pkg/issuer/acme/dns/clouddns/clouddns.go @@ -27,12 +27,13 @@ import ( // DNSProvider is an implementation of the DNSProvider interface. type DNSProvider struct { + hostedZoneName string dns01Nameservers []string project string client *dns.Service } -func NewDNSProvider(project string, saBytes []byte, dns01Nameservers []string, ambient bool) (*DNSProvider, error) { +func NewDNSProvider(project string, saBytes []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*DNSProvider, error) { // project is a required field if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") @@ -43,11 +44,11 @@ func NewDNSProvider(project string, saBytes []byte, dns01Nameservers []string, a if !ambient { return nil, fmt.Errorf("unable to construct clouddns provider: empty credentials; perhaps you meant to enable ambient credentials?") } - return NewDNSProviderCredentials(project, dns01Nameservers) + return NewDNSProviderCredentials(project, dns01Nameservers, hostedZoneName) } // if service account data is provided, we instantiate using that if len(saBytes) != 0 { - return NewDNSProviderServiceAccountBytes(project, saBytes, dns01Nameservers) + return NewDNSProviderServiceAccountBytes(project, saBytes, dns01Nameservers, hostedZoneName) } return nil, fmt.Errorf("missing Google Cloud DNS provider credentials") } @@ -56,17 +57,17 @@ func NewDNSProvider(project string, saBytes []byte, dns01Nameservers []string, a // DNS. Project name must be passed in the environment variable: GCE_PROJECT. // A Service Account file can be passed in the environment variable: // GCE_SERVICE_ACCOUNT_FILE -func NewDNSProviderEnvironment(dns01Nameservers []string) (*DNSProvider, error) { +func NewDNSProviderEnvironment(dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { project := os.Getenv("GCE_PROJECT") if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok { - return NewDNSProviderServiceAccount(project, saFile, dns01Nameservers) + return NewDNSProviderServiceAccount(project, saFile, dns01Nameservers, hostedZoneName) } - return NewDNSProviderCredentials(project, dns01Nameservers) + return NewDNSProviderCredentials(project, dns01Nameservers, hostedZoneName) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for Google Cloud DNS. -func NewDNSProviderCredentials(project string, dns01Nameservers []string) (*DNSProvider, error) { +func NewDNSProviderCredentials(project string, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") } @@ -84,12 +85,13 @@ func NewDNSProviderCredentials(project string, dns01Nameservers []string) (*DNSP project: project, client: svc, dns01Nameservers: dns01Nameservers, + hostedZoneName: hostedZoneName, }, nil } // NewDNSProviderServiceAccount uses the supplied service account JSON file to // return a DNSProvider instance configured for Google Cloud DNS. -func NewDNSProviderServiceAccount(project string, saFile string, dns01Nameservers []string) (*DNSProvider, error) { +func NewDNSProviderServiceAccount(project string, saFile string, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") } @@ -101,12 +103,12 @@ func NewDNSProviderServiceAccount(project string, saFile string, dns01Nameserver if err != nil { return nil, fmt.Errorf("Unable to read Service Account file: %v", err) } - return NewDNSProviderServiceAccountBytes(project, dat, dns01Nameservers) + return NewDNSProviderServiceAccountBytes(project, dat, dns01Nameservers, hostedZoneName) } // NewDNSProviderServiceAccountBytes uses the supplied service account JSON // file data to return a DNSProvider instance configured for Google Cloud DNS. -func NewDNSProviderServiceAccountBytes(project string, saBytes []byte, dns01Nameservers []string) (*DNSProvider, error) { +func NewDNSProviderServiceAccountBytes(project string, saBytes []byte, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") } @@ -130,6 +132,7 @@ func NewDNSProviderServiceAccountBytes(project string, saBytes []byte, dns01Name project: project, client: svc, dns01Nameservers: dns01Nameservers, + hostedZoneName: hostedZoneName, }, nil } @@ -204,6 +207,10 @@ func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { // getHostedZone returns the managed-zone func (c *DNSProvider) getHostedZone(domain string) (string, error) { + if c.hostedZoneName != "" { + return c.hostedZoneName, nil + } + authZone, err := util.FindZoneByFqdn(util.ToFqdn(domain), c.dns01Nameservers) if err != nil { return "", err diff --git a/pkg/issuer/acme/dns/clouddns/clouddns_test.go b/pkg/issuer/acme/dns/clouddns/clouddns_test.go index 0ba87121c..4027a2ea2 100644 --- a/pkg/issuer/acme/dns/clouddns/clouddns_test.go +++ b/pkg/issuer/acme/dns/clouddns/clouddns_test.go @@ -45,7 +45,7 @@ func TestNewDNSProviderValid(t *testing.T) { t.Skip("skipping live test (requires credentials)") } os.Setenv("GCE_PROJECT", "") - _, err := NewDNSProviderCredentials("my-project", util.RecursiveNameservers) + _, err := NewDNSProviderCredentials("my-project", util.RecursiveNameservers, "") assert.NoError(t, err) restoreGCloudEnv() } @@ -55,14 +55,14 @@ func TestNewDNSProviderValidEnv(t *testing.T) { t.Skip("skipping live test (requires credentials)") } os.Setenv("GCE_PROJECT", "my-project") - _, err := NewDNSProviderEnvironment(util.RecursiveNameservers) + _, err := NewDNSProviderEnvironment(util.RecursiveNameservers, "") assert.NoError(t, err) restoreGCloudEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { os.Setenv("GCE_PROJECT", "") - _, err := NewDNSProviderEnvironment(util.RecursiveNameservers) + _, err := NewDNSProviderEnvironment(util.RecursiveNameservers, "") assert.EqualError(t, err, "Google Cloud project name missing") restoreGCloudEnv() } @@ -72,7 +72,7 @@ func TestLiveGoogleCloudPresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers) + provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers, "") assert.NoError(t, err) err = provider.Present(gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") @@ -84,7 +84,7 @@ func TestLiveGoogleCloudPresentMultiple(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers) + provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers, "") assert.NoError(t, err) // Check that we're able to create multiple entries @@ -101,9 +101,46 @@ func TestLiveGoogleCloudCleanUp(t *testing.T) { time.Sleep(time.Second * 1) - provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers) + provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers, "") assert.NoError(t, err) err = provider.CleanUp(gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") assert.NoError(t, err) } + +func TestDNSProvider_getHostedZone(t *testing.T) { + testProvider, err := NewDNSProviderCredentials("my-project", util.RecursiveNameservers, "test-zone") + assert.NoError(t, err) + + type args struct { + domain string + } + tests := []struct { + name string + args args + want string + wantErr bool + provider *DNSProvider + }{ + { + name: "test given hosted zone name", + provider: testProvider, + want: "test-zone", + wantErr: false, + args: args{domain: "example.com"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := tt.provider + got, err := c.getHostedZone(tt.args.domain) + if (err != nil) != tt.wantErr { + t.Errorf("getHostedZone() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getHostedZone() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/issuer/acme/dns/dns.go b/pkg/issuer/acme/dns/dns.go index 4b30b4d24..b9a1cc23b 100644 --- a/pkg/issuer/acme/dns/dns.go +++ b/pkg/issuer/acme/dns/dns.go @@ -57,7 +57,7 @@ type solver interface { // It is useful for mocking out a given provider since an alternate set of // constructors may be set. type dnsProviderConstructors struct { - cloudDNS func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool) (*clouddns.DNSProvider, error) + cloudDNS func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.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, ambient bool) (*azuredns.DNSProvider, error) @@ -239,7 +239,7 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1alpha2.Generic } // attempt to construct the cloud dns provider - impl, err = s.dnsProviderConstructors.cloudDNS(providerConfig.CloudDNS.Project, keyData, s.DNS01Nameservers, s.CanUseAmbientCredentials(issuer)) + impl, err = s.dnsProviderConstructors.cloudDNS(providerConfig.CloudDNS.Project, keyData, s.DNS01Nameservers, s.CanUseAmbientCredentials(issuer), providerConfig.CloudDNS.HostedZoneName) if err != nil { return nil, nil, fmt.Errorf("error instantiating google clouddns challenge solver: %s", err) } diff --git a/pkg/issuer/acme/dns/util_test.go b/pkg/issuer/acme/dns/util_test.go index aec5dd7ed..5aacf6706 100644 --- a/pkg/issuer/acme/dns/util_test.go +++ b/pkg/issuer/acme/dns/util_test.go @@ -128,8 +128,8 @@ func newFakeDNSProviders() *fakeDNSProviders { calls: []fakeDNSProviderCall{}, } f.constructors = dnsProviderConstructors{ - cloudDNS: func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool) (*clouddns.DNSProvider, error) { - f.call("clouddns", project, serviceAccount, util.RecursiveNameservers, ambient) + cloudDNS: func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.DNSProvider, error) { + f.call("clouddns", project, serviceAccount, util.RecursiveNameservers, ambient, hostedZoneName) return nil, nil }, cloudFlare: func(email, apikey, apiToken string, dns01Nameservers []string) (*cloudflare.DNSProvider, error) {