SPFレコードをフラット化してルックアップ数制限を乗り越える
SPFレコードのルックアップ数制限とは
FirebaseやSendGridなど、様々なSaaSを活用してウェブアプリケーションを作っていると、色々な場所からメールを送ることになります。
サービスで使用するドメインにSPFレコードを記述していく訳ですが、送信のサービスが増える度、include
が増えていきます。
v=spf1 include:_spf.firebasemail.com
include:_spf.google.com
include:ex1.example.com
(....and more) -all
ところが、SPFレコードにはDNSの参照回数に制限があり最大で10回までになっていて、これを越えると正しく動作しません。
うまくいかない状態で、バリデーターで確認すると下記のようなエラーが出力されていました。
PermError SPF Permanent Error: Too many DNS lookups
include
を使うことによって、include
した先のレコードもさらに確認することになり、SPFレコードが示したいIPアドレスに辿り付く前に10回の制限を越えてしまいます。
対処方法
-
余計なSPFレコードのエントリを削除する
使っていないエントリが放置されているならどちらにせよ削除したいです。 -
SPFレコードをフラット化する
include
によってネストしてしまうことで余計なDNS参照が加わってしまうので、あらかじめ展開することで参照回数を削減します。 -
SPF Macro を使う
SPFレコードをフラット化する
include
で指定されたレコードを展開していく例:
# 元々のレコード
v=spf1 include:_spf.firebasemail.com
include:_spf.google.com -all
# 展開
v=spf1 include:sendgrid.net
include:_spf.google.com
include:_netblocks.google.com
include:_netblocks2.google.com
include:_netblocks3.google.com ~all
# さらに展開(一部)
v=spf1
ip4:167.89.0.0/17 ip4:208.117.48.0/20 ip4:50.31.32.0/19 ip4:198.37.144.0/20 ip4:198.21.0.0/21 ip4:192.254.112.0/20 ip4:168.245.0.0/17 ip4:149.72.0.0/16 ip4:159.183.0.0/16
include:ab.sendgrid.net
include:_netblocks.google.com
include:_netblocks2.google.com
include:_netblocks3.google.com ~all
展開してみると以下のことがわかりました。
- 実は同じメール送信サービスを使用していて重複する場合がある。
- IPアドレス(or 範囲)は変更される可能性があるので、深く展開する必要はない。
- 展開しすぎると、トップレベルのレコード自体が長くなりすぎる可能性がある。
- 随時変更される可能性がある。
自動化
状況がわかりましたので、自動化していきます。
GCPのCloud DNSで管理していたのでAPIで操作することができそうです。
Go言語で書かれた、github.com/StackExchange/dnscontrol
パッケージに含まれる、pkg/spflib
というサブパッケージを使うと、簡単にフラット化することができそうです。
また、SPFレコードを単にフラット化するだけでなく、自前のサーバに設定するための分割したデータまで作ることができる便利なライブラリです。
流れ
ほとんどの処理はライブラリが面倒を見てくれるので、GCPのCloud DNSと元データとの繋ぎこみを行うだけでよさそうです。
以下の流れで実装します。
- あらかじめテンプレートになるレコードをサブドメインに作っておいてそこに素直に(フラット化せず)レコードを登録しておく(GCPのコンソールでやります)
- テンプレートの内容を取得
- テンプレートを元にフラット化する
- 想定レコードを生成して、現在のレコードとの差分が無くなるようにCloud DNS側を更新
テンプレートの内容を取得
普通にCLIオプションなどで渡しても良いのですが、基本的にドメインの管理をGCPのコンソールから行っているため、_spf.example.com
のようなサブドメインのTXTレコードに置いておき、参照しています。
テンプレートを元にフラット化する
フラット化の処理自体はspflib#SPFRecord.Flattenをそのまま使うだけです。
また、長くなりすぎたSPFレコード自体を複数に分割(@.example.com, spf1.example.com, spf2.example.com...
)する機能もあるのでそのまま利用します。
想定レコードを生成して、現在のレコードとの差分が無くなるようにCloud DNS側を更新
完成
私の環境では、以上のコマンドにCLIとしてオプション等を渡せるようにした上で、バイナリをGitHub Releasesにアップロードしておき、GitHub Actionsで定期的に実行して変更を適用しています。
GitHub Actionsのワークフローサンプル
name: SPF Flatter
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
env:
TEMPLATE_OR_DOMAIN: _spf_tmpl.<FQDN>
PROJECT_ID: <GCP Project ID>
ZONE_NAME: <ZONE NAME>
FQDN: "<FQDN>."
jobs:
run:
permissions:
id-token: write
contents: read
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Fetch latest binary
uses: dsaltares/fetch-gh-release-asset@0.0.7
with:
token: ${{ secrets.GITHUB_TOKEN }}
file: "spf_flatter_Linux_x86_64.zip"
- name: Unzip toolkit
run: |
7z x spf_flatter_Linux_x86_64.zip
- id: 'auth'
uses: 'google-github-actions/auth@v0'
with:
workload_identity_provider: ...
service_account: ...
- name: Run
run: |
./spf-flatter $TEMPLATE_OR_DOMAIN -y
終わりに
以上、SPFレコードで遭遇する参照回数の制限とフラット化による対策をGo言語でのサンプルとして紹介しました。
本文中のコードは以下のリポジトリに置いてあります。
Discussion
素晴らしい記事です。今日さまざま検証をしていてこの記事にたどり着きました。
今回はこのサービスを使って解決してみることにしたのですが、有料ですし、外部サービスに依存してていいのかということもありますので、実装も検討しています
お役にたててよかったです!
定期的にチェックしてほぼ存在を忘れてしまうくらいの方が良いので外部サービスの利用も良い手段だと思ってます。