独自ドメインのGmailとAmazon SESをTerraformにて設定する

7 min読了の目安(約6300字TECH技術記事

SESによるメール自動送信を行っていたドメインにGmailを追加する機会があったので、備忘録として残します✍️

記念すべきzenn.devでの初記事になります🎂

考え方

今回対応するまでちゃんと理解できていなかったことなのですが、
SESでのメール送信に際して必要となるのは、ドメインを所有していることの検証と、有効なメール送信元のひとつとしてAWSが用意しているサーバを指定(SPF)することであり、
ドメインに対するメールを受信した時の挙動については特に指定していない状態になります

なので、メール送信先としてドメインが指定された場合にGmailのサーバを利用(MX)する…という設定と、有効なメール送信元にGmailのサーバを追加(SPF)してあげることができればOK、ということになるものと思います

加えて、SES/Gmailそれぞれのサーバから送信されたメールがなりすまし/改竄されていないことを示すレコード(DKIM)と、
なりすまし/改竄が検出された際の挙動を受信者側に表明するレコード(DMARC)を設定できればより信頼性を高くメールが送受信できます

ということで、以下のような構成となるようにRoute53へレコードを登録できるとよさそうです
(と、ここまで書いてちょっと自信がないです…間違ってたらそっと教えてください🙄)

外観図

あと、オマケの内容として、
独自ドメインのGmailは http://mail.example.com/ の形式でアクセスできるように設定できる…ということだったので、
今回は internal.example.com という社内向けのホストゾーンを別途作成し、そちらに登録することとしました

作ったもの

関係のある部分のみ抜粋して、tfファイルの内容を記載しました
簡便のため幾つかの箇所でリテラルを設定していますが、実運用時は適宜tfvarsで定義してもらえるとよいものと思います

なお、有志によりGsuiteのプロバイダも公開されているようでしたが、
管理側の立場でGsuiteを使うのが初めてだったということもあり、今回はAWSのプロバイダから指定できるもののみをコード化の範囲としました

各項目を設定する際に参考にしたリンクをできる限り貼っています🔗

プライマリのホストゾーン設定

# プライマリドメインのホストゾーン
resource "aws_route53_zone" "primary" {
  name = "example.com"
}

# 社内向けサブドメインのホストゾーン(オマケ)
resource "aws_route53_zone" "internal" {
  name = "internal.${aws_route53_zone.primary.name}"
}

SES関連の設定

# SESドメイン
resource "aws_ses_domain_identity" "primary" {
  domain = aws_route53_zone.primary.name
}

# SESのDKIM認証用レコードを作成
resource "aws_ses_domain_dkim" "ses" {
  domain = aws_route53_zone.primary.name
}

プライマリのホストゾーンに登録するレコード

# SES向けドメイン検証
resource "aws_route53_record" "primary_ses_txt" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "_amazonses.${aws_route53_zone.primary.name}"
  type    = "TXT"
  ttl     = var.route53_record_ttl
  records = [
    # https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/dns-txt-records.html
    aws_ses_domain_identity.primary.verification_token
  ]
}

# Gsuite向けドメイン所有権の証明とSPF
resource "aws_route53_record" "primary_txt" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = ""
  type    = "TXT"
  ttl     = var.route53_record_ttl
  records = [
    # https://support.google.com/a/answer/183895
    # 手ポチにて作成
    "google-site-verification=xxxxx",
    # https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-email-authentication-spf.html
    # https://support.google.com/a/answer/33786
    "v=spf1 include:amazonses.com include:_spf.google.com ~all"
  ]
}

# メール送受信をGmail経由で実施するようMX指定
resource "aws_route53_record" "primary_mx" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = ""
  type    = "MX"
  ttl     = var.route53_record_ttl
  records = [
    # https://support.google.com/a/answer/6149697
    "1 ASPMX.L.GOOGLE.COM.",
    "5 ALT1.ASPMX.L.GOOGLE.COM.",
    "5 ALT2.ASPMX.L.GOOGLE.COM.",
    "10 ALT3.ASPMX.L.GOOGLE.COM.",
    "10 ALT4.ASPMX.L.GOOGLE.COM."
  ]
}

# SES向けDKIM設定
resource "aws_route53_record" "primary_ses_dkim" {
  for_each = { for el in aws_ses_domain_dkim.ses.dkim_tokens : el => el }

  zone_id = aws_route53_zone.primary.zone_id
  name    = "${each.value}._domainkey.${aws_route53_zone.primary.name}"
  type    = "CNAME"
  ttl     = var.route53_record_ttl
  records = [
    # https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-email-authentication-dkim-easy-setup-domain.html
    "${each.value}.dkim.amazonses.com"
  ]
}

# Gsuite向けDKIM設定
resource "aws_route53_record" "primary_gsuite_dkim" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "google._domainkey.${aws_route53_zone.primary.name}"
  type    = "TXT"
  ttl     = var.route53_record_ttl
  records = [
    # https://support.google.com/a/answer/174124
    # 手ポチにて作成
    # レコード長の上限の関係で1024ビットで生成する必要あり
    "v=DKIM1; k=rsa; p=xxxxxxxxxx"
  ]
}

# DMARC設定はSESとGmailで共通
resource "aws_route53_record" "primary_dmarc" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "_dmarc.${aws_route53_zone.primary.name}"
  type    = "TXT"
  ttl     = var.route53_record_ttl
  records = [
    # https://sendgrid.kke.co.jp/blog/?p=3137
    # ruaとrufに指定できるメールアドレスのドメインはホストゾーンと揃える必要あり
    "v=DMARC1; p=quarantine; rua=mailto:xxxxx@${aws_route53_zone.primary.name}; ruf=mailto:xxxxx@${aws_route53_zone.primary.name}; rf=afrf; pct=100"
  ]
}

# サブドメインのレコードはサブドメインのホストゾーンに向ける
resource "aws_route53_record" "internal_ns" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = aws_route53_zone.internal.name
  type    = "NS"
  ttl     = var.route53_record_ttl
  # https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/dns-routing-traffic-for-subdomains.html
  records = aws_route53_zone.internal.name_servers
}

社内向けのホストゾーンに登録する内容

# Gsuiteを http://mail.internal.example.com/ の形式で利用できるように設定(オマケ)
# 設定有効化は手ポチにて実施
resource "aws_route53_record" "gsuite_alias" {
  for_each = toset(["mail", "sites", "calendar", "drive", "groups"])

  zone_id = aws_route53_zone.internal.zone_id
  name    = each.value
  type    = "CNAME"
  ttl     = var.route53_record_ttl
  records = [
    # https://support.google.com/a/answer/53340
    "ghs.googlehosted.com."
  ]
}

検証

実装&applyしてはみたものの、どうやってテストすればいいんだ…🤢と一瞬途方に暮れたのですが、
SendGridのブログで紹介されていたmail-tester.comというサービスが、SPF、DKIM、DMARCを包括的にチェックしてくれるようだったので利用してみました

今回は、SES経由での自動応答メールとGmailからのメール送信のユースケースがあったので、それぞれのアドレスで試してみます…

結果

充分なスコアが出たのでヨシ!🐈⛑

なお、件名や本文がそれっぽくないとスコアが下がるようなので、個人情報などを隠した範囲である程度内容を入力した方が良いようです

また、最初に説明したSPFレコードが問題なく解決されているか気になったので、
G Suite Toolbox Check MXというツールを使って確認してみました

結果

今回、SPFレコードにamazonses.com_spf.google.comを登録していましたが、それぞれがどのIPアドレス範囲であるかを教えてくれるので、
GmailのWebクライアントからメールヘッダを確認する方法を参考に、送信元IPアドレスを見てみると、
ちょうどスクショ上に記載のある23.251.224.0/19に収まる範囲のIPから送信されているようでした
Gmailから送信したメールについても同じ方法でIPが確認できたので、いい感じのようです

メールの内容

おわりに

電子メールは基本的ながらビジネス重要度の高い技術と思いますので、引き続き触れ合いたいと思います💌
今回は要求仕様を満たすことに重点を起きましたが、メールヘッダも含めた各技術仕様に対する基礎理解が圧倒的に足りてない感じがしたので、本など買って勉強します…