AWS ACM Terraform Module with Variable SANs
Here is a a flexible terraform module for creating an AWS ACM with a variable number of additional SANs.
Our infrastructure architecture is such that we have application load balancers that may serve multiple apps, and there was a need to create SSL certificates with multiple SANs to support those apps. While possible to add multiple SSL certificates to an application load balancer there is in fact a limit and so I wanted to avoid that altogether. There may be argument that we are exposing data unecessarily by including what seem to be unrelated domains in the SSL cert but for a QA or staging environment it felt less critical or a concern. At any rate, this solved a problem and reduced the total number of certificates we need to create for the various environments.
This module will create the appropriate Route53 validation records for all of the defined domains and subject alternative names. The module outputs the ARN of the ACM it creates.
The important bit is in locals
in main.tf. From the variables domain_name
and subject_alternative_names
we create a map of domains, distinct zones, certificate sans, and all certificate validation records that need to be created.
# Create a data source for each distinct domain zone found
data "aws_route53_zone" "domain" {
count = length(local.distinct_zones)
name = local.distinct_zones[count.index]
private_zone = false
}
# Define local data resources
locals {
all_domains = concat([var.domain_name.domain], [
for v in var.subject_alternative_names : v.domain
])
all_zones = concat([var.domain_name.zone], [
for v in var.subject_alternative_names : v.zone
])
distinct_zones = distinct(local.all_zones)
zone_name_to_id_map = zipmap(local.distinct_zones, data.aws_route53_zone.domain[*].zone_id)
domain_to_zone_map = zipmap(local.all_domains, local.all_zones)
cert_san = reverse(sort([
for v in var.subject_alternative_names : v.domain
]))
cert_validation_domains = [
for v in aws_acm_certificate.certificate.domain_validation_options : tomap(v)
]
}
The rest of the module is very simple.
# Request a certificate from ACM
resource "aws_acm_certificate" "certificate"{
domain_name = var.domain_name.domain
subject_alternative_names = local.cert_san
validation_method = "DNS"
tags = var.tags
lifecycle {
create_before_destroy = true
}
}
# Create route53 records for each validation record discovered
resource "aws_route53_record" "validation_records" {
count = length(distinct(local.all_domains))
zone_id = lookup(local.zone_name_to_id_map, lookup(local.domain_to_zone_map, local.cert_validation_domains[count.index]["domain_name"]))
name = local.cert_validation_domains[count.index]["resource_record_name"]
type = local.cert_validation_domains[count.index]["resource_record_type"]
ttl = 60
allow_overwrite = true
records = [
local.cert_validation_domains[count.index]["resource_record_value"]
]
}
# This resource represents a successful validation of an ACM certificate in concert with other resources.
resource "aws_acm_certificate_validation" "cert_validation" {
count = 1
certificate_arn = aws_acm_certificate.certificate.arn
validation_record_fqdns = local.cert_validation_domains[*]["resource_record_name"]
}
Example usage to create an ACM with the primary domain and a wildcard domain.
module "ssl_cert_default" {
source = "../modules/multi-domain-acm/"
domain_name = {
zone = "roylindauer.com"
domain = "roylindauer.com"
}
subject_alternative_names = [{ "zone" : "roylindauer.com", "domain" : "*.roylindauer.com" }]
tags = {
"Environment" = "prod",
"Description" = "Managed by Terraform",
"Creator" = "Terraform",
"Name" = "Prod Cluster - Roycom ACM"
}
}
Here's an example for creating an SSL certificate with two distinct domain names and zones. For example, for an application load balancer serving multiple apps in a qa or test environment.
module "ssl_cert_develop_alb" {
source = "./modules/multi-domain-acm/"
domain_name = {
zone = "roylindauer.com"
domain = "*.develop.roylindauer.com"
}
subject_alternative_names = [
{
"zone" : "roylindauer.art",
"domain" : "*.develop.roylindauer.art"
}
]
tags = {
"Environment" = "develop",
"Description" = "Managed by Terraform",
"Creator" = "Terraform",
"Name" = "Develop Cluster - Default ACM"
}
}