Terraform Recipes: CloudFront TLS certificates

At the end of the previous article, I briefly hinted at various intricacies of provisioning a TLS certificate for your CloudFront distribution. Let’s briefly have a look at what you can expect and how to deal with it!

Terraform Recipes: CloudFront TLS certificates

At the end of the previous article, I briefly hinted at various intricacies of provisioning a TLS certificate for your CloudFront distribution. Let’s briefly have a look at what you can expect and how to deal with it!

In general, you will likely encounter two issues if you want to serve your CloudFront traffic over HTTPS: while most AWS services (for example, Elastic Load Balancer) require the certificates to be issued in the same region as the service itself is provisioned, the situation is quite different for CloudFront as that service is region-less. For CloudFront, all certificates must be issued in the North Virginia (us-east-1) region.

The second issue is that before Amazon Certificate Manager turns your certificate request into a valid certificate, it requires you to validate the request either by e-mail or by provisioning a special DNS record. That means that if you try assigning the certificate to a CloudFront distribution while the generation is still in process (or the validation is pending), you’ll be met with a swift failure. But there’s an easy way around both issues!

Requesting freedom certificates 🦅

Let’s deal with the regional issue first. This is how you can force a region for a single Terraform resource, even if the rest of your Terraform resources reside in another region:

###################################
# ACM Certificate
###################################
resource "aws_acm_certificate" "cf_gitbook" {
  provider = aws.virginia

  domain_name       = aws_s3_bucket.gitbook.bucket
  validation_method = "DNS"

  tags = {
    Name        = "${local.name_prefix}cf_gitbook"
    Environment = terraform.workspace
  }

  lifecycle {
    create_before_destroy = true
  }
}

But where does the aws.virginia value come from? Good question! You have to define a provider alias like so:

###################################
# Providers
###################################
provider "aws" {
  version = "~> 2.0"
  region  = "ap-northeast-1"
}

provider "aws" {
  alias = "virginia"

  version = "~> 2.0"
  region  = "us-east-1"
}

Automatically validating certificate request ✅

As you can see in the snippet above, I chose to go for the DNS validation method – that’s because unlike with the e-mail validation method, it can be done automatically and the progress can be tracked by Terraform – that sounds like something we’d precisely want.

###################################
# Route 53 Record
###################################
resource "aws_route53_record" "cf_gitbook_validation" {
  zone_id = aws_route53_zone.public[0].zone_id
  name    = aws_acm_certificate.cf_gitbook.domain_validation_options.0.resource_record_name
  type    = aws_acm_certificate.cf_gitbook.domain_validation_options.0.resource_record_type
  records = [aws_acm_certificate.cf_gitbook.domain_validation_options.0.resource_record_value]
  ttl     = 300
}

With the record in place, we can track the progress of certificate issuance like so:

###################################
# ACM Certificate Validation
###################################
resource "aws_acm_certificate_validation" "cf_gitbook" {
  provider = aws.virginia

  certificate_arn         = aws_acm_certificate.cf_gitbook.arn
  validation_record_fqdns = [aws_route53_record.cf_gitbook_validation.fqdn]
}

We need to also specify the provider alias we created earlier as the aws_acm_certificate.cf_gitbook only exists in the us-east-1 region. Fortunately, Route 53 is a global service, so we won’t run into any issues related to accessing resources from two different regions in one resource.

Putting it all together 🧩

But what was the purpose of creating the aws_acm_certificate_validation? After all, it does not show anywhere in AWS Console, as it’s a virtual resource.

Well, while aws_acm_certificate resource will show up in Terraform as created as soon as certificate issuance request is accepted, aws_acm_certificate_validation will only register as created once validation and issuance is finished and the certificate is ready to be used. aws_acm_certificate_validation resources can be used like so:

###################################
# CloudFront
###################################
resource "aws_cloudfront_distribution" "gitbook" {
  …
  
  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate_validation.cf_gitbook.certificate_arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2018"
  }
}

With the code above, the CloudFront distribution will start provisioning only once the certificate is ready to be used, exactly as we want it.