Skip to content

Commit 7189cc3

Browse files
authored
Merge pull request #105 from answerdigital/cloudflare-dns
Cloudflare DNS module
2 parents 0d124fa + 3752d97 commit 7189cc3

File tree

7 files changed

+223
-0
lines changed

7 files changed

+223
-0
lines changed

.github/dependabot.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,12 @@ updates:
6868
update-types:
6969
- "version-update:semver-patch"
7070
- "version-update:semver-minor"
71+
- package-ecosystem: terraform
72+
directory: /modules/cloudflare/dns
73+
schedule:
74+
interval: daily
75+
ignore:
76+
- dependency-name: "*"
77+
update-types:
78+
- "version-update:semver-patch"
79+
- "version-update:semver-minor"

modules/cloudflare/dns/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Cloudflare DNS Module
2+
3+
This Terraform module manages a zone and multiple records in Cloudflare.
4+
The module also simplifies a few boilerplate records at the apex for security purposes.
5+
6+
<!-- BEGIN_TF_DOCS -->
7+
## Requirements
8+
9+
| Name | Version |
10+
|------|---------|
11+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.3 |
12+
| <a name="requirement_cloudflare"></a> [cloudflare](#requirement\_cloudflare) | >= 4.39.0 |
13+
14+
## Providers
15+
16+
| Name | Version |
17+
|------|---------|
18+
| <a name="provider_cloudflare"></a> [cloudflare](#provider\_cloudflare) | >= 4.39.0 |
19+
20+
## Resources
21+
22+
| Name | Type |
23+
|------|------|
24+
| [cloudflare_record.apex_txt](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
25+
| [cloudflare_record.caa](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
26+
| [cloudflare_record.dns](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
27+
| [cloudflare_zone.dns](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone) | resource |
28+
| [cloudflare_zones.lookup](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zones) | data source |
29+
30+
## Inputs
31+
32+
| Name | Description | Type | Default | Required |
33+
|------|-------------|------|---------|:--------:|
34+
| <a name="input_account_id"></a> [account\_id](#input\_account\_id) | The ID of the Cloudflare account. | `string` | n/a | yes |
35+
| <a name="input_apex_txt"></a> [apex\_txt](#input\_apex\_txt) | List of TXT records to be added at the apex. | `list(string)` | `[]` | no |
36+
| <a name="input_caa_issuers"></a> [caa\_issuers](#input\_caa\_issuers) | List of CAs that can issue certificates. | `list(string)` | <pre>[<br/> "letsencrypt.org"<br/>]</pre> | no |
37+
| <a name="input_create_zone"></a> [create\_zone](#input\_create\_zone) | Whether to create the zone. Defaults to `true`. | `bool` | `true` | no |
38+
| <a name="input_default_ttl"></a> [default\_ttl](#input\_default\_ttl) | Default TTL for DNS records. Defaults to 1, which means “automatic”. | `number` | `1` | no |
39+
| <a name="input_domain"></a> [domain](#input\_domain) | The top-level domain name to hold the records. | `string` | n/a | yes |
40+
| <a name="input_records"></a> [records](#input\_records) | List of DNS records for the domain.<br/><br/> • `name` - (Optional) The name of the record. Defaults to "@" (i.e. an apex record).<br/> • `ttl` - (Optional) The TTL of the record. Defaults to `default_ttl`.<br/> • `type` - (Required) The record type.<br/> • `content` - (Required) The content of the record.<br/> • `priority` - (Optional) The priority of the record.<br/> • `proxied` - (Optional) Whether to use Cloudflare’s origin protection. Defaults to `false`. | <pre>map(object({<br/> name = optional(string, "@")<br/> ttl = optional(number)<br/> type = string<br/> content = string<br/> priority = optional(number)<br/> proxied = optional(bool, false)<br/> }))</pre> | n/a | yes |
41+
| <a name="input_security_contact"></a> [security\_contact](#input\_security\_contact) | Security contact for the domain. Defaults to 'security@DOMAIN', where `DOMAIN` is the top-level domain name. | `string` | `null` | no |
42+
| <a name="input_spf"></a> [spf](#input\_spf) | List of SPF directives for the domain. | `list(string)` | `[]` | no |
43+
44+
## Outputs
45+
46+
| Name | Description |
47+
|------|-------------|
48+
| <a name="output_zone_id"></a> [zone\_id](#output\_zone\_id) | The Zone ID. |
49+
<!-- END_TF_DOCS -->
50+
51+
# Example Usage
52+
53+
Below is a simple example for an example.com zone with a single subdomain record.
54+
55+
```terraform
56+
module "example_com" {
57+
source = "github.com/answerdigital/terraform-modules//modules/cloudflare/dns?ref=v4"
58+
59+
domain = "example.com"
60+
records = {
61+
www = {
62+
name = "www"
63+
type = "A"
64+
value = "1.2.3.4"
65+
}
66+
}
67+
spf = [
68+
"include:_spf.google.com"
69+
]
70+
}
71+
```
72+
73+
By default, the module will create the zone as well as the records. If the zone
74+
was created elsewhere, set the `create_zone` flag to `false`.

modules/cloudflare/dns/dns.tf

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
data "cloudflare_zones" "lookup" {
2+
count = var.create_zone ? 0 : 1
3+
4+
filter {
5+
name = var.domain
6+
account_id = var.account_id
7+
}
8+
}
9+
10+
resource "cloudflare_zone" "dns" {
11+
count = var.create_zone ? 1 : 0
12+
zone = var.domain
13+
account_id = var.account_id
14+
}
15+
16+
resource "cloudflare_record" "dns" {
17+
for_each = var.records
18+
19+
zone_id = local.zone_id
20+
name = each.value.name
21+
ttl = each.value.ttl != null ? each.value.ttl : var.default_ttl
22+
type = each.value.type
23+
content = each.value.content
24+
priority = each.value.priority
25+
proxied = each.value.proxied
26+
}
27+
28+
resource "cloudflare_record" "apex_txt" {
29+
for_each = toset(concat(var.apex_txt, [
30+
format("security_contact=mailto:%s", local.security_contact),
31+
replace("v=spf1 ${join(" ", var.spf)} -all", " ", " ")
32+
]))
33+
34+
zone_id = local.zone_id
35+
name = "@"
36+
ttl = var.default_ttl
37+
type = "TXT"
38+
content = each.value
39+
proxied = false
40+
}
41+
42+
resource "cloudflare_record" "caa" {
43+
for_each = toset(var.caa_issuers)
44+
zone_id = local.zone_id
45+
name = "@"
46+
type = "CAA"
47+
48+
data {
49+
flags = "0"
50+
tag = "issue"
51+
value = each.value
52+
}
53+
}

modules/cloudflare/dns/locals.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
locals {
2+
zone_id = var.create_zone ? cloudflare_zone.dns[0].id : data.cloudflare_zones.lookup[0].id
3+
4+
security_contact = var.security_contact != null ? var.security_contact : format("security@%s", var.domain)
5+
}

modules/cloudflare/dns/outputs.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
output "zone_id" {
2+
value = local.zone_id
3+
description = "The Zone ID."
4+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
variable "domain" {
2+
description = "The top-level domain name to hold the records."
3+
type = string
4+
}
5+
6+
variable "account_id" {
7+
description = "The ID of the Cloudflare account."
8+
type = string
9+
}
10+
11+
variable "create_zone" {
12+
description = "Whether to create the zone. Defaults to `true`."
13+
type = bool
14+
default = true
15+
}
16+
17+
variable "records" {
18+
description = <<EOT
19+
List of DNS records for the domain.
20+
21+
• `name` - (Optional) The name of the record. Defaults to "@" (i.e. an apex record).
22+
• `ttl` - (Optional) The TTL of the record. Defaults to `default_ttl`.
23+
• `type` - (Required) The record type.
24+
• `content` - (Required) The content of the record.
25+
• `priority` - (Optional) The priority of the record.
26+
• `proxied` - (Optional) Whether to use Cloudflare’s origin protection. Defaults to `false`.
27+
EOT
28+
type = map(object({
29+
name = optional(string, "@")
30+
ttl = optional(number)
31+
type = string
32+
content = string
33+
priority = optional(number)
34+
proxied = optional(bool, false)
35+
}))
36+
}
37+
38+
variable "default_ttl" {
39+
description = "Default TTL for DNS records. Defaults to 1, which means “automatic”."
40+
type = number
41+
default = 1
42+
}
43+
44+
variable "apex_txt" {
45+
description = "List of TXT records to be added at the apex."
46+
type = list(string)
47+
default = []
48+
}
49+
50+
variable "security_contact" {
51+
description = "Security contact for the domain. Defaults to 'security@DOMAIN', where `DOMAIN` is the top-level domain name."
52+
type = string
53+
default = null
54+
}
55+
56+
variable "spf" {
57+
description = "List of SPF directives for the domain."
58+
type = list(string)
59+
default = []
60+
}
61+
62+
variable "caa_issuers" {
63+
description = "List of CAs that can issue certificates."
64+
type = list(string)
65+
default = [
66+
"letsencrypt.org",
67+
]
68+
}

modules/cloudflare/dns/versions.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
terraform {
2+
required_version = "~> 1.3"
3+
4+
required_providers {
5+
cloudflare = {
6+
source = "cloudflare/cloudflare"
7+
version = ">= 4.39.0"
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)