メインコンテンツまでスキップ

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 オンライン検証ツール

推奨されるオンラインツール:

  1. Kitterman SPF Validator

    • URL: https://www.kitterman.com/spf/validate.html
    • 機能: 構文チェック、DNSクエリカウント、解析詳細
  2. MXToolbox SPF Record Check

    • URL: https://mxtoolbox.com/spf.aspx
    • 機能: レコード検証、警告とエラーの検出
  3. DMARCIAN SPF Inspector

    • URL: https://dmarcian.com/spf-survey/
    • 機能: 深層分析、最適化の推奨事項

チェック項目:

✓ 構文の正確性
✓ 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:`&lt;[email protected]&gt;`
RCPT TO:`&lt;[email protected]&gt;`
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:&lt;@relay.example.com:[email protected]>
期待: example.comをドメインとして正しく抽出

%-hack:

MAIL FROM:&lt;user%[email protected]>
期待: 正しく処理または拒否

Bang Path:

MAIL FROM:&lt;[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}%)")