Networking & Content Delivery
Automating DNS infrastructure using Route 53 Resolver endpoints
Introduction
DNS name resolution is a fundamental part of all on-premises and cloud networks. For customers with hybrid networks, additional infrastructure and configuration are needed for private DNS resolution to work seamlessly across environments. However, building this type of DNS infrastructure in a multi-account environment is complex.
In this post, we show how to automate the construction of DNS infrastructure with Route 53 Resolver endpoints using AWS CloudFormation. We also show how to update your DNS infrastructure on an ongoing basis to simplify management and reduce the cost of running conditional DNS forwarders on EC2 instances.
Solution overview
This solution uses Route 53 Resolver endpoints. It creates Route 53 Resolver endpoints in a Virtual Private Cloud (VPC) that is used for DNS management.
The appropriate Route 53 resolver rules are created in the same AWS account where Resolver endpoints are present. The Route 53 resolver rules are shared via AWS Resource Access Manager (RAM) with other AWS accounts (spoke accounts). VPCs in spoke accounts are then associated with the Route 53 resolver rules.
In our example, we have two DNS domain names – cloud.dev.example.com and onprem.dev.example.com. The domain cloud.dev.example.com is a private hosted zone in Route 53. The domain onprem.dev.example.com is a zone hosted within an on-premises DNS server. We have an outbound endpoint and an inbound endpoint created in the VPC. We also have a Route 53 resolver rule created and shared via RAM to the two spoke accounts.
Rule 1 for onprem.dev.example.com is to associate the outbound endpoint with target IPs of the on-premises DNS server. For the private hosted zone cloud.dev.example.com, we directly associate the VPCs that require resolutions of records in it.
The DNS query for any record that belongs to onprem.dev.example.com goes to outbound endpoint by the way of AmazonProvidedDNS server. The DNS query then gets forwarded to the on-premises DNS server, authoritative for resolving those records. The DNS query for any records that belong to cloud.dev.example.com goes to the AmazonProvidedDNS server. Since the VPC is associated to private hosted zone of cloud.dev.example.com, the DNS query gets forwarded to the authoritative name servers of private hosted zone for resolution of those records. Similarly, any DNS query for records under cloud.dev.example.com from on-premises servers gets forwarded to the inbound endpoint. The VPC is associated with the private hosted zone. Hence the DNS query gets forwarded to authoritative name servers of private hosted zone by the way of AmazonProvidedDNS server for resolution of those set of records. This whitepaper provides details on how Route 53 Resolver works and outlines several different hybrid DNS architectures possible on AWS.
Note: Avoid DNS loops that occur due to wildcard Route 53 resolver rule “.” . For example, consider a scenario where you have Route 53 resolver rule “.” with forwarding IPs set up to be on-premises DNS server. You also have a forwarding rule on the on-premises DNS server to forward queries for cloud.dev.example.com to inbound endpoint that reside in AWS VPC. The query for records that belong to cloud.dev.example.com from a spoke account gets forwarded to an on-premises DNS server. The on-premises DNS server again forwards it back to the AWS going into a DNS loop.
Assumptions:
- Network connectivity between the DNS-VPC and the on-premises is in place. Connectivity is by way of VPN or DX.
- VPC attribute enableDNShostnames is set to true.
- If your workload performs 10,000 DNS queries per second or above to a Resolver endpoint IP, create additional endpoint ENIs to scale your Queries Per Second (QPS).
Our solution uses AWS CloudFormation to build the DNS infrastructure required to solve three primary use-cases for private domain resolution:
- Resolving on-premises private domains from workloads running in your VPCs.
- Resolving private domains in your AWS environment from workloads running on premises.
- Resolving private domains between workloads running in different AWS accounts.
- Using VPC associations to the private hosted zone.
Five CloudFormation templates are used:
- Resolver.yaml – creates the Route 53 resolver endpoints.
AWSTemplateFormatVersion: 2010-09-09
Description: >-
AWS CloudFormation Template to deploy Resolver endpoints
Metadata:
'AWS::CloudFormation::Interface':
ParameterGroups:
- Label:
default: VPC Configuration
Parameters:
- endpointtype
- vpcId
- privatesubnet1
- privatesubnet2
- endpointcidr
Parameters:
vpcId:
Type: AWS::EC2::VPC::Id
Description: VPC ID that hosts resolver endpoints
privatesubnet1:
Type: AWS::EC2::Subnet::Id
Description: Chose the private subnet in AZ-1
privatesubnet2:
Type: AWS::EC2::Subnet::Id
Description: Chose the private subnet in AZ-2
endpointtype:
Type: String
Default: 'OutboundEndpoint'
AllowedValues:
- OutboundEndpoint
- InboundEndpoint
Description: Name for Route53 resolver security group
endpointcidr:
Type: String
Description: Provide the CIDRs of resources in on-prem that will be accessed from AWS via outbound endpoint or CIDR of resources in on-prem accessing AWS Private Hosted Zones via inbound endpoints
Conditions:
CreateOutboundEndpoint: !Equals [ !Ref endpointtype, OutboundEndpoint ]
CreateInboundEndpoint: !Equals [ !Ref endpointtype, InboundEndpoint ]
Resources:
resolverSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
!If
- CreateOutboundEndpoint
- 'OutboundResolverEndpointSecurityGroup'
- 'InboundResolverEndpointSecurityGroup'
GroupDescription: Security group controlling Route53 Endpoint access
SecurityGroupEgress:
!If
- CreateOutboundEndpoint
- - IpProtocol: tcp
FromPort: 53
ToPort: 53
CidrIp: !Ref endpointcidr
- IpProtocol: udp
FromPort: 53
ToPort: 53
CidrIp: !Ref endpointcidr
- !Ref AWS::NoValue
SecurityGroupIngress:
!If
- CreateInboundEndpoint
- - IpProtocol: tcp
FromPort: 53
ToPort: 53
CidrIp: !Ref endpointcidr
- IpProtocol: udp
FromPort: 53
ToPort: 53
CidrIp: !Ref endpointcidr
- !Ref AWS::NoValue
VpcId: !Ref vpcId
resolverEndpoint:
Type: AWS::Route53Resolver::ResolverEndpoint
Properties :
Name :
!If
- CreateOutboundEndpoint
- OutboundEndpoint
- InboundEndpoint
Direction :
!If
- CreateOutboundEndpoint
- OUTBOUND
- INBOUND
IpAddresses :
- SubnetId: !Ref privatesubnet1
- SubnetId: !Ref privatesubnet2
SecurityGroupIds :
- !GetAtt resolverSecurityGroup.GroupId
Outputs:
ResolverEndpointId:
Description: Route 53 Resolver Endpoint ID
Value: !GetAtt resolverEndpoint.ResolverEndpointId
- Rule-share.yaml – creates the Resolver rule and Resource Access Manager (RAM) share for sharing the rule.
AWSTemplateFormatVersion: 2010-09-09
Description: >-
AWS CloudFormation Template to create the resolver rules and share the Resolver rules via Resource Access Manager
Metadata:
'AWS::CloudFormation::Interface':
ParameterGroups:
- Label:
default: Resolver Endpoint Configuration
Parameters:
- outboundresolverendpointId
- Label:
default: Resolver Rule Configuration
Parameters:
- domainFQDN
- domainTargets
- Label:
default: Account Details
Parameters:
- AccountIds
Parameters:
outboundresolverendpointId:
Type: String
Description: Provide the Outbound Resolver Endpoint Id
AccountIds:
Type: List<Number>
Description: List of account ids with which this rule will be shared.
RuleName:
Type: String
Description: resolver rule name
ResourceShareName:
Type: String
Description: resource share name
domainFQDN:
Type: String
Description: Provide FQDN for domain
domainTargetCount:
Type: String
Description: count for number targets ip for the resolver rule
AllowedValues:
- 1
- 2
- 3
- 4
- 5
- 6
domainTargets:
Type: List<String>
Default: 192.168.1.13:53, 192.168.2.14:53
Description: A comma separated list of IP:port targets (two targets) for example1.com domain resolution. Please change the default IPs as per your environment.
Conditions:
isCountOne: !Equals [!Ref domainTargetCount, 1]
isCountTwo: !Equals [!Ref domainTargetCount, 2]
isCountThree: !Equals [!Ref domainTargetCount, 3]
isCountFour: !Equals [!Ref domainTargetCount, 4]
isCountFive: !Equals [!Ref domainTargetCount, 5]
isCountSix: !Equals [!Ref domainTargetCount, 6]
Resources:
domainRuleWithOneTargets:
Condition: isCountOne
Type: AWS::Route53Resolver::ResolverRule
Properties:
DomainName: !Ref domainFQDN
Name: !Ref RuleName
ResolverEndpointId: !Ref outboundresolverendpointId
RuleType: FORWARD
TargetIps:
- Ip: !Select [ 0, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
domainRuleWithTwoTargets:
Condition: isCountTwo
Type : AWS::Route53Resolver::ResolverRule
Properties :
DomainName : !Ref domainFQDN
Name : !Ref RuleName
ResolverEndpointId : !Ref outboundresolverendpointId
RuleType : FORWARD
TargetIps :
- Ip: !Select [ 0, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
domainRuleWithThreeTargets:
Condition: isCountThree
Type : AWS::Route53Resolver::ResolverRule
Properties :
DomainName : !Ref domainFQDN
Name : !Ref RuleName
ResolverEndpointId : !Ref outboundresolverendpointId
RuleType : FORWARD
TargetIps :
- Ip: !Select [ 0, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
domainRuleWithFourTargets:
Condition: isCountFour
Type : AWS::Route53Resolver::ResolverRule
Properties :
DomainName : !Ref domainFQDN
Name : !Ref RuleName
ResolverEndpointId : !Ref outboundresolverendpointId
RuleType : FORWARD
TargetIps :
- Ip: !Select [ 0, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 3, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 3, !Ref domainTargets ] ] ]
domainRuleWithFiveTargets:
Condition: isCountFive
Type : AWS::Route53Resolver::ResolverRule
Properties :
DomainName : !Ref domainFQDN
Name : !Ref RuleName
ResolverEndpointId : !Ref outboundresolverendpointId
RuleType : FORWARD
TargetIps :
- Ip: !Select [ 0, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 3, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 3, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 4, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 4, !Ref domainTargets ] ] ]
domainRuleWithSixTargets:
Condition: isCountSix
Type : AWS::Route53Resolver::ResolverRule
Properties :
DomainName : !Ref domainFQDN
Name : !Ref RuleName
ResolverEndpointId : !Ref outboundresolverendpointId
RuleType : FORWARD
TargetIps :
- Ip: !Select [ 0, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 0, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 1, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 2, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 3, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 3, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 4, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 4, !Ref domainTargets ] ] ]
- Ip: !Select [ 0, !Split [ ":", !Select [ 5, !Ref domainTargets ] ] ]
Port: !Select [ 1, !Split [ ":", !Select [ 5, !Ref domainTargets ] ] ]
resolverruleshare:
Type: AWS::RAM::ResourceShare
Properties:
Name: !Ref ResourceShareName
ResourceArns:
- !If
- isCountOne
- !GetAtt domainRuleWithOneTargets.Arn
- !Ref AWS::NoValue
- !If
- isCountTwo
- !GetAtt domainRuleWithTwoTargets.Arn
- !Ref AWS::NoValue
- !If
- isCountThree
- !GetAtt domainRuleWithThreeTargets.Arn
- !Ref AWS::NoValue
- !If
- isCountFour
- !GetAtt domainRuleWithFourTargets.Arn
- !Ref AWS::NoValue
- !If
- isCountFive
- !GetAtt domainRuleWithFiveTargets.Arn
- !Ref AWS::NoValue
- !If
- isCountSix
- !GetAtt domainRuleWithSixTargets.Arn
- !Ref AWS::NoValue
Principals: !Ref AccountIds
Outputs:
domainRuleWithOneTargets:
Condition: isCountOne
Description: Route 53 Resolver Rule ID for domain1fqdn
Value: !Ref domainRuleWithOneTargets
domainRuleWithTwoTargets:
Condition: isCountTwo
Description: Route 53 Resolver Rule ID for domain1fqdn
Value: !Ref domainRuleWithTwoTargets
domainRuleWithThreeTargets:
Condition: isCountThree
Description: Route 53 Resolver Rule ID for domain1fqdn
Value: !Ref domainRuleWithThreeTargets
domainRuleWithFourTargets:
Condition: isCountFour
Description: Route 53 Resolver Rule ID for domain1fqdn
Value: !Ref domainRuleWithFourTargets
domainRuleWithFiveTargets:
Condition: isCountFive
Description: Route 53 Resolver Rule ID for domain1fqdn
Value: !Ref domainRuleWithFiveTargets
domainRuleWithSixTargets:
Condition: isCountSix
Description: Route 53 Resolver Rule ID for domain1fqdn
Value: !Ref domainRuleWithSixTargets
- Rule-association.yaml – creates the association of VPC to the Route 53 Resolver rule.
AWSTemplateFormatVersion: 2010-09-09
Description: >-
AWS CloudFormation Template to associate the spoke VPC to the resolver rules
Parameters:
ruleId:
Type: String
Description: Provide the resolver rule ID for rule-1
associationName:
Type: String
Description: name for the association
vpcId:
Type: AWS::EC2::VPC::Id
Description: Provide the VPC ID with which Route 53 Resolver rules are associated
Resources:
ruleAssociation:
Type: AWS::Route53Resolver::ResolverRuleAssociation
Properties:
Name: !Ref associationName
ResolverRuleId: !Ref ruleId
VPCId: !Ref vpcId
- AuthAndAssociationLambda.yaml – creates a Lambda function and necessary IAM role for it. The function is triggered through a custom resource defined in AuthOrAssociation.yaml template. The Lambda function makes necessary API calls to authorize, associate, disassociate, and delete authorization of a VPC to private hosted zone based on the action user choose.
AWSTemplateFormatVersion: 2010-09-09
Description: Custom Resource Lambda to associate & authorize VPC
Resources:
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: route53-access
PolicyDocument:
Statement:
- Effect: Allow
Action:
- route53:AssociateVPCWithHostedZone
- route53:DisassociateVPCFromHostedZone
- ec2:DescribeVpcs
- route53:CreateVPCAssociationAuthorization
- route53:DeleteVPCAssociationAuthorization
- logs:*
Resource: '*'
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.7
Role: !GetAtt LambdaRole.Arn
Handler: index.handler
Code:
ZipFile: |
import os, boto3, json
import logging
import cfnresponse
logger = logging.getLogger("__name__")
logger.setLevel(os.environ.get("LOG_LEVEL", logging.INFO))
r53_client = boto3.client('route53')
def parameters(vpc_id, phz_id):
return dict(
HostedZoneId=phz_id,
VPC={
'VPCRegion': os.environ['AWS_REGION'],
'VPCId': vpc_id
}
)
def associate_vpc_to_hosted_zone(vpc_ids, phz_id):
for vpc_id in vpc_ids:
try:
logger.info("associating {} to hosted zone {}".format(vpc_id, phz_id))
response = r53_client.associate_vpc_with_hosted_zone(**dict(parameters(vpc_id, phz_id)))
logger.info("association is complete : \n {}".format(response))
except Exception as ex:
logger.error("Error associating %s to hosted zone %s : %s", vpc_id, phz_id, ex, exc_info=True)
def authorize_vpc_to_hosted_zone(vpc_ids, phz_id):
for vpc_id in vpc_ids:
try:
logger.info("authorizing {} to hosted zone {}".format(vpc_id, phz_id))
response = r53_client.create_vpc_association_authorization(**dict(parameters(vpc_id, phz_id)))
logger.info("authorization is complete :\n {}".format(response))
except Exception as ex:
logger.error("Error authorizing %s to hosted zone %s : %s", vpc_id, phz_id, ex, exc_info=True)
def disassociate_vpc_from_hosted_zone(vpc_ids, phz_id):
for vpc_id in vpc_ids:
try:
logger.info("disassociation {} to hosted zone {}".format(vpc_id, phz_id))
response = r53_client.disassociate_vpc_from_hosted_zone(**dict(parameters(vpc_id, phz_id)))
logger.info("disassociation is complete :\n {}".format(response))
except Exception as ex:
logger.error("Error disassociation %s to hosted zone %s : %s", vpc_id, phz_id, ex, exc_info=True)
def deauthorize_vpc_to_hosted_zone(vpc_ids, phz_id):
for vpc_id in vpc_ids:
try:
logger.info("delete authorization {} to hosted zone {}".format(vpc_id, phz_id))
response = r53_client.delete_vpc_association_authorization(**dict(parameters(vpc_id, phz_id)))
logger.info("delete authorization is complete:\n {}".format(response))
except Exception as ex:
logger.error("Error deleting authorization %s to hosted zone %s : %s", vpc_id, phz_id, ex,
exc_info=True)
def perform_action(action, vpc_ids, phz_id):
if action == 'ASSOCIATE':
associate_vpc_to_hosted_zone(vpc_ids, phz_id)
elif action == 'AUTHORIZE':
authorize_vpc_to_hosted_zone(vpc_ids, phz_id)
elif action == 'DEAUTHORIZE':
deauthorize_vpc_to_hosted_zone(vpc_ids, phz_id)
elif action == 'DISASSOCIATE':
disassociate_vpc_from_hosted_zone(vpc_ids, phz_id)
def handler(event, context):
try:
logger.info("custom resource triggered by {} request type: {}".format(event.get('StackId'), event.get('RequestType')))
logger.info("custom resource invoked with these properties: {}".format(event.get('ResourceProperties')))
properties = event.get("ResourceProperties")
vpc_ids = properties.get("VPCID")
phz_id = properties.get("HostedZoneID")
event_type = event.get('RequestType')
action = properties.get("Action")
if event_type == 'Create' or event_type == 'Update':
perform_action(action, vpc_ids, phz_id)
elif event_type == 'Delete':
perform_action(action, vpc_ids, phz_id)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, phz_id)
except Exception as ex:
logger.error("Error performing actions: %s", ex, exc_info=True)
if 'PhysicalResourceId' in event:
cfnresponse.send(event, context, cfnresponse.FAILED, {}, event.get("PhysicalResourceId"))
else:
cfnresponse.send(event, context, cfnresponse.FAILED, {})
Outputs:
AuthorizeAssociateVPCLambda:
Value: !GetAtt LambdaFunction.Arn
Export:
Name: AuthorizeAssociateVPCLambdaArn
- AuthOrAssociation.yaml – creates the custom resource that triggers Lambda function for association, authorization, disassociation, or deleting authorization of VPCs to private hosted zone. The custom resource passes a list of VPCId, Private HostedZoneId and an Action (Association or Authorization) to the Lambda function.
AWSTemplateFormatVersion: 2010-09-09
Description: Custom Resource to authorize or associate VPC
Parameters:
AddVPCID:
Type: List<String>
RemoveVPCID:
Type: List<String>
HostedZoneID:
Type: String
ActionType:
Type: String
AllowedValues:
- AUTHORIZE
- ASSOCIATE
- DEAUTHORIZE
- DISASSOCIATE
Conditions:
isAssociateOrAuthorize: !Or [!Equals [!Ref ActionType, AUTHORIZE], !Equals [!Ref ActionType, ASSOCIATE] ]
Resources:
HostedZoneVPC:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !ImportValue AuthorizeAssociateVPCLambdaArn
VPCID: !If [isAssociateOrAuthorize, !Ref AddVPCID, !Ref RemoveVPCID]
HostedZoneID: !Ref HostedZoneID
Action: !Ref ActionType
Note: Authorization is done in the account where the private hosted zone resides, and association is done in the spoke accounts from where you are trying to resolve the records of the private hosted zone.
To summarize – invoking these templates create the following resources:
- In the AWS Account where you create DNS resources –
- Route 53 Resolver endpoints.
- Security groups that are associated with the Resolver endpoints.
- Route 53 resolver rules.
- RAM share to share the resolver rules
- In all the Spoke Accounts –
- Association of VPCs to resolver rules
Deployment steps
Resolving on-premises domains from workloads running in your VPCs.
- Launch the stack using (Resolver.yaml) template in the AWS CloudFormation console in AWS account where you create DNS resources.
- Select the type – “Outbound Endpoint” in the dropdown.
- Select at least two unique subnets from different Availability Zones in the VPC where you want to create the outbound endpoint.
- Launch the second stack using (Rule-share.yaml) template in the AWS CloudFormation console, again in the same AWS account.
- Provide the details of on-premises domain and corresponding authoritative DNS servers.
- Provide AWS Account IDs of spoke accounts with which the rule requires to be shared.
- Launch a stack using (Rule-association.yaml) template in the AWS CloudFormation console in all the spoke accounts.
- Select the resolver rule that is shared via RAM.
- Select the VPCId from where you want to resolve the on-premises domain.
Resolving private domains in your AWS environment from workloads running on-premises.
- Launch the stack using (Resolver.yaml) template in the AWS CloudFormation console in AWS account where you create DNS resources.
- Select the type – “Inbound Endpoint” in the dropdown.
- Select at least two unique subnets from different Availability Zones in the VPC where you want to create the inbound endpoint.
- Associate the VPC from the AWS account in which you have created inbound endpoints to private hosted zone for the domain in AWS being queried from on-premises.
Note: You are not required to create additional endpoint rules for inbound endpoints. Share and associate the spoke VPCs to them as mentioned in the previous case.
Resolving private domains between workloads running in different AWS accounts.
Route 53’s private hosted zone is a container that holds private domain records. You can make the records visible to your resources in VPCs in two ways – associate the VPCs to the private hosted zones or by using Route 53 Resolver endpoints. We recommend that you directly associate the VPCs to the private hosted zone.
To associate the VPCs to the private hosted zone that reside in the same or different AWS accounts, follow these steps:
Authorization of VPCs to a private hosted zone:
In the account, where the private hosted zone resides, follow the below steps:
- Launch a stack using AuthAndAssociationLambda.yaml template in the AWS CloudFormation console.
- Launch the next stack using AuthOrAssociation.yaml template in the AWS CloudFormation console.
- Select ActionType as AUTHORIZE.
- Provide the parameters for PrivateHostedZoneId and VPCId (a list of VPC Ids is also supported).
Association of VPCs to a private hosted zone:
In the spoke accounts that must be associated to the private hosted zone, follow these steps:
- Launch a stack using AuthAndAssociationLambda.yaml template in the AWS CloudFormation console.
- Launch the next stack using AuthOrAssociation.yaml in the AWS CloudFormation console.
- Select ActionType as ASSOCIATE.
- Provide the parameters for PrivateHostedZoneId and VPCId (a list of VPC Ids is also supported).
How you can update your DNS infrastructure on an ongoing basis:
In this section, we show how to use our solution to update your DNS infrastructure on an ongoing basis by looking at three common tasks.
- Add/Remove accounts in your RAM share:
- In the AWS CloudFormation console, Select the stack created using Rule-share.yaml
- In the center pane, select action Update.
- Select the first option – use current template.
- In the parameters section, add or remove accounts from the AccountIds
- Click Next and Update the stack.
- Add/Remove targets from an existing rule:
- In the AWS CloudFormation console, Select the stack created using Rule-share.yaml
- In the center pane, select action Update.
- Select the first option – use current template.
- To add a target, select the domainTargetCount parameter and select one of the allowed values.
- Add the target IPs in the domainTargets based on the domainTargetCount.
- Click Next and Update the stack.
- Go to AWS CloudFormation console in spoke accounts.
- Select the stack created using Rule-association.yaml
- In the center pane, select action Update.
- Select the first option – use current template.
- Provide the new rule ID in ruleId.
- Click Next and Update the stack.
- Authorize/Deauthorize and Associate/Disassociate VPCs:
- To authorize more VPCs to private hosted zone:
- In AWS account where you have private hosted zone, go to the AWS CloudFormation. Select the stack created using AuthOrAssociation.yaml template.
- In the center pane, select action Update.
- Select the first option – use current template.
- To authorize more VPCs, add the VPC IDs into the AddVPCId parameter and select ActionType to AUTHORIZE.
- Click Next and Update the stack.
- To delete authorization from VPCs to private hosted zone:
- In AWS account where you have private hosted zone, go to the AWS CloudFormation. Select the stack created using AuthOrAssociation.yaml template.
- In the center pane, select action Update.
- Select the first option – use current template.
- Provide the VPC ID in the RemoveVPCId parameter and select ActionType to DEAUTHORIZE.
- Click Next and Update the stack.
- To associate more VPCs to Private Hosted Zone
- Go to the spoke accounts.
- Go to the AWS CloudFormation. Select the stack created using AuthOrAssociation.yaml template.
- In the center pane, select action Update.
- To associate more VPCs, pass the VPC IDs into the AddVPCId parameter and select ActionType to ASSOCIATE.
- Click Next and Update the stack.
- To delete association from VPCs to Private Hosted Zone:
- Go to the spoke accounts.
- Go to the AWS CloudFormation. Select the stack created using AuthOrAssociation.yaml template.
- In the center pane, select action Update.
- Select the first option – use current template.
- Add the VPC ID into the RemoveVPCId parameter and select ActionType to DISASSOCIATE.
- Click Next and Update the stack.
- To authorize more VPCs to private hosted zone:
Note: View CloudWatch Logs for the Lambda function that was created through AuthAndAssociationLambda.yaml for more insights on the operations performed.
Cleanup Steps
On successful testing and validation, all the resources deployed through CloudFormation templates should be deleted in order to avoid any unwanted costs. Simply go to the CloudFormation console, identify the stacks appropriately, and delete them.
Note: If you use a multi account setup, you must navigate through account boundaries and follow the above mentioned steps as needed.
Conclusion
We’ve shown how easy it is to deploy a scalable and easily manageable DNS infrastructure using Route 53 Resolver endpoints. We remove the undifferentiated heavy lifting of deployment process by providing the necessary CloudFormation templates.
We hope that you’ve found this post informative and we look forward to hearing how you use this new feature!