How to Configure ExternalDNS with Cross-Account Route53


When I’m working with a private EKS cluster, I recently encountered a requirement to configure ExternalDNS to update records in a Route53 hosted zone belonging to a different AWS account. After extensive research on articles, documentation, and forums, I couldn’t find a clear solution or even a solid hint. Eventually, a GitHub issue provided the crucial insight leveraging OIDC provider integration in EKS to enable cross-account access.
Using that information, I successfully configured ExternalDNS to update records across AWS accounts. If you're facing a similar challenge, this blog will save you time by providing a direct, step-by-step solution.
Prerequisites: ✅
Before we dive in, make sure you have the following in place:
Two AWS Accounts:
Account A: This is where your Route 53 hosted zone resides.
Account B: This will host your EKS cluster and where ExternalDNS will run.
Tools Configured: Aws cli, Kubectl , lens (optional), Ingress controller (optional to check with ingress)
Route53 (Account A): You should have your DNS hosted zone already setup in Account A.
EKS Cluster (Account B): You need a running EKS cluster in Account B that ExternalDNS will interact with.
High-Level Overview: 💡
Here is a quick rundown of the steps I'll be taking:
Create an IAM Role in the Route 53 Account (Account A): This role will grant necessary Route 53 permissions and trust the IAM role we will create for ExternalDNS in Account B.
Create an IAM Role in the EKS Account (Account B): This role will have the permission to assume the IAM role we created in Account A. We will configure its trust policy to trust your OIDC provider associated with your EKS cluster.
Install ExternalDNS in your EKS Cluster (Account B): We will use Addons to install ExternalDNS with custom configuration values that leverage the cross-account IAM roles.
Verification: We will verify the setup by deploying application by pointing hostname.
Let's get started! 🚀
1. Create the IAM Role in the Route 53 Account (Account A): 1️⃣
In your Account A, navigate to the IAM console and follow these steps:
Click on Roles in the left-hand navigation pane.
Click Create role.
For the "Select type of trusted entity," choose AWS account.
Enter the Account ID of Account B.
Click Next. (ignore other options)
Now, we need to attach permissions for Route 53. Search for and select the AmazonRoute53FullAccess policy.
- Important Security Consideration: For production environments, it's highly recommended to scope down these permissions to the minimum required for ExternalDNS to function.
Click Next.
Give your role a descriptive name (e.g.,
Route53ExternalDNSAccess
).Review the role details and click Create role.
Take note of the ARN (Amazon Resource Name) of this newly created role. You'll need it in the next step. It will look something like:
arn:aws:iam::ACCOUNT_A_ID:role/Route53ExternalDNSAccess
.
2. Create the IAM Role in the EKS Account (Account B): 2️⃣
Now, switch to your Account B and follow these steps in the IAM console:
Click on Roles.
Click Create role.
For the "Select type of trusted entity," choose Web identity (OIDC).
For the "Identity provider," select the OIDC provider associated with your EKS cluster. You can find this information in your EKS cluster details under the "Overview" tab. It will typically look like
oidc.eks.REGION.amazonaws.com/id/YOUR_OIDC_ID
.For the "Audience," enter
sts.amazonaws.com
.Then click Add Condition to provide service account and external DNS namespace to give access
Before creating role, we need to add permissions that allow this role to assume the role we created in Account A.
So, for that open policy console in new tab and click Create policy.
In the JSON tab of the "Create policy" page, paste the following policy, replacing
arn:aws:iam::<ACCOUNT_A_ID>:role/Route53ExternalDNSAccess
with the actual ARN of the role you created in Account A:{ "Statement": [ { "Action": [ "sts:AssumeRole" ], "Effect": "Allow", "Resource": [ "arn:aws:iam::<ACCOUNT_A_ID>:role/Route53ExternalDNSAccess" #arn of route53 role which we created above. ], "Sid": "Statement1" } ], "Version": "2012-10-17" }
Click Next
Give your policy a descriptive name (e.g.,
ExternalDNSAssumeRoute53RolePolicy
).Click Create policy.
Go back to the "Create role" page (where you selected the OIDC trust). Click Next: Permissions.
Search for and select the policy you just created (
ExternalDNSAssumeRoute53RolePolicy
).Click Next: Review.
Give your role a descriptive name (e.g.,
ExternalDNSIRSAccess
).Review the role details and click Create role.
Take note of the ARN of this newly created role. You'll need this for the
Route53ExternalDNSAccess
Role (Account A) to assume this. It will look something like:arn:aws:iam::ACCOUNT_B_ID:role/ExternalDNSIRSAccess
.
3. Install ExternalDNS with Configuration Values 🔻
(Account B):
- Now, in your Account B, use Addons to install ExternalDNS.
Click Next
Now add the role which we have created under IRSA option
To pass the below configuration to the external DNS, expand the
Optional configuration settings
option.domainFilters: - nginx.local txtOwnerId: Z03011653ICHPFH5D6TUJ extraArgs: - --aws-zone-type=private - --aws-assume-role=arn:aws:iam::<ACCOUNT_A_ID>:role/Route53ExternalDNSAccess
Once you have configured the values, you can install ExternalDNS using Addons
4.Verification: 🍾
After the ExternalDNS is deployed, you can check the ExternalDNS pod logs in your EKS cluster (Account B) using kubectl logs -n kube-system -l app=external-dns
. Look for any errors related to IAM permissions or Route 53 connectivity.
To verify that ExternalDNS is correctly creating DNS records, you can deploy a sample application and create an Ingress resource with a hostname. Here are example Kubernetes manifests:
Deployment (deploy.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
Service (service.yaml):
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
Ingress (ingress.yaml):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: test.nginx.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
- Replace
test.nginx.local
with a hostname that matches thedomainFilters
you configured for ExternalDNS (e.g., if yourdomainFilters
isnginx.local
).
Apply these manifests to your EKS cluster:
kubectl apply -f nginx.yaml
kubectl apply -f nginx-service.yaml
kubectl apply -f nginx-ingress.yaml
After the Ingress is created, ExternalDNS should automatically create your records in your Route 53 hosted zone (in Account A) pointing test.nginx.local
to the load balancer associated with your Ingress.
This is how it looks.
Conclusion: ⭐
Setting up ExternalDNS with cross-account Route 53 felt daunting at first for me, but now you can easily setup by following these precise steps, you can save yourself a significant amount of time and get your DNS records managed seamlessly across your AWS accounts. Remember to always adhere to the principle of least privilege when granting IAM permissions in production environments.
I hope this guide has been helpful! ❤️Let me know in the comments if you have any questions or run into any issues. Happy Automating! 🥳
Subscribe to my newsletter
Read articles from Gerlyn M directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Gerlyn M
Gerlyn M
Gerlyn is a DevOps engineer with a strong passion for Kubernetes and automation. He is always eager to learn and continuously strives to enhance his skills, aiming to become an expert in the field. He loves to share his knowledge with the community.