Easy AWS ACM DNS Validation with Terraform
Perform AWS ACM DNS validation in Terraform without having to start using loops in your IaaC.

When you create a certificate in AWS ACM, it can be validated by using the Email or DNS method. For the DNS one, Amazon Certificate Manager will offer you to create by hand the validation record inside Route 53, however, if you want to have your certificates in Terraform, this means that this validation record can linger in your hosted zone after destroying the certificate with a terraform destroy
.
One way to do so is to also create a Route53 record right after the certificate to validate it. In that sense, we want to ensure that the record will be created with a minimal amount of hardcoded values in it. To achieve this we have to take a look inside the state generated by the creation of the certificate:
resource "aws_acm_certificate" "example" {
arn = "arn:aws:acm:region:account_id:certificate/foo-bar"
domain_name = "foo.bar.io"
domain_validation_options = [
{
domain_name = "foo.bar.io"
resource_record_name = "_xxxxx.foo.bar.io."
resource_record_type = "CNAME"
resource_record_value = "_yyyyyyy.zzzzzz.acm-validations.aws."
},
]
id = "arn:aws:acm:region:account_id:certificate/foo-bar"
The values that we are looking for are located inside the domain_validation_options
object. That makes it a bit tricky to recover as we either need to loop inside the object to recover the values we want, this inserting some logic inside our infrastructure declaration, or manipulate the data and feed it to the record resource. I don't like loops in Terraform so I will choose the second option.
First, we need to isolate the object we want. For the purpose of showing you, I will be using an output to capture the functions I will be using:
output "test" {
value = aws_acm_certificate.example.domain_validation_options
}
Changes to Outputs:
+ test = [
+ {
+ domain_name = "foo.bar.io"
+ resource_record_name = "_xxxxxxxxx.foo.bar.io."
+ resource_record_type = "CNAME"
+ resource_record_value = "_yyyyyyyyyy.zzzzzzzzzz.acm-validations.aws."
},
]
To be able to recover the data inside the set
object structure, we will need to change it to a list by using the tolist
function:
output "test" {
value = tolist(aws_acm_certificate.example.domain_validation_options)[0]
}
Changes to Outputs:
+ test = {
+ domain_name = "foo.bar.io"
+ resource_record_name = "_xxxxxxxxxxxx.foo.bar.io."
+ resource_record_type = "CNAME"
+ resource_record_value = "_yyyyyyyyyy.zzzzzzzzzzz.acm-validations.aws."
}
Now that the set
is out of the way, we can now use the lookup
function to recover the values we want to create our record with:
output "test" {
value = lookup(tolist(aws_acm_certificate.example.domain_validation_options)[0], "resource_record_name")
}
Changes to Outputs:
+ test = "_xxxxxxxxxxxx.foo.bar.io."
By using this method, you can now create the certificate by applyingthe same functions to the resource_record_type
and resource_record_value
keys:
resource "aws_route53_record" "this" {
allow_overwrite = true
name = lookup(tolist(aws_acm_certificate.example.domain_validation_options)[0], "resource_record_name")
records = [lookup(tolist(aws_acm_certificate.example.domain_validation_options)[0], "resource_record_value")]
ttl = 300
type = lookup(tolist(aws_acm_certificate.example.domain_validation_options)[0], "resource_record_type")
zone_id = var.hosted_zone_id
}
Once applying this resource in terraform, you will need to wait up to 30 minutes.