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 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..d41ef61 --- /dev/null +++ b/examples/multiple-zones-dns-validation/main.tf @@ -0,0 +1,96 @@ +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, ".") + + 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" { + 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.${local.alternate_subdomain_name}", + "def.${local.alternate_subdomain_name}", + "*.${local.alternate_subdomain_name}", + ] + + domain_zones = { + (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 + + 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..b656562 --- /dev/null +++ b/examples/multiple-zones-dns-validation/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 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. 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/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..abe42a5 100644 --- a/main.tf +++ b/main.tf @@ -1,15 +1,18 @@ locals { - # Get distinct list of domains and SANs + # 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, "*.", "")] + [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) + ] ) # Get the list of distinct domain_validation_options, with wildcard - # domain names replaced by the domain name + # 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 : merge( - tomap(v), { domain_name = replace(v.domain_name, "*.", "") } - )] + [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, "*.", ""))] ) : [] } @@ -34,7 +37,11 @@ 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 = ( + 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 diff --git a/outputs.tf b/outputs.tf index 2834c03..05898e0 100644 --- a/outputs.tf +++ b/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 = local.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 = local.validation_domains } diff --git a/variables.tf b/variables.tf index 4a5c670..7d05459 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,17 @@ 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 mapping of distinct domain names to Route 53 zone details." + type = map(any) + default = {} +} + variable "tags" { description = "A mapping of tags to assign to the resource" type = map(string) @@ -59,7 +65,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 }