mirror of
https://github.com/jimeh/terraform-cloudflare-email.git
synced 2026-02-18 17:36:40 +00:00
feat(module): initial implementation
This commit is contained in:
20
LICENSE
Normal file
20
LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Jim Myhrberg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
4
Makefile
Normal file
4
Makefile
Normal file
@@ -0,0 +1,4 @@
|
||||
.PHONY: docs
|
||||
.SILENT: docs
|
||||
docs:
|
||||
terraform-docs markdown .
|
||||
103
README.md
Normal file
103
README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
<h1 align="center">
|
||||
terraform-cloudflare-email
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Terraform module to configure various email related DNS records on
|
||||
Cloudflare.
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/jimeh/terraform-cloudflare-email/releases">
|
||||
<img src="https://img.shields.io/github/v/tag/jimeh/terraform-cloudflare-email?label=release" alt="GitHub tag (latest SemVer)">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/terraform-cloudflare-email/issues">
|
||||
<img src="https://img.shields.io/github/issues-raw/jimeh/terraform-cloudflare-email.svg?style=flat&logo=github&logoColor=white" alt="GitHub issues">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/terraform-cloudflare-email/pulls">
|
||||
<img src="https://img.shields.io/github/issues-pr-raw/jimeh/terraform-cloudflare-email.svg?style=flat&logo=github&logoColor=white" alt="GitHub pull requests">
|
||||
</a>
|
||||
<a href="https://github.com/jimeh/terraform-cloudflare-email/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/jimeh/terraform-cloudflare-email.svg?style=flat" alt="License Status">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Module that configures various email related DNS records on Cloudflare,
|
||||
including serving a MTA-STS policy text file via Cloudflare Workers.
|
||||
|
||||
## Features
|
||||
|
||||
- Configure MX records.
|
||||
- Configure SPF record.
|
||||
- Configure DMARC record.
|
||||
- Configure SMTP TLS reporting record.
|
||||
- Configure MTA-STS record, generate `mta-sts.txt` policy file and serve it with
|
||||
a Cloudflare Worker on
|
||||
`https://mta-sts.<your-domain>/.well-known/mta-sts.txt`.
|
||||
- Configure domain key records (`<selector>._domainkey.<your-domain>`).
|
||||
|
||||
## Requirements
|
||||
|
||||
| Name | Version |
|
||||
| --------------------------------------------------------------------------- | ------------- |
|
||||
| <a name="requirement_cloudflare"></a> [cloudflare](#requirement_cloudflare) | >= 3.0, < 5.0 |
|
||||
|
||||
## Providers
|
||||
|
||||
| Name | Version |
|
||||
| --------------------------------------------------------------------- | ------------- |
|
||||
| <a name="provider_cloudflare"></a> [cloudflare](#provider_cloudflare) | >= 3.0, < 5.0 |
|
||||
|
||||
## Modules
|
||||
|
||||
No modules.
|
||||
|
||||
## Resources
|
||||
|
||||
| Name | Type |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| [cloudflare_record.dmarc](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_record.domainkeys](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_record.mta-sts-a](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_record.mta-sts-aaaa](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_record.mta_sts](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_record.mx](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_record.smtp_tls](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_record.spf](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record) | resource |
|
||||
| [cloudflare_worker_route.mta_sts_route](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/worker_route) | resource |
|
||||
| [cloudflare_worker_script.mta_sts](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/worker_script) | resource |
|
||||
| [cloudflare_workers_kv.mta_sts](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/workers_kv) | resource |
|
||||
| [cloudflare_workers_kv_namespace.mta_sts](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/workers_kv_namespace) | resource |
|
||||
| [cloudflare_zone.zone](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zone) | data source |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default | Required |
|
||||
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------- | :------: |
|
||||
| <a name="input_account_id"></a> [account_id](#input_account_id) | Cloudflare Account ID | `string` | n/a | yes |
|
||||
| <a name="input_dmarc_dkim_mode"></a> [dmarc_dkim_mode](#input_dmarc_dkim_mode) | The DMARC DKIM mode for alignment (options: `relaxed`, `strict`). | `string` | `"relaxed"` | no |
|
||||
| <a name="input_dmarc_fo"></a> [dmarc_fo](#input_dmarc_fo) | Failure reporting options for DMARC (characters: `0`, `1`, `d`, `s`, separated by `:`). | `string` | `"1:d:s"` | no |
|
||||
| <a name="input_dmarc_percent"></a> [dmarc_percent](#input_dmarc_percent) | Percentage of messages to apply the DMARC policy to (0-100). | `number` | `100` | no |
|
||||
| <a name="input_dmarc_policy"></a> [dmarc_policy](#input_dmarc_policy) | The DMARC policy to apply (options: `none`, `quarantine`, `reject`). | `string` | `"none"` | no |
|
||||
| <a name="input_dmarc_rua"></a> [dmarc_rua](#input_dmarc_rua) | Where aggregate DMARC reports about policy violations should be sent. | `list(string)` | n/a | yes |
|
||||
| <a name="input_dmarc_ruf"></a> [dmarc_ruf](#input_dmarc_ruf) | Where failure/forensic DMARC reports about policy violations should be sent. | `list(string)` | `[]` | no |
|
||||
| <a name="input_dmarc_spf_mode"></a> [dmarc_spf_mode](#input_dmarc_spf_mode) | The DMARC SPF mode for alignment (options: `relaxed`, `strict`). | `string` | `"relaxed"` | no |
|
||||
| <a name="input_dmarc_ttl"></a> [dmarc_ttl](#input_dmarc_ttl) | TTL for `_dmarc` DNS record. `1` is auto. Default is `1`. | `number` | `1` | no |
|
||||
| <a name="input_domainkeys"></a> [domainkeys](#input_domainkeys) | Map of domain keys with name, record type (`TXT` or `CNAME`), and value. | <pre>map(object({<br> type = string<br> value = string<br> }))</pre> | `{}` | no |
|
||||
| <a name="input_mta_sts_max_age"></a> [mta_sts_max_age](#input_mta_sts_max_age) | Maximum lifetime of the policy in seconds, up to 31557600, defaults to 604800 (1 week) | `number` | `604800` | no |
|
||||
| <a name="input_mta_sts_mode"></a> [mta_sts_mode](#input_mta_sts_mode) | MTA policy mode, https://tools.ietf.org/html/rfc8461#section-5 | `string` | `"testing"` | no |
|
||||
| <a name="input_mta_sts_mx"></a> [mta_sts_mx](#input_mta_sts_mx) | Additional permitted MX hosts for the MTA STS policy. | `list(string)` | `[]` | no |
|
||||
| <a name="input_mx"></a> [mx](#input_mx) | A map representing the MX records. Key is the priority and value is the mail server hostname. | `map(number)` | n/a | yes |
|
||||
| <a name="input_mx_subdomains"></a> [mx_subdomains](#input_mx_subdomains) | List of sub-domains to also apply MX records to. | `list(string)` | `[]` | no |
|
||||
| <a name="input_record_ttl"></a> [record_ttl](#input_record_ttl) | TTL for DNS records. `1` is auto. Default is `1`. | `number` | `1` | no |
|
||||
| <a name="input_spf_terms"></a> [spf_terms](#input_spf_terms) | List of SPF terms that should be included in the SPF TXT record. | `list(string)` | <pre>[<br> "mx",<br> "a",<br> "~all"<br>]</pre> | no |
|
||||
| <a name="input_tlsrpt_rua"></a> [tlsrpt_rua](#input_tlsrpt_rua) | Locations to which aggregate TLS SMTP reports about policy violations should be sent, either `mailto:` or `https:` schema. | `list(string)` | n/a | yes |
|
||||
| <a name="input_zone_id"></a> [zone_id](#input_zone_id) | Cloudflare Zone ID | `string` | n/a | yes |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
| ----------------------------------------------------------------------------------------- | ------------------------------- |
|
||||
| <a name="output_mta_sts_policy_url"></a> [mta_sts_policy_url](#output_mta_sts_policy_url) | URL to the MTA-STS policy file. |
|
||||
189
main.tf
Normal file
189
main.tf
Normal file
@@ -0,0 +1,189 @@
|
||||
#
|
||||
# General
|
||||
#
|
||||
|
||||
data "cloudflare_zone" "zone" {
|
||||
account_id = var.account_id
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
locals {
|
||||
zone_name = data.cloudflare_zone.zone.name
|
||||
}
|
||||
|
||||
#
|
||||
# MX
|
||||
#
|
||||
|
||||
locals {
|
||||
mx_sets = flatten([
|
||||
for name in concat([local.zone_name], var.mx_subdomains) : [
|
||||
for mx, priority in var.mx : {
|
||||
name = name
|
||||
mx = mx
|
||||
priority = priority
|
||||
} if name != ""
|
||||
]
|
||||
])
|
||||
mx_records = {
|
||||
for v in local.mx_sets :
|
||||
"${v.name == local.zone_name ? "" : "${v.name}:"}${v.mx}" => v
|
||||
}
|
||||
}
|
||||
|
||||
resource "cloudflare_record" "mx" {
|
||||
for_each = local.mx_records
|
||||
|
||||
name = each.value.name
|
||||
priority = each.value.priority
|
||||
proxied = false
|
||||
ttl = var.record_ttl
|
||||
type = "MX"
|
||||
value = each.value.mx
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
#
|
||||
# SPF
|
||||
#
|
||||
|
||||
resource "cloudflare_record" "spf" {
|
||||
name = local.zone_name
|
||||
proxied = false
|
||||
ttl = var.record_ttl
|
||||
type = "TXT"
|
||||
value = join(" ", concat(["v=spf1"], var.spf_terms))
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
#
|
||||
# TLS SMTP
|
||||
#
|
||||
|
||||
resource "cloudflare_record" "smtp_tls" {
|
||||
name = "_smtp._tls"
|
||||
type = "TXT"
|
||||
value = "v=TLSRPTv1; rua=${join(",", var.tlsrpt_rua)}"
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
#
|
||||
# MTA-STS
|
||||
#
|
||||
|
||||
locals {
|
||||
policy = templatefile("${path.module}/mta-sts.txt.tpl", {
|
||||
mode = var.mta_sts_mode
|
||||
max_age = var.mta_sts_max_age
|
||||
mx = sort(distinct(concat(keys(var.mx), var.mta_sts_mx)))
|
||||
})
|
||||
policy_sha = sha1(local.policy)
|
||||
}
|
||||
|
||||
resource "cloudflare_record" "mta-sts-a" {
|
||||
name = "mta-sts"
|
||||
proxied = true
|
||||
ttl = var.record_ttl
|
||||
type = "A"
|
||||
value = "192.0.2.1"
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
resource "cloudflare_record" "mta-sts-aaaa" {
|
||||
name = "mta-sts"
|
||||
proxied = true
|
||||
ttl = var.record_ttl
|
||||
type = "AAAA"
|
||||
value = "100::"
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
resource "cloudflare_record" "mta_sts" {
|
||||
name = "_mta-sts"
|
||||
ttl = var.record_ttl
|
||||
type = "TXT"
|
||||
value = "v=STSv1; id=${local.policy_sha}"
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
resource "cloudflare_workers_kv_namespace" "mta_sts" {
|
||||
title = "mta-sts.${local.zone_name}"
|
||||
account_id = var.account_id
|
||||
}
|
||||
|
||||
resource "cloudflare_workers_kv" "mta_sts" {
|
||||
namespace_id = cloudflare_workers_kv_namespace.mta_sts.id
|
||||
key = "mta-sts.txt"
|
||||
value = local.policy
|
||||
account_id = var.account_id
|
||||
}
|
||||
|
||||
resource "cloudflare_worker_script" "mta_sts" {
|
||||
name = "mta-sts-${replace(local.zone_name, "/[^A-Za-z0-9-]/", "-")}"
|
||||
content = file("${path.module}/mta-sts.js")
|
||||
account_id = var.account_id
|
||||
|
||||
kv_namespace_binding {
|
||||
name = "FILES"
|
||||
namespace_id = cloudflare_workers_kv_namespace.mta_sts.id
|
||||
}
|
||||
}
|
||||
|
||||
resource "cloudflare_worker_route" "mta_sts_route" {
|
||||
pattern = "mta-sts.${local.zone_name}/*"
|
||||
script_name = cloudflare_worker_script.mta_sts.name
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
#
|
||||
# DMARC
|
||||
#
|
||||
|
||||
locals {
|
||||
dmarc_modes = {
|
||||
"relaxed" = "r"
|
||||
"strict" = "s"
|
||||
}
|
||||
dmarc_values = {
|
||||
"rua" = join(",", compact(var.dmarc_rua))
|
||||
"ruf" = join(",", compact(var.dmarc_ruf))
|
||||
}
|
||||
}
|
||||
|
||||
resource "cloudflare_record" "dmarc" {
|
||||
name = "_dmarc"
|
||||
proxied = false
|
||||
ttl = floor(var.dmarc_ttl)
|
||||
type = "TXT"
|
||||
value = join(" ", flatten([
|
||||
"v=DMARC1;",
|
||||
"p=${var.dmarc_policy};",
|
||||
"pct=${floor(var.dmarc_percent)};",
|
||||
"aspf=${local.dmarc_modes[var.dmarc_spf_mode]};",
|
||||
"adkim=${local.dmarc_modes[var.dmarc_dkim_mode]};",
|
||||
[
|
||||
for k, v in local.dmarc_values :
|
||||
"${k}=${v};" if trimspace(v) != ""
|
||||
],
|
||||
[
|
||||
for v in [var.dmarc_fo] :
|
||||
"fo=${v};" if trimspace(local.dmarc_values["ruf"]) != ""
|
||||
],
|
||||
]))
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
|
||||
#
|
||||
# Domain Keys (DKIM)
|
||||
#
|
||||
|
||||
resource "cloudflare_record" "domainkeys" {
|
||||
for_each = var.domainkeys
|
||||
|
||||
name = "${each.key}._domainkey"
|
||||
proxied = false
|
||||
ttl = var.record_ttl
|
||||
type = upper(each.value.type)
|
||||
value = each.value.value
|
||||
zone_id = var.zone_id
|
||||
}
|
||||
17
mta-sts.js
Normal file
17
mta-sts.js
Normal file
@@ -0,0 +1,17 @@
|
||||
addEventListener('fetch', (event) => {
|
||||
event.respondWith(handleRequest(event.request));
|
||||
});
|
||||
|
||||
async function handleRequest(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === '/.well-known/mta-sts.txt') {
|
||||
const response = await FILES.get('mta-sts.txt');
|
||||
|
||||
if (response) {
|
||||
return new Response(response, { status: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
6
mta-sts.txt.tpl
Normal file
6
mta-sts.txt.tpl
Normal file
@@ -0,0 +1,6 @@
|
||||
version: STSv1
|
||||
mode: ${mode}
|
||||
max_age: ${max_age}
|
||||
%{for hostname in mx ~}
|
||||
mx: ${hostname}
|
||||
%{endfor ~}
|
||||
4
output.tf
Normal file
4
output.tf
Normal file
@@ -0,0 +1,4 @@
|
||||
output "mta_sts_policy_url" {
|
||||
value = "https://mta-sts.${local.zone_name}/.well-known/mta-sts.txt"
|
||||
description = "URL to the MTA-STS policy file."
|
||||
}
|
||||
245
variables.tf
Normal file
245
variables.tf
Normal file
@@ -0,0 +1,245 @@
|
||||
#
|
||||
# General
|
||||
#
|
||||
|
||||
variable "account_id" {
|
||||
type = string
|
||||
description = "Cloudflare Account ID"
|
||||
}
|
||||
|
||||
variable "zone_id" {
|
||||
type = string
|
||||
description = "Cloudflare Zone ID"
|
||||
}
|
||||
|
||||
variable "record_ttl" {
|
||||
type = number
|
||||
default = 1
|
||||
nullable = false
|
||||
description = "TTL for DNS records. `1` is auto. Default is `1`."
|
||||
}
|
||||
|
||||
#
|
||||
# MX
|
||||
#
|
||||
|
||||
variable "mx" {
|
||||
type = map(number)
|
||||
description = "A map representing the MX records. Key is the priority and value is the mail server hostname."
|
||||
|
||||
validation {
|
||||
condition = length(var.mx) > 0
|
||||
error_message = "At least one MX record is required."
|
||||
}
|
||||
}
|
||||
|
||||
variable "mx_subdomains" {
|
||||
type = list(string)
|
||||
description = "List of sub-domains to also apply MX records to."
|
||||
default = []
|
||||
}
|
||||
|
||||
#
|
||||
# SPF
|
||||
#
|
||||
|
||||
variable "spf_terms" {
|
||||
type = list(string)
|
||||
default = ["mx", "a", "~all"]
|
||||
description = "List of SPF terms that should be included in the SPF TXT record."
|
||||
}
|
||||
|
||||
#
|
||||
# TLS SMTP
|
||||
#
|
||||
|
||||
variable "tlsrpt_rua" {
|
||||
type = list(string)
|
||||
description = "Locations to which aggregate TLS SMTP reports about policy violations should be sent, either `mailto:` or `https:` schema."
|
||||
|
||||
validation {
|
||||
condition = length(var.tlsrpt_rua) != 0
|
||||
error_message = "At least one `mailto:` or `https:` endpoint provided."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = can([
|
||||
for loc in var.tlsrpt_rua : regex("^(mailto|https):", loc)
|
||||
])
|
||||
error_message = "Locations must start with either the `mailto:` or `https` schema."
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# MTA-STS
|
||||
#
|
||||
|
||||
variable "mta_sts_mode" {
|
||||
type = string
|
||||
default = "testing"
|
||||
description = "MTA policy mode, https://tools.ietf.org/html/rfc8461#section-5"
|
||||
|
||||
validation {
|
||||
condition = contains(["enforce", "testing", "none"], var.mta_sts_mode)
|
||||
error_message = "Must be `enforce`, `testing`, or `none`."
|
||||
}
|
||||
}
|
||||
|
||||
variable "mta_sts_max_age" {
|
||||
type = number
|
||||
default = 604800 # 1 week
|
||||
description = "Maximum lifetime of the policy in seconds, up to 31557600, defaults to 604800 (1 week)"
|
||||
|
||||
validation {
|
||||
condition = var.mta_sts_max_age >= 0
|
||||
error_message = "Policy validity time must be positive."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = var.mta_sts_max_age <= 31557600
|
||||
error_message = "Policy validity time must be less than 1 year (31557600 seconds)."
|
||||
}
|
||||
}
|
||||
|
||||
variable "mta_sts_mx" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "Additional permitted MX hosts for the MTA STS policy."
|
||||
}
|
||||
|
||||
#
|
||||
# DMARC
|
||||
#
|
||||
|
||||
variable "dmarc_policy" {
|
||||
type = string
|
||||
default = "none"
|
||||
description = "The DMARC policy to apply (options: `none`, `quarantine`, `reject`)."
|
||||
|
||||
validation {
|
||||
condition = contains(["none", "quarantine", "reject"], var.dmarc_policy)
|
||||
error_message = "Must be `none`, `quarantine`, or `reject`."
|
||||
}
|
||||
}
|
||||
|
||||
variable "dmarc_spf_mode" {
|
||||
type = string
|
||||
default = "relaxed"
|
||||
description = "The DMARC SPF mode for alignment (options: `relaxed`, `strict`)."
|
||||
|
||||
validation {
|
||||
condition = contains(["relaxed", "strict"], var.dmarc_spf_mode)
|
||||
error_message = "Must be `relaxed` or `strict`."
|
||||
}
|
||||
}
|
||||
|
||||
variable "dmarc_dkim_mode" {
|
||||
type = string
|
||||
default = "relaxed"
|
||||
description = "The DMARC DKIM mode for alignment (options: `relaxed`, `strict`)."
|
||||
|
||||
validation {
|
||||
condition = contains(["relaxed", "strict"], var.dmarc_dkim_mode)
|
||||
error_message = "Must be `relaxed` or `strict`."
|
||||
}
|
||||
}
|
||||
|
||||
variable "dmarc_percent" {
|
||||
type = number
|
||||
default = 100
|
||||
description = "Percentage of messages to apply the DMARC policy to (0-100)."
|
||||
|
||||
validation {
|
||||
condition = var.dmarc_percent > 0 && var.dmarc_percent <= 100
|
||||
error_message = "Must be between 0 and 100."
|
||||
}
|
||||
}
|
||||
|
||||
variable "dmarc_ttl" {
|
||||
type = number
|
||||
default = 1
|
||||
description = "TTL for `_dmarc` DNS record. `1` is auto. Default is `1`."
|
||||
|
||||
validation {
|
||||
condition = var.dmarc_ttl > 0 && var.dmarc_ttl <= 604800
|
||||
error_message = "Must be between 1 and 604800."
|
||||
}
|
||||
}
|
||||
|
||||
variable "dmarc_rua" {
|
||||
type = list(string)
|
||||
description = "Where aggregate DMARC reports about policy violations should be sent."
|
||||
|
||||
validation {
|
||||
condition = length(var.dmarc_rua) != 0
|
||||
error_message = "At least one `mailto:` endpoint must be provided."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = can([
|
||||
for loc in var.dmarc_rua : regex("^mailto:.+", loc)
|
||||
])
|
||||
error_message = "All must start with `mailto:`."
|
||||
}
|
||||
}
|
||||
|
||||
variable "dmarc_ruf" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "Where failure/forensic DMARC reports about policy violations should be sent."
|
||||
|
||||
validation {
|
||||
condition = can([
|
||||
for loc in var.dmarc_ruf : regex("^mailto:.+", loc)
|
||||
])
|
||||
error_message = "All must start with `mailto:`."
|
||||
}
|
||||
}
|
||||
|
||||
variable "dmarc_fo" {
|
||||
type = string
|
||||
default = "1:d:s"
|
||||
description = "Failure reporting options for DMARC (characters: `0`, `1`, `d`, `s`, separated by `:`)."
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for v in split(":", var.dmarc_fo) : contains(["0", "1", "d", "s"], v)
|
||||
])
|
||||
error_message = "Only `0`, `1`, `d`, and `s` are supported, separated by `:`."
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Domain Keys (DKIM)
|
||||
#
|
||||
|
||||
variable "domainkeys" {
|
||||
type = map(object({
|
||||
type = string
|
||||
value = string
|
||||
}))
|
||||
default = {}
|
||||
description = "Map of domain keys with name, record type (`TXT` or `CNAME`), and value."
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for name, dk in var.domainkeys : trimspace(name) != ""
|
||||
])
|
||||
error_message = "Domain key name cannot be empty."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for name, dk in var.domainkeys :
|
||||
contains(["TXT", "CNAME"], upper(dk.type))
|
||||
])
|
||||
error_message = "Domain key type must be `TXT` or `CNAME`."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for name, dk in var.domainkeys : trimspace(dk.value) != ""
|
||||
])
|
||||
error_message = "Domain key value cannot be empty."
|
||||
}
|
||||
}
|
||||
8
versions.tf
Normal file
8
versions.tf
Normal file
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = ">= 3.0, < 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user