Skip to content

Instantly share code, notes, and snippets.

@chancez
Last active November 20, 2023 21:56
Show Gist options
  • Select an option

  • Save chancez/dfaaf799b98698839d65ebba55db7d44 to your computer and use it in GitHub Desktop.

Select an option

Save chancez/dfaaf799b98698839d65ebba55db7d44 to your computer and use it in GitHub Desktop.

Revisions

  1. chancez revised this gist May 1, 2020. 1 changed file with 13 additions and 13 deletions.
    26 changes: 13 additions & 13 deletions main.tf
    Original file line number Diff line number Diff line change
    @@ -30,13 +30,13 @@ locals {
    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
    # 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.
    # 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
    # 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, "*."))
    @@ -45,17 +45,17 @@ locals {
    && (san == var.domain_name || trimprefix(san, "*.") != var.domain_name)
    }

    // *.foo.example.com => {...}
    # *.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
    # 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
    # Also get domain_name
    for_each = merge({ (var.domain_name) = var.domain_name }, local.san_to_zone)

    name = each.value
    @@ -77,11 +77,11 @@ resource "aws_acm_certificate" "cert" {
    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.
    # 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 = [
  2. chancez revised this gist May 1, 2020. 1 changed file with 12 additions and 10 deletions.
    22 changes: 12 additions & 10 deletions main.tf
    Original file line number Diff line number Diff line change
    @@ -23,25 +23,26 @@ locals {
    san => zone
    }

    # A list of just the SANs
    sans = keys(local.san_to_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_filtered = {
    for san, zone in local.san_to_zone :
    san => zone
    if ! contains(var.skip_validations, san)
    }
    san_to_zone_final = {
    for san, zone in local.san_to_zone_filtered :
    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.
    if ! contains(local.sans, trimprefix(san, "*.")) || trimprefix(san, "*.") == var.domain_name
    && ! 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 => {...}
    @@ -54,7 +55,8 @@ locals {
    // Keyed by SAN, allowing lookup of a zone_id by the SAN used
    data "aws_route53_zone" "selected" {
    provider = aws.route53_cert_validator
    for_each = local.san_to_zone
    // Also get domain_name
    for_each = merge({ (var.domain_name) = var.domain_name }, local.san_to_zone)

    name = each.value
    }
  3. chancez revised this gist May 1, 2020. 2 changed files with 55 additions and 22 deletions.
    69 changes: 48 additions & 21 deletions main.tf
    Original file line number Diff line number Diff line change
    @@ -18,18 +18,43 @@ locals {

    # produces a map of SAN => zone
    # { "*.foo.example.org" = "foo.example.org"}
    san_to_zone = merge(flatten([local.list_of_sans_to_zone])...)
    san_to_zone = {
    for san, zone in merge(flatten([local.list_of_sans_to_zone])...) :
    san => zone
    }

    # A list of just the SANs
    sans = keys(local.san_to_zone)

    san_to_zone_filtered = {
    for san, zone in local.san_to_zone :
    san => zone
    if ! contains(var.skip_validations, san)
    }
    san_to_zone_final = {
    for san, zone in local.san_to_zone_filtered :
    san => zone
    // 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.
    if ! contains(local.sans, trimprefix(san, "*.")) || trimprefix(san, "*.") == var.domain_name
    }

    sans = flatten([
    for zone, sans in var.zone_to_san :
    sans
    ])
    // *.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
    for_each = merge(local.san_to_zone, { (var.domain_name) = var.domain_name })
    for_each = local.san_to_zone

    name = each.value
    }
    @@ -48,25 +73,27 @@ resource "aws_acm_certificate" "cert" {
    }

    resource "aws_acm_certificate_validation" "cert" {
    provider = aws.certificate_requester
    certificate_arn = aws_acm_certificate.cert.arn
    validation_record_fqdns = aws_route53_record.cert_validation.*.fqdn
    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

    # We use count on the SANs because
    # aws_acm_certificate.cert.domain_validation_options is dynamic and the
    # number is unknown until after apply.
    #
    # We + 1 because there is 1 validation option per SAN, and 1 for the
    # domain_name itself.
    count = length(local.sans) + 1

    zone_id = data.aws_route53_zone.selected[aws_acm_certificate.cert.domain_validation_options[count.index].domain_name].zone_id
    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]
    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
    }
    8 changes: 7 additions & 1 deletion variables.tf
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,13 @@ variable "zone_to_san" {
    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 = {}
    }
    }
  4. chancez revised this gist May 1, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion main.tf
    Original file line number Diff line number Diff line change
    @@ -29,7 +29,7 @@ locals {
    // Keyed by SAN, allowing lookup of a zone_id by the SAN used
    data "aws_route53_zone" "selected" {
    provider = aws.route53_cert_validator
    for_each = local.san_to_zone
    for_each = merge(local.san_to_zone, { (var.domain_name) = var.domain_name })

    name = each.value
    }
  5. chancez revised this gist May 1, 2020. 2 changed files with 7 additions and 8 deletions.
    2 changes: 1 addition & 1 deletion example.tf
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@ module "combined_acm_certificate" {

    domain_name = "infra.example.com"
    zone_to_san = {
    "infra.rigett.com" = [
    "infra.example.com" = [
    "*.infra.example.com",
    "*.dev.infra.example.com",
    "*.staging.infra.example.com",
    13 changes: 6 additions & 7 deletions main.tf
    Original file line number Diff line number Diff line change
    @@ -9,13 +9,12 @@ provider "aws" {
    locals {
    # produces a list of maps of san to zone
    # [ { "*.foo.example.org" = "foo.example.org"} ]
    list_of_sans_to_zone = flatten([
    for zone, sans in var.zone_to_san : [
    for san in sans : {
    san = zone
    }
    ]
    ])
    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"}
  6. chancez revised this gist May 1, 2020. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion main.tf
    Original file line number Diff line number Diff line change
    @@ -19,7 +19,6 @@ locals {

    # produces a map of SAN => zone
    # { "*.foo.example.org" = "foo.example.org"}
    # The extra flatten([]) is to make the ... expansion work.
    san_to_zone = merge(flatten([local.list_of_sans_to_zone])...)

    sans = flatten([
    @@ -58,6 +57,12 @@ resource "aws_acm_certificate_validation" "cert" {
    resource "aws_route53_record" "cert_validation" {
    provider = aws.route53_cert_validator

    # We use count on the SANs because
    # aws_acm_certificate.cert.domain_validation_options is dynamic and the
    # number is unknown until after apply.
    #
    # We + 1 because there is 1 validation option per SAN, and 1 for the
    # domain_name itself.
    count = length(local.sans) + 1

    zone_id = data.aws_route53_zone.selected[aws_acm_certificate.cert.domain_validation_options[count.index].domain_name].zone_id
  7. chancez revised this gist May 1, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion example.tf
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    module "combined_acm_certificate" {
    source = "../../modules/acm_certificate_dns_validated_v2"
    source = "../../modules/acm_certificate_dns_validated_multi_zone"

    domain_name = "infra.example.com"
    zone_to_san = {
  8. chancez created this gist May 1, 2020.
    19 changes: 19 additions & 0 deletions example.tf
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    module "combined_acm_certificate" {
    source = "../../modules/acm_certificate_dns_validated_v2"

    domain_name = "infra.example.com"
    zone_to_san = {
    "infra.rigett.com" = [
    "*.infra.example.com",
    "*.dev.infra.example.com",
    "*.staging.infra.example.com",
    "*.production.infra.example.com",
    ]
    "foo.test.com" = [
    "*.foo.test.com"
    ]
    "foo-dev.test.com" = [
    "*.foo-dev.test.com"
    ]
    }
    }
    68 changes: 68 additions & 0 deletions main.tf
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    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 = flatten([
    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"}
    # The extra flatten([]) is to make the ... expansion work.
    san_to_zone = merge(flatten([local.list_of_sans_to_zone])...)

    sans = flatten([
    for zone, sans in var.zone_to_san :
    sans
    ])
    }

    // Keyed by SAN, allowing lookup of a zone_id by the SAN used
    data "aws_route53_zone" "selected" {
    provider = aws.route53_cert_validator
    for_each = 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
    }
    }

    resource "aws_acm_certificate_validation" "cert" {
    provider = aws.certificate_requester
    certificate_arn = aws_acm_certificate.cert.arn
    validation_record_fqdns = aws_route53_record.cert_validation.*.fqdn
    }

    resource "aws_route53_record" "cert_validation" {
    provider = aws.route53_cert_validator

    count = length(local.sans) + 1

    zone_id = data.aws_route53_zone.selected[aws_acm_certificate.cert.domain_validation_options[count.index].domain_name].zone_id
    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
    }
    14 changes: 14 additions & 0 deletions variables.tf
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,14 @@
    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 "tags" {
    type = map(string)
    default = {}
    }