Skip to content

Commit

Permalink
NE-1323: Add AWS RoleARN for Shared VPC support
Browse files Browse the repository at this point in the history
Adds the ability to specify a RoleARN to create Route 53 DNS
records in a different AWS Account. Supports Shared VPC scenario.

`api/v1beta1/externaldns_types.go`: Add RoleARN API
`api/v1beta1/webhook_test.go`: Add RoleARN WebHook Validation test
`api/v1beta1/zz_generated.deepcopy.go`: Generated
`bundle/manifests/externaldns.olm.openshift.io_externaldnses.yaml`:
Generated
`config/crd/bases/externaldns.olm.openshift.io_externaldnses.yaml`:
Generated
`pkg/operator/controller/externaldns/credentials_request.go`: Add
sts:AssumeRole to credential requests
`pkg/operator/controller/externaldns/deployment_test.go`: Unit test
`desiredExternalDNSDeployment`
`pkg/operator/controller/externaldns/pod.go`: Add --aws-assume-role
argument with RoleARN value
  • Loading branch information
gcs278 committed Jul 17, 2023
1 parent 0cd8bee commit b4e7cbc
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 7 deletions.
11 changes: 9 additions & 2 deletions api/v1beta1/externaldns_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,16 @@ type ExternalDNSAWSProviderOptions struct {
// https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md
// for more information.
//
// +kubebuilder:validation:Required
// +required
// +kubebuilder:validation:Optional
// +optional
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)
Expand Down
12 changes: 12 additions & 0 deletions api/v1beta1/externaldns_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"regexp"

"github.com/aws/aws-sdk-go/aws/arn"
"k8s.io/apimachinery/pkg/runtime"

utilErrors "k8s.io/apimachinery/pkg/util/errors"
Expand Down Expand Up @@ -69,6 +70,7 @@ func (r *ExternalDNS) validate(old runtime.Object) error {
r.validateFilters(),
r.validateSources(old),
r.validateHostnameAnnotationPolicy(),
r.validateAWSRoleARN(),
r.validateProviderCredentials(),
})
}
Expand Down Expand Up @@ -155,3 +157,13 @@ func (r *ExternalDNS) validateProviderCredentials() error {
}
return nil
}

func (r *ExternalDNS) validateAWSRoleARN() error {
// Ensure we have a valid arn if it is specified.
provider := r.Spec.Provider
if provider.AWS != nil && provider.AWS.RoleARN != nil && !arn.IsARN(*provider.AWS.RoleARN) {
return errors.New(fmt.Sprintf("arn %s is not a valid AWS ARN", *provider.AWS.RoleARN))
}

return nil
}
48 changes: 48 additions & 0 deletions api/v1beta1/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,29 @@ var _ = Describe("ExternalDNS admission webhook when platform is OCP", func() {
err := k8sClient.Create(context.Background(), resource)
Expect(err).Should(Succeed())
})
It("valid RoleARN", func() {
resource := makeExternalDNS("test-valid-rolearn-openshift", nil)
resource.Spec.Provider = ExternalDNSProvider{
Type: ProviderTypeAWS,
AWS: &ExternalDNSAWSProviderOptions{
RoleARN: pointer.String("arn:aws:iam::123456789012:role/foo"),
},
}
err := k8sClient.Create(context.Background(), resource)
Expect(err).Should(Succeed())
})
It("invalid RoleARN rejected", func() {
resource := makeExternalDNS("test-invalid-rolearn-openshift", nil)
resource.Spec.Provider = ExternalDNSProvider{
Type: ProviderTypeAWS,
AWS: &ExternalDNSAWSProviderOptions{
RoleARN: pointer.String("arn:aws:iam:bad123456789012:role/foo"),
},
}
err := k8sClient.Create(context.Background(), resource)
Expect(err).ShouldNot(Succeed())
Expect(err.Error()).Should(ContainSubstring(`arn arn:aws:iam:bad123456789012:role/foo is not a valid AWS ARN`))
})
})
Context("resource with Azure provider", func() {
It("ignores when provider Azure credentials are not specified", func() {
Expand Down Expand Up @@ -175,6 +198,31 @@ var _ = Describe("ExternalDNS admission webhook", func() {
Expect(err).ShouldNot(Succeed())
Expect(err.Error()).Should(ContainSubstring("credentials secret must be specified when provider type is AWS"))
})
It("valid RoleARN", func() {
resource := makeExternalDNS("test-valid-rolearn", nil)
resource.Spec.Provider = ExternalDNSProvider{
Type: ProviderTypeAWS,
AWS: &ExternalDNSAWSProviderOptions{
RoleARN: pointer.String("arn:aws:iam::123456789012:role/foo"),
Credentials: SecretReference{Name: "credentials"},
},
}
err := k8sClient.Create(context.Background(), resource)
Expect(err).Should(Succeed())
})
It("invalid RoleARN rejected", func() {
resource := makeExternalDNS("test-invalid-rolearn", nil)
resource.Spec.Provider = ExternalDNSProvider{
Type: ProviderTypeAWS,
AWS: &ExternalDNSAWSProviderOptions{
RoleARN: pointer.String("arn:aws:iam:bad123456789012:role/foo"),
Credentials: SecretReference{Name: "credentials"},
},
}
err := k8sClient.Create(context.Background(), resource)
Expect(err).ShouldNot(Succeed())
Expect(err.Error()).Should(ContainSubstring(`arn arn:aws:iam:bad123456789012:role/foo is not a valid AWS ARN`))
})
})

Context("resource with Azure provider", func() {
Expand Down
7 changes: 6 additions & 1 deletion api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -582,8 +582,11 @@ spec:
required:
- name
type: object
required:
- credentials
roleARN:
description: 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.
type: string
type: object
azure:
description: Azure describes provider configuration options specific
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,11 @@ spec:
required:
- name
type: object
required:
- credentials
roleARN:
description: 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.
type: string
type: object
azure:
description: Azure describes provider configuration options specific
Expand Down
1 change: 1 addition & 0 deletions pkg/operator/controller/externaldns/credentials_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ func createProviderConfig(externalDNS *operatorv1beta1.ExternalDNS, platformStat
"route53:ListHostedZones",
"route53:ListResourceRecordSets",
"tag:GetResources",
"sts:AssumeRole",
},
Resource: "*",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ func desiredAWSProviderSpec() runtime.Object {
"route53:ListHostedZones",
"route53:ListResourceRecordSets",
"tag:GetResources",
"sts:AssumeRole",
},
Resource: "*",
},
Expand All @@ -311,6 +312,7 @@ func desiredAWSProviderSpecGovARN() runtime.Object {
"route53:ListHostedZones",
"route53:ListResourceRecordSets",
"tag:GetResources",
"sts:AssumeRole",
},
Resource: "*",
},
Expand Down
57 changes: 57 additions & 0 deletions pkg/operator/controller/externaldns/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions pkg/operator/controller/externaldns/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
93 changes: 93 additions & 0 deletions vendor/github.com/aws/aws-sdk-go/aws/arn/arn.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ github.com/asaskevich/govalidator
# github.com/aws/aws-sdk-go v1.41.6
## explicit; go 1.11
github.com/aws/aws-sdk-go/aws
github.com/aws/aws-sdk-go/aws/arn
github.com/aws/aws-sdk-go/aws/awserr
github.com/aws/aws-sdk-go/aws/awsutil
github.com/aws/aws-sdk-go/aws/client
Expand Down

0 comments on commit b4e7cbc

Please sign in to comment.