Why isn't my AWS ACM certificate validating? - terraform

I have a domain name registered in AWS Route53 with an ACM certificate. I am now attempting to both move that domain name and certificate to a new account as well as manage the resources with Terraform. I used the AWS CLI to move the domain name to the new account and it appears to have worked fine. Then I tried running this Terraform code to create a new certificate and hosted zone for the domain.
resource "aws_acm_certificate" "default" {
domain_name = "mydomain.io"
validation_method = "DNS"
}
resource "aws_route53_zone" "external" {
name = "mydomain.io"
}
resource "aws_route53_record" "validation" {
name = aws_acm_certificate.default.domain_validation_options.0.resource_record_name
type = aws_acm_certificate.default.domain_validation_options.0.resource_record_type
zone_id = aws_route53_zone.external.zone_id
records = [aws_acm_certificate.default.domain_validation_options.0.resource_record_value]
ttl = "60"
}
resource "aws_acm_certificate_validation" "default" {
certificate_arn = aws_acm_certificate.default.arn
validation_record_fqdns = [
aws_route53_record.validation.fqdn,
]
}
There are two things that are strange about this. Primarily, the certificate is created but the validation never completes. It's still in Pending validation status. I read somewhere after this failed that you can't auto validate and you need to create the CNAME record manually. So I went into the console and clicked the "add cname to route 53" button. This added the CNAME record appropriately to my new Route53 record that Terraform created. But it's been pending for hours. I've clicked that same button several times, only one CNAME was created, subsequent clicks have no effect.
Another oddity, and perhaps a clue, is that my website is still up and working. I believe this should have broken the website since the domain is now owned by a new account, routing to a different hosted zone on that new account, and has a certificate that's now still pending. However, everything still works as normal. So I think it's possible that the old certificate and hosted zone is effecting this. Do they need to release the domain and do I need to delete that certificate? Deleting the certificate on the old account sounds unnecessary. I should just no longer be given out.
I have not, yet, associated the certificate with Cloudfront or ALB which I intend to do. But since it's not validated, my Terrform code for creating a Cloudfront instance dies.

It turns out that my transferred domain came transferred with a set of name servers, however, the name servers in the Route53 hosted zone were all different. When these are created together through the console, it does the right thing. I'm not sure how to do the right thing here with Terraform, which I'm going to post another question about in the moment. But for now, the solution is to change the name servers on either the hosted zone or the registered domain to match each other.

It's working for me
######################
data "aws_route53_zone" "main" {
name = var.domain
private_zone = false
}
locals {
final_domain = var.wildcard_enable == true ? *.var.domain : var.domain
# final_domain = "${var.wildcard_enable == true ? "*.${var.domain}" : var.domain}"
}
resource "aws_acm_certificate" "cert" {
domain_name = local.final_domain
validation_method = "DNS"
tags = {
"Name" = var.domain
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "cert_validation" {
depends_on = [aws_acm_certificate.cert]
zone_id = data.aws_route53_zone.main.id
name = sort(aws_acm_certificate.cert.domain_validation_options[*].resource_record_name)[0]
type = "CNAME"
ttl = "300"
records = [sort(aws_acm_certificate.cert.domain_validation_options[*].resource_record_value)[0]]
allow_overwrite = true
}
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = [
aws_route53_record.cert_validation.fqdn
]
timeouts {
create = "60m"
}
}

Related

Cross-account subdomain/hosted zone delegation in Route 53 with Terraform

I have two environments and two AWS accounts: dev and prod. Hence, I have two hosted zones:
dev.example.com in the dev account
example.com in my prod account
In order to successfully route traffic to my dev.example.com subdomain, I need to delegate to my top-level domain (TLD) with a name server record in my TLD's hosted zone. E.g.,
dev.example.com NS Simple [ns-1960.awsdns-22.co.uk. ns-188.awsdns-20.com. ns-208.awsdns-37.net. ns-1089.awsdns-01.org.]
In Terraform code, I would define the two hosted zones as such:
resource "aws_route53_zone" "top_level_domain" {
count = var.env == "prod" ? 1 : 0
name = "example.com"
tags = {
name = "Hosted Zone for top-level domain in production"
env = var.env
}
}
resource "aws_route53_zone" "subdomain" {
count = var.env == "prod" ? 0 : 1
name = "dev.example.com"
tags = {
name = "Hosted Zone for ${var.env} environment"
env = var.env
}
}
In the interests of keeping everything codified, I would like to be able to define my delegation record in Terraform configuration. E.g.,
resource "aws_route53_record" "subdomain_delegation" {
count = var.env == "prod" ? 1 : 0
zone_id = aws_route53_zone.top_level_domain.zone_id
name = "dev.example.com"
type = "NS"
ttl = 300
records = [
aws_route53_zone.subdomain.name_servers
]
}
The issue lies in the aws_route53_zone.subdomain resource not existing in my Terraform state for the prod environment (and so aws_route53_zone.subdomain.name_servers) cannot be found.
Is there an elegant way to solve this? Or is this just a fact of life if one chooses to use AWS accounts for physical environment separation?
Update
The folder structure for my Terraform configuration roughly resembles:
dns/ (Terraform module)
dev/ (makes use of module)
prod/ (makes use of module)
The approach I'm currently using is to have two providers.
I have a master IAM user which can assume role in sub accounts.
That way - in one terraform script - I can target some actions to the root account alias, then other actions can be targeted to the sub-account alias.
So this allows some sharing of state between multiple accounts within one terraform module.

AWS NLB over Helm in Terraform - how to find DNS name?

I am using Helm chart provisioned by Terraform which creates Network Load Balancer, but I do not know how to get DNS name of this balancer so I can create Route53 records in Terraform for it.
If I can get it's ARN, I can call it over data block and read dns_name, however there is nothing like thit that Helm can return for me.
Do you have any suggestions?
I would like to keep it as IaC as possible
PS: I am passing some values to Helm chart so it's creating NLB, native functionality of this Chart is to create Classic LB.
service.beta.kubernetes.io/aws-load-balancer-type: nlb
I just found and answer, it's simple using:
Note: I had to specify namespace, otherwise was service null (not found).
data "kubernetes_service" "ingress_nginx" {
metadata {
name = "ingress-nginx-controller"
namespace = "kube-system"
}
}
output "k8s_service_ingress" {
description = "External DN name of load balancer"
value = data.kubernetes_service.ingress_nginx.status.0.load_balancer.0.ingress.0.hostname
}
It can be found in official docs too - https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/data-sources/service
I had to use kubernetes_ingress_v1 so to create a Route 53 entry for the ingress hostname:
data "kubernetes_ingress_v1" "this" {
metadata {
name = "ingress-myservice"
namespace = "myservice"
}
depends_on = [
module.myservice-eks
]
}
resource "aws_route53_record" "this" {
zone_id = local.route53_zone_id
name = "whatever.myservice.com"
type = "CNAME"
ttl = "300"
records = [data.kubernetes_ingress_v1.this.status.0.load_balancer.0.ingress.0.hostname]
}

Terraform Cloudfront InvalidViewerCertificate

Im trying to create a Cloudform distribution using an existing ACM Certificate:
data "aws_acm_certificate" "issued" {
domain = "*.mydomain.com"
statuses = ["ISSUED"]
}
resource "aws_cloudfront_distribution" "cloudfront" {
...
viewer_certificate {
cloudfront_default_certificate = false
acm_certificate_arn = data.aws_acm_certificate.issued.id
minimum_protocol_version = "TLSv1.1_2016"
ssl_support_method = "sni-only"
}
...
}
I'm getting the error: Error: error updating CloudFront Distribution (EMLDE0O3OG6CZ): InvalidViewerCertificate: The specified SSL certificate doesn't exist, isn't in us-east-1 region, isn't valid, or doesn't include a valid certificate chain.
The certificate is already in use with another manually created distribution, also when I replace data.aws_acm_certificate.issued.id by the certificate ARN as a string everything works fine.
Ok so looking a bit closer I've realised that the certificate was coming from the region that I'm deploying my resources and not "us-east-1"
Based on this answer, this is how I've fixed the problem:
provider "aws" {
region = var.aws_region
}
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
data "aws_acm_certificate" "issued" {
domain = "*.example.com"
statuses = ["ISSUED"]
provider = aws.virginia
}
According with Terraform's docs, the provider without an alias is the default and I'll use the second only to fetch my certificate data!

Terraform error - Error update server is not set

Using Terraform v0.14.7 I'm creating a cname record set, but when I put the command terraform apply, I got this message error:
Error: update server is not set
In the Azure plataform in the "DNS ZONES" I have already created a zone called: tech.com.br:
provider "azurerm" {
features {}
}
resource "dns_cname_record" "foo" {
zone = "tech.com.br."
name = "foo"
cname = "info.tech.com.br."
ttl = 3600
}
Anyone could help me?
If you have created a DNS zone in the Azure platform, you can manage DNS CNAME Records within Azure DNS by using resource azurerm_dns_cname_record instead of resource "dns_cname_record". Also, we don't need to provide the zone name with the dot . suffix.
Change your code like this:
resource "azurerm_dns_cname_record" "foo" {
zone_name = "tech.com.br"
name = "foo"
record = "info.tech.com.br"
ttl = 3600
resource_group_name = "YourDNSZoneRG"
}

Terraform: Multiple ACM certificates

I'm trying to write some TF that, given a single FQDN for a site, will generate an ACM certificate, create the R53 records for validation and run the validation in a single TF pass.
I'm not using subdomains, and I got it working for a single FQDN, but as is the nature with TF, I want to be able to add another FQDN to the variable in the future to have multiple certs.
When I run the below code I get the error:
Error: Error running plan: 1 error occurred:
* aws_acm_certificate_validation.cert: 2 errors occurred:
* aws_acm_certificate_validation.cert[0]: Resource 'aws_route53_record.cert_validation' does not have attribute 'fqdn' for variable 'aws_route53_record.cert_validation.*.fqdn'
* aws_acm_certificate_validation.cert[1]: Resource 'aws_route53_record.cert_validation' does not have attribute 'fqdn' for variable 'aws_route53_record.cert_validation.*.fqdn'
But I know that the R53 record does export the fqdn attribute.
acm.tf:
resource "aws_acm_certificate" "cert" {
count = "${length(var.certificate_fqdns)}"
domain_name = "${element(var.certificate_fqdns, count.index)}"
validation_method = "DNS"
tags = "${local.all_tags}"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "cert_validation" {
count = "${length(var.certificate_fqdns)}"
name = "${lookup(local.domain_validation_options[count.index], "resource_record_name")}"
type = "${lookup(local.domain_validation_options[count.index], "resource_record_type")}"
zone_id = "${data.aws_route53_zone.cert_fqdn_zone.*.id}"
records = ["${lookup(local.domain_validation_options[count.index], "resource_record_value")}"]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
count = "${length(var.certificate_fqdns)}"
certificate_arn = "${element(aws_acm_certificate.cert.*.arn, count.index)}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn}"]
}
variables.tf:
variable "certificate_fqdns" {
description = "The FQDNs to be used to create ACM certificates."
type = "list"
default = []
}
locals {
domain_validation_options = "${flatten(aws_acm_certificate.cert.*.domain_validation_options)}"
}
data "aws_route53_zone" "cert_fqdn_zone" {
name = "${element(var.certificate_fqdns, count.index)}"
}
and my vars file contains an entry like this:
"certificate_fqdns": [
"example.com"
]
EDIT: Added the data lookup for the Route53 Zones, which only seems to return the zone for the first domain provided in the variable even when there are multiple, different domains. i.e. example1.com and example2.com it will use the same zone ID for both sets of R53 records which will obviously fail

Resources