TerraformでAmazon SESのベストプラクティスに則った送信メール認証を設定をする


はじめに

AWS上にインフラ環境に選定している時に外部のメールサービス(SendGrid等)を使わずにAWSで完結したい場合に出てくるAWSのメールサービスAmazon SESがあります。
Amazon SESをTerraformで管理する記事はいっぱいあるのですが、
Amazon SESのベストプラクティス に則ってTerraform管理をする記事がないのでメールのレコードの意味合いなんかを含めて説明していきます。
メールに関する知識が乏しいですが、お付き合いください。

参考資料: https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/tips-and-best-practices.html

メールについて

メールの世界で重要なのは、送信レピュテーションです。
送信レピュテーションが高ければ、受信側の迷惑メールボックスに入ったり、メール開封率が低下することを防ぐことができる可能性が高まります。
送信レピュテーションには以下の2つの要素が重要です。

  • IPアドレス
  • ドメイン

IP、ドメインが健全であれば、送信レピュテーションが下がることがなく、受信側にメールがちゃんと届くようになります。自身が使っているMTAの送信レピュテーションを確認したい場合は参考資料にあるサイトでツールを紹介しているので確認してみるといいかもしれません。

参考資料: https://sendgrid.kke.co.jp/blog/?p=11000

他にも送信レピュテーションを下げないために気をつけないといけないことがあります。
それは以下の4つの要素です。

  • メールサーバの設定を正しく行う
  • 送信ドメイン認証をする
  • 送り先アドレスにメールが送れない場合に発生するbounceメールをなるべく発生させない
  • 迷惑メールに報告されないようにする

参考資料: https://baremail.jp/blog/2019/05/24/125/

Amazon SESで設定できるのは「送信ドメイン認証をする」なので、送信レピュテーションを下げないために設定します。

メールの信頼性を高める送信ドメイン認証方式SPF、DKIM、DMARCの概要

Amazon SESではSPF、DKIMを設定することが推奨されています。これはメールの信頼性を高めて、迷惑メールに認定されないために必要な設定です。

ここでは、ざっくりとした概要をわかっていただければと思います。

また、Amazon SESのベストプラクティス ではありませんが、メールの信頼性を高めるDMARCも今回設定するので説明します。

  • SPF

送られてきたメールが、メールのドメインから送信が許可されているサーバから来たのかどうかを判断するための認証方法。SPFがあれば、受信側が送信側が悪意のあるMTAであるかどうか判断できます。

  • DKIM

受信側が送信ドメインの正当性を判断するための認証方法。正規でないサーバからのなりすましメール等の悪意があるメールを防ぐことができます。

  • DMARC

SPFとDKIMを利用して、受信側がなりすましメール等の悪意があるメールを見破りやすくします。

ちなみにどのメールサービスもSPF、DKIMを設定することが推奨されています。

詳しい内容はは以下のURLに記載されています。

Terraform

前提条件

Terraformのバージョンはv0.14を使用します。

設定内容

メールの世界は送信レピュテーションが重要であり、迷惑メールに判定されて、送信レピュテーションを下げないためにSPF、DKIM、DMARACを設定することが重要ということもわかっていただけたと思います。
SESでSPF、DKIM、DMARCを設定した内容をTerraformコードに落とすとこうなります。

# SES
resource "aws_ses_domain_identity" "example_com" {
  domain = data.aws_route53_zone.example_com.name
}

# SESのドメイン認証確認をしようとするとDKIMの認証に最長72時間かかるため無効化する
# Ref https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/troubleshoot-dkim.html
# resource "aws_ses_domain_identity_verification" "example_com" {
#   domain = aws_ses_domain_identity.example_com.id
# 
#   depends_on = [aws_route53_record.txt_example_com]
# }

## For DKIM
resource "aws_ses_domain_dkim" "example_com" {
  domain = data.aws_route53_zone.example_com.name
}

## For SPF
resource "aws_ses_domain_mail_from" "example_com" {
  domain           = data.aws_route53_zone.example_com.name
  mail_from_domain = "mail.${data.aws_route53_zone.example_com.name}"
}

# Route53
data "aws_route53_zone" "example_com" {
  name = "example.com"
}

## For SES
resource "aws_route53_record" "txt_example_com" {
  zone_id = data.aws_route53_zone.example_com.zone_id
  name    = "_amazonses.${data.aws_route53_zone.example_com.name}"
  type    = "TXT"
  ttl     = "600"
  records = [aws_ses_domain_identity.example_com.verification_token]
}

## For DKIM
resource "aws_route53_record" "cname_dkim_example_com" {
  count   = 3
  zone_id = data.aws_route53_zone.example_com.zone_id
  name    = "${element(aws_ses_domain_dkim.example_com.dkim_tokens, count.index)}._domainkey.${data.aws_route53_zone.example_com.name}"
  type    = "CNAME"
  ttl     = "600"
  records = ["${element(aws_ses_domain_dkim.example_com.dkim_tokens, count.index)}.dkim.amazonses.com"]
}

## For SPF
resource "aws_route53_record" "mx_mail_example_com" {
  zone_id = data.aws_route53_zone.example_com.zone_id
  name    = aws_ses_domain_mail_from.example_com.mail_from_domain
  type    = "MX"
  ttl     = "600"
  records = ["10 feedback-smtp.ap-northeast-1.amazonses.com"] # Ref https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/regions.html
}

resource "aws_route53_record" "txt_mail_example_com" {
  zone_id = data.aws_route53_zone.example_com.zone_id
  name    = aws_ses_domain_mail_from.example_com.mail_from_domain
  type    = "TXT"
  ttl     = "600"
  records = ["v=spf1 include:amazonses.com ~all"]
}

## For DMARC
resource "aws_route53_record" "txt_dmarc_example_com" {
  zone_id = data.aws_route53_zone.example_com.zone_id
  name    = "_dmarc.example.com"
  type    = "TXT"
  ttl     = "600"
  records = ["v=DMARC1;p=quarantine;pct=25;rua=mailto:[email protected]"] # Ref https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-email-authentication-dmarc.html
}

Tips

メールのエンベロープFromはサブドメインでも大丈夫

メールアドレスを偽装してなりすましメールを送る時にヘッダFromを詐称する場合があります。例えば、ヘッダFrom(メールに出てくるFrom)を example.com と設定し、エンベロープFrom(本当の送り主)は example.com ではなくて hogehoge.com と言った別のドメインから送信できるためです。そのため、 ヘッダFromとエンベロープFromが一致してないとメールとして正しくない(なりすましメールにみなされる可能性がある)と思ってましたが、ヘッダFromのサブドメインであればエンベロープFromに設定して問題ないです。

  • 差出人を詐称して送られるメールをなりすましメールと言います。これを悪用すれば「大手ECサイトからのメールと見せかけて受信者を不正なWebサイトに誘導し、個人情報を抜き取る」といったこともできてしまうかもしれません。
  • 郵便の場合、封筒と手紙に差出人の名前を書きますが、メールの場合も郵便と同じような仕組みがあります。メールの場合、封筒の差出人にあたる情報をエンベロープFrom、手紙の差出人にあたる情報をヘッダFromと呼びます。メールソフトで見えている差出人は後者のヘッダFromで、メールの仕組み上、ヘッダFromは簡単に詐称できてしまいます。

引用: https://sendgrid.kke.co.jp/blog/?p=10121

この記事のTerraformのコード上では、Amazon SESで送られるメールは example.com ドメインから送られているように見えるが、実態は mail.example.com ドメインから送られています。

Amazon SESにSPFを設定しなくてもSPFエラーにはならない

Amazon SESで何も設定しない場合、以下の理由でSPFエラーにはなりません。

引用: https://www.slideshare.net/AmazonWebServicesJapan/20190521-aws-black-belt-online-seminar-amazon-simple-email-service-amazon-ses-148042995#33

ただし、これはヘッダーFromとエンベロープFromが異なっている状態(ヘッダーFromは example.com なのにエンベロープFromは amazonses.com)なので、健全な状態ではないです。そのため、Amazon SESでMAIL FROMドメインを設定し、ヘッダーFromとエンベロープFromが正しい状態にしましょう。