Appendix C. Further Testing Advice (さらなるテストアドバイス)
Appendix C. Further Testing Advice (さらなるテストアドバイス)
この付録は、SPF実装のテストと検証に関する詳細な推奨事項を提供します。
C.1 SPFレコードのテスト
C.1.1 基本的な検証
DNSクエリテスト:
# SPFレコードをクエリ
dig example.com TXT | grep "v=spf1"
# またはnslookupを使用
nslookup -type=TXT example.com
# またはhostを使用
host -t TXT example.com
検証ポイント:
- ✓ レコードが
v=spf1で始まる - ✓ SPFレコードは1つのみ
- ✓ 構文が正しい(誤字がない)
- ✓ レコードサイズ < 512オクテット
C.1.2 オンライン検証ツール
推奨されるオンラインツール:
-
Kitterman SPF Validator
- URL:
https://www.kitterman.com/spf/validate.html - 機能: 構文チェック、DNSクエリカウント、解析詳細
- URL:
-
MXToolbox SPF Record Check
- URL:
https://mxtoolbox.com/spf.aspx - 機能: レコード検証、警告とエラーの検出
- URL:
-
DMARCIAN SPF Inspector
- URL:
https://dmarcian.com/spf-survey/ - 機能: 深層分析、最適化の推奨事項
- URL:
チェック項目:
✓ 構文の正確性
✓ DNSクエリ数(≤ 10)
✓ Void lookup数(≤ 2)
✓ レコード長
✓ メカニズムの有効性
✓ Includeチェーンの深さ
C.2 SPFチェック実装のテスト
C.2.1 ユニットテストシナリオ
テストケース1: 基本的なIP一致
SPFレコード: v=spf1 ip4:192.0.2.1 -all
テストIP: 192.0.2.1
期待される結果: pass
テストIP: 192.0.2.2
期待される結果: fail
テストケース2: CIDR範囲
SPFレコード: v=spf1 ip4:192.0.2.0/24 -all
テストIP: 192.0.2.100
期待される結果: pass
テストIP: 192.0.3.1
期待される結果: fail
テストケース3: MXメカニズム
example.com MXレコード: 10 mail.example.com
mail.example.com Aレコード: 192.0.2.10
SPFレコード: v=spf1 mx -all
テストIP: 192.0.2.10
期待される結果: pass
テストケース4: Includeメカニズム
example.com: v=spf1 include:_spf.example.org -all
_spf.example.org: v=spf1 ip4:192.0.2.0/24 -all
テストIP: 192.0.2.50
期待される結果: pass
テストケース5: ソフトフェイル
SPFレコード: v=spf1 ip4:192.0.2.1 ~all
テストIP: 203.0.113.1
期待される結果: softfail
C.2.2 境界ケーステスト
空のMAIL FROM:
MAIL FROM: <>
HELO: mail.example.com
期待: HELO識別を使用、local-partは"postmaster"
無効なドメイン名:
MAIL FROM: [email protected]
期待される結果: none (またはpermerror)
DNSタイムアウト:
DNSタイムアウトをシミュレート
期待される結果: temperror
複数のSPFレコード:
example.com TXT "v=spf1 mx -all"
example.com TXT "v=spf1 a -all"
期待される結果: permerror
C.2.3 制限テスト
DNSクエリ制限テスト:
# 疑似コード
def test_dns_lookup_limit():
# 11個のincludeを含むSPFレコードを作成
spf = "v=spf1"
for i in range(11):
spf += f" include:domain{i}.example.com"
spf += " -all"
result = check_spf(spf, "192.0.2.1", "[email protected]")
assert result == "permerror"
MXレコード制限テスト:
# 11個のMXレコードを持つドメインを作成
# 期待: mxメカニズムがpermerrorを返す
Void Lookupテスト:
# NXDOMAINを返すincludeを作成
# カウントはvoid lookup制限に含まれるべき
C.3 テストメールの送信
C.3.1 テストフロー
ステップ1: テストドメインを準備
test.example.com IN TXT "v=spf1 ip4:YOUR_IP -all"
ステップ2: テストメールを送信
# swaksツールを使用
swaks --to [email protected] \
--from [email protected] \
--server smtp.example.com
# またはtelnetを使用
telnet smtp.recipient.com 25
HELO test.example.com
MAIL FROM:`<[email protected]>`
RCPT TO:`<[email protected]>`
DATA
Subject: SPF Test
.
QUIT
ステップ3: メールヘッダーを確認
Received-SPF: pass (recipient.com: domain of [email protected]
designates YOUR_IP as permitted sender)
client-ip=YOUR_IP;
[email protected];
C.3.2 異なるシナリオのテスト
シナリオ1: Passテスト
設定: 承認されたIPから送信
期待されるヘッダー: Received-SPF: pass
シナリオ2: Failテスト
設定: 承認されていないIPから送信
期待: メールが拒否またはマーク
期待されるヘッダー: Received-SPF: fail
シナリオ3: SoftFailテスト
設定: SPFレコードが~allを使用
承認されていないIPから送信
期待: メールは受け入れられるがマーク
期待されるヘッダー: Received-SPF: softfail
シナリオ4: Neutralテスト
設定: SPFレコードが?allを使用
期待されるヘッダー: Received-SPF: neutral
C.4 マクロテスト
C.4.1 マクロ展開の検証
テストケース:
送信者: [email protected]
クライアントIP: 192.0.2.100
マクロ %\{s} → [email protected]
マクロ %\{l} → user
マクロ %\{o} → example.com
マクロ %\{d} → example.com
マクロ %\{i} → 192.0.2.100
マクロ %\{ir} → 100.2.0.192
マクロ %\{d2} → example.com
マクロ %\{d1} → com
複雑なマクロテスト:
SPF: v=spf1 exists:%\{ir}.%\{l}._spf.%\{d} -all
送信者: [email protected]
IP: 192.0.2.100
展開先: 100.2.0.192.user._spf.example.com
検証: このドメイン名のAレコードをクエリ
C.4.2 マクロ区切り文字テスト
送信者: [email protected]
%\{l} → user+tag
%\{l-} → user-tag (+が-に置き換えられる)
%\{lr} → gat+resu (逆順)
%\{lr-} → gat-resu (逆順かつ置換)
C.5 パフォーマンステスト
C.5.1 応答時間テスト
ベンチマークテスト:
# DNSクエリ時間をテスト
time dig example.com TXT
# 完全なSPFチェックをテスト
time spf_check example.com 192.0.2.1 [email protected]
パフォーマンス目標:
- シンプルなレコード(1-2メカニズム): < 100ms
- 複雑なレコード(複数のinclude): < 500ms
- 最大許容時間: 20秒
C.5.2 負荷テスト
高並行性をシミュレート:
import concurrent.futures
import time
def check_spf_concurrent(num_checks):
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
futures = [
executor.submit(check_spf, "example.com", "192.0.2.1", "[email protected]")
for _ in range(num_checks)
]
results = [f.result() for f in futures]
return results
# 1000件の並行SPFチェックをテスト
start = time.time()
results = check_spf_concurrent(1000)
end = time.time()
print(f"1000件のチェックが完了: {end - start}秒")
print(f"平均1件あたり: {(end - start) / 1000 * 1000}ms")
C.6 リグレッションテスト
C.6.1 テストスイート
最小テストセット:
1. 基本的なpass/failシナリオ
2. すべての7つのメカニズム(all, include, a, mx, ptr, ip4, ip6, exists)
3. すべての4つの修飾子(+, -, ~, ?)
4. 両方の修飾子(redirect, exp)
5. マクロ展開
6. エラー処理(temperror, permerror)
7. 制限テスト(DNSクエリ、タイムアウト)
RFC 7208テストスイート:
# 公式テストスイートを使用
git clone https://github.com/openspf/openspf.git
cd openspf/tests
./run_tests.sh
C.6.2 継続的インテグレーション
CI/CD設定例:
# .github/workflows/spf-tests.yml
name: SPF Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run SPF Tests
run: |
python -m pytest tests/test_spf.py
python tests/validate_spf_records.py
C.7 一般的な問題のテスト
C.7.1 古いメール機能
ソースルーティング:
MAIL FROM:<@relay.example.com:[email protected]>
期待: example.comをドメインとして正しく抽出
%-hack:
MAIL FROM:<user%[email protected]>
期待: 正しく処理または拒否
Bang Path:
MAIL FROM:<[email protected]>
期待: 正しく処理または拒否
C.7.2 国際化ドメイン名
IDNテスト:
ドメイン: münchen.de
A-label: xn--mnchen-3ya.de
SPFレコード: A-labelを使用する必要がある
テスト: 正しい変換とクエリを確認
C.7.3 IPv6テスト
IPv6アドレスフォーマット:
完全フォーマット: 2001:0db8:0000:0000:0000:0000:0000:0001
圧縮フォーマット: 2001:db8::1
IPv4マップ: ::ffff:192.0.2.1
SPF: v=spf1 ip6:2001:db8::/32 -all
テスト: すべてのフォーマットが正しく一致するべき
C.8 デバッグテクニック
C.8.1 詳細ログを有効化
送信者:
# Postfix
postconf -e "smtpd_sender_login_maps = hash:/etc/postfix/sender_login"
postconf -e "smtpd_sender_restrictions = reject_sender_login_mismatch"
tail -f /var/log/mail.log
受信者:
# SPFデバッグログを有効化
spf_debug_level = 5
tail -f /var/log/mail.log | grep SPF
C.8.2 digトレースを使用
SPFクエリをトレース:
# 完全なDNS解決プロセスを表示
dig +trace example.com TXT
# Includeチェーンを表示
dig _spf.google.com TXT
dig _netblocks.google.com TXT
C.8.3 SPFデバッグツールを使用
Python pyspf:
import spf
result, explanation = spf.check2(
i='192.0.2.1',
s='[email protected]',
h='mail.example.com'
)
print(f"結果: {result}")
print(f"説明: {explanation}")
Perl Mail::SPF:
use Mail::SPF;
my $spf_server = Mail::SPF::Server->new();
my $request = Mail::SPF::Request->new(
versions => [1],
scope => 'mfrom',
identity => '[email protected]',
ip_address => '192.0.2.1',
helo_identity => 'mail.example.com'
);
my $result = $spf_server->process($request);
print "結果: " . $result->code . "\n";
C.9 本番環境の監視
C.9.1 監視メトリクス
主要メトリクス:
- SPF passレート
- SPF failレート
- SPF softfailレート
- SPF temperrorレート(非常に低いはず)
- SPF permerrorレート(0であるべき)
- 平均チェック時間
- DNSタイムアウトレート
C.9.2 アラート設定
推奨されるアラートルール:
- permerrorレート > 0%: 即座にアラート(SPFレコードエラー)
- temperrorレート > 5%: 警告(DNS問題)
- failレートの突然の上昇 > 20%: アラート(設定変更または攻撃の可能性)
- 平均チェック時間 > 1秒: 警告(パフォーマンス問題)
C.9.3 ログ分析
SPF失敗を分析:
# SPF failのIPアドレスを抽出
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'client-ip=\K[0-9.]+' | \
sort | uniq -c | sort -rn
# failした送信者ドメインを分析
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'envelope-from=\K[^;]+' | \
cut -d@ -f2 | sort | uniq -c | sort -rn
レポートを生成:
# SPF統計レポートジェネレーター
def generate_spf_report(log_file):
results = {
'pass': 0, 'fail': 0, 'softfail': 0,
'neutral': 0, 'none': 0,
'temperror': 0, 'permerror': 0
}
with open(log_file) as f:
for line in f:
if 'Received-SPF:' in line:
for result in results:
if f'Received-SPF: {result}' in line:
results[result] += 1
total = sum(results.values())
for result, count in results.items():
percentage = (count / total * 100) if total > 0 else 0
print(f"{result}: {count} ({percentage:.2f}%)")