├── test ├── .gitignore ├── vars.auto.tfvars.example ├── variables.tf └── main.tf ├── outputs.tf ├── readme.md ├── variables.tf └── main.tf /test/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform* 2 | terraform.tfstate* 3 | vars.auto.tfvars 4 | -------------------------------------------------------------------------------- /test/vars.auto.tfvars.example: -------------------------------------------------------------------------------- 1 | aws_region = 2 | email = 3 | api_key = 4 | account_id = 5 | cloudflare_zone = 6 | domain_name = 7 | subject_alternative_names = [] 8 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "arn" { 2 | description = "The ARN of the certificate" 3 | value = var.wait_for_validation ? aws_acm_certificate_validation.validation.certificate_arn : aws_acm_certificate.cert.arn 4 | } 5 | -------------------------------------------------------------------------------- /test/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | type = string 3 | } 4 | 5 | variable "email" { 6 | type = string 7 | } 8 | 9 | variable "api_key" { 10 | type = string 11 | } 12 | 13 | variable "account_id" { 14 | type = string 15 | } 16 | 17 | variable "cloudflare_zone" { 18 | type = string 19 | } 20 | 21 | variable "domain_name" { 22 | type = string 23 | } 24 | variable "subject_alternative_names" { 25 | type = list 26 | } 27 | -------------------------------------------------------------------------------- /test/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "local" {} 3 | } 4 | 5 | provider "aws" { 6 | region = var.aws_region 7 | } 8 | 9 | provider "cloudflare" { 10 | email = var.email 11 | api_key = var.api_key 12 | account_id = var.account_id 13 | } 14 | 15 | module "cert" { 16 | source = "../" 17 | 18 | cloudflare_zone = var.cloudflare_zone 19 | domain_name = var.domain_name 20 | subject_alternative_names = var.subject_alternative_names 21 | 22 | tags = { 23 | terraform = "true" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # acm-cloudflare 2 | Terraform module to provide an ACM certificate validated by a Cloudflare DNS challenge 3 | 4 | 5 | ## Variables 6 | | Name | Description | Default | 7 | |---|---|---| 8 | | `cloudflare_zone` | Cloudflare zone ID | | 9 | | `domain_name` | Domain name for which the certificate should be issued | | 10 | | `subject_alternative_names` | (Optional) List of domain names that should be SANs in the issued certificate | `[]` | 11 | | `tags` | (Optional) A map of tags to assign to the certificate resource | `{}` | 12 | | `wait_for_validation` | (Optional) Whether to wait for the certificate to become valid | `true` | 13 | 14 | 15 | ## Outputs 16 | | Name | Description | 17 | |---|---| 18 | | `arn` | The ARN of the certificate | 19 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "cloudflare_zone" { 2 | description = "Cloudflare zone ID" 3 | type = string 4 | } 5 | 6 | variable "domain_name" { 7 | description = "Domain name for which the certificate should be issued" 8 | type = string 9 | } 10 | 11 | variable "subject_alternative_names" { 12 | description = "List of domain names that should be SANs in the issued certificate" 13 | type = list(string) 14 | default = [] 15 | } 16 | 17 | variable "tags" { 18 | description = "A map of tags to assign to the certificate resource" 19 | type = map(string) 20 | default = {} 21 | } 22 | 23 | variable "wait_for_validation" { 24 | description = "Whether to wait for the certificate to become valid" 25 | type = bool 26 | default = true 27 | } 28 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | subject_alternative_names = distinct( 3 | [for s in var.subject_alternative_names : s if s != var.domain_name] 4 | ) 5 | } 6 | 7 | resource "aws_acm_certificate" "cert" { 8 | validation_method = "DNS" 9 | domain_name = var.domain_name 10 | subject_alternative_names = local.subject_alternative_names 11 | } 12 | 13 | resource "aws_acm_certificate_validation" "validation" { 14 | certificate_arn = aws_acm_certificate.cert.arn 15 | validation_record_fqdns = [for record in cloudflare_record.dns_records : record.hostname] 16 | } 17 | 18 | resource "cloudflare_record" "dns_records" { 19 | for_each = { 20 | for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => { 21 | name = dvo.resource_record_name 22 | type = dvo.resource_record_type 23 | 24 | # aws asks for the record with a trailing dot, but cloudflare removes it 25 | # this makes terraform want to constantly recreate the dns record 26 | record = trim(dvo.resource_record_value, ".") 27 | } 28 | } 29 | 30 | zone_id = var.cloudflare_zone 31 | name = each.value.name 32 | value = each.value.record 33 | type = each.value.type 34 | ttl = 60 35 | } 36 | --------------------------------------------------------------------------------