📧
TerraformでSESv2の複数ドメイン検証とメール認証(SPF/DKIM/DMARC)対応
はじめに
- 勉強と備忘録を兼ねてアウトプットしています
- 本記事では複数ドメインのSES認証設定を扱います
- 単一ドメインのみの場合は、より簡単な実装も可能です
前提条件
- Route 53でホストゾーンが作成済みであること
構成
- ドメイン認証に必要なリソースを ses モジュールとして作成します。
- Route 53 のホストゾーンは、別途 route53 モジュールで作成されていることを前提とします。
.
├── main.tf # 各モジュールを呼び出すファイル
└── modules/
├── route53/ # 事前準備: Route 53 ホストゾーンを作成するモジュール
└── ses/ # 本記事で作成: SES 認証レコード用モジュール
1. モジュールの作成
variables.tf
# SESで設定するドメイン
variable "email_identities" {
type = map(object({
email_identity = string
}))
}
# レコードを作成するRoute 53ゾーンのID
variable "route53_zone_id" {
type = string
}
main.tf
# SESv2 Email Identity を作成し、Easy DKIM を有効化
resource "aws_sesv2_email_identity" "email_identities" {
for_each = var.email_identities
email_identity = each.value.email_identity
}
# ドメインを抽出(メールアドレスではなくドメインのみ)
locals {
domain_identities = {
for key, identity in var.email_identities : key => identity
if !can(regex("@", identity.email_identity)) # Only for domains, not email addresses
}
# DKIMレコード用のデータ構造を作成
dkim_records = flatten([
for domain_key, domain_value in local.domain_identities : [
for i in range(3) : {
key = "${domain_key}-${i}"
domain_key = domain_key
domain = domain_value.email_identity
index = i
token = aws_sesv2_email_identity.email_identities[domain_key].dkim_signing_attributes[0].tokens[i]
}
]
])
}
# DKIM Records using SESv2 Easy DKIM(複数ドメイン対応)
resource "aws_route53_record" "dkim_verification" {
for_each = { for record in local.dkim_records : record.key => record }
zone_id = var.route53_zone_id
name = "${each.value.token}._domainkey.${each.value.domain}"
type = "CNAME"
ttl = 600
records = ["${each.value.token}.dkim.amazonses.com"]
depends_on = [aws_sesv2_email_identity.email_identities]
}
# SPF Records (TXT record for each domain)
resource "aws_route53_record" "spf" {
for_each = local.domain_identities
zone_id = var.route53_zone_id
name = each.value.email_identity
type = "TXT"
ttl = 600
records = ["v=spf1 include:amazonses.com ~all"]
}
# DMARC Records (TXT record for each domain)
# p=none: 監視モードでメール配信への影響なし。段階的にp=quarantine、p=rejectに変更可能
resource "aws_route53_record" "dmarc" {
for_each = local.domain_identities
zone_id = var.route53_zone_id
name = "_dmarc.${each.value.email_identity}"
type = "TXT"
ttl = 600
records = ["v=DMARC1; p=none; rua=mailto:dmarc@${each.value.email_identity};"]
}
2. モジュールの利用
- ルートの main.tf で route53 モジュールと ses モジュールを連携させます。
- route53 モジュールの出力(zone_id)を ses モジュールへ渡します。
# 1. 事前に Route 53 ホストゾーンを作成
module "route53" {
source = "./modules/route53"
domain_name = "example.com"
# ... その他必要な変数
}
# 2. SES モジュールを呼び出し(複数ドメイン対応)
module "ses_auth" {
source = "./modules/ses"
route53_zone_id = module.route53.zone_id
email_identities = {
"example-com" = {
email_identity = "example.com"
}
"test-example-com" = {
email_identity = "test.example.com"
}
}
}
3. 実行と確認
AWS コンソールで確認
サービス | 確認項目 |
---|---|
SES | 各ドメインのIDステータス が「検証済み」 |
Route 53 | 各ドメインのDKIM(CNAME×3)、SPF(TXT)、DMARC(TXT)レコードが作成されている |
メールヘッダーで確認
各ドメインからテストメールを送信し、ヘッダーに spf=pass, dkim=pass, dmarc=pass が含まれていることを確認。
Authentication-Results: spf=pass smtp.mailfrom=ap-northeast-1.amazonses.com;
dkim=pass header.d=example.com;
dkim=pass header.d=amazonses.com;
dmarc=pass action=none header.from=example.com;
compauth=pass reason=100
4. 複数ドメイン対応の実装ポイント
DKIMレコード作成の課題と解決
複数ドメインそれぞれに3つのDKIMレコードを作成する必要があるため、以下のような実装にしました:
# ポイント1: flatten関数でネストしたリストを平坦化
locals {
dkim_records = flatten([
for domain_key, domain_value in local.domain_identities : [
for i in range(3) : {
key = "${domain_key}-${i}" # 一意なキー生成
domain_key = domain_key
domain = domain_value.email_identity
index = i
token = aws_sesv2_email_identity.email_identities[domain_key].dkim_signing_attributes[0].tokens[i]
}
]
])
}
# ポイント2: for_eachで各レコードを作成
resource "aws_route53_record" "dkim_verification" {
for_each = { for record in local.dkim_records : record.key => record }
# ...
}
失敗例:countとfor_eachの混在
初期実装時に以下のような方法を試みましたが、Terraformではcountとfor_eachの混在ができないためエラーになりました:
# 失敗例
resource "aws_route53_record" "dkim_verification" {
for_each = local.domain_identities
count = 3 # これはエラーになる
# ...
}
MAIL FORM はまた次回にでも設定していきます...
Discussion