取得したドメインで送信するメールの信頼性を上げる方法

Published on

ドメインを取得後にそれを使ったメールアドレスで送信できるようになったが、受信先でそのメールが迷惑フォルダへ分類されることがある。

会社では Google Domain でドメインを取得後、Google Workspace を利用してメールを送信できるようになった。DNS の管理は Cloud DNS を利用していて、その設定は Terraform を用いて管理している。

当初の設定はシンプルなものであった。

  1. DNS ゾーンを設定
  2. 設定したゾーンに対して MX レコードを設定
resource "google_dns_managed_zone" "example_com_domain" {
  name     = "example-com"
  dns_name = "example.com."
}

# https://support.google.com/a/answer/9222085
resource "google_dns_record_set" "example_com_email" {
  name         = google_dns_managed_zone.example_com_domain.dns_name
  type         = "MX"
  ttl          = 300
  managed_zone = google_dns_managed_zone.example_com_domain.name
  rrdatas = [
    "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.",
  ]
}

これだけで取得したドメインを利用したアドレスでメールの送信ができる。しかし受信先の Gmail 上では次のような警告が表示されていた。

146682538-4be73b57-f202-4ad7-a47c-8316388e9149.png

これは Gmail 側で行われる認証ロジックによって、ドメインを取得した本人(もしくは法人)から送信されているメールかどうか判断できなかった場合に表示される。

本記事では Terraform で管理された Cloud DNS を例にメールの信頼性をどう向上させたのかを紹介していく。

SPF、DKIM、DMARC を設定する

前述した問題を回避するために Google のサポートでは SPF、DKIM、DMARC の設定を推奨している1

設定方法を紹介する前にこれらが何か簡単に紹介する。

  • SPF (Sender Policy Framework)
    • 特定のドメイン名の使用を、許可されているホストのリストを提供するために使用される
    • 受信側はこの情報を使って、詐称されていないかを検査できる
  • DKIM (Domain Keys Identified Mail)
    • メールを送信する際に送信元が電子署名を行い、受信者がそれを検証することで送信者のなりすましやメールの改ざんを検知できるようにする
  • DMARC (Domain-based Message Authentication, Reporting, and Conformance)
    • SPF と DKIM を利用して認証に失敗したメールをどう扱うかは受信側に任されている
    • 送信者が受信者に対して、認証に失敗したメールをどう扱って欲しいか指示が可能
    • 受信者から送信者に対して認証に失敗したことを通知するレポートも送信可能

DNS レコードに SPF であればホストや IP などのリストを、DKIM だと検証に必要な公開鍵を記録することで認証が機能する。DMARC はポリシーを公開することで受信者へ認証に失敗したメールをどうするか指示を行える。

SPF と DKIM を設定する

Google サポートで案内されている方法に従って、まずは Terraform に SPF と DKIM の設定のみ記述する。DMARC はこれらの設定が反映された後に設定する。

# https://support.google.com/a/answer/10684623
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/dns_record_set#adding-an-spf-record
resource "google_dns_record_set" "example_com_email_spf" {
  name         = google_dns_managed_zone.example_com_domain.dns_name
  type         = "TXT"
  ttl          = 300
  managed_zone = google_dns_managed_zone.example_com_domain.name
  rrdatas = [
    "\"v=spf1 include:_spf.google.com ~all\"",
  ]
}

# https://support.google.com/a/answer/174124
resource "google_dns_record_set" "example_com_email_dkim" {
  name         = "google._domainkey.${google_dns_managed_zone.example_com_domain.dns_name}"
  type         = "TXT"
  ttl          = 300
  managed_zone = google_dns_managed_zone.example_com_domain.name
  rrdatas = [
    "\"v=DKIM1; k=rsa; p=<DKIM-KEY>\"",
  ]
}

DKIM の値は Google Workspace の管理コンソールにアクセスし、[アプリ] > [Google Workspace] > [Gmail] を選択して [メールの認証] から取得することができる。

146678437-00ae1693-104f-48ef-ac1d-94b29301e5ee.png

画像から分かるように DKIM の値(青く選択されている箇所)はとても長い文字列になる。これは 255 文字を超える長さになる。この長さの値をそのまま TXT レコードへ反映しようとすると失敗する。

回避策として Terraform Google Provider の dns_record_set (rrdatas) のドキュメント\" \" を使用することが記述されている。

To specify a single record value longer than 255 characters such as a TXT record for DKIM, add " " inside the Terraform configuration string (e.g. "first255characters" "morecharacters").

筆者は 255 文字目で区切れるような次のワンライナーを活用した。

$ pbpaste | perl -ne 'my @a = $_ =~ /.{255}|.+/g; print join "\\\" \\\"", @a;' | pbcopy

このワンライナーは DKIM の値を管理コンソールでコピーした後に実行する。するとクリップボードの中身は 255 文字目で区切られた状態へ更新される。あとは rrdatas へペーストすれば良い。

apply が完了し、反映されたら次は DMARC の設定を記述する。

DMARC を設定する

Terraform の記述はシンプルになる。

# https://support.google.com/a/answer/2466563
resource "google_dns_record_set" "example_com_email_dmarc" {
  name         = "_dmarc.${google_dns_managed_zone.example_com_domain.dns_name}"
  type         = "TXT"
  ttl          = 300
  managed_zone = google_dns_managed_zone.example_com_domain.name
  rrdatas      = ["\"v=DMARC1; p=reject; rua=mailto:dmarc@example.com\""]
}

DMARC の値は v p タグが必須になる。ここでは p タグについて触れておきたい。

p タグでは受信したメールが認証できなかった場合、そのメールをどう処理するか指示できる。指定できる値は none quarantine reject のいずれかである。

筆者として DMARC を導入するのであれば none は絶対に避けるべきだと考える。なぜなら認証に失敗したメールを自動的に不正なメール、もしくは迷惑メールとして処理しないからである。処理されない場合、そのメールはメインの受信トレイへ保存されることになる。一般的な受信者にとって、メールが本当に信頼している組織から送られてきているかどうかを開封したときに判断することが難しい。つまり攻撃者が組織を装って送信したメールによって、受信者の個人情報が流出するなどのインシデントが容易に発生することが想定できる。

会社では reject を利用する選択をした。ちなみに Microsoft や Gmail は既に reject を利用している2

apply 完了後、メールを送信してテストしてみると警告が表示されない。これで信頼度を上げるために必要な設定は終わりである。

最後に

これらの技術は全て DNS を信頼する前提で成り立っている。例えば中間者攻撃によって DNS の応答を変更できる場合上記で挙げた内容を完全に検証することは不可能になる。このような改ざんを防ぐために DNSSEC を利用する。(HTTPS をなぜ利用するのか知っているとイメージがつきやすい)

Cloud DNS の場合 Terraform で管理されていれば dnssec_config を記述し state を on へ設定すれば有効になる。

resource "google_dns_managed_zone" "example_com_domain" {
  name     = "example-com"
  dns_name = "example.com."
+ dnssec_config {
+   kind          = "dns#managedZoneDnsSecConfig"
+   non_existence = "nsec3"
+   state         = "on"
+ }
}

apply 後、ドメインを取得したプロバイダで DNSSEC の DS レコードを保存する必要がある。保存すべきレコードの値は Cloud DNS だと gcloud コマンドを利用して取得できる。

gcloud dns dns-keys describe 0 --zone <ZONE> --format "value(ds_record())"

Google Cloud のドキュメント(高度な DNSSEC を使用する) では、本記事で紹介した設定が正しく構成されていることを確認するために https://en.internet.nl/ を利用することを推奨している。

本記事では Terraform で管理された Cloud DNS を例に、送信元の電子メールのセキュリティをどう向上させるか紹介した。記述に間違いや修正すべき点があれば @codehex まで知らせてほしい。

参考