diff --git a/api/v1beta1/externaldns_types.go b/api/v1beta1/externaldns_types.go index bcc67cd0..7349e02d 100644 --- a/api/v1beta1/externaldns_types.go +++ b/api/v1beta1/externaldns_types.go @@ -257,6 +257,13 @@ type ExternalDNSAWSProviderOptions struct { // +kubebuilder:validation:Required // +required Credentials SecretReference `json:"credentials"` + + // RoleARN contains the ARN of a IAM role that will be assumed when using the AWS API. + // It provides the ability to use a hosted zone in another AWS account. + // + // +kubebuilder:validation:Optional + // +optional + RoleARN *string `json:"roleARN,omitempty"` // TODO: Additionally support access for: // - kiam/kube2iam enabled clusters ("iam.amazonaws.com/role" POD's annotation to assume IAM role) // - EKS clusters ("eks.amazonaws.com/role-arn" ServiceAccount's annotation to assume IAM role) diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 22383946..075b580c 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -58,6 +58,11 @@ func (in *ExternalDNS) DeepCopyObject() runtime.Object { func (in *ExternalDNSAWSProviderOptions) DeepCopyInto(out *ExternalDNSAWSProviderOptions) { *out = *in out.Credentials = in.Credentials + if in.RoleARN != nil { + in, out := &in.RoleARN, &out.RoleARN + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDNSAWSProviderOptions. @@ -253,7 +258,7 @@ func (in *ExternalDNSProvider) DeepCopyInto(out *ExternalDNSProvider) { if in.AWS != nil { in, out := &in.AWS, &out.AWS *out = new(ExternalDNSAWSProviderOptions) - **out = **in + (*in).DeepCopyInto(*out) } if in.GCP != nil { in, out := &in.GCP, &out.GCP diff --git a/config/crd/bases/externaldns.olm.openshift.io_externaldnses.yaml b/config/crd/bases/externaldns.olm.openshift.io_externaldnses.yaml index 4c46596d..a7dba5d6 100644 --- a/config/crd/bases/externaldns.olm.openshift.io_externaldnses.yaml +++ b/config/crd/bases/externaldns.olm.openshift.io_externaldnses.yaml @@ -507,6 +507,9 @@ spec: description: spec is the specification of the desired behavior of the ExternalDNS. properties: + awsRoleARN: + description: AWSRoleARN is a string + type: string domains: description: "Domains specifies which domains that ExternalDNS should create DNS records for. Multiple domain values can be specified diff --git a/pkg/operator/controller/externaldns/deployment_test.go b/pkg/operator/controller/externaldns/deployment_test.go index 79ec8d59..75abf22d 100644 --- a/pkg/operator/controller/externaldns/deployment_test.go +++ b/pkg/operator/controller/externaldns/deployment_test.go @@ -1783,6 +1783,55 @@ func TestDesiredExternalDNSDeployment(t *testing.T) { }, }, }, + { + name: "RoleARN set AWS Route", + inputExternalDNS: testAWSExternalDNSRoleARN(operatorv1beta1.SourceTypeRoute, "arn:aws:iam:123456789012:role/foo"), + expectedTemplatePodSpec: corev1.PodSpec{ + ServiceAccountName: test.OperandName, + NodeSelector: map[string]string{ + osLabel: linuxOS, + masterNodeRoleLabel: "", + }, + Tolerations: []corev1.Toleration{ + { + Key: masterNodeRoleLabel, + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoSchedule, + }, + }, + Containers: []corev1.Container{ + { + Name: ExternalDNSContainerName, + Image: test.OperandImage, + Args: []string{ + "--aws-assume-role=arn:aws:iam:123456789012:role/foo", + "--metrics-address=127.0.0.1:7979", + "--txt-owner-id=external-dns-test", + "--zone-id-filter=my-dns-public-zone", + "--provider=aws", + "--source=openshift-route", + "--policy=sync", + "--registry=txt", + "--log-level=debug", + "--ignore-hostname-annotation", + `--fqdn-template={{""}}`, + "--txt-prefix=external-dns-", + }, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{allCapabilities}, + }, + Privileged: pointer.Bool(false), + RunAsNonRoot: pointer.Bool(true), + AllowPrivilegeEscalation: pointer.Bool(false), + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, + }, + }, + }, + }, { name: "Nominal Azure Route", inputSecretName: azureSecret, @@ -5317,6 +5366,14 @@ func testAWSExternalDNSDomainFilter(zones []string, source operatorv1beta1.Exter return extdns } +func testAWSExternalDNSRoleARN(source operatorv1beta1.ExternalDNSSourceType, roleARN string) *operatorv1beta1.ExternalDNS { + extdns := testCreateDNSFromSourceWRTCloudProvider(source, operatorv1beta1.ProviderTypeAWS, nil, "") + extdns.Spec.Provider.AWS = &operatorv1beta1.ExternalDNSAWSProviderOptions{ + RoleARN: pointer.String(roleARN), + } + return extdns +} + func testPlatformStatusGCP(projectID string) *configv1.PlatformStatus { return &configv1.PlatformStatus{ Type: configv1.GCPPlatformType, diff --git a/pkg/operator/controller/externaldns/pod.go b/pkg/operator/controller/externaldns/pod.go index 237eeec5..e64096c4 100644 --- a/pkg/operator/controller/externaldns/pod.go +++ b/pkg/operator/controller/externaldns/pod.go @@ -194,6 +194,10 @@ func (b *externalDNSContainerBuilder) fillProviderAgnosticFields(seq int, zone s args = append(args, "--ignore-hostname-annotation") } + if b.externalDNS.Spec.Provider.AWS != nil && b.externalDNS.Spec.Provider.AWS.RoleARN != nil { + args = append(args, fmt.Sprintf("--aws-assume-role=%s", *b.externalDNS.Spec.Provider.AWS.RoleARN)) + } + if len(b.externalDNS.Spec.Source.FQDNTemplate) > 0 { args = append(args, fmt.Sprintf("--fqdn-template=%s", strings.Join(b.externalDNS.Spec.Source.FQDNTemplate, ","))) } else {