From b2faf5ba714d3eb5b4f3eabdd4a8dc260f0ebfcf Mon Sep 17 00:00:00 2001 From: flora-five <72858916+flora-five@users.noreply.github.com> Date: Mon, 28 Jun 2021 23:07:13 +0300 Subject: [PATCH 1/3] feat: certificate validation with multiple Route 53 zones --- .../multiple-zones-dns-validation/README.md | 60 ++++++++++++ .../multiple-zones-dns-validation/main.tf | 94 +++++++++++++++++++ .../multiple-zones-dns-validation/outputs.tf | 39 ++++++++ .../variables.tf | 0 .../multiple-zones-dns-validation/versions.tf | 7 ++ main.tf | 79 +++++++++++++--- outputs.tf | 10 ++ variables.tf | 15 ++- 8 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 examples/multiple-zones-dns-validation/README.md create mode 100644 examples/multiple-zones-dns-validation/main.tf create mode 100644 examples/multiple-zones-dns-validation/outputs.tf create mode 100644 examples/multiple-zones-dns-validation/variables.tf create mode 100644 examples/multiple-zones-dns-validation/versions.tf diff --git a/examples/multiple-zones-dns-validation/README.md b/examples/multiple-zones-dns-validation/README.md new file mode 100644 index 0000000..929cd6d --- /dev/null +++ b/examples/multiple-zones-dns-validation/README.md @@ -0,0 +1,60 @@ +# ACM example with multiple Route53 DNS validation + +Configuration in this directory creates three Route53 zones (one of them delegates a subdomain to another zone from the set) and one ACM certificate (valid for the three domain names, their wildcards and various example subdomains of them). + +Also, ACM certificate is being validate using DNS method. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.12.26 | +| [aws](#requirement\_aws) | >= 2.53 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.53 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | ../../ | | + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the certificate | +| [acm\_certificate\_domain\_validation\_options](#output\_acm\_certificate\_domain\_validation\_options) | A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used. | +| [acm\_certificate\_validation\_emails](#output\_acm\_certificate\_validation\_emails) | A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used. | +| [distinct\_domain\_names](#output\_distinct\_domain\_names) | List of distinct domains names used for the validation. | +| [validation\_domains](#output\_validation\_domains) | List of distinct domain validation options. This is useful if subject alternative names contain wildcards. | +| [validation\_route53\_record\_fqdns](#output\_validation\_route53\_record\_fqdns) | List of FQDNs built using the zone domain and name. | + diff --git a/examples/multiple-zones-dns-validation/main.tf b/examples/multiple-zones-dns-validation/main.tf new file mode 100644 index 0000000..8256da8 --- /dev/null +++ b/examples/multiple-zones-dns-validation/main.tf @@ -0,0 +1,94 @@ +locals { + # Use existing (via data source) or create new zones (will fail validation, if zone is not reachable) + use_existing_route53_zones = true + + domain = "terraform-aws-modules.modules.tf" + alternate_domain = "terraform-aws-modules.modules-alt.tf" + alternate_subdomain = "sub.${local.alternate_domain}" + + # Removing trailing dot from domains - just to be sure :) + domain_name = trimsuffix(local.domain, ".") + alternate_domain_name = trimsuffix(local.alternate_domain, ".") + alternate_subdomain_name = trimsuffix(local.alternate_subdomain, ".") +} + +data "aws_route53_zone" "domain" { + count = local.use_existing_route53_zones ? 1 : 0 + + name = local.domain_name + private_zone = false +} + +data "aws_route53_zone" "alternate_domain" { + count = local.use_existing_route53_zones ? 1 : 0 + + name = local.alternate_domain_name + private_zone = false +} + +data "aws_route53_zone" "alternate_subdomain" { + count = local.use_existing_route53_zones ? 1 : 0 + + name = local.alternate_subdomain_name + private_zone = false +} + +resource "aws_route53_zone" "domain" { + count = !local.use_existing_route53_zones ? 1 : 0 + name = local.domain_name +} + +resource "aws_route53_zone" "alternate_domain" { + count = !local.use_existing_route53_zones ? 1 : 0 + name = local.alternate_domain_name +} + +resource "aws_route53_zone" "alternate_subdomain" { + count = !local.use_existing_route53_zones ? 1 : 0 + name = local.alternate_subdomain_name +} + +resource "aws_route53_record" "alternate_subdomain_delegation" { + count = !local.use_existing_route53_zones ? 1 : 0 + name = local.alternate_subdomain_name + type = "NS" + ttl = 300 + records = aws_route53_zone.alternate_subdomain.*.name_servers[0] + zone_id = aws_route53_zone.alternate_domain.*.zone_id[0] +} + +module "acm" { + source = "../../" + + domain_name = local.domain_name + + subject_alternative_names = [ + "*.alerts.${local.domain_name}", + "new.sub.${local.domain_name}", + "*.${local.domain_name}", + "alerts.${local.domain_name}", + local.alternate_domain_name, + "*.${local.alternate_domain_name}", + "quite.deep.abc.sub.${local.alternate_domain_name}", + "def.${local.alternate_subdomain_name}", + "*.${local.alternate_subdomain_name}", + ] + + domain_zones = { + (local.domain_name) = { + zone_id = coalescelist(data.aws_route53_zone.domain.*.zone_id, aws_route53_zone.domain.*.zone_id)[0] + } + (local.alternate_domain_name) = { + zone_id = coalescelist(data.aws_route53_zone.alternate_domain.*.zone_id, aws_route53_zone.alternate_domain.*.zone_id)[0] + } + (local.alternate_subdomain_name) = { + zone_id = coalescelist(data.aws_route53_zone.alternate_subdomain.*.zone_id, aws_route53_zone.alternate_subdomain.*.zone_id)[0] + } + } + + wait_for_validation = true + + tags = { + Name = local.domain_name + } +} diff --git a/examples/multiple-zones-dns-validation/outputs.tf b/examples/multiple-zones-dns-validation/outputs.tf new file mode 100644 index 0000000..b003871 --- /dev/null +++ b/examples/multiple-zones-dns-validation/outputs.tf @@ -0,0 +1,39 @@ +output "acm_certificate_arn" { + description = "The ARN of the certificate" + value = module.acm.acm_certificate_arn +} + +output "acm_certificate_domain_validation_options" { + description = "A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if DNS-validation was used." + value = module.acm.acm_certificate_domain_validation_options +} + +output "acm_certificate_validation_emails" { + description = "A list of addresses that received a validation E-Mail. Only set if EMAIL-validation was used." + value = module.acm.acm_certificate_validation_emails +} + +output "validation_route53_record_fqdns" { + description = "List of FQDNs built using the zone domain and name." + value = module.acm.validation_route53_record_fqdns +} + +output "distinct_domain_names" { + description = "List of distinct domains names used for the validation." + value = module.acm.distinct_domain_names +} + +output "distinct_domain_names_without_matching_zone" { + description = "List of distinct domains names that must be used for the validation but for which no Route 53 zone could be determined from the given input." + value = module.acm.distinct_domain_names_without_matching_zone +} + +output "validation_domains" { + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + value = module.acm.validation_domains +} + +output "validation_domains_without_matching_zone" { + description = "List of distinct domain validation options for which validation records have not been created because no Route 53 zone could be determined from the given input." + value = module.acm.validation_domains_without_matching_zone +} diff --git a/examples/multiple-zones-dns-validation/variables.tf b/examples/multiple-zones-dns-validation/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/multiple-zones-dns-validation/versions.tf b/examples/multiple-zones-dns-validation/versions.tf new file mode 100644 index 0000000..a36527a --- /dev/null +++ b/examples/multiple-zones-dns-validation/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 0.12.26" + + required_providers { + aws = ">= 2.53" + } +} diff --git a/main.tf b/main.tf index 0adc774..f317d8b 100644 --- a/main.tf +++ b/main.tf @@ -1,16 +1,73 @@ locals { # Get distinct list of domains and SANs - distinct_domain_names = distinct( + distinct_domain_names = tolist(distinct( [for s in concat([var.domain_name], var.subject_alternative_names) : replace(s, "*.", "")] + )) + # The subset of distinct domain names which we can map to a hosted zone + distinct_domain_names_with_matching_zone = ( + length(var.domain_zones) == 0 + ? local.distinct_domain_names + : [for d in local.distinct_domain_names : d + if 0 < length([for k in keys(var.domain_zones) : 1 + if lower(d) == lower(k) || trimsuffix(lower(d), lower(format(".%s", k))) != lower(d)]) + ] ) + # Output: the subset of distinct domain names which we cannot map to a hosted zone + distinct_domain_names_without_matching_zone = tolist([ + for d in local.distinct_domain_names : d + if !contains(local.distinct_domain_names_with_matching_zone, d) + ]) # Get the list of distinct domain_validation_options, with wildcard # domain names replaced by the domain name - validation_domains = var.create_certificate ? distinct( - [for k, v in aws_acm_certificate.this[0].domain_validation_options : merge( - tomap(v), { domain_name = replace(v.domain_name, "*.", "") } - )] - ) : [] + validation_domains = tolist( + var.create_certificate ? distinct( + [for k, v in aws_acm_certificate.this[0].domain_validation_options : tomap(merge( + v, { domain_name = replace(v.domain_name, "*.", "") } + ))] + ) : [] + ) + + # The subset of the domain validation options from which we can create records + validation_domains_with_matching_zone = var.create_certificate ? distinct( + [for k, v in aws_acm_certificate.this[0].domain_validation_options : + merge(v, { domain_name = replace(v.domain_name, "*.", "") }) + if contains(local.distinct_domain_names_with_matching_zone, replace(v.domain_name, "*.", "")) + ]) : [] + # Output: the subset of the domain validation options for which we cannot create records + validation_domains_without_matching_zone = tolist(distinct( + var.create_certificate ? [ + for k, v in aws_acm_certificate.this[0].domain_validation_options : + tomap(merge(v, { domain_name = replace(v.domain_name, "*.", "") })) + if contains(local.distinct_domain_names_without_matching_zone, replace(v.domain_name, "*.", "")) + ] : [] + )) + + # Prepare map for iterating over the keys of var.domain_zones in the order + # of their length, from the longest one to the shortest one + name_max_length = max(concat([0], [for n in keys(var.domain_zones) : length(n) ])...) + name_format = "%0${local.name_max_length}d_%s" + domains_by_name_length = { + for n, v in var.domain_zones : + format(local.name_format, length(n), n) => merge(v, { domain = n }) + } + + # For each item in validation_domains_with_matching_zone map to a zone_id from + # var.domain_zones or, if var.domain_zones is empty, to var.zone_id. + zone_ids = var.create_certificate ? [ + for vd in local.validation_domains_with_matching_zone : ( + length(var.domain_zones) == 0 + ? var.zone_id + : element([for k in reverse(keys(local.domains_by_name_length)) : + local.domains_by_name_length[k].zone_id + # Is the FQDN a parent of the fully-qualified validation record name? + if trimsuffix( + lower(vd.resource_record_name), # already FQDN + lower(".${local.domains_by_name_length[k].domain}.") + ) != lower(vd.resource_record_name) + ], 0) + ) + ] : [] } resource "aws_acm_certificate" "this" { @@ -32,15 +89,15 @@ resource "aws_acm_certificate" "this" { } resource "aws_route53_record" "validation" { - count = var.create_certificate && var.validation_method == "DNS" && var.validate_certificate ? length(local.distinct_domain_names) : 0 + count = var.create_certificate && var.validation_method == "DNS" && var.validate_certificate ? length(local.distinct_domain_names_with_matching_zone) : 0 - zone_id = var.zone_id - name = element(local.validation_domains, count.index)["resource_record_name"] - type = element(local.validation_domains, count.index)["resource_record_type"] + zone_id = element(local.zone_ids, count.index) + name = element(local.validation_domains_with_matching_zone, count.index)["resource_record_name"] + type = element(local.validation_domains_with_matching_zone, count.index)["resource_record_type"] ttl = var.dns_ttl records = [ - element(local.validation_domains, count.index)["resource_record_value"] + element(local.validation_domains_with_matching_zone, count.index)["resource_record_value"] ] allow_overwrite = var.validation_allow_overwrite_records diff --git a/outputs.tf b/outputs.tf index 2834c03..1e7ebbc 100644 --- a/outputs.tf +++ b/outputs.tf @@ -23,7 +23,17 @@ output "distinct_domain_names" { value = local.distinct_domain_names } +output "distinct_domain_names_without_matching_zone" { + description = "List of distinct domains names that must be used for the validation but for which no Route 53 zone could be determined from the given input." + value = local.distinct_domain_names_without_matching_zone +} + output "validation_domains" { description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." value = local.validation_domains } + +output "validation_domains_without_matching_zone" { + description = "List of distinct domain validation options for which validation records have not been created because no Route 53 zone could be determined from the given input. This is useful for creating the validation records outside this module." + value = local.validation_domains_without_matching_zone +} diff --git a/variables.tf b/variables.tf index 4a5c670..1251aa2 100644 --- a/variables.tf +++ b/variables.tf @@ -5,7 +5,7 @@ variable "create_certificate" { } variable "validate_certificate" { - description = "Whether to validate certificate by creating Route53 record" + description = "Whether to validate certificate by creating Route53 record(s)" type = bool default = true } @@ -47,11 +47,20 @@ variable "validation_method" { } variable "zone_id" { - description = "The ID of the hosted zone to contain this record." + description = "The ID of the hosted zone to contain the validation record(s)." type = string default = "" } +variable "domain_zones" { + description = "A mappping of distinct domain names to Route 53 zone ids." + type = map(any) + # domain_name => object({ + # zone_id = string + # })) + default = {} +} + variable "tags" { description = "A mapping of tags to assign to the resource" type = map(string) @@ -59,7 +68,7 @@ variable "tags" { } variable "dns_ttl" { - description = "The TTL of DNS recursive resolvers to cache information about this record." + description = "The TTL of DNS recursive resolvers to cache information about the validation record(s)." type = number default = 60 } From efb854b88031a057cb342d68ccb3d8ea4e73a7af Mon Sep 17 00:00:00 2001 From: flora-five <72858916+flora-five@users.noreply.github.com> Date: Thu, 1 Jul 2021 21:20:38 +0300 Subject: [PATCH 2/3] feat: simple implementation of certificate validation with multiple Route 53 zones --- examples/complete-dns-validation/outputs.tf | 4 +- .../multiple-zones-dns-validation/README.md | 1 + .../multiple-zones-dns-validation/main.tf | 30 +++--- .../multiple-zones-dns-validation/outputs.tf | 14 +-- main.tf | 92 +++++-------------- outputs.tf | 14 +-- variables.tf | 5 +- 7 files changed, 45 insertions(+), 115 deletions(-) diff --git a/examples/complete-dns-validation/outputs.tf b/examples/complete-dns-validation/outputs.tf index 1eb07eb..b656562 100644 --- a/examples/complete-dns-validation/outputs.tf +++ b/examples/complete-dns-validation/outputs.tf @@ -19,11 +19,11 @@ output "validation_route53_record_fqdns" { } output "distinct_domain_names" { - description = "List of distinct domains names used for the validation." + description = "List of distinct domains names used for validation. It does not include the certificate distinct domain names that were not mapped to a hosted zone." value = module.acm.distinct_domain_names } output "validation_domains" { - description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards. It does not include the domain validation options for the certificate distinct domain names that were not mapped to a hosted zone." value = module.acm.validation_domains } diff --git a/examples/multiple-zones-dns-validation/README.md b/examples/multiple-zones-dns-validation/README.md index 929cd6d..68bb46f 100644 --- a/examples/multiple-zones-dns-validation/README.md +++ b/examples/multiple-zones-dns-validation/README.md @@ -40,6 +40,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Type | |------|------| +| [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | diff --git a/examples/multiple-zones-dns-validation/main.tf b/examples/multiple-zones-dns-validation/main.tf index 8256da8..d41ef61 100644 --- a/examples/multiple-zones-dns-validation/main.tf +++ b/examples/multiple-zones-dns-validation/main.tf @@ -2,14 +2,18 @@ locals { # Use existing (via data source) or create new zones (will fail validation, if zone is not reachable) use_existing_route53_zones = true - domain = "terraform-aws-modules.modules.tf" - alternate_domain = "terraform-aws-modules.modules-alt.tf" + domain = "terraform-aws-modules.modules.tf" + alternate_domain = "terraform-aws-modules.modules-alt.tf" alternate_subdomain = "sub.${local.alternate_domain}" # Removing trailing dot from domains - just to be sure :) - domain_name = trimsuffix(local.domain, ".") - alternate_domain_name = trimsuffix(local.alternate_domain, ".") + domain_name = trimsuffix(local.domain, ".") + alternate_domain_name = trimsuffix(local.alternate_domain, ".") alternate_subdomain_name = trimsuffix(local.alternate_subdomain, ".") + + domain_zone_id = coalescelist(data.aws_route53_zone.domain.*.zone_id, aws_route53_zone.domain.*.zone_id)[0] + alternate_domain_zone_id = coalescelist(data.aws_route53_zone.alternate_domain.*.zone_id, aws_route53_zone.alternate_domain.*.zone_id)[0] + alternate_subdomain_zone_id = coalescelist(data.aws_route53_zone.alternate_subdomain.*.zone_id, aws_route53_zone.alternate_subdomain.*.zone_id)[0] } data "aws_route53_zone" "domain" { @@ -69,21 +73,19 @@ module "acm" { "alerts.${local.domain_name}", local.alternate_domain_name, "*.${local.alternate_domain_name}", - "quite.deep.abc.sub.${local.alternate_domain_name}", + "quite.deep.abc.${local.alternate_subdomain_name}", "def.${local.alternate_subdomain_name}", "*.${local.alternate_subdomain_name}", ] domain_zones = { - (local.domain_name) = { - zone_id = coalescelist(data.aws_route53_zone.domain.*.zone_id, aws_route53_zone.domain.*.zone_id)[0] - } - (local.alternate_domain_name) = { - zone_id = coalescelist(data.aws_route53_zone.alternate_domain.*.zone_id, aws_route53_zone.alternate_domain.*.zone_id)[0] - } - (local.alternate_subdomain_name) = { - zone_id = coalescelist(data.aws_route53_zone.alternate_subdomain.*.zone_id, aws_route53_zone.alternate_subdomain.*.zone_id)[0] - } + (local.domain_name) = { zone_id = local.domain_zone_id } + "alerts.${local.domain_name}" = { zone_id = local.domain_zone_id } + "new.sub.${local.domain_name}" = { zone_id = local.domain_zone_id } + (local.alternate_domain_name) = { zone_id = local.alternate_domain_zone_id } + (local.alternate_subdomain_name) = { zone_id = local.alternate_subdomain_zone_id } + "def.${local.alternate_subdomain_name}" = { zone_id = local.alternate_subdomain_zone_id } + "quite.deep.abc.${local.alternate_subdomain_name}" = { zone_id = local.alternate_subdomain_zone_id } } wait_for_validation = true diff --git a/examples/multiple-zones-dns-validation/outputs.tf b/examples/multiple-zones-dns-validation/outputs.tf index b003871..b656562 100644 --- a/examples/multiple-zones-dns-validation/outputs.tf +++ b/examples/multiple-zones-dns-validation/outputs.tf @@ -19,21 +19,11 @@ output "validation_route53_record_fqdns" { } output "distinct_domain_names" { - description = "List of distinct domains names used for the validation." + description = "List of distinct domains names used for validation. It does not include the certificate distinct domain names that were not mapped to a hosted zone." value = module.acm.distinct_domain_names } -output "distinct_domain_names_without_matching_zone" { - description = "List of distinct domains names that must be used for the validation but for which no Route 53 zone could be determined from the given input." - value = module.acm.distinct_domain_names_without_matching_zone -} - output "validation_domains" { - description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards. It does not include the domain validation options for the certificate distinct domain names that were not mapped to a hosted zone." value = module.acm.validation_domains } - -output "validation_domains_without_matching_zone" { - description = "List of distinct domain validation options for which validation records have not been created because no Route 53 zone could be determined from the given input." - value = module.acm.validation_domains_without_matching_zone -} diff --git a/main.tf b/main.tf index f317d8b..abe42a5 100644 --- a/main.tf +++ b/main.tf @@ -1,73 +1,19 @@ locals { - # Get distinct list of domains and SANs - distinct_domain_names = tolist(distinct( - [for s in concat([var.domain_name], var.subject_alternative_names) : replace(s, "*.", "")] - )) - # The subset of distinct domain names which we can map to a hosted zone - distinct_domain_names_with_matching_zone = ( - length(var.domain_zones) == 0 - ? local.distinct_domain_names - : [for d in local.distinct_domain_names : d - if 0 < length([for k in keys(var.domain_zones) : 1 - if lower(d) == lower(k) || trimsuffix(lower(d), lower(format(".%s", k))) != lower(d)]) - ] + # Get distinct list of domains and SANs that can be mapped to a zone + distinct_domain_names = distinct( + [for s in concat([var.domain_name], var.subject_alternative_names) : replace(s, "*.", "") + if length(var.domain_zones) == 0 || contains(keys(var.domain_zones), s) + ] ) - # Output: the subset of distinct domain names which we cannot map to a hosted zone - distinct_domain_names_without_matching_zone = tolist([ - for d in local.distinct_domain_names : d - if !contains(local.distinct_domain_names_with_matching_zone, d) - ]) # Get the list of distinct domain_validation_options, with wildcard - # domain names replaced by the domain name - validation_domains = tolist( - var.create_certificate ? distinct( - [for k, v in aws_acm_certificate.this[0].domain_validation_options : tomap(merge( - v, { domain_name = replace(v.domain_name, "*.", "") } - ))] - ) : [] - ) - - # The subset of the domain validation options from which we can create records - validation_domains_with_matching_zone = var.create_certificate ? distinct( - [for k, v in aws_acm_certificate.this[0].domain_validation_options : - merge(v, { domain_name = replace(v.domain_name, "*.", "") }) - if contains(local.distinct_domain_names_with_matching_zone, replace(v.domain_name, "*.", "")) - ]) : [] - # Output: the subset of the domain validation options for which we cannot create records - validation_domains_without_matching_zone = tolist(distinct( - var.create_certificate ? [ - for k, v in aws_acm_certificate.this[0].domain_validation_options : - tomap(merge(v, { domain_name = replace(v.domain_name, "*.", "") })) - if contains(local.distinct_domain_names_without_matching_zone, replace(v.domain_name, "*.", "")) - ] : [] - )) - - # Prepare map for iterating over the keys of var.domain_zones in the order - # of their length, from the longest one to the shortest one - name_max_length = max(concat([0], [for n in keys(var.domain_zones) : length(n) ])...) - name_format = "%0${local.name_max_length}d_%s" - domains_by_name_length = { - for n, v in var.domain_zones : - format(local.name_format, length(n), n) => merge(v, { domain = n }) - } - - # For each item in validation_domains_with_matching_zone map to a zone_id from - # var.domain_zones or, if var.domain_zones is empty, to var.zone_id. - zone_ids = var.create_certificate ? [ - for vd in local.validation_domains_with_matching_zone : ( - length(var.domain_zones) == 0 - ? var.zone_id - : element([for k in reverse(keys(local.domains_by_name_length)) : - local.domains_by_name_length[k].zone_id - # Is the FQDN a parent of the fully-qualified validation record name? - if trimsuffix( - lower(vd.resource_record_name), # already FQDN - lower(".${local.domains_by_name_length[k].domain}.") - ) != lower(vd.resource_record_name) - ], 0) - ) - ] : [] + # domain names replaced by the domain name. Validation records will be + # created from this list. + validation_domains = var.create_certificate ? distinct( + [for k, v in aws_acm_certificate.this[0].domain_validation_options : tomap(merge( + v, { domain_name = replace(v.domain_name, "*.", "") } + )) if contains(local.distinct_domain_names, replace(v.domain_name, "*.", ""))] + ) : [] } resource "aws_acm_certificate" "this" { @@ -89,15 +35,19 @@ resource "aws_acm_certificate" "this" { } resource "aws_route53_record" "validation" { - count = var.create_certificate && var.validation_method == "DNS" && var.validate_certificate ? length(local.distinct_domain_names_with_matching_zone) : 0 + count = var.create_certificate && var.validation_method == "DNS" && var.validate_certificate ? length(local.distinct_domain_names) : 0 - zone_id = element(local.zone_ids, count.index) - name = element(local.validation_domains_with_matching_zone, count.index)["resource_record_name"] - type = element(local.validation_domains_with_matching_zone, count.index)["resource_record_type"] + zone_id = ( + length(var.domain_zones) == 0 + ? var.zone_id + : var.domain_zones[element(local.validation_domains, count.index)["domain_name"]]["zone_id"] + ) + name = element(local.validation_domains, count.index)["resource_record_name"] + type = element(local.validation_domains, count.index)["resource_record_type"] ttl = var.dns_ttl records = [ - element(local.validation_domains_with_matching_zone, count.index)["resource_record_value"] + element(local.validation_domains, count.index)["resource_record_value"] ] allow_overwrite = var.validation_allow_overwrite_records diff --git a/outputs.tf b/outputs.tf index 1e7ebbc..05898e0 100644 --- a/outputs.tf +++ b/outputs.tf @@ -19,21 +19,11 @@ output "validation_route53_record_fqdns" { } output "distinct_domain_names" { - description = "List of distinct domains names used for the validation." + description = "List of distinct domains names used for validation. It does not include the certificate distinct domain names that were not mapped to a hosted zone." value = local.distinct_domain_names } -output "distinct_domain_names_without_matching_zone" { - description = "List of distinct domains names that must be used for the validation but for which no Route 53 zone could be determined from the given input." - value = local.distinct_domain_names_without_matching_zone -} - output "validation_domains" { - description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards. It does not include the domain validation options for the certificate distinct domain names that were not mapped to a hosted zone." value = local.validation_domains } - -output "validation_domains_without_matching_zone" { - description = "List of distinct domain validation options for which validation records have not been created because no Route 53 zone could be determined from the given input. This is useful for creating the validation records outside this module." - value = local.validation_domains_without_matching_zone -} diff --git a/variables.tf b/variables.tf index 1251aa2..7d05459 100644 --- a/variables.tf +++ b/variables.tf @@ -53,11 +53,8 @@ variable "zone_id" { } variable "domain_zones" { - description = "A mappping of distinct domain names to Route 53 zone ids." + description = "A mapping of distinct domain names to Route 53 zone details." type = map(any) - # domain_name => object({ - # zone_id = string - # })) default = {} } From 06a5a1e0bfa0e04cbe10b56a4da71bbfc51a8f96 Mon Sep 17 00:00:00 2001 From: flora-five <72858916+flora-five@users.noreply.github.com> Date: Thu, 1 Jul 2021 22:17:57 +0300 Subject: [PATCH 3/3] fix: remove manual change of auto-generated part of the documentation --- examples/multiple-zones-dns-validation/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/multiple-zones-dns-validation/README.md b/examples/multiple-zones-dns-validation/README.md index 68bb46f..929cd6d 100644 --- a/examples/multiple-zones-dns-validation/README.md +++ b/examples/multiple-zones-dns-validation/README.md @@ -40,7 +40,6 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Type | |------|------| -| [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |