Skip to content

Commit

Permalink
PLT-977 added support for multi-domain certs by enabling dns validati…
Browse files Browse the repository at this point in the history
…on for SANs
  • Loading branch information
vselcuk committed Oct 21, 2024
1 parent 066e194 commit 0e3d8c7
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 63 deletions.
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ Description: The Cloudflare API token.

Type: `string`

### <a name="input_zone_name"></a> [zone\_name](#input\_zone\_name)

Description: The Name of the zone to contain this record.

Type: `string`

## Optional Inputs

The following input variables are optional (have default values):
Expand Down Expand Up @@ -103,11 +97,19 @@ Type: `string`

Default: `""`

### <a name="input_dns_validation_provider"></a> [dns\_validation\_provider](#input\_dns\_validation\_provider)

Description: DNS validation provider setting for the domain name; either aws zone id or cloudflare

Type: `string`

Default: `"cloudflare"`

### <a name="input_subject_alternative_names"></a> [subject\_alternative\_names](#input\_subject\_alternative\_names)

Description: A list of domains that should be SANs in the issued certificate
Description: The list of domains and their dns providers as tuples; either aws zone id or cloudflare as dns provider

Type: `list(string)`
Type: `list(tuple([string, string]))`

Default: `[]`

Expand Down Expand Up @@ -135,10 +137,6 @@ Default: `120`
| Name | Description |
|------|-------------|
| <a name="output_acm_certificate_arn"></a> [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | The ARN of the certificate |
| <a name="output_acm_certificate_domain_validation_options"></a> [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. |
| <a name="output_distinct_domain_names"></a> [distinct\_domain\_names](#output\_distinct\_domain\_names) | List of distinct domains names used for the validation. |
| <a name="output_validation_dns_record_fqdns"></a> [validation\_dns\_record\_fqdns](#output\_validation\_dns\_record\_fqdns) | List of FQDNs built using the zone domain and name. |
| <a name="output_validation_domains"></a> [validation\_domains](#output\_validation\_domains) | List of distinct domain validation options. This is useful if subject alternative names contain wildcards. |

<!-- TFDOCS_OUTPUTS_END -->

Expand Down
4 changes: 2 additions & 2 deletions data.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
data "cloudflare_zone" "this" {
count = var.create_certificate ? 1 : 0
name = var.zone_name
for_each = { for d, p in local.domain_validation_providers : d => p if p == "cloudflare" }
name = regex("^.+\\.(.+\\..+)$", each.key)[0]
}
4 changes: 2 additions & 2 deletions examples/san/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module "this" {
domain_name = "test-san.${var.domain_name}"

subject_alternative_names = [
"subdomain.test-san.${var.domain_name}",
"test-other.${var.domain_name}"
["subdomain.test-san.${var.domain_name}", "cloudflare"],
["test-other.${var.domain_name}", "cloudflare"]
]
}
18 changes: 7 additions & 11 deletions locals.tf
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
locals {
# Get distinct list of domains and SANs
distinct_domain_names = distinct(
[for s in concat([var.domain_name], var.subject_alternative_names) : replace(s, "*.", "")]
)
# Creates a map of domains and their dns providers
domain_validation_providers = { for v in concat([[var.domain_name, var.dns_validation_provider]], var.subject_alternative_names) : v[0] => v[1] }

# 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, "*.", "") }
)]
) : []
# Enrich domain_validation_options with their dns providers
acm_domain_validation_options = var.create_certificate && var.validate_certificate ? [
for i, dvo in aws_acm_certificate.this[0].domain_validation_options :
merge(dvo, { provider = lookup(local.domain_validation_providers, dvo.domain_name, "cloudflare") })
] : []
}
32 changes: 24 additions & 8 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,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 = [for san in var.subject_alternative_names : san[0]]
validation_method = "DNS"

options {
Expand All @@ -16,13 +16,29 @@ resource "aws_acm_certificate" "this" {
}
}

resource "cloudflare_record" "validation" {
count = var.create_certificate && var.validate_certificate ? length(local.distinct_domain_names) : 0
resource "aws_route53_record" "this" {
for_each = { for i, dvo in local.acm_domain_validation_options : dvo.domain_name => dvo if var.validate_certificate && dvo.provider != "cloudflare" }

zone_id = data.cloudflare_zone.this[0].id
name = element(local.validation_domains, count.index)["resource_record_name"]
type = element(local.validation_domains, count.index)["resource_record_type"]
content = replace(element(local.validation_domains, count.index)["resource_record_value"], "/.$/", "")
zone_id = each.value.provider
name = each.value.resource_record_name
type = each.value.resource_record_type
ttl = var.dns_ttl

records = [each.value.resource_record_value]

allow_overwrite = var.validation_allow_overwrite_records

depends_on = [aws_acm_certificate.this]
}

resource "cloudflare_record" "this" {
for_each = { for i, dvo in local.acm_domain_validation_options : dvo.domain_name => dvo if var.validate_certificate && dvo.provider == "cloudflare" }

zone_id = data.cloudflare_zone.this[each.value.domain_name].id
# https://github.com/cloudflare/terraform-provider-cloudflare/issues/2407#issuecomment-1960712054
name = trimsuffix(each.value.resource_record_name, ".${each.value.resource_record_name}.")
type = each.value.resource_record_type
content = replace(each.value.resource_record_value, "/.$/", "")
ttl = var.dns_ttl
proxied = false

Expand All @@ -36,5 +52,5 @@ resource "aws_acm_certificate_validation" "this" {

certificate_arn = aws_acm_certificate.this[0].arn

validation_record_fqdns = cloudflare_record.validation.*.hostname
validation_record_fqdns = flatten([try(values(aws_route53_record.this).*.fqdn, []), try(values(cloudflare_record.this).*.hostname, [])])
}
22 changes: 1 addition & 21 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -1,24 +1,4 @@
output "acm_certificate_arn" {
description = "The ARN of the certificate"
value = element(concat(aws_acm_certificate_validation.this.*.certificate_arn, aws_acm_certificate.this.*.arn, [""]), 0)
}

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."
value = flatten(aws_acm_certificate.this.*.domain_validation_options)
}

output "validation_dns_record_fqdns" {
description = "List of FQDNs built using the zone domain and name."
value = cloudflare_record.validation.*.hostname
}

output "distinct_domain_names" {
description = "List of distinct domains names used for the validation."
value = local.distinct_domain_names
}

output "validation_domains" {
description = "List of distinct domain validation options. This is useful if subject alternative names contain wildcards."
value = local.validation_domains
value = var.create_certificate ? aws_acm_certificate.this[0].arn : null
}
15 changes: 8 additions & 7 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,16 @@ variable "domain_name" {
default = ""
}

variable "subject_alternative_names" {
description = "A list of domains that should be SANs in the issued certificate"
type = list(string)
default = []
variable "dns_validation_provider" {
description = "DNS validation provider setting for the domain name; either aws zone id or cloudflare"
type = string
default = "cloudflare"
}

variable "zone_name" {
description = "The Name of the zone to contain this record."
type = string
variable "subject_alternative_names" {
description = "The list of domains and their dns providers as tuples; either aws zone id or cloudflare as dns provider"
type = list(tuple([string, string]))
default = []
}

variable "tags" {
Expand Down

0 comments on commit 0e3d8c7

Please sign in to comment.