Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PLT-977 added support for multi-domain certs by enabling dns validation for SANs #12

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.provider == "cloudflare" }
name = each.value.zone_domain
}
9 changes: 3 additions & 6 deletions examples/san/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ module "this" {
source = "../../"

cloudflare_api_token = var.cloudflare_api_token

zone_name = var.domain_name
domain_name = "test-san.${var.domain_name}"

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"]
]
}
4 changes: 1 addition & 3 deletions examples/simple/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@ module "this" {
source = "../../"

cloudflare_api_token = var.cloudflare_api_token

zone_name = var.domain_name
domain_name = "test.${var.domain_name}"
domain_name = "test.${var.domain_name}"
}
26 changes: 15 additions & 11 deletions locals.tf
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
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] => { provider = v[1]
# if condition to check the root domain; flaconi.de vs www.flaconi.de
zone_domain = length(split(".", v[0])) == 2 ? v[0] : regex("^.+\\.(.+\\..+)$", v[0])[0]
}
}

# 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 = local.domain_validation_providers[dvo.domain_name]["provider"]
zone_domain = local.domain_validation_providers[dvo.domain_name]["zone_domain"]
})
] : []
}
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.key].id
# https://github.com/cloudflare/terraform-provider-cloudflare/issues/2407#issuecomment-1960712054
name = trimsuffix(each.value.resource_record_name, ".${each.value.zone_domain}.")
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