RFC 7208 - Sender Policy Framework (SPF)
- ステータス: Proposed Standard
- 発行日: April 2014
- ストリーム: IETF
- 廃止: RFC4408
- エラッタ: エラッタなし
基本情報
- RFC番号: 7208
- タイトル: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email
- 日本語タイトル: 送信者ポリシーフレームワーク
- 公開日: 2014年4月
- ステータス: PROPOSED STANDARD (提案標準)
- 著者: S. Kitterman
概要 (Abstract)
SPFは、ドメイン所有者がDNSレコードを通じて、そのドメインのメール送信を許可されたメールサーバーを指定できるようにします。受信者はSPFレコードを照会して、メールが許可されたサーバーから送信されたかを検証でき、メールスプーフィングの検出とブロックに役立ちます。
Contents
- 1. Introduction (導入)
- 2. Operational Overview (運用概要)
- 3. SPF Records (SPFレコード)
- 4. The check_host() Function (check_host()関数)
- 5. Mechanism Definitions (メカニズム定義)
- 6. Modifier Definitions (修飾子定義)
- 7. Macros (マクロ)
- 8. Result Handling (結果処理)
- 9. Recording the Result (結果の記録)
- 10. Effects on Infrastructure (インフラへの影響)
- 11. Security Considerations (セキュリティに関する考慮事項)
- 12. Collected ABNF (収集されたABNF)
- 13-15. Additional Sections (追加セクション)
Appendices (付録)
- Appendix A. Extended Examples (拡張例)
- Appendix B. Changes from RFC 4408 (RFC 4408からの変更)
- Appendix C. Further Testing Advice (追加のテストアドバイス)
- Appendix D. SPF/Mediator Interactions (SPF/仲介者相互作用)
- Appendix E. Mail Services (メールサービス)
- Appendix F. MTA Relays (MTAリレー)
- Appendix G. Local Policy Considerations (ローカルポリシーに関する考慮事項)
SPF概要
SPFとは?
定義:
SPF = Sender Policy Framework (送信者ポリシーフレームワーク)
機能: 認可されたメール送信サーバーの検証
方法: DNS TXTレコード
目的:
✓ メールスプーフィングの防止
✓ スパムの削減
✓ メール配信性の向上
メールセキュリティトリオ:
1. SPF (このRFC) - 送信サーバーを検証
2. DKIM (RFC 6376) - メールコンテンツを検証
3. DMARC (RFC 7489) - 統一ポリシーとレポート
動作原理:
送信者 (example.com):
1. DNSにSPFレコードを公開
example.com. IN TXT "v=spf1 ip4:203.0.113.1 -all"
→ 203.0.113.1のみ認可
2. メールサーバーが通常どおりメールを送信
MAIL FROM: `<[email protected]>`
受信者:
1. 送信者ドメインを抽出
MAIL FROM: [email protected] → ドメイン: example.com
2. SPFレコードを照会
DNSクエリ: example.com TXTレコード
3. 送信サーバーIPを確認
送信サーバーIP: 203.0.113.1
SPFレコード許可: ip4:203.0.113.1
→ 一致!
4. SPF結果:
Pass ✓ → 認可されたサーバー
Fail ✗ → 認可されていないサーバー
SPF vs DKIM vs DMARC
機能比較:
SPF (RFC 7208):
- 検証対象: 送信サーバーIP
- 位置: SMTP MAIL FROM
- DNSレコード: TXT
- 制限: 転送メールは失敗する
DKIM (RFC 6376):
- 検証対象: メールデジタル署名
- 位置: DKIM-Signatureヘッダー
- DNSレコード: TXT (_domainkey)
- 制限: 正しい鍵設定が必要
DMARC (RFC 7489):
- 検証対象: SPF + DKIMアライメント
- 位置: Fromヘッダー
- DNSレコード: TXT (_dmarc)
- 機能: ポリシー + レポート
組み合わせ使用:
SPF + DKIM → DMARC合格 → 最適な保護
SPFレコード形式
基本構文
v=spf1 <mechanisms> <qualifiers> <modifiers>
例:
v=spf1 ip4:192.0.2.0/24 include:_spf.example.com -all
コンポーネント:
- v=spf1: バージョン識別子 (必須、常にspf1)
- mechanisms: マッチングメカニズム
- qualifiers: 結果修飾子
- modifiers: 修飾子
メカニズム (Mechanisms)
1. all:
定義: すべてのIPにマッチ
使用: 通常は最後のデフォルトポリシーとして
例:
v=spf1 -all すべてのIP不許可 (最も厳格)
v=spf1 ~all すべてのIPソフト失敗 (推奨)
v=spf1 +all すべてのIP許可 (非推奨!)
2. ip4/ip6:
定義: 明示的なIPアドレスまたは範囲
例:
v=spf1 ip4:203.0.113.1 -all
→ 203.0.113.1のみ許可
v=spf1 ip4:192.0.2.0/24 -all
→ 192.0.2.0-192.0.2.255を許可
v=spf1 ip6:2001:db8::1 -all
→ IPv6アドレスを許可
v=spf1 ip4:203.0.113.0/24 ip4:198.51.100.0/24 -all
→ 複数の範囲を許可
3. a:
定義: 現在のドメインのA/AAAAレコード
例:
v=spf1 a -all
→ example.comのAレコードからのIPを許可
v=spf1 a:mail.example.com -all
→ mail.example.comのAレコードからのIPを許可
v=spf1 a/24 -all
→ example.comのAレコードIPの/24ネットワークを許可
4. mx:
定義: 現在のドメインのMXレコード
例:
v=spf1 mx -all
→ example.comのMXサーバーIPを許可
v=spf1 mx:example.com -all
→ example.comのMXサーバーIPを許可
v=spf1 mx/24 -all
→ MXサーバーの/24ネットワークを許可
5. include:
定義: 別のドメインのSPFレコードを含める
例:
v=spf1 include:_spf.google.com -all
→ Googleメールサーバーを許可 (Gmail for Business)
v=spf1 include:spf.protection.outlook.com -all
→ Microsoft 365メールサーバーを許可
複数のinclude:
v=spf1 include:_spf.google.com include:spf.protection.outlook.com -all
注意: 最大10 DNS検索制限!
6. exists:
定義: 指定されたドメインにAレコードがある場合にマッチ
例:
v=spf1 exists:%\{i}.spamhaus.example.com -all
→ 高度な使用、通常はスパムブラックリストチェック用
マクロ展開:
%\{i} = 送信サーバーIP (逆順)
7. ptr (非推奨):
定義: 逆引きDNSクエリ
例:
v=spf1 ptr:example.com -all
問題点:
❌ パフォーマンスが悪い (逆引きDNSクエリが必要)
❌ 信頼性が低い
❌ RFC明示的に使用非推奨
代替: ip4/ip6またはincludeを使用
修飾子 (Qualifiers)
シンボル | 名前 | 意味 | 推奨使用
---------|------|------|----------
+ | Pass | 合格 (デフォルト) | 認可されたサーバー
- | Fail | 失敗 | メール拒否
~ | SoftFail | ソフト失敗 | 受け入れるがマーク
? | Neutral | 中立 | 明確なポリシーなし
例:
v=spf1 +ip4:203.0.113.1 -all
↑明示的pass ↑明示的fail
v=spf1 ip4:203.0.113.1 ~all
↑デフォルト+ ↑softfail
v=spf1 ?all
↑neutral (SPFなしと同等)
修飾子使用推奨:
+ (Pass):
✓ 認可されたメールサーバー
例: +ip4:203.0.113.1
- (Fail):
✓ 最終的な-all (厳格)
✓ 明示的に特定のIPを禁止
例: -all
~ (SoftFail):
✓ 最終的な~all (緩やか、初期推奨)
✓ 移行期間に使用
例: ~all
? (Neutral):
✗ まれに使用
✗ ポリシーなしと同等
修飾子 (Modifiers)
1. redirect:
定義: 別のドメインのSPFレコードにリダイレクト
例:
example.com: v=spf1 redirect=_spf.example.com
_spf.example.com: v=spf1 ip4:203.0.113.1 -all
目的:
✓ 集中的なSPF管理
✓ 複数のドメインがポリシーを共有
注意:
- redirectの後に他のメカニズムは続けられない
- allと一緒に使用できない
2. exp:
定義: 説明 (SPF失敗時の説明テキスト)
例:
v=spf1 -all exp=explain.example.com
explain.example.com TXTレコード:
"This domain does not send email"
目的:
✓ ユーザーフレンドリーなエラーメッセージを提供
- 実際にはまれに使用される
SPFレコード例
基本設定
1. 単一メールサーバー:
v=spf1 ip4:203.0.113.1 -all
説明:
- 203.0.113.1のみメール送信可能
- 他のIPは拒否される
2. MXレコードの使用:
v=spf1 mx -all
説明:
- ドメインのMXサーバーがメール送信を許可
- MX変更に自動的に適応
3. 複数のIP範囲:
v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/24 -all
説明:
- 2つのクラスCネットワークを許可
- マルチデータセンター展開に適している
サードパーティサービス
4. Google Workspace (Gmail for Business):
v=spf1 include:_spf.google.com -all
説明:
- Googleを使用してメール送信
- GoogleのSPFレコードを含める
5. Microsoft 365:
v=spf1 include:spf.protection.outlook.com -all
6. SendGrid:
v=spf1 include:sendgrid.net -all
7. Mailchimp:
v=spf1 include:servers.mcsv.net -all
混合設定
8. 自前サーバー + サードパーティ:
v=spf1 ip4:203.0.113.1 include:_spf.google.com -all
説明:
- 自前サーバー: 203.0.113.1
- Google Workspace: include
9. 複数のサードパーティサービス:
v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:sendgrid.net -all
注意: 各includeがDNS検索としてカウントされる
10. メールを送信しないドメイン:
v=spf1 -all
説明:
- ドメインはメールを送信しない
- スプーフィングを防止
- 受信専用ドメインに適している
サブドメイン
11. サブドメインの個別設定:
example.com: v=spf1 ip4:203.0.113.1 -all
mail.example.com: v=spf1 include:_spf.google.com -all
説明:
- メインドメインは自前サーバーを使用
- mailサブドメインはGoogleを使用
12. サブドメインの継承 (SPFレコードなしの場合):
mail.example.comにSPFレコードがない場合:
→ example.comのSPFレコードを使用
継承を望まない場合:
mail.example.com: v=spf1 -all
SPF検証フロー
受信者検証ステップ
// SPF検証擬似コード
async function checkSPF(clientIP, sender, helo) {
// 1. ドメインを抽出
const domain = sender.split('@')[1]; // [email protected] → example.com
// 2. SPFレコードを照会
const spfRecord = await queryDNS(domain, 'TXT', 'v=spf1');
if (!spfRecord) {
return 'none'; // SPFレコードなし
}
// 3. SPFレコードを解析
const mechanisms = parseSPF(spfRecord);
// 4. メカニズムを順番にチェック
for (const mechanism of mechanisms) {
const result = await evaluateMechanism(mechanism, clientIP, domain);
if (result !== null) {
return result; // マッチが見つかった、結果を返す
}
}
return 'neutral'; // マッチなし
}
// 単一メカニズムの評価
async function evaluateMechanism(mechanism, clientIP, domain) {
const { type, value, qualifier } = mechanism;
switch (type) {
case 'ip4':
if (isInIPRange(clientIP, value)) {
return mapQualifier(qualifier); // +pass, -fail, ~softfail
}
break;
case 'a':
const aRecords = await queryDNS(value || domain, 'A');
if (aRecords.includes(clientIP)) {
return mapQualifier(qualifier);
}
break;
case 'mx':
const mxRecords = await queryDNS(value || domain, 'MX');
for (const mx of mxRecords) {
const mxIPs = await queryDNS(mx, 'A');
if (mxIPs.includes(clientIP)) {
return mapQualifier(qualifier);
}
}
break;
case 'include':
const includeResult = await checkSPF(clientIP, `user@${value}`, null);
if (includeResult === 'pass') {
return mapQualifier(qualifier);
}
break;
case 'all':
return mapQualifier(qualifier);
}
return null; // マッチなし
}
SPF結果
戻り値 | 意味 | 推奨処理
---------------|-------------------|------------------
none | SPFレコードなし | 受け入れる (信頼度低)
neutral | 明示的にポリシー | 受け入れる
| なし |
pass | 合格 | 受け入れる
fail | 失敗 | 拒否する
softfail | ソフト失敗 | 受け入れるがマーク
temperror | 一時エラー | 後で再試行
permerror | 永続エラー | 拒否する
SMTP応答例:
pass: 250 OK (SPF pass)
fail: 550 SPF check failed
softfail: 250 OK (X-SPF: softfailヘッダー追加)
DNS検索制限
10検索制限
問題:
SPF検証は最大10回のDNS検索を実行
制限超過 → permerror (永続エラー)
10検索にカウントされる:
✓ include
✓ a
✓ mx
✓ exists
✓ redirect
カウントされない:
✗ ip4/ip6 (直接マッチング)
✗ all (直接マッチング)
例 - 制限超過:
v=spf1
include:_spf1.example.com ← 1
include:_spf2.example.com ← 2
include:_spf3.example.com ← 3
include:_spf4.example.com ← 4
include:_spf5.example.com ← 5
include:_spf6.example.com ← 6
include:_spf7.example.com ← 7
include:_spf8.example.com ← 8
include:_spf9.example.com ← 9
include:_spf10.example.com ← 10
include:_spf11.example.com ← 超過! permerror
-all
includeがさらにincludeを含む場合、それらもカウントされる!
解決策:
1. a/mxの代わりにip4/ip6を使用
❌ v=spf1 a mx -all (2検索)
✓ v=spf1 ip4:203.0.113.1 ip4:198.51.100.1 -all (0検索)
2. includeをマージ
❌ include:service1.com include:service2.com
✓ すべてのIPを含む独自のSPFレコードを維持
3. SPF Flattening (SPF平坦化)
定期的にincludeからIPを照会し、ip4/ip6に変換
SPF Flatteningツール
// SPF Flattening例
async function flattenSPF(domain) {
const spf = await querySPF(domain);
const ips = [];
// SPFを解析
const mechanisms = parseSPF(spf);
for (const mech of mechanisms) {
if (mech.type === 'ip4' || mech.type === 'ip6') {
ips.push(mech.value);
} else if (mech.type === 'include') {
// 再帰的にincludeを照会
const includeIPs = await resolveInclude(mech.value);
ips.push(...includeIPs);
} else if (mech.type === 'a') {
const aRecords = await queryDNS(mech.value, 'A');
ips.push(...aRecords.map(ip => `ip4:${ip}`));
} else if (mech.type === 'mx') {
const mxRecords = await queryDNS(mech.value, 'MX');
for (const mx of mxRecords) {
const mxIPs = await queryDNS(mx, 'A');
ips.push(...mxIPs.map(ip => `ip4:${ip}`));
}
}
}
// 平坦化されたSPFを生成
return `v=spf1 ${ips.join(' ')} -all`;
}
// 使用
const flatSPF = await flattenSPF('example.com');
console.log('Flattened SPF:', flatSPF);
// v=spf1 ip4:74.125.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19 -all
実用ツール
SPFレコードジェネレーター
class SPFBuilder {
constructor(domain) {
this.domain = domain;
this.mechanisms = [];
this.modifier = null;
}
addIP(ip) {
if (ip.includes(':')) {
this.mechanisms.push(`ip6:${ip}`);
} else {
this.mechanisms.push(`ip4:${ip}`);
}
return this;
}
addIPRange(cidr) {
if (cidr.includes(':')) {
this.mechanisms.push(`ip6:${cidr}`);
} else {
this.mechanisms.push(`ip4:${cidr}`);
}
return this;
}
useA() {
this.mechanisms.push('a');
return this;
}
useMX() {
this.mechanisms.push('mx');
return this;
}
include(domain) {
this.mechanisms.push(`include:${domain}`);
return this;
}
setDefault(qualifier) {
const qualifiers = { pass: '+all', fail: '-all', softfail: '~all', neutral: '?all' };
this.mechanisms.push(qualifiers[qualifier] || '-all');
return this;
}
redirect(domain) {
this.modifier = `redirect=${domain}`;
return this;
}
build() {
let spf = 'v=spf1';
if (this.mechanisms.length > 0) {
spf += ' ' + this.mechanisms.join(' ');
}
if (this.modifier) {
spf += ' ' + this.modifier;
}
return spf;
}
countLookups() {
let count = 0;
for (const mech of this.mechanisms) {
if (mech.startsWith('include:') || mech.startsWith('a') ||
mech.startsWith('mx') || mech.startsWith('exists:')) {
count++;
}
}
if (this.modifier && this.modifier.startsWith('redirect=')) {
count++;
}
return count;
}
}
// 使用例
const spf = new SPFBuilder('example.com')
.addIP('203.0.113.1')
.addIPRange('192.0.2.0/24')
.include('_spf.google.com')
.include('spf.protection.outlook.com')
.setDefault('fail')
.build();
console.log('SPF Record:', spf);
console.log('DNS Lookups:', spf.countLookups());
// 出力:
// SPF Record: v=spf1 ip4:203.0.113.1 ip4:192.0.2.0/24 include:_spf.google.com include:spf.protection.outlook.com -all
// DNS Lookups: 2
SPF検証ツール
const dns = require('dns').promises;
class SPFChecker {
async check(domain, ip) {
try {
// SPFレコードを照会
const records = await dns.resolveTxt(domain);
const spfRecord = records.find(r =>
r.join('').startsWith('v=spf1')
);
if (!spfRecord) {
return { result: 'none', message: 'No SPF record found' };
}
const spf = spfRecord.join('');
console.log('SPF Record:', spf);
// 解析して検証
const result = await this.evaluate(spf, ip, domain);
return result;
} catch (err) {
return { result: 'temperror', message: err.message };
}
}
async evaluate(spf, ip, domain, depth = 0) {
if (depth > 10) {
return { result: 'permerror', message: 'Too many DNS lookups' };
}
const parts = spf.split(/\s+/);
for (const part of parts) {
if (part === 'v=spf1') continue;
// 修飾子を抽出
let qualifier = '+';
let mechanism = part;
if (['+', '-', '~', '?'].includes(part[0])) {
qualifier = part[0];
mechanism = part.slice(1);
}
// メカニズムをチェック
if (mechanism.startsWith('ip4:')) {
const range = mechanism.slice(4);
if (this.isIPInRange(ip, range)) {
return this.mapResult(qualifier);
}
} else if (mechanism.startsWith('include:')) {
const includeDomain = mechanism.slice(8);
const includeRecords = await dns.resolveTxt(includeDomain);
const includeSPF = includeRecords.find(r =>
r.join('').startsWith('v=spf1')
);
if (includeSPF) {
const result = await this.evaluate(
includeSPF.join(''),
ip,
includeDomain,
depth + 1
);
if (result.result === 'pass') {
return this.mapResult(qualifier);
}
}
} else if (mechanism === 'all' || mechanism === '-all' ||
mechanism === '~all' || mechanism === '?all') {
return this.mapResult(qualifier);
}
// 他のメカニズムをここに追加可能...
}
return { result: 'neutral', message: 'No match found' };
}
isIPInRange(ip, range) {
// 簡易版、本番環境では完全な実装が必要
if (!range.includes('/')) {
return ip === range;
}
// CIDRマッチング実装は省略...
return false;
}
mapResult(qualifier) {
const map = {
'+': { result: 'pass', message: 'SPF pass' },
'-': { result: 'fail', message: 'SPF fail' },
'~': { result: 'softfail', message: 'SPF softfail' },
'?': { result: 'neutral', message: 'SPF neutral' }
};
return map[qualifier] || map['+'];
}
}
// 使用
const checker = new SPFChecker();
const result = await checker.check('example.com', '203.0.113.1');
console.log('SPF Check Result:', result);
デプロイのベストプラクティス
1. 段階的デプロイ
フェーズ1: 監視モード
v=spf1 ?all
または
v=spf1 ~all
目的: データを収集し、どのサーバーがメールを送信しているか観察
期間: 2-4週間
フェーズ2: ソフト失敗
v=spf1 ip4:x.x.x.x include:provider.com ~all
目的: 認可されていないメールをマークするが拒否しない
期間: 4-8週間
フェーズ3: 厳格モード
v=spf1 ip4:x.x.x.x include:provider.com -all
目的: 認可されていないメールを拒否
2. よくある間違い
❌ 間違い1: -allを忘れる
v=spf1 ip4:203.0.113.1
→ v=spf1 ip4:203.0.113.1 ?all と同等
→ どのIPもneutral
✓ 正しい:
v=spf1 ip4:203.0.113.1 -all
❌ 間違い2: 複数のSPFレコード
example.com TXT "v=spf1 ip4:203.0.113.1 -all"
example.com TXT "v=spf1 include:provider.com -all"
→ permerror
✓ 正しい: 1つにマージ
v=spf1 ip4:203.0.113.1 include:provider.com -all
❌ 間違い3: 10検索以上
v=spf1 include:a include:b include:c ... (多すぎる)
✓ 正しい: ip4を直接使用またはflattening
❌ 間違い4: ptrの使用
v=spf1 ptr:example.com -all
→ パフォーマンスが悪い、非推奨
✓ 正しい: ip4またはincludeを使用
3. テストと検証
# コマンドラインテストツール
# 1. SPFレコードを照会
dig example.com TXT | grep "v=spf1"
または
nslookup -type=TXT example.com
# 2. オンラインツールを使用
# - https://mxtoolbox.com/spf.aspx
# - https://www.kitterman.com/spf/validate.html
# 3. テストメールを送信
# 自分のメールアドレスに送信し、ヘッダーを確認:
# Received-SPF: pass ...
DKIM/DMARCとの統合
完全なメールセキュリティ設定
1. SPFレコード:
example.com. IN TXT "v=spf1 ip4:203.0.113.1 include:_spf.google.com -all"
2. DKIMレコード:
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0..."
3. DMARCレコード:
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]"
結果:
- SPFが送信サーバーを検証 ✓
- DKIMがメールコンテンツを検証 ✓
- DMARCがポリシーを統一 ✓
→ 三重保護!
参考資料
SPF関連RFC:
- [RFC 7208] SPF ← この文書
- [RFC 7489] DMARC
- [RFC 6376] DKIM
関連リソース:
まとめ: SPFはメールセキュリティの最前線です。DNSレコードを通じて送信サーバーが認可され、メールスプーフィングが効果的に防止されます。DKIMとDMARCと組み合わせることで、完全なメールセキュリティシステムが構築されます。忘れないでください: ソフト失敗から始めて、徐々に厳格モードに強化し、10 DNS検索制限に注意してください!