From 2e24e0a5562e9fb61e183f0c21576602545badcc Mon Sep 17 00:00:00 2001 From: Novikov Sergey Date: Mon, 22 Nov 2021 17:04:19 +0100 Subject: [PATCH] Create all required resources (#2) * Add resource creation * Add examples --- README.md | 99 +++++++++++++++++++++++++++++++++--- data.tf | 3 ++ examples/san/README.md | 40 +++++++++++++++ examples/san/main.tf | 13 +++++ examples/san/providers.tf | 3 ++ examples/san/variables.tf | 16 ++++++ examples/simple/README.md | 40 +++++++++++++++ examples/simple/main.tf | 8 +++ examples/simple/providers.tf | 3 ++ examples/simple/variables.tf | 16 ++++++ main.tf | 55 ++++++++++++++++++++ outputs.tf | 24 +++++++++ providers.tf | 2 +- variables.tf | 61 +++++++++++++++++++++- versions.tf | 2 +- 15 files changed, 376 insertions(+), 9 deletions(-) create mode 100644 data.tf create mode 100644 examples/san/README.md create mode 100644 examples/san/main.tf create mode 100644 examples/san/providers.tf create mode 100644 examples/san/variables.tf create mode 100644 examples/simple/README.md create mode 100644 examples/simple/main.tf create mode 100644 examples/simple/providers.tf create mode 100644 examples/simple/variables.tf create mode 100644 main.tf create mode 100644 outputs.tf diff --git a/README.md b/README.md index 13bdb44..13407b3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,10 @@ Create ACM certificate with DNS validation and validate using Cloudflare Hosted ## Providers -No providers. +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3 | +| [cloudflare](#provider\_cloudflare) | ~> 3.2 | @@ -26,7 +29,7 @@ No providers. |------|---------| | [terraform](#requirement\_terraform) | >= 0.12.26 | | [aws](#requirement\_aws) | >= 3 | -| [cloudflare](#requirement\_cloudflare) | ~> 3.1.0 | +| [cloudflare](#requirement\_cloudflare) | ~> 3.2 | @@ -35,22 +38,106 @@ No providers. The following input variables are required: -### [api\_token](#input\_api\_token) +### [cloudflare\_api\_token](#input\_cloudflare\_api\_token) Description: The Cloudflare API token. Type: `string` +### [zone\_name](#input\_zone\_name) + +Description: The Name of the zone to contain this record. + +Type: `string` + ## Optional Inputs -No optional inputs. +The following input variables are optional (have default values): + +### [create\_certificate](#input\_create\_certificate) + +Description: Whether to create ACM certificate + +Type: `bool` + +Default: `true` + +### [validate\_certificate](#input\_validate\_certificate) + +Description: Whether to validate certificate by creating DNS record + +Type: `bool` + +Default: `true` + +### [validation\_allow\_overwrite\_records](#input\_validation\_allow\_overwrite\_records) + +Description: Whether to allow overwrite of Route53 records + +Type: `bool` + +Default: `true` + +### [wait\_for\_validation](#input\_wait\_for\_validation) + +Description: Whether to wait for the validation to complete + +Type: `bool` + +Default: `true` + +### [certificate\_transparency\_logging\_preference](#input\_certificate\_transparency\_logging\_preference) + +Description: Specifies whether certificate details should be added to a certificate transparency log + +Type: `bool` + +Default: `true` + +### [domain\_name](#input\_domain\_name) + +Description: A domain name for which the certificate should be issued + +Type: `string` + +Default: `""` + +### [subject\_alternative\_names](#input\_subject\_alternative\_names) + +Description: A list of domains that should be SANs in the issued certificate + +Type: `list(string)` + +Default: `[]` + +### [tags](#input\_tags) + +Description: A mapping of tags to assign to the resource + +Type: `map(string)` + +Default: `{}` + +### [dns\_ttl](#input\_dns\_ttl) + +Description: The TTL of DNS recursive resolvers to cache information about this record. + +Type: `number` + +Default: `120` ## Outputs -No 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. | +| [distinct\_domain\_names](#output\_distinct\_domain\_names) | List of distinct domains names used for the validation. | +| [validation\_dns\_record\_fqdns](#output\_validation\_dns\_record\_fqdns) | List of FQDNs built using the zone domain and name. | +| [validation\_domains](#output\_validation\_domains) | List of distinct domain validation options. This is useful if subject alternative names contain wildcards. | @@ -59,4 +146,4 @@ No outputs. **[MIT License](LICENSE)** -Copyright (c) 2021 **[flaconi](https://github.com/flaconi)** +Copyright (c) 2021 **[Flaconi GmbH](https://github.com/flaconi)** diff --git a/data.tf b/data.tf new file mode 100644 index 0000000..79d4ba3 --- /dev/null +++ b/data.tf @@ -0,0 +1,3 @@ +data "cloudflare_zone" "this" { + name = var.zone_name +} diff --git a/examples/san/README.md b/examples/san/README.md new file mode 100644 index 0000000..922e00d --- /dev/null +++ b/examples/san/README.md @@ -0,0 +1,40 @@ +# Certificate with Subject Alternate Names (SAN) + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [region](#input\_region) | The AWS region this module is strictly bound to. | `string` | `"eu-central-1"` | no | +| [cloudflare\_api\_token](#input\_cloudflare\_api\_token) | The Cloudflare API token. | `string` | n/a | yes | +| [domain\_name](#input\_domain\_name) | A domain name for which the certificate should be issued | `string` | `"example.com"` | no | + +## Outputs + +No outputs. + + + +## License + +**[MIT License](../../LICENSE)** + +Copyright (c) 2021 **[Flaconi GmbH](https://github.com/flaconi)** diff --git a/examples/san/main.tf b/examples/san/main.tf new file mode 100644 index 0000000..6b3d75d --- /dev/null +++ b/examples/san/main.tf @@ -0,0 +1,13 @@ +module "this" { + source = "../../" + + cloudflare_api_token = var.cloudflare_api_token + + zone_name = var.domain_name + domain_name = "test-san.${var.domain_name}" + + subject_alternative_names = [ + "subdomain.test-san.${var.domain_name}", + "test-other.${var.domain_name}" + ] +} diff --git a/examples/san/providers.tf b/examples/san/providers.tf new file mode 100644 index 0000000..dc58d9a --- /dev/null +++ b/examples/san/providers.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = var.region +} diff --git a/examples/san/variables.tf b/examples/san/variables.tf new file mode 100644 index 0000000..c7036e2 --- /dev/null +++ b/examples/san/variables.tf @@ -0,0 +1,16 @@ +variable "region" { + type = string + description = "The AWS region this module is strictly bound to." + default = "eu-central-1" +} + +variable "cloudflare_api_token" { + description = "The Cloudflare API token." + type = string +} + +variable "domain_name" { + description = "A domain name for which the certificate should be issued" + type = string + default = "example.com" +} diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 0000000..80f1b90 --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,40 @@ +# Simple example with single domain + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [region](#input\_region) | The AWS region this module is strictly bound to. | `string` | `"eu-central-1"` | no | +| [cloudflare\_api\_token](#input\_cloudflare\_api\_token) | The Cloudflare API token. | `string` | n/a | yes | +| [domain\_name](#input\_domain\_name) | A domain name for which the certificate should be issued | `string` | `"example.com"` | no | + +## Outputs + +No outputs. + + + +## License + +**[MIT License](../../LICENSE)** + +Copyright (c) 2021 **[Flaconi GmbH](https://github.com/flaconi)** diff --git a/examples/simple/main.tf b/examples/simple/main.tf new file mode 100644 index 0000000..c8fd51b --- /dev/null +++ b/examples/simple/main.tf @@ -0,0 +1,8 @@ +module "this" { + source = "../../" + + cloudflare_api_token = var.cloudflare_api_token + + zone_name = var.domain_name + domain_name = "test.${var.domain_name}" +} diff --git a/examples/simple/providers.tf b/examples/simple/providers.tf new file mode 100644 index 0000000..dc58d9a --- /dev/null +++ b/examples/simple/providers.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = var.region +} diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf new file mode 100644 index 0000000..c7036e2 --- /dev/null +++ b/examples/simple/variables.tf @@ -0,0 +1,16 @@ +variable "region" { + type = string + description = "The AWS region this module is strictly bound to." + default = "eu-central-1" +} + +variable "cloudflare_api_token" { + description = "The Cloudflare API token." + type = string +} + +variable "domain_name" { + description = "A domain name for which the certificate should be issued" + type = string + default = "example.com" +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..06a8d95 --- /dev/null +++ b/main.tf @@ -0,0 +1,55 @@ +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, "*.", "")] + ) + + # 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, "*.", "") } + )] + ) : [] +} + +resource "aws_acm_certificate" "this" { + count = var.create_certificate ? 1 : 0 + + domain_name = var.domain_name + subject_alternative_names = var.subject_alternative_names + validation_method = "DNS" + + options { + certificate_transparency_logging_preference = var.certificate_transparency_logging_preference ? "ENABLED" : "DISABLED" + } + + tags = var.tags + + lifecycle { + create_before_destroy = true + } +} + +resource "cloudflare_record" "validation" { + count = var.create_certificate && var.validate_certificate ? length(local.distinct_domain_names) : 0 + + zone_id = data.cloudflare_zone.this.id + name = element(local.validation_domains, count.index)["resource_record_name"] + type = element(local.validation_domains, count.index)["resource_record_type"] + value = replace(element(local.validation_domains, count.index)["resource_record_value"], "/.$/", "") + ttl = var.dns_ttl + proxied = false + + allow_overwrite = var.validation_allow_overwrite_records + + depends_on = [aws_acm_certificate.this] +} + +resource "aws_acm_certificate_validation" "this" { + count = var.create_certificate && var.validate_certificate && var.wait_for_validation ? 1 : 0 + + certificate_arn = aws_acm_certificate.this[0].arn + + validation_record_fqdns = cloudflare_record.validation.*.hostname +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..a3a3fd4 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,24 @@ +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 +} diff --git a/providers.tf b/providers.tf index 16ff320..344ab85 100644 --- a/providers.tf +++ b/providers.tf @@ -1,3 +1,3 @@ provider "cloudflare" { - api_token = var.api_token + api_token = var.cloudflare_api_token } diff --git a/variables.tf b/variables.tf index d581e36..f8982b7 100644 --- a/variables.tf +++ b/variables.tf @@ -1,4 +1,63 @@ -variable "api_token" { +variable "cloudflare_api_token" { description = "The Cloudflare API token." type = string } + +variable "create_certificate" { + description = "Whether to create ACM certificate" + type = bool + default = true +} + +variable "validate_certificate" { + description = "Whether to validate certificate by creating DNS record" + type = bool + default = true +} + +variable "validation_allow_overwrite_records" { + description = "Whether to allow overwrite of Route53 records" + type = bool + default = true +} + +variable "wait_for_validation" { + description = "Whether to wait for the validation to complete" + type = bool + default = true +} + +variable "certificate_transparency_logging_preference" { + description = "Specifies whether certificate details should be added to a certificate transparency log" + type = bool + default = true +} + +variable "domain_name" { + description = "A domain name for which the certificate should be issued" + type = string + default = "" +} + +variable "subject_alternative_names" { + description = "A list of domains that should be SANs in the issued certificate" + type = list(string) + default = [] +} + +variable "zone_name" { + description = "The Name of the zone to contain this record." + type = string +} + +variable "tags" { + description = "A mapping of tags to assign to the resource" + type = map(string) + default = {} +} + +variable "dns_ttl" { + description = "The TTL of DNS recursive resolvers to cache information about this record." + type = number + default = 120 +} diff --git a/versions.tf b/versions.tf index 1eb8ff7..2bc74be 100644 --- a/versions.tf +++ b/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "~> 3.1.0" + version = "~> 3.2" } aws = { source = "hashicorp/aws"