Skip to content

Instantly share code, notes, and snippets.

@chancez
Last active May 28, 2020 21:18
Show Gist options
  • Save chancez/d074ea00b02c65a0d1bb5cc60737a5a9 to your computer and use it in GitHub Desktop.
Save chancez/d074ea00b02c65a0d1bb5cc60737a5a9 to your computer and use it in GitHub Desktop.
module "combined_acm_certificate" {
source = "../../modules/acm_certificate_dns_validated_multi_zone"
providers = {
aws.certificate_requester = aws.infra-production-account
aws.route53_cert_validator = aws.main-account
}
domain_name = "infra.example.com"
zone_to_san = {
"infra.example.com" = [
"*.infra.example.com",
"*.dev.infra.example.com",
"*.staging.infra.example.com",
"*.production.infra.example.com",
]
"test.example.com" = [
"*.test.example.com"
]
"test-dev.example.com" = [
"*.test-dev.example.com"
]
}
skip_validations = [
"infra.example.com",
"*.test-dev.example.com",
"*.test.example.com",
"*.infra.example.com",
]
}
provider "aws" {
alias = "certificate_requester"
}
provider "aws" {
alias = "route53_cert_validator"
}
locals {
# produces a list of maps of san to zone
# [ { "*.foo.example.org" = "foo.example.org"} ]
list_of_sans_to_zone = [
for zone, sans in var.zone_to_san : {
for san in sans :
san => zone
}
]
# produces a map of SAN => zone
# { "*.foo.example.org" = "foo.example.org"}
san_to_zone = {
for san, zone in merge(flatten([local.list_of_sans_to_zone])...) :
san => zone
}
# A list of just the SANs. Sorted to ensure stability if the map order
# changes.
sans = sort(keys(local.san_to_zone))
san_to_zone_final = {
for san, zone in merge({ (var.domain_name) = var.domain_name }, local.san_to_zone) :
san => zone
# Skip validating anything specified in skip_validations
if ! contains(var.skip_validations, san)
# if the san without the "*." wildcard exists in the list of SANs or is
# the same as the domain_name, then they will have the same ACM validation
# record.
# See
# https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html
# for details and examples of how *.example.com and example.com both have
# the same CNAME validation record.
&& ! contains(local.sans, trimprefix(san, "*."))
# if the SAN isnt var.domain name, then check if it's a wildcard of the
# domain_name
&& (san == var.domain_name || trimprefix(san, "*.") != var.domain_name)
}
# *.foo.example.com => {...}
validations = {
for validation_option in aws_acm_certificate.cert.domain_validation_options :
validation_option.domain_name => validation_option
}
}
# Keyed by SAN, allowing lookup of a zone_id by the SAN used
data "aws_route53_zone" "selected" {
provider = aws.route53_cert_validator
# Also get domain_name
for_each = merge({ (var.domain_name) = var.domain_name }, local.san_to_zone)
name = each.value
}
resource "aws_acm_certificate" "cert" {
provider = aws.certificate_requester
domain_name = var.domain_name
subject_alternative_names = local.sans
validation_method = "DNS"
tags = var.tags
lifecycle {
create_before_destroy = true
ignore_changes = [subject_alternative_names]
}
}
resource "aws_acm_certificate_validation" "cert" {
provider = aws.certificate_requester
certificate_arn = aws_acm_certificate.cert.arn
# We use an explicit dependency and pass this directly from the
# domain_validation_options instead of using the fqdn from
# aws_route53_record.cert_validation because we may be skipping the creation
# of some route53 records, and we need to provide all validation FQDNs, even
# if we do not create them.
validation_record_fqdns = aws_acm_certificate.cert.domain_validation_options[*].resource_record_name
depends_on = [
aws_route53_record.cert_validation
]
}
resource "aws_route53_record" "cert_validation" {
provider = aws.route53_cert_validator
for_each = local.san_to_zone_final
zone_id = data.aws_route53_zone.selected[each.key].zone_id
name = local.validations[each.key].resource_record_name
type = local.validations[each.key].resource_record_type
records = [local.validations[each.key].resource_record_value]
ttl = 60
}
output "certificate" {
value = aws_acm_certificate.cert
}
output "certificate_validation" {
value = aws_acm_certificate_validation.cert
}
variable "domain_name" {
type = string
description = "A domain name for which the certificate should be issued."
}
variable "zone_to_san" {
type = map(list(string))
description = "A mapping of hosted zone name to SANs."
}
variable "skip_validations" {
type = list(string)
description = "A list of SANs to skip validation for. For when validations already exist for the SAN. Include both the wildcard and base domain of the wildcard if your SANs includes both."
default = []
}
variable "tags" {
type = map(string)
default = {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment