From f7c654834c23687d205922554661285e5fb16aee Mon Sep 17 00:00:00 2001 From: Paul Harrington Date: Thu, 13 Jan 2022 14:02:06 +1100 Subject: [PATCH] added extension for alternative zone_id's for SAN's --- README.md | 31 ++++++++++ .../dns-validation-multiple-zones/README.md | 59 +++++++++++++++++++ .../dns-validation-multiple-zones/main.tf | 44 ++++++++++++++ .../dns-validation-multiple-zones/outputs.tf | 29 +++++++++ .../variables.tf | 0 .../dns-validation-multiple-zones/versions.tf | 10 ++++ main.tf | 33 +++++++++-- variables.tf | 2 +- 8 files changed, 202 insertions(+), 6 deletions(-) create mode 100644 examples/dns-validation-multiple-zones/README.md create mode 100644 examples/dns-validation-multiple-zones/main.tf create mode 100644 examples/dns-validation-multiple-zones/outputs.tf create mode 100644 examples/dns-validation-multiple-zones/variables.tf create mode 100644 examples/dns-validation-multiple-zones/versions.tf diff --git a/README.md b/README.md index ae1aa42..c042f0f 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,37 @@ module "acm" { ``` +## Usage with Route53 DNS validation & different zones + +```hcl +module "acm" { + source = "terraform-aws-modules/acm/aws" + version = "~> 3.3" + + domain_name = "my-domain.com" + zone_id = "Z2ES7B9AZ6SHAE" + + subject_alternative_names = [ + "*.my-domain.com", + "app.sub.my-domain.com", + { + name = "my-other-domain.com" + zone_id = "Z06730ESICABNX" + }, + { + name = "*.my-other-domain.com" + zone_id = "Z06730ESICABNX" + } + ] + + wait_for_validation = true + + tags = { + Name = "my-domain.com" + } +} +``` + ## Examples - [Complete example with DNS validation (recommended)](https://github.com/terraform-aws-modules/terraform-aws-acm/tree/master/examples/complete-dns-validation) diff --git a/examples/dns-validation-multiple-zones/README.md b/examples/dns-validation-multiple-zones/README.md new file mode 100644 index 0000000..ce43937 --- /dev/null +++ b/examples/dns-validation-multiple-zones/README.md @@ -0,0 +1,59 @@ +# ACM example with Route53 DNS validation and multiple zones + +Configuration in this directory creates a new ACM certificate which has SAN's that need a different Route53 Zone compared to `var.domain_name` to be validated. + + +## 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) | ../../ | n/a | + +## 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/dns-validation-multiple-zones/main.tf b/examples/dns-validation-multiple-zones/main.tf new file mode 100644 index 0000000..b40346a --- /dev/null +++ b/examples/dns-validation-multiple-zones/main.tf @@ -0,0 +1,44 @@ +locals { + primary_domain = "terraform-aws-modules.modules.tf" + extra_domain = "terraform-aws-modules.extra-modules.tf" + + # Removing trailing dot from domain - just to be sure :) + primary_domain_name = trimsuffix(local.primary_domain, ".") + extra_domain_name = trimsuffix(local.extra_domain, ".") +} + +data "aws_route53_zone" "primary_domain" { + name = local.primary_domain_name + private_zone = false +} + +data "aws_route53_zone" "extra_domain" { + name = local.extra_domain_name + private_zone = false +} + + +module "acm" { + source = "../../" + + domain_name = local.primary_domain_name + zone_id = data.aws_route53_zone.primary_domain.zone_id + + subject_alternative_names = [ + "*.${local.primary_domain_name}", + { + name = "alerts.${local.extra_domain_name}" + zone_id = data.aws_route53_zone.extra_domain.zone_id + }, + { + name = "*.alerts.${local.extra_domain_name}" + zone_id = data.aws_route53_zone.extra_domain.zone_id + } + ] + + wait_for_validation = true + + tags = { + Name = local.primary_domain_name + } +} diff --git a/examples/dns-validation-multiple-zones/outputs.tf b/examples/dns-validation-multiple-zones/outputs.tf new file mode 100644 index 0000000..1eb07eb --- /dev/null +++ b/examples/dns-validation-multiple-zones/outputs.tf @@ -0,0 +1,29 @@ +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 "validation_domains" { + description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards." + value = module.acm.validation_domains +} diff --git a/examples/dns-validation-multiple-zones/variables.tf b/examples/dns-validation-multiple-zones/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/dns-validation-multiple-zones/versions.tf b/examples/dns-validation-multiple-zones/versions.tf new file mode 100644 index 0000000..67d15ec --- /dev/null +++ b/examples/dns-validation-multiple-zones/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.12.26" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.53" + } + } +} diff --git a/main.tf b/main.tf index 0adc774..6d09b8f 100644 --- a/main.tf +++ b/main.tf @@ -1,8 +1,31 @@ locals { + # Normalize var.subject_alternative_names to a list of maps: [{name = NAME, zone_id = ZONE_ID}] + ## var.subject_alternative_names is a list of mixed elements: Strings and Maps of Strings + ## string elements use the string as the "name" and use var.zone_id as their "zone_id" + ## map elements passthrough their "name" and "zone_id" values (if zone_id not specified use var.zone_id) + subject_alternative_names = [ + for element in var.subject_alternative_names : + { + name = try(tostring(element["name"]), tostring(element)) + zone_id = try(tostring(element["zone_id"]), var.zone_id) + } + ] + + # Create a map from domain name (minus wildcard) to zone id + ## The extra flatten([]) is to resolve a bug in terraform 0.12.26 (keeping backwards compatibility) + ## https://github.com/hashicorp/terraform/issues/22404 + domainname_to_zoneid = merge(flatten([[ + for element in concat([{ name = var.domain_name, zone_id = var.zone_id }], local.subject_alternative_names) : + { + replace(element.name, "*.", "") = element.zone_id + } + ]])...) + # Get distinct list of domains and SANs - distinct_domain_names = distinct( - [for s in concat([var.domain_name], var.subject_alternative_names) : replace(s, "*.", "")] - ) + distinct_domain_names = distinct([ + for s in concat([var.domain_name], local.subject_alternative_names[*].name) : + replace(s, "*.", "") + ]) # Get the list of distinct domain_validation_options, with wildcard # domain names replaced by the domain name @@ -17,7 +40,7 @@ resource "aws_acm_certificate" "this" { count = var.create_certificate ? 1 : 0 domain_name = var.domain_name - subject_alternative_names = var.subject_alternative_names + subject_alternative_names = local.subject_alternative_names[*].name validation_method = var.validation_method options { @@ -34,7 +57,7 @@ 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 - zone_id = var.zone_id + zone_id = local.domainname_to_zoneid[element(local.validation_domains, count.index)["domain_name"]] name = element(local.validation_domains, count.index)["resource_record_name"] type = element(local.validation_domains, count.index)["resource_record_type"] ttl = var.dns_ttl diff --git a/variables.tf b/variables.tf index 4a5c670..4189d08 100644 --- a/variables.tf +++ b/variables.tf @@ -36,7 +36,7 @@ variable "domain_name" { variable "subject_alternative_names" { description = "A list of domains that should be SANs in the issued certificate" - type = list(string) + type = any # list(string | map(string)) default = [] }