2nd January 2021 ¡ 10 minute read
Sharing a domain registered with Route53 and using subdomains to separate each account in an organization is complex. A domain registered with Route53 can only belong to a single account, so we must manage resources across both accounts in order to route traffic effectively.
Before We Start
For this guide, we will set up the following domain scheme for our environments:
*.macklin.xyz
â Production
*.dev.macklin.xyz
â Development
Architecture
The architecture for this guide will align with the following diagram;
Building Blocks
Route 53
We'll use Route 53 to create Hosted Zones that act as containers for groups of four name servers spread across 4 top level domains. We'll use Record Sets to specify additional DNS records which will direct traffic between accounts. We'll also use Route 53 to register the domain.
Certificate Manager
It would be irresponsible to open our account up to public internet traffic without enabling HTTPS. Certificate Manager will handle the provisioning and management of our TLS certificates.
CloudFormation
We'll be making a deliberate effort to keep the configuration as minimal and default as possible. While there may be more advanced tools for deploying infrastructure-as-code, I respect CloudFormation in some part for it's simplicity. However, there is no reason not to use other tools such as Terraform or Pulumi if preferred.
Configuration
jq
Development Environment
Below is the template for provisioning the Hosted Zone using Route 53, and Certificate using Certificate Manager. Copy the template and save it to your working directory as dev-dns.yml
.
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Provisions DNS infrastructure for the Development Environment'
Parameters:
HostedZoneDomain:
Type: String
Resources:
HostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: !Ref HostedZoneDomain
Certificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref HostedZoneDomain
DomainValidationOptions:
- DomainName: !Ref HostedZoneDomain
HostedZoneId: !Ref HostedZone
ValidationMethod: DNS
Tags:
- Key: Name
Value: certificate
Next, run the following to create a new stack using CloudFormation:
$ aws cloudformation create-stack --template-body file://dev-dns.yml \
--stack-name dns --profile dev --parameters \
ParameterKey=HostedZoneDomain,ParameterValue='dev.macklin.xyz'
If you're watching and waiting for your CloudFormation stack to deploy, you'll notice it will remain in the CREATE_IN_PROGRESS
state indefinitely. This is because your certificate request is waiting to be validated by AWS, which cannot happen until we have also set up the Production environment (see here for more information).
It is okay to proceed in the meantime - the stack will move into the CREATE_COMPLETE
state once this guide is complete.
Name Server (NS) Records
Next, we're going to use the AWS CLI to extract the NS records that were assigned to the newly created Hosted Zone in Route 53. Since we're aiming to automate this process, we're going to use the AWS CLI to retrieve the 4 NS records associated with the freshly provisioned Hosted Zone.
Run the following in a terminal window to first retrieve the Hosted Zone ID for the Development environment:
$ aws route53 list-hosted-zones-by-name \
--dns-name macklin.xyz. --profile dev \
| jq -r '.HostedZones[0].Id'
/hostedzone/Z095EXAMPLEGW15BTTB
Now we'll flex some muscle with JQ to list the record sets for the Hosted Zone, selecting its associated NS Records. Run the following in a terminal window:
$ aws route53 list-resource-record-sets \
--hosted-zone-id /hostedzone/Z095EXAMPLEGW15BTTB --profile dev \
| jq -r '.ResourceRecordSets' | jq -r 'map(select(.Type == "NS"))' \
| jq -r '.[0].ResourceRecords' | jq -r 'map(.Value)'
[
"ns-1077.awsdns-06.org.",
"ns-935.awsdns-52.net.",
"ns-452.awsdns-56.com.",
"ns-1911.awsdns-46.co.uk."
]
This will extract the NS records that are associated with the Hosted Zone. Save the output as we will need it later.
Production Environment
Below is the CloudFormation template we will use to provision infrastructure in the Production environment. Copy the template and save it to your working directory as prod-dns.yml
.
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Provisions DNS infrastructure for the Production Environment'
Parameters:
HostedZoneId:
Type: String
HostedZoneDomain:
Type: String
DevHostedZoneDomain:
Type: String
DevNameServer1:
Type: String
DevNameServer2:
Type: String
DevNameServer3:
Type: String
DevNameServer4:
Type: String
Resources:
Certificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref HostedZoneDomain
DomainValidationOptions:
- DomainName: !Ref HostedZoneDomain
HostedZoneId: !Ref HostedZoneId
ValidationMethod: DNS
Tags:
- Key: Name
Value: certificate
DevNameServers:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId : !Ref HostedZoneId
Name: !Ref DevHostedZoneDomain
ResourceRecords:
- !Ref DevNameServer1
- !Ref DevNameServer2
- !Ref DevNameServer3
- !Ref DevNameServer4
TTL: '300'
Type: NS
Run the following in a terminal window to first retrieve the Hosted Zone ID for the Production environment (this Hosted Zone was created on your behalf when you registered the domain):
$ aws route53 list-hosted-zones-by-name \
--dns-name macklin.xyz. --profile prod \
| jq -r '.HostedZones[0].Id'
/hostedzone/Z095EXAMPLEGW15BTTB
Next, run the following to create a new stack using CloudFormation, replacing the values such as HostedZoneId
, HostedZoneDomain
, DevHostedZoneDomain
, and the four NS Records from before:
$ aws cloudformation create-stack --template-body file://prod-dns.yml \
--stack-name dns --profile prod --parameters \
ParameterKey=HostedZoneId,ParameterValue='Z095EXAMPLEGW15BTTB' \
ParameterKey=HostedZoneDomain,ParameterValue='macklin.xyz' \
ParameterKey=DevHostedZoneDomain,ParameterValue='dev.macklin.xyz' \
ParameterKey=DevNameServer1,ParameterValue='ns-1077.awsdns-06.org.' \
ParameterKey=DevNameServer2,ParameterValue='ns-935.awsdns-52.net.' \
ParameterKey=DevNameServer3,ParameterValue='ns-452.awsdns-56.com.' \
ParameterKey=DevNameServer4,ParameterValue='ns-1911.awsdns-46.co.uk.'
What's Next?
Under Route 53 in the AWS Console we can now view the newly created NS Records for macklin.xyz
and dev.macklin.xyz
.
You may know that DNS propagation can take up to 30 minutes, so now is a good time to take a coffee or tea break. In the meantime, you can confirm DNS propagation status using an online tool, or by using the dig
CLI tool. Below is an example dig
output for the Development environment;
$ dig dev.macklin.xyz -t NS
...
;; ANSWER SECTION:
dev.macklin.xyz. 172800 IN NS ns-1077.awsdns-06.org.
dev.macklin.xyz. 172800 IN NS ns-1911.awsdns-46.co.uk.
dev.macklin.xyz. 172800 IN NS ns-452.awsdns-56.com.
dev.macklin.xyz. 172800 IN NS ns-935.awsdns-52.net.
And below is an example output for the Production environment, noting that the answer section contains different NS Records to the Development environment;
$ dig macklin.xyz -t NS
...
;; ANSWER SECTION:
macklin.xyz. 172800 IN NS ns-1253.awsdns-28.org.
macklin.xyz. 172800 IN NS ns-129.awsdns-16.com.
macklin.xyz. 172800 IN NS ns-1583.awsdns-05.co.uk.
macklin.xyz. 172800 IN NS ns-756.awsdns-30.net.
Pricing
Cost can vary depending on the TLD of the domain you select. The cost of my .xyz
domain is US$12.00/year (AU$16.29/year at the time of writing). The costs associated with AWS, including Route53 and Certificate Manager are US$13.20/year (AU$17.76/year at the time of writing).
Resources
The CloudFormation templates and install scripts are made available in the following Github repository;
This post expands on the following post from https://serverless-stack.com with the use of declarative infrastructure-as-code (CloudFormation) and command-line tools (AWS CLI);