error ACM (AWS) and cloudflare for validate a certificate - amazon-web-services

resource "aws_acm_certificate" "cert" {
domain_name = "atlantis.mydomain"
validation_method = "DNS"
}
data "cloudflare_zones" "this" {
filter {
name = "myzone.com"
status = "active"
paused = false
}
}
resource "cloudflare_record" "cert_validation" {
zone_id = data.cloudflare_zones.this.zones[0].id
name = aws_acm_certificate.cert.domain_validation_options.0.resource_record_name
type = aws_acm_certificate.cert.domain_validation_options.0.resource_record_type
value = aws_acm_certificate.cert.domain_validation_options.0.resource_record_value
ttl = 3600
proxied = false
}
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = [cloudflare_record.cert_validation.hostname]
}
1 error occurred:
* missing atlantis.mydomain DNS validation record: _1002e16ebd84cda6c12a865cf899175a.atlantis.mydomain
i don't know how to resolve this error
i have a problem when i want create a certificat ACM and validate with cloudflare in aws and with terraform.
i have an error during the deployment about dns vérification
the record and the certificate have been created , but not validate
my cloudflare module is working well and for months

You need to use the values from the Terraform aws_acm_certificate resource's domain_validation_options values to create validation DNS records in CloudFlare. These will be entirely separate from the DNS record you are creating to point to your load balancer.

Related

trying to create an aws_route53_record to point to a load balancer but keep getting an error using terraform

As the domain already existed I imported the zone into my configuration:
resource "aws_route53_zone" "example_hosted_zone" {
name = "example.club"
}
Route 53 record:
resource "aws_route53_record" "us-battasks" {
zone_id = aws_route53_zone.example_hosted_zone.zone_id
name = "us-battasks"
type = "CNAME"
ttl = "60"
records = [aws_lb.restricted_access_lb.id]
}
resource "aws_route53_record" "us-battasksapi" {
zone_id = aws_route53_zone.example_hosted_zone.zone_id
name = "us-battasksapi"
type = "CNAME"
ttl = "60"
records = [aws_lb.restricted_access_lb.id]
}
The Terraform plan shows it will create the resource but when I apply I get this following error:
Error: [ERR]: Error building changeset: InvalidChangeBatch: [Invalid Resource Record: FATAL problem: DomainLabelTooLong (Domain label is too long) encountered with 'arn:aws:elasticloadbalancing:us-east-1:221124075124:loadbalancer', Unparseable CNAME encountered]
status code: 400, request id: e43e5ced-957f-4bcd-83d2-1e7eaea7665b
Error: [ERR]: Error building changeset: InvalidChangeBatch: [Invalid Resource Record: FATAL problem: DomainLabelTooLong (Domain label is too long) encountered with 'arn:aws:elasticloadbalancing:us-east-1:221124075124:loadbalancer', Unparseable CNAME encountered]
status code: 400, request id: 33d3340e-f2f2-4c95-bc96-a9de1349afc4
Here is the Terraform code for the load balancer if it helps:
resource "aws_lb" "restricted_access_lb" {
name = "restricted-access-lb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.swarm_node_sg.id, aws_security_group.monolaunch_instance_sg.id, aws_security_group.restricted_access_sg.id]
subnets = [aws_subnet.public_subnet_b.id, aws_subnet.public_subnet_a.id]
enable_deletion_protection = true
}
The id of the aws_lb resource is the ARN which is why you see the ARN for the load balancer shown in the error when it's trying to create a Route53 record.
Instead you should be using the dns_name attribute instead which will map to the address of the load balancer.
resource "aws_route53_record" "us-battasksapi" {
zone_id = aws_route53_zone.example_hosted_zone.zone_id
name = "us-battasksapi"
type = "CNAME"
ttl = "60"
records = [aws_lb.restricted_access_lb.dns_name]
}
If, instead, you wanted to use an alias A record to avoid the second DNS lookup (plus issues around apex records in a zone) you would instead use the following:
resource "aws_route53_record" "us-battasksapi" {
zone_id = aws_route53_zone.example_hosted_zone.zone_id
name = "us-battasksapi"
type = "A"
alias {
name = aws_lb.restricted_access_lb.dns_name
zone_id = aws_lb.restricted_access_lb.zone_id
evaluate_target_health = true
}
}
In your aws_route53_record you are using:
records = [aws_lb.restricted_access_lb.id]
This will try making CNAME to ARN of your load balancer. Instead you should be using
records = [aws_lb.restricted_access_lb.dns_name]
Ideally, it also should be A Alias record, not CNAME, as shown in the docs.

Validate certificate using new record

I am trying to validate ACM certificate in terraform using method outlined here, basically it's a DNS validation using Route53 record. The problem is, as I understand, it needs already existing Route53 record so it can use records property of the resource. But in my case it's a new record being created, so if I try both alias and records properties at the same time, e.g.
resource aws_route53_record wildcard {
zone_id = var.environment.route53_zone.zone_id
name = "*.${local.cname}."
type = "A"
alias {
name = aws_cloudfront_distribution.main.domain_name
zone_id = aws_cloudfront_distribution.main.hosted_zone_id
evaluate_target_health = false
}
records = [aws_acm_certificate.wildcard[0].domain_validation_options.0.resource_record_value]
}
I am getting error "alias" conflicts with "records". Is there a way within the same script to create Route53 record and use the same for certificate validation?
You need to use the aws_acm_certificate_validation resource, and luckily that page has a great example for how to do this.
resource "aws_acm_certificate" "cert" {
domain_name = "example.com"
validation_method = "DNS"
}
data "aws_route53_zone" "zone" {
name = "example.com."
private_zone = false
}
resource "aws_route53_record" "cert_validation" {
name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}"
type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}"
zone_id = "${data.aws_route53_zone.zone.zone_id}"
records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]
}
resource "aws_lb_listener" "front_end" {
# [...]
certificate_arn = "${aws_acm_certificate_validation.cert.certificate_arn}"
}
I realized that Route53 record used for certificate validation has nothing to do with Route53 records used for CloudFront distribution. Both have to be created, each serves its separate purpose.

Dynamic Route 53 records for ACM certificate validation

I have a Route 53 zone (example.org) with a few record sets (example.org, sub1.example.org). I also have an ACM certificate with DNS validation for the domains described by the record sets.
All of this is described in Terraform.
Currently for the validation to work I have to list all of the domain names in the certificate (domain_name, subject_alternative_names), the certificate validation (validation_record_fqdns), and the Route 53 records (aws_route53_record resources).
Is there a better, more dynamic way of doing this so if I want to add a new subdomain I only have to do so in one place?
I read about the for and for_each features in recent versions of Terraform but so far it's not trivial for me to use that in this case.
resource "aws_acm_certificate" "example" {
domain_name = "example.org"
subject_alternative_names = ["sub1.example.org"]
validation_method = "DNS"
}
resource "aws_acm_certificate_validation" "example" {
certificate_arn = "${aws_acm_certificate.example.arn}"
validation_record_fqdns = ["${aws_route53_record.example-validation.fqdn}", "${aws_route53_record.example-validation-sub1.fqdn}"]
}
resource "aws_route53_record" "example-validation" {
zone_id = "${data.aws_route53_zone.example.id}"
name = "${aws_acm_certificate.example.domain_validation_options.0.resource_record_name}"
type = "${aws_acm_certificate.example.domain_validation_options.0.resource_record_type}"
ttl = 60
records = ["${aws_acm_certificate.example.domain_validation_options.0.resource_record_value}"]
}
resource "aws_route53_record" "example-validation-sub1" {
zone_id = "${data.aws_route53_zone.example.id}"
name = "${aws_acm_certificate.example.domain_validation_options.1.resource_record_name}"
type = "${aws_acm_certificate.example.domain_validation_options.1.resource_record_type}"
ttl = 60
records = ["${aws_acm_certificate.example.domain_validation_options.1.resource_record_value}"]
}

Terraform with API-Gateway, Route53, and SSL Certification interdependency problem

I can't seem to get an SSL certificate from ACM working on API-Gateway, Route53, using terraform. There seems to be an interdependency problem.
data "aws_route53_zone" "root_domain" {
name = "${var.route53_root_domain_name}"
private_zone = false
}
# The domain name to use with api-gateway
resource "aws_api_gateway_domain_name" "domain_name" {
domain_name = "${var.route53_sub_domain_name}"
certificate_arn = "${aws_acm_certificate.cert.arn}"
}
resource "aws_route53_record" "sub_domain" {
name = "${var.route53_sub_domain_name}"
type = "A"
zone_id = "${data.aws_route53_zone.root_domain.zone_id}"
alias {
name = "${aws_api_gateway_domain_name.domain_name.cloudfront_domain_name}"
zone_id = "${aws_api_gateway_domain_name.domain_name.cloudfront_zone_id}"
evaluate_target_health = false
}
}
resource "aws_acm_certificate" "cert" {
# api-gateway / cloudfront certificates need to use the us-east-1 region
provider = "aws.cloudfront-acm-certs"
domain_name = "${var.route53_sub_domain_name}"
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "cert_validation" {
name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}"
type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}"
zone_id = "${aws_route53_record.sub_domain.zone_id}"
records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
# api-gateway / cloudfront certificates need to use the us-east-1 region
provider = "aws.cloudfront-acm-certs"
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]
}
The problem appears to be that:
aws_api_gateway_domain_name requires aws_acm_certificate
aws_acm_certificate has to be validated, so step 3
aws_route53_record.cert_validation requires aws_route53_record.sub_domain
aws_route53_record.subdomain requires aws_api_gateway_domain_name
Go to 1
Everytime I try to use the configuration given, I get the following error:
aws_api_gateway_domain_name.domain_name: Error creating API Gateway
Domain Name: BadRequestException: Unable to associate certificate
arn:aws:acm:us-east-1:yyyy:certificate/zzzz with CloudFront. This
error may prevent the domain name audit-log.taspli.com from being used
in API Gateway for up to 40 minutes. Please ensure the certificate
domain name matches the requested domain name, and that this user has
permission to call cloudfront:UpdateDistribution on '*' resources.
status code: 400, request id: xxxx
I seem to have fixed the problem by adding the certificate validation records to the root domain instead of the sub domain. Therefore breaking the cyclic dependency.
The problem appears to be that the sub domain can't be created without the certificate and the certificate can't be validated without the sub domain. So the situation is stuck and unresolvable.
You could manually create the sub domain, but then whats the point in automation if you have to make manual efforts to solve problems.
So I tried adding the cert validation records to the root. Suddenly it starts to work, because the root domain is something that is created externally to the project. A sort of global infrastructure project which can be handled externally. Then your individual projects can hang off of that infrastructure on a case-by-case basis.
Here is the terraform configuration which worked:
data "aws_route53_zone" "root_domain" {
name = "${var.route53_root_domain_name}"
private_zone = false
}
# The domain name to use with api-gateway
resource "aws_api_gateway_domain_name" "domain_name" {
domain_name = "${var.route53_sub_domain_name}"
certificate_arn = "${aws_acm_certificate.cert.arn}"
}
resource "aws_route53_record" "sub_domain" {
name = "${var.route53_sub_domain_name}"
type = "A"
zone_id = "${data.aws_route53_zone.root_domain.zone_id}"
alias {
name = "${aws_api_gateway_domain_name.domain_name.cloudfront_domain_name}"
zone_id = "${aws_api_gateway_domain_name.domain_name.cloudfront_zone_id}"
evaluate_target_health = false
}
}
resource "aws_acm_certificate" "cert" {
# api-gateway / cloudfront certificates need to use the us-east-1 region
provider = "aws.cloudfront-acm-certs"
domain_name = "${var.route53_sub_domain_name}"
validation_method = "DNS"
}
resource "aws_route53_record" "cert_validation" {
name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}"
type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}"
zone_id = "${data.aws_route53_zone.root_domain.zone_id}"
records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
# api-gateway / cloudfront certificates need to use the us-east-1 region
provider = "aws.cloudfront-acm-certs"
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]
timeouts {
create = "45m"
}
}
#Christopher Thomas thank you for your initial answer
With current terraform version it needs some modifications in the certificate blocks and I propose to set a provider in case your main region is not us-east-1. Since the changes are too long for a comment I add another answer for now
# api-gateway / cloudfront certificates need to use the us-east-1 region
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
resource "aws_acm_certificate" "cert" {
provider = aws.virginia
domain_name = local.api_subdomain
validation_method = "DNS"
}
resource "aws_acm_certificate_validation" "cert" {
provider = aws.virginia
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
name = each.value.name
records = [each.value.record]
type = each.value.type
zone_id = data.aws_route53_zone.root_domain.zone_id
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
provider = aws.virginia
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
# Finally Hook up Custom api domain name with api stage
resource "aws_api_gateway_base_path_mapping" "avxapi_base_path_mapping" {
api_id = aws_api_gateway_rest_api.xxxxxx.id
stage_name = aws_api_gateway_stage.xxxx.stage_name
domain_name = aws_api_gateway_domain_name.xxxx.domain_name
}
For me I have changed the aws_route53_record.cert_validation resource and it worked:
resource "aws_route53_record" "cert_validation" {
name = "${tolist(aws_acm_certificate.cert.domain_validation_options)[0].resource_record_name}"
type = "${tolist(aws_acm_certificate.cert.domain_validation_options)[0].resource_record_type}"
zone_id = "${aws_route53_record.sub_domain.zone_id}"
records = ["${tolist(aws_acm_certificate.cert.domain_validation_options)[0].resource_record_value}"]
ttl = 60
}

CertificateNotfound error when creating LB Listener

I am having trouble adding a certificate to my LB listener. Here is the code used to do so (note these is a snippets of code):
global/main.tf
resource "aws_acm_certificate" "demo_cert_east" {
provider = "aws.east"
domain_name = "*.mydomain.com"
validation_method = "DNS"
tags {
Name = "demo certificate"
Environment = "demo"
}
lifecycle {
create_before_destroy = true
}
}
stage/main.tf
data "aws_acm_certificate" "demo_cert" {
domain = "*.mydomain.com"
statuses = ["ISSUED", "PENDING_VALIDATION"]
}
resource "aws_lb_listener" "wfe_demo_ssl" {
load_balancer_arn = "${aws_lb.wfe_demo.arn}"
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = "${data.aws_acm_certificate.demo_cert.arn}"
default_action {
target_group_arn = "${aws_lb_target_group.wfe_demo.arn}"
type = "forward"
}
}
I have ensured that both resources are in the aws-east region. I am getting the error:
Error creating LB Listener: CertificateNotFound: Certificate 'arn:aws:acm:us-east-1:078395932517:certificate/b83ba534-ef9d-4a07-ae13-832695dc8b5a' not found.
So the certificate is getting retrieved correctly by the data source but the listener then can't seem to find it.
To be able to attach an ACM certificate to a load balancer or other AWS resource such as Cloudfront, it must have been validated first.
Changing your data source to find only ISSUED certificates should then push the error to happen in the data source if there are no validated certificates that match your pattern:
data "aws_acm_certificate" "demo_cert" {
domain = "*.mydomain.com"
statuses = ["ISSUED"]
}
To validate the certificate you can either handle this out of band manually when you request it, use some other tool to automatically validate it for you or you can use Terraform's aws_acm_certificate_validation resource when creating the ACM certificate request:
resource "aws_acm_certificate" "cert" {
domain_name = "example.com"
validation_method = "DNS"
}
data "aws_route53_zone" "zone" {
name = "example.com."
private_zone = false
}
resource "aws_route53_record" "cert_validation" {
name = aws_acm_certificate.cert.domain_validation_options.0.resource_record_name
type = aws_acm_certificate.cert.domain_validation_options.0.resource_record_type
zone_id = data.aws_route53_zone.zone.id
records = [aws_acm_certificate.cert.domain_validation_options.0.resource_record_value]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = [aws_route53_record.cert_validation.fqdn]
}
Because I just had the issue: certificate and resource must be in the same region. (#ydaetskcoR kind of mentioned this already).