Issues creating terraform resources with dependencies to resources not yet created - amazon-web-services

I can't come up with a proper title as my issue is rather complicated (at least for me).
I need to create an infrastructure in AWS
CloudFront
ElasticBeanstalk (backend)
S3 (frontend)
Route53 (dns)
CertificateManager (ssl)
...
Now I can create my hosted zone without an issue, but when I'm trying to create the cloudfront, the first thing terraform tries to do is create and validate a certificate.
As I'm not aware on how my cloudfront url will be yet, I can't create an A record pointing to it. The certificate points to that record though (it's a subdomain of my hosted zone) and therefore the certificate validation times out and terraform ends the apply.
As domain and certificate came later in the development it didn't come up yet as the cloudfront distribution has been there already, but while migrating to a environment I'm hitting a wall.
I can't force terraform to create the record first via a null_resource or a depends_on entry because that will form a loop.
Any ideas?
Update:
I'm using an alias in CloudFront, and I'm hosting my domain in Route53.
My issue though is that for the route53 record (not the validation but the certificate itself) I'm using a cloudfront reference:
resource "aws_route53_record" "frontend_record" {
name = ...
zone_id = ...
type = "A"
alias {
name = local.cloudfront_domain_name <-- this here
...
}
}
And I can't get this because the CloudFront distribution isn't created yet.

If you are using the default CloudFront URL for the CloudFront distribution that you are creating and do not define any aliases then you will want to specify the following in your configuration:
viewer_certificate {
cloudfront_default_certificate = true
}
If you have aliases defined in your CloudFront configuration like this:
aliases = ["mysite.example.com", "yoursite.example.com"]
Then that is the domain you use to create your certificates. In which case you not only want to create your certificate but validate it as well before CloudFront can use it:
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}"]
}
The above assumes your domain is hosted in Route53. If your domain is not hosted in Route53 then you will likely need to create the certificate and validate it before using it in your CloudFront terraform configuration. You can validate certificates via DNS or email.

Related

AWS ACM certificate created with terraform can't be validated, domain doesn't have valid NS (?)

I have a domain generated and managed with AWS Route53. This domain has been manually created but the rest of the infrastructure is created using terraform in different regions to avoid the initial FARGATE CPU limit.
The infrastructure is updated using a GitHub action.
I am trying to create dev environment in eu-north-1 but terraform apply failes after 1H+ of **maws_acm_certifi***e_validation.default: Still creating... [***h***5m0s elapsed] with this error:
**m│ **m**mError: **mwaiting for ACM Certifi***e (arn:aws:acm:***:***:certifi***e***7a0***bccb-0c***7-***776-ab9***-***e670b6a38f***) to be issued: timeout while waiting for state to become 'ISSUED' (last state: 'PENDING_VALIDATION', timeout: ***h***5m0s)
**m│
**m│ with aws_acm_certifi***e_validation.default,
**m│ on aws-acm.tf line ***, in resource "aws_acm_certifi***e_validation" "default":
**m│ ***: resource "aws_acm_certifi***e_validation" "default" **m{
**m│
**m╵
**m╷
**m│ **m**mError: **mcreating ELBv*** Listener (arn:aws:elasticloadbalancing:***:***:loadbalancer***app***-legacy-dev-alb***7***e5baa5dab6d3e6): UnsupportedCertifi***e: The certifi***e 'arn:aws:acm:***:***:certifi***e***7a0***bccb-0c***7-***776-ab9***-***e670b6a38f***' must have a fully-qualified domain name, a supported signature, and a supported key size.
**m│ status code: ***00, request id: 3***f5a0e9-c***ac-***fd3-aed0-60ba39***0590***
**m│
**m│ with aws_lb_listener.https_listener,
**m│ on aws-alb.tf line 70, in resource "aws_lb_listener" "https_listener":
**m│ 70: resource "aws_lb_listener" "https_listener" **m{
I think the second error is just related to the first one because the certificate isn't correctly in issued status. It's a little bit hard to read but the error says waiting for ACM Certificate to be issued: timeout while waiting for state to become 'ISSUED' (last state: 'PENDING_VALIDATION', timeout: ...).
This is part of terraform code related to the certificate:
resource "aws_acm_certificate" "default" {
domain_name = var.root_domain_name
subject_alternative_names = ["*.${var.root_domain_name}"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_acm_certificate_validation" "default" {
certificate_arn = aws_acm_certificate.default.arn
validation_record_fqdns = local.validation_record_fqdns
}
resource "aws_route53_record" "default" {
name = "${local.resource_prefix}.${var.root_domain_name}"
type = "CNAME"
zone_id = var.route53_record_zone_id
records = [aws_lb.main.dns_name]
ttl = 60
depends_on = [aws_lb.main]
}
resource "aws_route53_record" "acm_validation" {
name = tolist(aws_acm_certificate.default.domain_validation_options)[0].resource_record_name
type = "CNAME"
zone_id = var.route53_record_zone_id
records = [tolist(aws_acm_certificate.default.domain_validation_options)[0].resource_record_value]
ttl = 300
depends_on = [aws_acm_certificate.default]
}
I tried to add the CNAME record manually and via AWS interface via "Create records in Route 53" button too but the certificate is still in pending.
This is the record entry in Route 53:
Is there a way to trigger again this verification and fix the problem?
UPDATE:
Tried like in the terraform docs but same result.
I am starting to think there's a DNS problem with my domain. The domain should have been registered directly in AWS (I didn't do it) and when I use tools like this (or this) online nslookup and input the domain I am working with I can't get the NS, instead I get them for another domain that my company owns.
Is there something wrong with the domain instead?
Domain info:
Requests for ACM certificates time out if they are not validated within 72 hours. To correct this condition, open the console, find the record for the certificate, click the checkbox for it, choose Actions, and choose Delete. Then choose Actions and Request a certificate to begin again. For more information, see DNS validation or Email validation. We recommend that you use DNS validation if possible.
Please check this link from the AWS Knowledge center
If the hosted zone is destroyed and re-provisioned, new name server records are associated with the new hosted zone. However, the domain name might still have the previous name server records associated with it.
If AWS Route 53 is used as the domain name registrar, head to Route 53 > Registered domains > ${your-domain-name} > Add or edit name servers and add the newly associated name server records from the hosted zone to the registered domain.

ACM certificates cross account DNS validation

I have 2 AWS accounts: dev and prod.
In the prod account, I setup a DNS domain (example.com), as well as 2 public Hosted Zone: example.com and prod.example.com. 2 ACM certificates are also issued for these domains internal.prod.example.com and eks.prod.example.com. Those certificates are correctly validated by DNS.
In the dev account, I have created 2 public Hosted Zones: dev.example.com and example.com. I issued 2 ACM certificates for internal.dev.example.com and eks.dev.example.com which, as far as I understand need to be validated with the DNS in the prod account.
These certificated are in pending state.
How can I validate them?
What I did so far:
I added a NS record called dev.example.com in the prod account for the example.com Hosted Zone. The value of the NS record are the ones of the dev.example.com Hosted Zone created in the dev account. This is to delegate the ownership of the R53 Hosted Zone in prod. See here.
In the dev account, the CNAME of the requested domain from ACM have been added in the dev.example.com Hosted Zone for validation.
The following code is how it's been done (and working) on the prod account.
Note - this is a code that I took over, so I'm not aware if any manual steps have been taken.
data "aws_route53_zone" "dns-zone" {
name = "${var.environment}.${var.zone_name}"
}
resource "aws_acm_certificate" "cert" {
domain_name = "*.${var.environment}.${var.zone_name}"
validation_method = "DNS"
subject_alternative_names = list("*.internal.${var.environment}.${var.zone_name}", "*.eks.${var.environment}.${var.zone_name}")
lifecycle {
create_before_destroy = true
prevent_destroy = true
}
}
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
zone_id = data.aws_route53_zone.dns-zone.zone_id
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = each.value.zone_id
}
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
Ps - Should you need more clarification, please let me know.
dev & prod account you have example.com? Only 1 can be used properly. Wherever the registrar is for example.com ... that registrar can only use the name servers from 1 of those hosted zones.
You mentioned you have 2 ACM certs for internal.dev.example.com & eks.dev.example.com ... those should be validated in the DEV Account if that's where their domains are created.
Also I recommend you just create 1 wild card cert in ACM for *.dev.example.com & validate that 1 in the DEV account. Any subdomains such as eks.dev.example.com will be able to use it.

terraform route53 simplest example to create a dns record in hosted zone pointing to public IP of ec2 instance

I am trying to create a simplest example where in I can
create a DNS A record pointing to the public IP of an EC2 instance created in default VPC.
I have the following code for the same
hostedzone.tf
resource "aws_route53_zone" "devopslink-public-zone" {
name = var.domain_mydevops_link
comment = "${var.domain_mydevops_link} public zone"
provider = aws
}
instance.tf
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
route53-record.tf
resource "aws_route53_record" "server1-record" {
zone_id = aws_route53_zone.devopslink-public-zone.zone_id
name = "server1.devops.link"
type = "A"
ttl = "300"
records = [aws_instance.web.public_ip]
}
Terraform apply successfully give me this
Outputs:
devopslink-name-servers = [
"ns-1363.awsdns-42.org",
"ns-1707.awsdns-21.co.uk",
"ns-466.awsdns-58.com",
"ns-941.awsdns-53.net",
]
devopslink-public-zone-id = Z06137733F7SHPRF6K9Q3
And I can see the route53 record getting created in AWS console with the public IP of the instance on route53 console
server1.devops.link A Simple - 35.174.12.184
However when i do nslookup online or by using my PC its not resolving
✗ nslookup server1.devops.link
Server: 127.0.0.53
Address: 127.0.0.53#53
** server can't find server1.devops.link: NXDOMAIN
What am i doing wrong here? I do not own the domain devops.link. My assumption is that it should be taken care by the terraform code above.
A public hosted zone is one part of the solution for hosting your DNS configuration, it will hold your DNS records. From what I can see your public hosted zone and record are correct.
The second part involves you not being able to resolve the record, as you do not own the domain there is no mapping to the public hosted zone which prevents anyone resolving the domain to your DNS records.
You need to own the domain to have it resolve to the nameserver, once you own this you would set its name servers values equal to the name server records in the public hosted zone.
As of now no official support for the Route 53 Domains service exists in Terraform, this s the service to purchase and configure the domain.
Basically for this task we need the following
A domain like mydomain.link which you own. When we say own, it means the domain is registered with the domain registrar. When you do so you can either choose their (domain registrar's) default name servers or your custom name servers created in AWS (or any other cloud provider) in a hosted zone.
an EC2 instance with public IP in default VPC
A hosted zone
route53 dns record resolving to the public IP of our instance
With all the resources given in the question, we have created an EC2 instance, a hosted zone and a route53 record.
So now we need to add the fqdn's of the nameservers generated by the terraform code in our domain-registrar's settings so that it uses our nameservers to resolve the record sets.
Link to Example - task link

Terraform can't create a CloudFront's origin with a static S3 website endpoint

I have a plan which uses two modules: bucket-website and cloudfront-website
Among other things (policies and such) inside the bucket module, there is the following resource for creating the bucket and serve it as a website:
resource "aws_s3_bucket" "bucket-website" {
bucket = "${var.bucket_name}"
region = "${var.region}"
website {
index_document = "index.html"
}
tags = "${local.common_tags}"
}
Also this module has the following output:
output "website_endpoint" {
value = "${aws_s3_bucket.bucket-website.website_endpoint}"
}
The cloudfront-website module has a resource with all those cloudfront properties (IPs, cache stuff, etc), but the relevant part is:
resource "aws_cloudfront_distribution" "distribution" {
.....
origin {
domain_name = "${var.domain_name}"
origin_id = "${var.domain_name}"
}
.....
}
The call to the cloudfront module in the plan passes the following parameter:
domain_name = "${module.bucket-website.website_endpoint}"
I can confirm that the value is correct, because in the log of terraform apply is can see:
origin.123456.domain_name: "" => "foo.s3-website-eu-west-1.amazonaws.com"
origin.123456.origin_id: "" => "foo.s3-website-eu-west-1.amazonaws.com"
Which is the same endpoint I would use if I was doing this setup using just the AWS Console, i.e. get the bucket's static web endpoint (different to the standard bucket endpoint) and use it as the origin of Cloudfront.
However, for some reason Terraform is complaining about the domain name:
* aws_cloudfront_distribution.distribution: error creating CloudFront Distribution: InvalidArgument: The parameter Origin DomainName does not refer to a valid S3 bucket.
And I'm already out of ideas. Everything looks good. The endpoint is correct. I have checked other examples and they also use ${aws_s3_bucket.<BUCKET_RESOURCE_NAME>.website_endpoint}, so I honestly don't understand what's wrong.
Just found the solution. When serving a S3 website through CloudFront, the following code must be added to the origin section, even though it's not specified elsewhere to do so.
custom_origin_config {
http_port = "80"
https_port = "443"
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}

Terraform AWS ACM certificates in us-east-1 for resources in eu-west-1

I have a terraform module that provisions resources primarily in eu-west-1. I need an ACM certificate to attach to a Cloudfront distribution. The certificate must be provisioned in us-east-1.
I have thus configured two providers:
provider "aws" {
version = "~> 1.0"
region = "eu-west-1"
}
provider "aws" {
version = "~> 1.0"
region = "us-east-1"
alias = "us-east-1"
}
In my module, I provision the certificate like so:
resource "aws_acm_certificate" "cert" {
provider = "aws.us-east-1"
domain_name = "${var.domain_name}"
validation_method = "DNS"
tags = "${var.tags}"
lifecycle {
create_before_destroy = true
}
}
Problem #1: I tried to import my existing ACM certificate using:
terraform import module.mymod.aws_acm_certificate.cert arn:aws:acm:us-east-1:xyz:certificate/uuid
This fails with: "Could not find certificate with id". Is terraform looking in the wrong region? I confirmed with the aws CLI that the certificate does indeed exist (e.g. no typos in the ARN).
Ok, so I figured I could just create new certificate. This does work, and I now have two certificates, but I then run into problem #2:
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
}
This attempts to set up DNS validation for ACM. The hosted zone exists in eu-west-1, so I'm expecting problems here. However, this still fails with "Could not find certificate ...", and I'm assuming terraform gets confused about regions. I tried adding provider = "aws.us-east-1" to this resource as well, but it still fails the same way.
So, no matter what I do, Terraform is unable to locate my certificate, even it has created it itself. Am I doing something wrong?
Turns out my problem was with aws_acm_certificate_validation. By specifying the provider in the same region as the certificate, it was all resolved.
resource "aws_acm_certificate_validation" "cert" {
provider = "aws.us-east-1" # <== Add this
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]
}
Since Terraform 0.12.14 Quoted references are deprecated.
So the accepted answer above should be like this if you're using a version >= 0.12.14 or Terraform 1.x
resource "aws_acm_certificate_validation" "cert" {
provider = aws.us-east-1 # <== Add this without quotes
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]
}
To avoid a Warning like this:
Warning: Quoted references are deprecated
52: provider = "aws.us-east-1"
In this context, references are expected literally rather than in quotes. Terraform 0.11 and earlier required quotes, but quoted references are now deprecated and will be removed
in a future version of Terraform. Remove the quotes surrounding this reference to silence this warning.
(and one more similar warning elsewhere)
For more info, see the release notes discussions at hashicorp: https://discuss.hashicorp.com/t/terraform-0-12-14-released/3898