Skip to content

Commit 2fda5ed

Browse files
committed
feat: add DMARC, SPF, and null MX record support
1 parent 5d128e5 commit 2fda5ed

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

terragrunt/modules/route53-records/main.tf

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
locals {
2+
dmarc_tags = compact([
3+
"v=DMARC1",
4+
"p=${var.dmarc_policy}",
5+
"pct=${var.dmarc_pct}",
6+
var.dmarc_subdomain_policy == null ? null : "sp=${var.dmarc_subdomain_policy}",
7+
var.dmarc_aggregate_report_uri == null ? null : "rua=mailto:${var.dmarc_aggregate_report_uri}",
8+
var.dmarc_forensic_report_uri == null ? null : "ruf=mailto:${var.dmarc_forensic_report_uri}",
9+
var.dmarc_alignment_dkim == null ? null : "adkim=${var.dmarc_alignment_dkim}",
10+
var.dmarc_alignment_spf == null ? null : "aspf=${var.dmarc_alignment_spf}",
11+
])
12+
dmarc_record = join("; ", local.dmarc_tags)
13+
}
114
resource "aws_route53_record" "cloudfront" {
215
zone_id = var.route53_zone_id
316
name = var.domain_name
@@ -47,3 +60,34 @@ resource "aws_route53_record" "www_ipv6" {
4760
evaluate_target_health = false
4861
}
4962
}
63+
64+
65+
resource "aws_route53_record" "spf" {
66+
count = var.enable_spf_record ? 1 : 0
67+
zone_id = var.route53_zone_id
68+
name = var.domain_name
69+
type = "TXT"
70+
ttl = var.txt_ttl
71+
72+
records = [var.spf_record_value]
73+
}
74+
75+
resource "aws_route53_record" "null_mx" {
76+
count = var.enable_null_mx_record ? 1 : 0
77+
zone_id = var.route53_zone_id
78+
name = var.domain_name
79+
type = "MX"
80+
ttl = var.txt_ttl
81+
82+
records = [var.null_mx_record_value]
83+
}
84+
85+
resource "aws_route53_record" "dmarc" {
86+
count = var.enable_dmarc_record ? 1 : 0
87+
zone_id = var.route53_zone_id
88+
name = "_dmarc.${var.domain_name}"
89+
type = "TXT"
90+
ttl = var.txt_ttl
91+
92+
records = [local.dmarc_record]
93+
}

terragrunt/modules/route53-records/variables.tf

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,109 @@ variable "create_www_subdomain" {
2525
default = true
2626
}
2727

28+
variable "txt_ttl" {
29+
description = "TTL in seconds for TXT records managed by this module"
30+
type = number
31+
default = 3600
32+
}
33+
34+
variable "enable_spf_record" {
35+
description = "Whether to create an SPF TXT record at the apex domain"
36+
type = bool
37+
default = true
38+
}
39+
40+
variable "spf_record_value" {
41+
description = "SPF TXT value to publish at apex (use v=spf1 -all for non-sending domains)"
42+
type = string
43+
default = "v=spf1 -all"
44+
}
45+
46+
variable "enable_null_mx_record" {
47+
description = "Whether to publish a null MX record (RFC7505) indicating the domain does not accept email"
48+
type = bool
49+
default = true
50+
}
51+
52+
variable "null_mx_record_value" {
53+
description = "MX record value for null MX (RFC7505); keep as '0 .' unless you need normal mail routing"
54+
type = string
55+
default = "0 ."
56+
}
57+
58+
variable "enable_dmarc_record" {
59+
description = "Whether to create a DMARC TXT record"
60+
type = bool
61+
default = true
62+
}
63+
64+
variable "dmarc_policy" {
65+
description = "DMARC policy for the organizational domain (none, quarantine, reject)"
66+
type = string
67+
default = "reject"
68+
69+
validation {
70+
condition = contains(["none", "quarantine", "reject"], var.dmarc_policy)
71+
error_message = "dmarc_policy must be one of: none, quarantine, reject."
72+
}
73+
}
74+
75+
variable "dmarc_subdomain_policy" {
76+
description = "Optional DMARC subdomain policy (sp tag). Set null to omit"
77+
type = string
78+
default = null
79+
80+
validation {
81+
condition = var.dmarc_subdomain_policy == null || contains(["none", "quarantine", "reject"], var.dmarc_subdomain_policy)
82+
error_message = "dmarc_subdomain_policy must be null or one of: none, quarantine, reject."
83+
}
84+
}
85+
86+
variable "dmarc_pct" {
87+
description = "DMARC pct tag value (0-100)"
88+
type = number
89+
default = 100
90+
91+
validation {
92+
condition = var.dmarc_pct >= 0 && var.dmarc_pct <= 100
93+
error_message = "dmarc_pct must be between 0 and 100."
94+
}
95+
}
96+
97+
variable "dmarc_aggregate_report_uri" {
98+
description = "Optional DMARC aggregate report mailbox, without mailto: prefix"
99+
type = string
100+
default = null
101+
}
102+
103+
variable "dmarc_forensic_report_uri" {
104+
description = "Optional DMARC forensic report mailbox, without mailto: prefix"
105+
type = string
106+
default = null
107+
}
108+
109+
variable "dmarc_alignment_dkim" {
110+
description = "Optional DMARC DKIM alignment mode (r or s). Set null to omit"
111+
type = string
112+
default = null
113+
114+
validation {
115+
condition = var.dmarc_alignment_dkim == null || contains(["r", "s"], var.dmarc_alignment_dkim)
116+
error_message = "dmarc_alignment_dkim must be null, r, or s."
117+
}
118+
}
119+
120+
variable "dmarc_alignment_spf" {
121+
description = "Optional DMARC SPF alignment mode (r or s). Set null to omit"
122+
type = string
123+
default = null
124+
125+
validation {
126+
condition = var.dmarc_alignment_spf == null || contains(["r", "s"], var.dmarc_alignment_spf)
127+
error_message = "dmarc_alignment_spf must be null, r, or s."
128+
}
129+
}
130+
28131
variable "tags" {
29132
description = "Common tags for all resources"
30133
type = map(string)

0 commit comments

Comments
 (0)