はじめに
これまでの記事では、Amazon SESを利用したメール送信について、
- 第1回:DMARCの
p/sp/np - 第2回:SPF / DKIM / Custom MAIL FROM
を整理しました。
今回の構成では、本番・STGから次のドメインを使ってメールを送信します。
本番:sample.com
STG :stg.sample.com
正規のメール送信については、SES・SPF・DKIM・DMARCを設定することで対策できます。
しかし、まだ気になるケースがあります。
例えば、
dummy.sample.com
test.sample.com
hoge.sample.com
のような、実際にはメール送信に使用していないサブドメインです。
攻撃者が、
From: attacker@dummy.sample.com
のようなメールを送信することも考えられます。
そこで今回は、Route 53上で、
*.sample.com TXT "v=spf1 -all"
*.sample.com MX 0 .
といったWildcard DNSレコードを利用し、未使用サブドメインのメール利用を明示的に制限する方法を整理します。
さらに、
sample.com
└─ stg.sample.com
を別のRoute 53 Hosted Zoneに分離している場合についても確認します。
Wildcard SPFやNull MXだけでHeader Fromのなりすましを防げるわけではありません。
第1回で設定したDMARCと組み合わせることが重要です。
今回の構成
まず、Hosted Zoneを1つだけ使用している場合を考えます。
今回の基本的なDMARC設定は次のとおりです。
_dmarc.sample.com
TXT "v=DMARC1; p=none; sp=reject; np=reject;"
つまり、
sample.com
→ p=none
存在するサブドメイン
→ sp=reject
存在しないサブドメイン
→ np=reject
という設計です。
さらにDNS側でも、メール送信に使用しないサブドメインについて明示的に制限していきます。
Wildcard SPFを設定する
まずSPFです。
今回、未使用サブドメインについて、
*.sample.com
TXT "v=spf1 -all"
を設定します。
-all は、
このドメインからメールを送信することを許可された送信元は存在しない
というポリシーです。
例えば、
dummy.sample.com
に個別のDNSレコードが存在しない場合、Wildcardによって、
dummy.sample.com
TXT "v=spf1 -all"
相当の応答を返せます。
SPFの仕様でも、メールを送信しないドメインに、
v=spf1 -all
を公開することはベストプラクティスとして示されています。
~all と -all の違い
第2回でAmazon SESのCustom MAIL FROMには、
v=spf1 include:amazonses.com ~all
を設定しました。
一方、未使用サブドメインには、
v=spf1 -all
を設定します。
違いを整理すると、
| 設定 | 意味 |
|---|---|
~all |
SPF SoftFail |
-all |
SPF Fail |
例えば、
mail.sample.com
はAmazon SESから正規にメールを送信するため、
v=spf1 include:amazonses.com ~all
とします。
一方、
dummy.sample.com
などは一切メールを送信しないので、
v=spf1 -all
です。
イメージとしては、
sample.com
│
├─ mail.sample.com
│ └─ SESから送信する
│ v=spf1 include:amazonses.com ~all
│
└─ その他の未使用サブドメイン
└─ メール送信しない
v=spf1 -all
となります。
Wildcardより個別レコードが優先される
ここで気になるのが、
*.sample.com
TXT "v=spf1 -all"
を設定すると、
mail.sample.com
まで -all になってしまわないか、という点です。
Route 53では、具体的なドメイン名のレコードが存在する場合、Wildcardより具体的な名前が優先されます。
そのため、
*.sample.com
TXT "v=spf1 -all"
があっても、
mail.sample.com
TXT "v=spf1 include:amazonses.com ~all"
を明示的に設定していれば、mail.sample.com はこちらが使用されます。
つまり、
原則
*.sample.com
→ SPF -all
例外
mail.sample.com
→ SESを許可
という構成が可能です。
Null MXとは?
続いてMXレコードです。
未使用サブドメインには、
*.sample.com
MX 0 .
を設定します。
これを Null MX と呼びます。
Null MXは、
このドメインではメールを受信しない
ことを明示するための仕組みです。
RFC 7505で正式に定義されています。
通常のMXレコードは、
sample.com
MX 10 mail.example.com
のように、メールを受信するサーバーを指定します。
Null MXでは、
MX 0 .
とします。
. はメールサーバーが存在しないことを表します。
なぜMXがないだけではダメなのか
ここはNull MXを理解するうえで重要です。
例えば、
api.sample.com
A 192.0.2.10
が存在しているとします。
しかしMXレコードはありません。
一見すると、
MXがないからメールを受信しない
ように見えます。
ところがSMTPでは、MXレコードが存在しない場合、A / AAAAレコードのホストへ配送を試みる動作があります。
RFC 7505がNull MXを定義した理由の一つも、この挙動を明確にするためです。
そこで、
api.sample.com
MX 0 .
と明示すれば、
api.sample.comではメールを受信しない
と宣言できます。
SPFとNull MXは役割が違う
ここも混同しやすいポイントです。
v=spf1 -all
と、
MX 0 .
では役割が違います。
| 設定 | 主な役割 |
|---|---|
SPF -all
|
そのドメインをMAIL FROMとして送信するサーバーを許可しない |
| Null MX | そのドメインではメールを受信しない |
DMARC sp / np
|
Header Fromを使ったDMARC失敗メールの処理方針 |
つまり、
それぞれ別の役割を持っています。
今回の構成では、これらを組み合わせます。
Wildcard SPF / MXをRoute 53へ設定する
Route 53では次の2レコードを作成します。
SPF
Record name:
*.sample.com
Record type:
TXT
Value:
"v=spf1 -all"
Null MX
Record name:
*.sample.com
Record type:
MX
Value:
0 .
整理すると、
*.sample.com
├─ TXT
│ └─ "v=spf1 -all"
│
└─ MX
└─ 0 .
です。
MXとTXTは別々のDNSレコードです。
「MX/TXT」という1つのレコードを登録するわけではありません。
重要:Wildcardは「すべて」に必ず適用されるわけではない
今回、特に注意したいポイントです。
*.sample.com
TXT "v=spf1 -all"
を作れば、
sample.com配下すべてにSPF
-allが付く
ように見えます。
しかし、DNSのWildcardはそれほど単純ではありません。
例えば、
api.sample.com
A 192.0.2.10
がすでに存在しているとします。
この場合、
api.sample.com
というDNS上の名前そのものが存在します。
そのため、
*.sample.com TXT
を使って、
api.sample.com TXT
を補完することはできません。
DNS Wildcardの仕様では、問い合わせ対象の名前自体が存在する場合、その名前に問い合わせ対象のレコードタイプがなくてもWildcardから合成されません。
SPFのRFCでも、Wildcard SPFを使用する場合、すでに何らかのDNSレコードを持っているホストにはSPF宣言を繰り返し設定する必要があると注意されています。
例えば api.sample.com はどうなる?
次の構成を考えます。
*.sample.com
TXT "v=spf1 -all"
api.sample.com
A 192.0.2.10
ここで、
api.sample.com TXT
を問い合わせても、
*.sample.com
TXT "v=spf1 -all"
が適用されるとは限りません。
api.sample.com 自体が存在するからです。
そのため、api.sample.com が絶対にメールを送信しないのであれば、
api.sample.com
TXT "v=spf1 -all"
を明示的に設定するのが確実です。
同じ考え方で、メールを受信しないのであれば、
api.sample.com
MX 0 .
も明示します。
api.sample.com
├─ A
│ └─ Web/API用途
│
├─ TXT
│ └─ v=spf1 -all
│
└─ MX
└─ 0 .
Wildcardは「未定義の名前」をまとめてカバーする仕組みと考えた方が分かりやすいです。
Web/API用途などでDNS上すでに存在するサブドメインについては、必要に応じてSPF -all やNull MXを個別に設定します。
Wildcardを設定すると np はどうなる?
第1回では、DMARCを、
v=DMARC1; p=none; sp=reject; np=reject;
としました。
ここで、
dummy.sample.com
がDNS上存在しない場合、
np=reject
の対象になります。
RFC 9989では、DMARC上の「存在しないドメイン」は、DNS問い合わせで NXDOMAIN が返るものとして定義されています。
しかし、
*.sample.com
TXT "v=spf1 -all"
*.sample.com
MX 0 .
というWildcardを設定すると、
dummy.sample.com
への問い合わせにWildcardからDNSレコードが合成されます。
すると、DNS上は NXDOMAIN ではなくなります。
つまり、
Wildcardなし
dummy.sample.com
↓
NXDOMAIN
↓
np
だったものが、
Wildcardあり
dummy.sample.com
↓
WildcardからDNS応答
↓
DNS上存在する扱い
↓
sp
となるケースがあります。
DMARCでは、MXやAがなくても、何らかのRRが存在すれば「存在する」と判断します。
これが今回、
sp=reject
np=reject
を両方設定している理由の一つです。
今回の親Hosted Zoneの構成
ここまでを整理すると、sample.com Hosted Zoneには概ね次のような設定があります。
sample.com Hosted Zone
│
├─ _dmarc.sample.com
│ └─ TXT
│ v=DMARC1; p=none; sp=reject; np=reject;
│
├─ mail.sample.com
│ ├─ MX
│ │ 10 feedback-smtp.ap-northeast-1.amazonses.com
│ │
│ └─ TXT
│ v=spf1 include:amazonses.com ~all
│
├─ SES DKIM
│ └─ CNAME × 3
│
└─ *.sample.com
├─ TXT
│ v=spf1 -all
│
└─ MX
0 .
さらに、Web/APIなどで既に存在するサブドメインについては、必要に応じて、
api.sample.com
TXT "v=spf1 -all"
api.sample.com
MX 0 .
を個別に設定します。
STGを別Hosted Zoneに分離する
次に、今回実際に確認したかったケースです。
現在は、
sample.com Hosted Zone
の中ですべて管理しているとしても、
将来的に、
stg.sample.com
を別Hosted Zoneへ分離する場合があります。
Route 53では、サブドメイン用に別Hosted Zoneを作成し、親Hosted ZoneからNSレコードで権限委譲できます。
構成は次のようになります。
親Hosted ZoneからNSを向ける
まず、
stg.sample.com
のHosted Zoneを作成します。
Route 53から4つのName Serverが割り当てられます。
例えば、
ns-xxx.awsdns-xx.com
ns-xxx.awsdns-xx.net
ns-xxx.awsdns-xx.org
ns-xxx.awsdns-xx.co.uk
です。
そして親側の、
sample.com Hosted Zone
に、
stg.sample.com
NS
レコードを作成し、子Hosted ZoneのName Serverを指定します。
これによって、
sample.com
↓
stg.sample.comは別DNSへ問い合わせ
↓
stg.sample.com Hosted Zone
という権限委譲が行われます。
STG配下のレコードは子Hosted Zoneで管理する
ここが最重要ポイントです。
stg.sample.com を別Hosted Zoneへ委譲した場合、
_dmarc.stg.sample.com
mail.stg.sample.com
xxxxx._domainkey.stg.sample.com
*.stg.sample.com
などのSTG配下のレコードは、
stg.sample.com Hosted Zone側で管理します。
AWSも、サブドメインを別Hosted Zoneへ分離した場合、そのサブドメイン以下のレコードを子Hosted Zoneで作成するよう案内しています。
つまり、
sample.com Hosted Zone
│
└─ stg.sample.com NS
│
↓
stg.sample.com Hosted Zone
│
├─ _dmarc.stg.sample.com
├─ mail.stg.sample.com
├─ DKIM
└─ *.stg.sample.com
となります。
親のWildcardはSTG配下まで使える?
ここは注意が必要です。
親側に、
*.sample.com
TXT "v=spf1 -all"
*.sample.com
MX 0 .
が存在していても、
stg.sample.com
を別Hosted Zoneへ委譲すると、STG配下の問い合わせは子Hosted Zoneが権威を持ちます。
そのため、
dummy.stg.sample.com
をカバーしたいのであれば、STG Hosted Zone側にも、
*.stg.sample.com
TXT "v=spf1 -all"
*.stg.sample.com
MX 0 .
を設定します。
Route 53では、親から子Hosted ZoneへNSで委譲すると、リゾルバは子Hosted ZoneのName Serverへ問い合わせを行います。
つまり、
親
*.sample.com
だけに頼らず、
子
*.stg.sample.com
も用意する、という考え方です。
STG側の最終構成
STG Hosted Zoneは次のようになります。
stg.sample.com Hosted Zone
│
├─ _dmarc.stg.sample.com
│ └─ TXT
│ v=DMARC1; p=none;
│
├─ mail.stg.sample.com
│ ├─ MX
│ │ 10 feedback-smtp.ap-northeast-1.amazonses.com
│ │
│ └─ TXT
│ v=spf1 include:amazonses.com ~all
│
├─ SES DKIM
│ └─ CNAME × 3
│
└─ *.stg.sample.com
├─ TXT
│ v=spf1 -all
│
└─ MX
0 .
これで、
mail.stg.sample.com
→ SESから正規送信
dummy.stg.sample.com
→ 原則SPF Fail / メール受信なし
という構成にできます。
親と子の両方に同じレコードを置かない
Hosted Zone分離でハマりやすいポイントです。
例えば、
sample.com Hosted Zone
と、
stg.sample.com Hosted Zone
の両方に、
mail.stg.sample.com
を残すような構成は避けます。
AWSも、親Hosted Zoneと子Hosted Zoneの両方に同一サブドメインのレコードが残っていると、DNSリゾルバがキャッシュしているName Serverによって挙動が不整合になる可能性があると注意しています。
基本的には、
親Hosted Zone
→ stg.sample.com のNSだけ持つ
子Hosted Zone
→ stg.sample.com配下の実レコードを持つ
という役割分担にします。
1つのHosted Zoneの場合と分離した場合
整理すると次のようになります。
Hosted Zoneを分けない
sample.com Hosted Zone
│
├─ sample.com
├─ mail.sample.com
├─ *.sample.com
├─ stg.sample.com
├─ mail.stg.sample.com
└─ *.stg.sample.com
すべて1つのHosted Zoneで管理します。
STG Hosted Zoneを分離
sample.com Hosted Zone
│
├─ sample.com
├─ mail.sample.com
├─ *.sample.com
│
└─ stg.sample.com NS
│
↓
stg.sample.com Hosted Zone
│
├─ stg.sample.com
├─ mail.stg.sample.com
├─ _dmarc.stg.sample.com
├─ DKIM
└─ *.stg.sample.com
DMARCの考え方はどちらも同じです。
変わるのは、
どのDNSサーバーがそのレコードに対して権威を持つか
です。
今回の最終構成
本番とSTGを別Hosted Zoneにした場合、全体としては次のようになります。
設計方針としては、
正規メール送信用ドメイン
→ SES / SPF / DKIM / DMARCを明示
未使用サブドメイン
→ SPF -all
→ Null MX
→ DMARC sp / np=reject
別Hosted Zone
→ そのZone側で同じポリシーを設定
となります。
よくある勘違い
*.sample.com を設定すれば、存在するすべてのサブドメインにも適用される?
必ずしもそうではありません。
例えば、
api.sample.com A ...
が存在すると、api.sample.com 自体がDNS上存在するためWildcard TXT/MXが期待どおり使われないケースがあります。
必要なら、
api.sample.com TXT "v=spf1 -all"
api.sample.com MX 0 .
を明示します。
SPF -all を設定すればメールを受信しなくなる?
違います。
SPFは送信側の認証です。
メールを受信しないことを宣言するのはNull MXです。
Null MXを設定すればなりすましを防げる?
Null MXだけではHeader Fromのなりすまし対策にはなりません。
Header FromについてはDMARCが重要です。
np=reject があるならWildcard SPFはいらない?
役割が違います。
np はDMARC Policyです。
Wildcard SPFはMAIL FROMに対するSPF Policyです。
またWildcardを設定するとDNS上存在する名前として扱われ、DMARCでは np ではなく sp 側のポリシーになるケースもあります。
親に *.sample.com があれば *.stg.sample.com は不要?
stg.sample.com を別Hosted Zoneへ委譲している場合は、子Hosted Zone側にも設定します。
親Hosted ZoneにもSTGのレコードを残しておいた方が安全?
逆です。
委譲後はSTG配下のレコードを子Hosted Zoneへ集約した方が安全です。
親と子の両方に同じレコードを残すと、DNSの挙動が不整合になる可能性があります。
ここまでのポイント
今回のポイントを整理します。
- メールを送信しないドメインには
v=spf1 -allを設定できる - Null MX
0 .はメールを受信しないことを宣言する - SPF
-allとNull MXは役割が違う - Header Fromのなりすまし対策にはDMARCも必要
- Route 53ではWildcardより具体的な名前が優先される
- DNS上すでに存在する名前にはWildcard SPFが効かないケースがある
- 既存のWeb/APIサブドメインには必要に応じてSPF
-all/ Null MXを個別設定する - Wildcard DNSによってNXDOMAINでなくなると、DMARCの
npではなくspの対象になる場合がある -
sp=reject/np=rejectの両方を設定しておく意味がある -
stg.sample.comは別Hosted Zoneへ分離できる - 親Hosted ZoneからNSレコードで子Hosted Zoneへ委譲する
- 委譲後はSTG配下のレコードを子Hosted Zoneで管理する
- 親のWildcardに頼らず、子Hosted Zoneにも
*.stg.sample.comを設定する - 親・子Hosted Zoneに同じSTGレコードを残さない
今回の検討を通して、
Wildcardを1つ設定すれば全部解決
というわけではない点が特に重要だと感じました。
DNS上すでに存在するサブドメインや、別Hosted Zoneへ委譲されたサブドメインについては、それぞれのDNS構成を理解したうえで設定する必要があります。
連載まとめ
今回の3回で、Amazon SESを利用したメール送信となりすまし対策について、
第1回
DMARC
p / sp / np
↓
第2回
Amazon SES
SPF / DKIM / Custom MAIL FROM
↓
第3回
Route 53
Wildcard SPF / Null MX / Hosted Zone分離
まで整理できました。
最終的には、
正規にメールを送信するドメインを明示的に認証し、それ以外のドメインについては送受信しないことをDNS・DMARC双方で明確にする
という設計になります。
SPF / DKIM / DMARCはそれぞれ役割が異なるため、一つの設定だけでなりすまし対策を完成させるのではなく、複数の仕組みを組み合わせて設計することが重要です。
参考資料