Appendix C. Further Testing Advice (Weitere Testempfehlungen)
Appendix C. Further Testing Advice (Weitere Testempfehlungen)
Dieser Anhang bietet detaillierte Empfehlungen zum Testen und Validieren von SPF-Implementierungen.
C.1 Testen von SPF-Einträgen
C.1.1 Grundlegende Validierung
DNS-Abfragetest:
# SPF-Eintrag abfragen
dig example.com TXT | grep "v=spf1"
# Oder nslookup verwenden
nslookup -type=TXT example.com
# Oder host verwenden
host -t TXT example.com
Validierungspunkte:
- ✓ Eintrag beginnt mit
v=spf1 - ✓ Nur ein SPF-Eintrag
- ✓ Syntax korrekt (keine Rechtschreibfehler)
- ✓ Eintragsgröße < 512 Oktetts
C.1.2 Online-Validierungstools
Empfohlene Online-Tools:
-
Kitterman SPF Validator
- URL:
https://www.kitterman.com/spf/validate.html - Funktionen: Syntaxprüfung, DNS-Abfragezählung, Parsing-Details
- URL:
-
MXToolbox SPF Record Check
- URL:
https://mxtoolbox.com/spf.aspx - Funktionen: Eintragsvalidierung, Warnungen und Fehlererkennung
- URL:
-
DMARCIAN SPF Inspector
- URL:
https://dmarcian.com/spf-survey/ - Funktionen: Tiefenanalyse, Optimierungsempfehlungen
- URL:
Prüfpunkte:
✓ Syntax-Korrektheit
✓ DNS-Abfrageanzahl (≤ 10)
✓ Void lookup-Anzahl (≤ 2)
✓ Eintragslänge
✓ Mechanismus-Gültigkeit
✓ Include-Ketten-Tiefe
C.2 Testen der SPF-Prüfimplementierung
C.2.1 Unit-Test-Szenarien
Testfall 1: Grundlegende IP-Übereinstimmung
SPF-Eintrag: v=spf1 ip4:192.0.2.1 -all
Test-IP: 192.0.2.1
Erwartetes Ergebnis: pass
Test-IP: 192.0.2.2
Erwartetes Ergebnis: fail
Testfall 2: CIDR-Bereich
SPF-Eintrag: v=spf1 ip4:192.0.2.0/24 -all
Test-IP: 192.0.2.100
Erwartetes Ergebnis: pass
Test-IP: 192.0.3.1
Erwartetes Ergebnis: fail
Testfall 3: MX-Mechanismus
example.com MX-Eintrag: 10 mail.example.com
mail.example.com A-Eintrag: 192.0.2.10
SPF-Eintrag: v=spf1 mx -all
Test-IP: 192.0.2.10
Erwartetes Ergebnis: pass
Testfall 4: Include-Mechanismus
example.com: v=spf1 include:_spf.example.org -all
_spf.example.org: v=spf1 ip4:192.0.2.0/24 -all
Test-IP: 192.0.2.50
Erwartetes Ergebnis: pass
Testfall 5: Soft-Fail
SPF-Eintrag: v=spf1 ip4:192.0.2.1 ~all
Test-IP: 203.0.113.1
Erwartetes Ergebnis: softfail
C.2.2 Grenzfall-Tests
Leerer MAIL FROM:
MAIL FROM: <>
HELO: mail.example.com
Erwartet: HELO-Identität verwenden, local-part ist "postmaster"
Ungültiger Domainname:
MAIL FROM: [email protected]
Erwartetes Ergebnis: none (oder permerror)
DNS-Timeout:
DNS-Timeout simulieren
Erwartetes Ergebnis: temperror
Mehrere SPF-Einträge:
example.com TXT "v=spf1 mx -all"
example.com TXT "v=spf1 a -all"
Erwartetes Ergebnis: permerror
C.2.3 Limit-Tests
DNS-Abfragelimit-Test:
# Pseudocode
def test_dns_lookup_limit():
# SPF-Eintrag mit 11 includes erstellen
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-Eintrags-Limit-Test:
# Domain mit 11 MX-Einträgen erstellen
# Erwartet: mx-Mechanismus gibt permerror zurück
Void Lookup-Test:
# Include erstellen, das NXDOMAIN zurückgibt
# Zählung sollte in void lookup-Limit enthalten sein
C.3 Test-E-Mails senden
C.3.1 Testablauf
Schritt 1: Test-Domain vorbereiten
test.example.com IN TXT "v=spf1 ip4:YOUR_IP -all"
Schritt 2: Test-E-Mail senden
# swaks-Tool verwenden
swaks --to [email protected] \
--from [email protected] \
--server smtp.example.com
# Oder telnet verwenden
telnet smtp.recipient.com 25
HELO test.example.com
MAIL FROM:`<[email protected]>`
RCPT TO:`<[email protected]>`
DATA
Subject: SPF Test
.
QUIT
Schritt 3: E-Mail-Header prüfen
Received-SPF: pass (recipient.com: domain of [email protected]
designates YOUR_IP as permitted sender)
client-ip=YOUR_IP;
[email protected];
C.3.2 Tests für verschiedene Szenarien
Szenario 1: Pass-Test
Konfiguration: Von autorisierter IP senden
Erwarteter Header: Received-SPF: pass
Szenario 2: Fail-Test
Konfiguration: Von nicht autorisierter IP senden
Erwartet: E-Mail wird abgelehnt oder markiert
Erwarteter Header: Received-SPF: fail
Szenario 3: SoftFail-Test
Konfiguration: SPF-Eintrag verwendet ~all
Von nicht autorisierter IP senden
Erwartet: E-Mail wird akzeptiert aber markiert
Erwarteter Header: Received-SPF: softfail
Szenario 4: Neutral-Test
Konfiguration: SPF-Eintrag verwendet ?all
Erwarteter Header: Received-SPF: neutral
C.4 Makro-Tests
C.4.1 Makroerweiterungs-Validierung
Testfall:
Absender: [email protected]
Client-IP: 192.0.2.100
Makro %\{s} → [email protected]
Makro %\{l} → user
Makro %\{o} → example.com
Makro %\{d} → example.com
Makro %\{i} → 192.0.2.100
Makro %\{ir} → 100.2.0.192
Makro %\{d2} → example.com
Makro %\{d1} → com
Komplexer Makro-Test:
SPF: v=spf1 exists:%\{ir}.%\{l}._spf.%\{d} -all
Absender: [email protected]
IP: 192.0.2.100
Erweitert zu: 100.2.0.192.user._spf.example.com
Validierung: A-Eintrag für diesen Domainnamen abfragen
C.4.2 Makro-Trennzeichen-Test
Absender: [email protected]
%\{l} → user+tag
%\{l-} → user-tag (+ wird durch - ersetzt)
%\{lr} → gat+resu (umgekehrt)
%\{lr-} → gat-resu (umgekehrt und ersetzt)
C.5 Leistungstests
C.5.1 Antwortzeitests
Benchmark-Test:
# DNS-Abfragezeit testen
time dig example.com TXT
# Vollständige SPF-Prüfung testen
time spf_check example.com 192.0.2.1 [email protected]
Leistungsziele:
- Einfacher Eintrag (1-2 Mechanismen): < 100ms
- Komplexer Eintrag (mehrere includes): < 500ms
- Maximal erlaubte Zeit: 20 Sekunden
C.5.2 Lasttests
Hohe Parallelität simulieren:
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 parallele SPF-Prüfungen testen
start = time.time()
results = check_spf_concurrent(1000)
end = time.time()
print(f"1000 Prüfungen abgeschlossen in: {end - start} Sekunden")
print(f"Durchschnitt pro Prüfung: {(end - start) / 1000 * 1000}ms")
C.6 Regressionstests
C.6.1 Test-Suite
Minimaler Testsatz:
1. Grundlegende pass/fail-Szenarien
2. Alle 7 Mechanismen (all, include, a, mx, ptr, ip4, ip6, exists)
3. Alle 4 Qualifizierer (+, -, ~, ?)
4. Beide Modifikatoren (redirect, exp)
5. Makroerweiterung
6. Fehlerbehandlung (temperror, permerror)
7. Limit-Tests (DNS-Abfragen, Timeout)
RFC 7208-Test-Suite:
# Offizielle Test-Suite verwenden
git clone https://github.com/openspf/openspf.git
cd openspf/tests
./run_tests.sh
C.6.2 Continuous Integration
CI/CD-Konfigurationsbeispiel:
# .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 Häufige Probleme testen
C.7.1 Veraltete E-Mail-Funktionen
Source Routing:
MAIL FROM:<@relay.example.com:[email protected]>
Erwartet: example.com korrekt als Domain extrahieren
%-hack:
MAIL FROM:<user%[email protected]>
Erwartet: Korrekt handhaben oder ablehnen
Bang Path:
MAIL FROM:<[email protected]>
Erwartet: Korrekt handhaben oder ablehnen
C.7.2 Internationalisierte Domainnamen
IDN-Test:
Domain: münchen.de
A-label: xn--mnchen-3ya.de
SPF-Eintrag: Muss A-label verwenden
Test: Sicherstellen, dass korrekt konvertiert und abgefragt wird
C.7.3 IPv6-Tests
IPv6-Adressformate:
Vollformat: 2001:0db8:0000:0000:0000:0000:0000:0001
Komprimiertes Format: 2001:db8::1
IPv4-mapped: ::ffff:192.0.2.1
SPF: v=spf1 ip6:2001:db8::/32 -all
Test: Alle Formate sollten korrekt übereinstimmen
C.8 Debug-Techniken
C.8.1 Ausführliche Protokollierung aktivieren
Sender:
# 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
Empfänger:
# SPF-Debug-Protokollierung aktivieren
spf_debug_level = 5
tail -f /var/log/mail.log | grep SPF
C.8.2 dig-Verfolgung verwenden
SPF-Abfragen verfolgen:
# Vollständigen DNS-Auflösungsprozess anzeigen
dig +trace example.com TXT
# Include-Kette anzeigen
dig _spf.google.com TXT
dig _netblocks.google.com TXT
C.8.3 SPF-Debug-Tools verwenden
Python pyspf:
import spf
result, explanation = spf.check2(
i='192.0.2.1',
s='[email protected]',
h='mail.example.com'
)
print(f"Ergebnis: {result}")
print(f"Erklärung: {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 "Ergebnis: " . $result->code . "\n";
C.9 Produktionsumgebung-Überwachung
C.9.1 Überwachungsmetriken
Schlüsselmetriken:
- SPF pass-Rate
- SPF fail-Rate
- SPF softfail-Rate
- SPF temperror-Rate (sollte sehr niedrig sein)
- SPF permerror-Rate (sollte 0 sein)
- Durchschnittliche Prüfzeit
- DNS-Timeout-Rate
C.9.2 Alarmeinstellungen
Empfohlene Alarmregeln:
- permerror-Rate > 0%: Sofortiger Alarm (SPF-Eintragsfehler)
- temperror-Rate > 5%: Warnung (DNS-Probleme)
- fail-Rate plötzlicher Anstieg > 20%: Alarm (mögliche Konfigurationsänderung oder Angriff)
- Durchschnittliche Prüfzeit > 1 Sekunde: Warnung (Leistungsproblem)
C.9.3 Protokollanalyse
SPF-Fehler analysieren:
# IP-Adressen mit SPF fail extrahieren
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'client-ip=\K[0-9.]+' | \
sort | uniq -c | sort -rn
# Absenderdomains mit fail analysieren
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'envelope-from=\K[^;]+' | \
cut -d@ -f2 | sort | uniq -c | sort -rn
Bericht generieren:
# SPF-Statistikbericht-Generator
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}%)")