From 4f0f613e80602d354ea3d19b422f2164d3120ff8 Mon Sep 17 00:00:00 2001 From: Script47 Date: Sat, 7 Mar 2026 07:21:43 +0000 Subject: [PATCH] feat(ses-domain-identity): add spf and refactor --- ses-domain-identity/README.md | 14 ++++++-- ses-domain-identity/data.tf | 4 --- ses-domain-identity/outputs.tf | 55 ++++++++++++++++++++++++++++++++ ses-domain-identity/route53.tf | 41 ++++++++++++++++++++---- ses-domain-identity/variables.tf | 24 +++++++++++--- 5 files changed, 121 insertions(+), 17 deletions(-) delete mode 100644 ses-domain-identity/data.tf create mode 100644 ses-domain-identity/outputs.tf diff --git a/ses-domain-identity/README.md b/ses-domain-identity/README.md index db8ceff..0447135 100644 --- a/ses-domain-identity/README.md +++ b/ses-domain-identity/README.md @@ -4,6 +4,7 @@ This module allows you to setup domain identification for SES with the following - Domain verification - DKIM +- SPF - DMARC ## Usage @@ -12,10 +13,10 @@ See `variables.tf` for the full argument reference. ```hcl module "ses_doamin_identity" { - source = "github.com/script47/aws-tf-modules/ses-domain-identity" + source = "github.com/script47/aws-tf-modules/ses-domain-identity" - hosted_zone = "my-hosted-zone" - domain = "example.org" + zone_id = "zone-id" + domain = "example.org" domain_verification = { ttl = 600 @@ -26,6 +27,13 @@ module "ses_doamin_identity" { ttl = 600 } + spf = { + enabled = true + includes = ["amazonses.com"] # amazonses.com is default + all = "~all" + ttl = 600 + } + dmarc = { enabled = true policy = "v=DMARC1; p=reject;" diff --git a/ses-domain-identity/data.tf b/ses-domain-identity/data.tf deleted file mode 100644 index f58b9c8..0000000 --- a/ses-domain-identity/data.tf +++ /dev/null @@ -1,4 +0,0 @@ -data "aws_route53_zone" "hosted_zone" { - name = var.hosted_zone - private_zone = false -} diff --git a/ses-domain-identity/outputs.tf b/ses-domain-identity/outputs.tf new file mode 100644 index 0000000..546a764 --- /dev/null +++ b/ses-domain-identity/outputs.tf @@ -0,0 +1,55 @@ +output "dkim" { + value = var.dkim.enabled ? [ + for token in aws_ses_domain_dkim.this[0].dkim_tokens : { + type = "CNAME" + name = "${token}._domainkey.${var.domain}" + value = "${token}.dkim.amazonses.com" + ttl = var.dkim.ttl + } + ] : [] +} +output "dns" { + value = { + domain = var.domain + records = concat( + [ + { + type = "TXT" + name = "_amazonses.${var.domain}" + value = aws_ses_domain_identity.this.verification_token + ttl = var.domain_verification.ttl + } + ], + + var.dkim.enabled ? [ + for token in aws_ses_domain_dkim.this[0].dkim_tokens : { + type = "CNAME" + name = "${token}._domainkey.${var.domain}" + value = "${token}.dkim.amazonses.com" + ttl = var.dkim.ttl + } + ] : [], + + var.spf.enabled ? [{ + type = "TXT" + name = var.domain + value = join( + " ", + concat( + ["v=spf1"], + [for d in var.spf.includes : "include:${d}"], + [var.spf.all] + ) + ) + ttl = var.spf.ttl + }] : [], + + var.dmarc.enabled ? [{ + type = "TXT" + name = "_dmarc.${var.domain}" + value = var.dmarc.policy + ttl = var.dmarc.ttl + }] : [] + ) + } +} diff --git a/ses-domain-identity/route53.tf b/ses-domain-identity/route53.tf index cbd32b4..a0dff2d 100644 --- a/ses-domain-identity/route53.tf +++ b/ses-domain-identity/route53.tf @@ -1,5 +1,19 @@ +locals { + configure_dns = var.zone_id != null + spf_record = join( + " ", + concat( + ["v=spf1"], + [for d in var.spf.includes : "include:${d}"], + [var.spf.all] + ) + ) +} + resource "aws_route53_record" "domain_verification" { - zone_id = data.aws_route53_zone.hosted_zone.zone_id + count = local.configure_dns ? 1 : 0 + + zone_id = var.zone_id name = "_amazonses.${var.domain}" type = "TXT" ttl = var.domain_verification.ttl @@ -7,19 +21,34 @@ resource "aws_route53_record" "domain_verification" { } resource "aws_route53_record" "dkim" { - count = var.dkim.enabled ? 3 : 0 - zone_id = data.aws_route53_zone.hosted_zone.zone_id + count = local.configure_dns && var.dkim.enabled ? 3 : 0 + + zone_id = var.zone_id name = "${aws_ses_domain_dkim.this[0].dkim_tokens[count.index]}._domainkey.${var.domain}" type = "CNAME" ttl = var.dkim.ttl records = ["${aws_ses_domain_dkim.this[0].dkim_tokens[count.index]}.dkim.amazonses.com"] } +resource "aws_route53_record" "spf" { + count = local.configure_dns && var.spf.enabled ? 1 : 0 + + zone_id = var.zone_id + name = var.domain + type = "TXT" + ttl = var.spf.ttl + + records = [ + local.spf_record + ] +} + resource "aws_route53_record" "dmarc" { - count = var.dmarc.enabled ? 1 : 0 - zone_id = data.aws_route53_zone.hosted_zone.zone_id + count = local.configure_dns && var.dmarc.enabled ? 1 : 0 + + zone_id = var.zone_id name = "_dmarc.${var.domain}" type = "TXT" ttl = var.dmarc.ttl records = [var.dmarc.policy] -} \ No newline at end of file +} diff --git a/ses-domain-identity/variables.tf b/ses-domain-identity/variables.tf index 819a7ed..3133f77 100644 --- a/ses-domain-identity/variables.tf +++ b/ses-domain-identity/variables.tf @@ -1,6 +1,7 @@ -variable "hosted_zone" { +variable "zone_id" { type = string - description = "The name of the hosted zone" + description = "The ID of the hosted zone" + default = null } variable "domain" { @@ -18,16 +19,31 @@ variable "domain_verification" { variable "dkim" { type = object({ enabled = optional(bool, true) - ttl = optional(number, 600) + ttl = optional(number, 600) + }) + default = {} +} + +variable "spf" { + type = object({ + enabled = optional(bool, false) + includes = optional(list(string), ["amazonses.com"]) + all = optional(string, "~all") + ttl = optional(number, 600) }) default = {} + + validation { + condition = contains(["~all", "-all"], var.spf.all) + error_message = "spf.all must be one of ~all or -all." + } } variable "dmarc" { type = object({ enabled = optional(bool, false) policy = optional(string, "v=DMARC1; p=reject;") - ttl = optional(number, 600) + ttl = optional(number, 600) }) default = {} }