How to create route53 zone with predefined NS, or update NS of a registered domain? - amazon-web-services

I have a domain registered on Route 53. This domain points towards some name servers of an old Route53 route. I'm now building my Terraform script to create a new Route53 zone. Is it possible to set the name servers when creating this? I tried the following, but that didn't work:
resource "aws_route53_record" "dev-ns" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "dev.example.com"
type = "NS"
ttl = "30"
records = [
"ns1.aws",
"ns2.aws",
"ns3.aws",
"ns4.aws",
]
}
I could imagine that this isn't possible, since the NS seem to assigned randomly. If this is indeed the case, is there a Terraform command to change the NS of my registered domain? I found this posting on Github, so I think there isn't any Terraform command for this: https://github.com/terraform-providers/terraform-provider-aws/issues/88
Any alternatives?

In your case you'd be better off importing the existing Route53 zone into your state file so that Terraform can then begin managing it instead of creating a new one that uses the same name servers.
You can import the zone with the following command:
terraform import aws_route53_zone.myzone Z1D633PJN98FT9
Where import aws_route53_zone.myzone refers to the resource name and Z1D633PJN98FT9 to the zone ID.

You can escape dynamic ns records via delegation set https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/route-53-concepts.html#route-53-concepts-reusable-delegation-set
By default, Route 53 assigns a random selection of name servers to each new hosted zone. To make it easier to migrate DNS service to Route 53 for a large number of domains, you can create a reusable delegation set and then associate the reusable delegation set with new hosted zones.
resource "aws_route53_delegation_set" "main" {
reference_name = "DynDNS"
}
resource "aws_route53_zone" "primary" {
name = "hashicorp.com"
delegation_set_id = "${aws_route53_delegation_set.main.id}"
}
resource "aws_route53_zone" "secondary" {
name = "terraform.io"
delegation_set_id = "${aws_route53_delegation_set.main.id}"
}
Example from https://www.terraform.io/docs/providers/aws/r/route53_delegation_set.html

Related

Issue to get all hosted zone id of AWS ELB through Terraform

I have created two AWS NLBs in the same region through terraform. Now I have to make DNS records in Route53 with type alias.
But there are an error.
Error: [ERR]: Error building changeset: InvalidChangeBatch: [Tried to create an alias that targets 11111111111111111111111-xxxxxxxxxxxx.elb.eu-west-2.amazonaws.com., type A in zone ZHURV0000000, but the alias target name does not lie within the target zone] status code: 400, request id: 2xxxxxxxxxxxxxxxxx
It was working fine, when I had only single NLB.
because, we need ELB zone id to make DNS entry with alias type. and both NLB has different zone ID. but terraform is providing only single zone id through below code.
data "aws_elb_hosted_zone_id" "main" {}
Im taking reference from below link:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_hosted_zone_id
The ultimate problem is:
How to get the 2nd, 3rd .. zone id of ELB in the same region ??
There is no such thing as second and third zone id for elastic load balancing. There is one per region for everyone, in fact you can get the IDs from here: https://docs.aws.amazon.com/general/latest/gr/elb.html.
You can reuse the same data block for multiple records. What will change is the domain name which is unique for each load balancer:
resource "aws_route53_record" "www" {
...
type = "A"
alias {
name = aws_lb.my_network_load_balancer.dns_name # This changes based on load balancer
zone_id = data.aws_elb_hosted_zone_id.main # The remains the same for each record
}
}
Update:
data "aws_elb_hosted_zone_id" "main" {} does not work with network load balancers. We can get the canoical hosted zone id by referencing an attribute of aws_elb resource: aws_lb.my_network_load_balancer.zone_id.
Finally I got below solution for NLB DNS entry:
Here, I'm fetching zone id from the NLB name.
Note: "aws_elb_hosted_zone_id" will provide you zone id of the ALB, not NLB
resource "aws_route53_zone" "this" {
name = lower("${var.base_domain_name}")
}
#get DNS zone
data "aws_route53_zone" "this" {
name = lower("${var.base_domain_name}")
depends_on = [
aws_route53_zone.this
]
}
data "aws_lb" "my_nlb" {
name = "my-nlb"
}
resource "aws_route53_record" "nlb_dns" {
zone_id = data.aws_route53_zone.this.zone_id
name = "my-nlb-dns"
type = "A"
alias {
name = "my_nlb-0000000.us-east-1.elb.amazonaws.com"
zone_id = data.aws_lb.my_nlb.zone_id # this is the fix
evaluate_target_health = true
}
}
Here is the snippet how I do this:
data "aws_lb_hosted_zone_id" "route53_zone_id_nlb" {
region = var.region
load_balancer_type = "network"
}
resource "aws_route53_record" "route53_wildcard" {
depends_on = [helm_release.nginx_ingress]
zone_id = data.terraform_remote_state.aws_remote.outputs.domain_zone_id # Replace with your zone ID
name = "*.${var.domain}" # Replace with your subdomain, Note: not valid with "apex" domains, e.g. example.com
type = "A"
alias {
name = data.kubernetes_service.nginx_ingress.status.0.load_balancer.0.ingress.0.hostname
zone_id = data.aws_lb_hosted_zone_id.route53_zone_id_nlb.id
evaluate_target_health = false
}
}
Attention!
Don't mix zone_id of LB (it is static and differs between regions here AWS document) and zone_id of Route 53 zone itself.

AWS CDK -- How do I retrieve my NS Records from my newly created Hosted Zone by AWS CDK

Say I created a public hosted zone or fetch a hosted zone from lookup and I want to retrieve the NS Records for other usage
const zone = new route53.PublicHostedZone(this, domain + 'HostedZone', {
zoneName: '' + domain
})
const zone = HostedZone.fromLookup(this, 'HostedZone', { domainName: config.zoneName });
Does the current CDK have any methods to do that. I've look around the API doco and found none. Any suggestions?
Update
I did try the hostedZoneNameServers property. However, it doesn't seem to return anything.
const zone = route53.HostedZone.fromLookup(this, 'DotnetHostedZone', {
domainName: <myDomain>,
});
new CfnOutput(this, `output1`, {
value: zone.zoneName
});
new CfnOutput(this, `output2`, {
value: zone.hostedZoneId
});
new CfnOutput(this, 'output3', {
value: zone.hostedZoneNameServers?.toString() || 'No NameServer'
});
✅ test-ops
Outputs:
test-ops.output1 = <myDomain>
test-ops.output2 = <myZoneId>
test-ops.output3 = No NameServer
And I confirm with my zone and used to do a record export, I can retrieve all my records.
The ultimate goal is to automate a subdomain provisioning. But I'm currently scratching my head on this route.
There is a hostedZoneNameServers property on the zone object.
const zone = HostedZone.fromLookup(this, 'HostedZone', { domainName: config.zoneName });
const nsRecords = zone.hostedZoneNameServers;
Reference:
https://docs.aws.amazon.com/cdk/api/latest/typescript/api/aws-route53/hostedzone.html#aws_route53_HostedZone_hostedZoneNameServers
I do not believe you can do that right from the script. The values will be just "Tokens" which will be replaced by CloudFormation after/during the deployment, but not at during synthesis. Outputting during synthesis will therefore leave you blind. You will need to fetch them in a post-process I guess..
I am running into the same issue, which is why I found your post :D
hostedZoneNameServers is not defined for private or imported zones as mentioned in the docs. You can use only if you create your zone in CDK (e.g. new PublicHostedZone(...).hostedZoneNameServers).
If you create the zone elsewhere, try to use AWS Route53 GetHostedZone API.
This worked for me
const nsRecords = hostedZone.hostedZoneNameServers;
if (nsRecords) {
for (let i=0; i<4; i++) {
context.cfnOutput(this, `NS Record${i+1}`, Fn.select(i, nsRecords));
}
}
As #JD D mentioned, there is a hostedZoneNameServers attribute on hosted zones, but they aren't available in cross stack. The documentation has been updated(or this was missed when first answered) to reflect this.
CDK V1 /
CDK V2
hostedZoneNameServers?
Type: string[] (optional)
Returns the set of name servers for the specific hosted zone. For example: ns1.example.com.
This attribute will be undefined for private hosted zones or hosted zones imported from another stack.
So in order to accomplish what you want, you will need to set the NS values as an output on the stack that created the hosted zone and consume them by referencing the stack that provides the NS output.
I was able to automate subdomain provisioning with the following code. Note that these hosted zones share the same stack, which may not work for your use case.
export const hostedZone = new HostedZone(stack, `${env}-hosted-zone`, {
zoneName: host,
})
// API
const apiHost = `api.${host}`
export const apiHostedZone = new HostedZone(stack, `${env}-hosted-zone-api`, {
zoneName: apiHost,
})
// note that this record is actually on the parent zone,
export const apiHostedZoneNsRecord = new NsRecord(stack, `${env}-hosted-zone-ns-api`, {authoritatively pointing to its sub-subdomain
recordName: apiHost,
values: apiHostedZone.hostedZoneNameServers as string[],
zone: hostedZone,
})
This resulted in the following snippet of CFT (${env} and ${rnd} replaced with concrete values, of course):
"ResourceRecords": {
"Fn::GetAtt": [
"${env}hostedzoneapi${rnd}",
"NameServers"
]
},
If you can accept the same stack constraint, you should be able to accomplish this. Note that while I could accept the constraint for this stack, more broadly I have a multi-account structure and had to manually add the sub-account's subdomain NS record to the parent account's root domain. Summary of this setup:
root account:
example.com
NS child.example.com // manually added
child account:
child.example.com // contents of `host` below
NS api.child.example.com
api.child.example.com // automatic subdomain created with code above

How to create multi-value SRV DNS record with terraform in AWS Route53 service?

I am trying to create multi-value SRV DNS entry in AWS route53 service via terraform. Values should be taken from instances tags. Due to the fact, that this is only one record, approach with count is not applicable.
The trick is, that I have 10 instances but they need to be filtered first by finding specific tags. Based on resultlist, SRV record should be created by using the Name tag assigned to each instance.
Any idea how to approach this issue?
Thanks in advance for any tip.
I did it like this:
resource "aws_instance" "myservers" {
count = 3
#.... other configuration....
}
resource "aws_route53_record" "srv" {
zone_id = aws_route53_zone.myzone.zone_id
name = "_service"
type = "SRV"
records = [for server in data.aws_instance.myservers : "0 10 5000 ${server.private_ip}."]
}
Terraform's for expression is being the key for the solution.
Regarding the SRV record in AWS Route 53, it should have a line per server and each line in the following form: priority weight port target (space is the delimiter). For the example above: 0 is the priority, 10 is the weight, 5000 is the port and the last one is the server IP (or name)

Setting "count" based on the length of an attribute on another resource

I have a fairly simple Terraform configuration, which creates a Route53 zone and then creates NS records in Cloudflare to delegate the subdomain to that zone. At present, it assumes there's always exactly four authoritative DNS servers for every Route53 zone, and creates four separate cloudflare_record resources, but I'd like to generalise that, partially because who knows if AWS will start putting a fifth authoritative server out there in the future, but also as a "test case" for more complicated stuff in the future (like AWS AZs, which I know vary in count between regions).
What I've come up with so far is:
resource "cloudflare_record" "public-zone-ns" {
domain = "example.com"
name = "${terraform.env}"
type = "NS"
ttl = "120"
count = "${length(aws_route53_zone.public-zone.name_servers)}"
value = "${lookup(aws_route53_zone.public-zone.name_servers, count.index)}"
}
resource "aws_route53_zone" "public-zone" {
name = "${terraform.env}.example.com"
}
When I run terraform plan over this, though, I get this error:
Error running plan: 1 error(s) occurred:
* cloudflare_record.public-zone-ns: cloudflare_record.public-zone-ns: value of 'count' cannot be computed
I think what that means is that because the aws_route53_zone hasn't actually be created, terraform doesn't know what length(aws_route53_zone.public-zone.name_servers) is, and therefore the interpolation into cloudflare_record.public-zone-ns.count fails and I'm screwed.
However, it seems surprising to me that Terraform would be so inflexible; surely being able to create a variable number of resources like this would be meat-and-potatoes stuff. Hard-coding the length, or creating separate resources, just seems so... limiting.
So, what am I missing? How can I create a number of resources when I don't know in advance how many I need?
Currently count not being able to be calculated is an open issue in terraform https://github.com/hashicorp/terraform/issues/12570
You could move the name servers to a variable array and then get the length of that, all in one terraform script.
I got your point, this surprised me as well. Even I added depends_on to resource cloudflare_record, it is helpless.
What you can do to pass this issue is to split it into two stacks and make sure the route 53 record is created before cloudflare record.
Stack #1
resource "aws_route53_zone" "public-zone" {
name = "${terraform.env}.example.com"
}
output "name_servers" {
value = "${aws_route53_zone.public-zone.name_servers}"
}
Stack #2
data "terraform_remote_state" "route53" {
backend = "s3"
config {
bucket = "terraform-state-prod"
key = "network/terraform.tfstate"
region = "us-east-1"
}
}
resource "cloudflare_record" "public-zone-ns" {
domain = "example.com"
name = "${terraform.env}"
type = "NS"
ttl = "120"
count = "${length(data.terraform_remote_state.route53.name_servers)}"
value = "${element(data.terraform_remote_state.route53.name_servers, count.index)}"
}

How can i specify the dns servers when terrafrorm uses aws_route53_zone

When terraform runs the following, it apparently picks random NS servers:
resource "aws_route53_zone" "example.com" {
name = "example.com"
}
The problem with this is that the registered domain that I have in AWS already has specified NS servers. Is there a way to specify the NS servers this resource uses - or maybe change the hosted domain's NS servers to what is picked when the zone is created?
When you create a new zone, AWS generates the Name server list for you. Using this example from Terraform.
resource "aws_route53_zone" "dev" {
name = "dev.example.com"
tags {
Environment = "dev"
}
}
resource "aws_route53_record" "dev-ns" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "dev.example.com"
type = "NS"
ttl = "30"
records = [
"${aws_route53_zone.dev.name_servers.0}",
"${aws_route53_zone.dev.name_servers.1}",
"${aws_route53_zone.dev.name_servers.2}",
"${aws_route53_zone.dev.name_servers.3}",
]
}
https://www.terraform.io/docs/providers/aws/r/route53_zone.html
API returns a Delegation Set after the call to Create Zone.
http://docs.aws.amazon.com/Route53/latest/APIReference/API_CreateHostedZone.html#API_CreateHostedZone_ResponseSyntax
I have been able to specify DNS servers but I would imagine that AWS is allocating servers based on availability, load etc... so you may want to think hard about baking these configs in.
resource "aws_route53_record" "primary-ns" {
zone_id = "${aws_route53_zone.primary.zone_id}"
name = "www.bacon.rocks"
type = "NS"
ttl = "172800"
records = ["ns-869.awsdns-44.net","ns-1237.awsdns-26.org","ns-1846.awsdns-38.co.uk","ns-325.awsdns-40.com"]
}
or something along those lines