Creating multiple DNS names by messing with Cloudfront, Route53 and ACM

I created my blog’s infrastructure in AWS with this repository as base. It served me well, but the time as come to tweak it a little bit.

In this post I will explain how I went from having only the DNS name dimmaski.com available to having www.dimmaski.com, blog.dimmaski.com and www.blog.dimmaski.com as well. Before reading this make sure you understand this explanation on the link above.

When I first approached the problem, in my head I was like: Well, basically all I have to get this done is creating some CNAME or ALIAS records in Route53. But when I created them I got errors from CloudFront. I found out that I also needed to configure Cloudfront’s aliases with the new domain names, and to cope with that I also needed to update my websites certificate to contain *.dimmaski.com and *.blog.dimmaski.com.

I won’t post the full code because I keep my infra code in the same repo as I keep the blog, but I will explain the changes I’ve made.

It all starts with this variable.

variable "alternative_domain_names" {
  description = "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements"
  type        = map(list(string))
  default = {
    "*.dimmaski.com"      = ["www.dimmaski.com", "blog.dimmaski.com"]
    "*.blog.dimmaski.com" = ["www.blog.dimmaski.com"]
  }
}

The keys

Are the alternative domain names to add to my certificate. Notice the subject_alternative_names, and the multi cert_validation resource, which will cope with our alternative domain names, two in this case (*.dimmaski.com, *.blog.dimmaski.com).

# The following resources generate an SSL certificate and
# validate it.
resource "aws_acm_certificate" "cert" {
  domain_name               = var.domain_name
  subject_alternative_names = keys(var.alternative_domain_names)
  validation_method         = "DNS"
}

resource "aws_route53_record" "cert_validation" {

  count = length(keys(var.alternative_domain_names))

  name    = aws_acm_certificate.cert.domain_validation_options[count.index].resource_record_name
  type    = aws_acm_certificate.cert.domain_validation_options[count.index].resource_record_type
  records = [aws_acm_certificate.cert.domain_validation_options[count.index].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 values

Will be used to update the cloudfront’s aliases variable and Route53 records. (ALIAS are not as clean as CNAMES, in my opinion, but I ended up using because they are free).

resource "aws_cloudfront_distribution" "cdn" {
  ...
  aliases             = concat(flatten(values(var.alternative_domain_names)), [var.domain_name])
  ...
}

resource "aws_route53_record" "alias" {

  for_each = toset(concat(flatten(values(var.alternative_domain_names)), [var.domain_name]))
  zone_id  = data.aws_route53_zone.zone.id
  name     = each.value
  type     = "A"

  alias {
    name                   = aws_cloudfront_distribution.cdn.domain_name
    zone_id                = aws_cloudfront_distribution.cdn.hosted_zone_id
    evaluate_target_health = false
  }
}

In the end this ends up being quite a simple change although I spent quite some time dealing with minor issues, trying to understand what was missing (being noob to AWS and Terraform does not make things easier). The aws docs where a good reference.

Anyway, this is my solution and I’m happy with it, hope this helps someone.

· aws, s3, cloudfront, route53, acm, terraform