Appendix C. Further Testing Advice (Ulteriori consigli per i test)
Appendix C. Further Testing Advice (Ulteriori consigli per i test)
Questa appendice fornisce raccomandazioni dettagliate per testare e validare le implementazioni SPF.
C.1 Test dei record SPF
C.1.1 Validazione di base
Test di query DNS:
# Interrogare il record SPF
dig example.com TXT | grep "v=spf1"
# O utilizzare nslookup
nslookup -type=TXT example.com
# O utilizzare host
host -t TXT example.com
Punti di validazione:
- ✓ Il record inizia con
v=spf1 - ✓ Solo un record SPF
- ✓ Sintassi corretta (nessun errore di battitura)
- ✓ Dimensione del record < 512 ottetti
C.1.2 Strumenti di validazione online
Strumenti online consigliati:
-
Kitterman SPF Validator
- URL:
https://www.kitterman.com/spf/validate.html - Funzionalità: Verifica della sintassi, conteggio query DNS, dettagli di analisi
- URL:
-
MXToolbox SPF Record Check
- URL:
https://mxtoolbox.com/spf.aspx - Funzionalità: Validazione record, rilevamento avvisi ed errori
- URL:
-
DMARCIAN SPF Inspector
- URL:
https://dmarcian.com/spf-survey/ - Funzionalità: Analisi approfondita, raccomandazioni di ottimizzazione
- URL:
Elementi da verificare:
✓ Correttezza della sintassi
✓ Numero di query DNS (≤ 10)
✓ Numero di void lookup (≤ 2)
✓ Lunghezza del record
✓ Validità dei meccanismi
✓ Profondità della catena include
C.2 Test dell'implementazione della verifica SPF
C.2.1 Scenari di test unitari
Caso di test 1: Corrispondenza IP di base
Record SPF: v=spf1 ip4:192.0.2.1 -all
IP di test: 192.0.2.1
Risultato atteso: pass
IP di test: 192.0.2.2
Risultato atteso: fail
Caso di test 2: Range CIDR
Record SPF: v=spf1 ip4:192.0.2.0/24 -all
IP di test: 192.0.2.100
Risultato atteso: pass
IP di test: 192.0.3.1
Risultato atteso: fail
Caso di test 3: Meccanismo MX
example.com record MX: 10 mail.example.com
mail.example.com record A: 192.0.2.10
Record SPF: v=spf1 mx -all
IP di test: 192.0.2.10
Risultato atteso: pass
Caso di test 4: Meccanismo Include
example.com: v=spf1 include:_spf.example.org -all
_spf.example.org: v=spf1 ip4:192.0.2.0/24 -all
IP di test: 192.0.2.50
Risultato atteso: pass
Caso di test 5: Soft-fail
Record SPF: v=spf1 ip4:192.0.2.1 ~all
IP di test: 203.0.113.1
Risultato atteso: softfail
C.2.2 Test di casi limite
MAIL FROM vuoto:
MAIL FROM: <>
HELO: mail.example.com
Atteso: Utilizzare identità HELO, local-part è "postmaster"
Nome di dominio non valido:
MAIL FROM: [email protected]
Risultato atteso: none (o permerror)
Timeout DNS:
Simulare un timeout DNS
Risultato atteso: temperror
Record SPF multipli:
example.com TXT "v=spf1 mx -all"
example.com TXT "v=spf1 a -all"
Risultato atteso: permerror
C.2.3 Test di limiti
Test del limite di query DNS:
# Pseudo-codice
def test_dns_lookup_limit():
# Creare un record SPF con 11 include
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"
Test del limite di record MX:
# Creare un dominio con 11 record MX
# Atteso: il meccanismo mx restituisce permerror
Test di Void Lookup:
# Creare un include che restituisce NXDOMAIN
# Il conteggio dovrebbe essere incluso nel limite void lookup
C.3 Invio di e-mail di test
C.3.1 Processo di test
Passo 1: Preparare il dominio di test
test.example.com IN TXT "v=spf1 ip4:YOUR_IP -all"
Passo 2: Inviare un'e-mail di test
# Utilizzare lo strumento swaks
swaks --to [email protected] \
--from [email protected] \
--server smtp.example.com
# O utilizzare telnet
telnet smtp.recipient.com 25
HELO test.example.com
MAIL FROM:`<[email protected]>`
RCPT TO:`<[email protected]>`
DATA
Subject: SPF Test
.
QUIT
Passo 3: Verificare le intestazioni dell'e-mail
Received-SPF: pass (recipient.com: domain of [email protected]
designates YOUR_IP as permitted sender)
client-ip=YOUR_IP;
[email protected];
C.3.2 Test di diversi scenari
Scenario 1: Test Pass
Configurazione: Inviare da IP autorizzato
Intestazione attesa: Received-SPF: pass
Scenario 2: Test Fail
Configurazione: Inviare da IP non autorizzato
Atteso: L'e-mail viene rifiutata o contrassegnata
Intestazione attesa: Received-SPF: fail
Scenario 3: Test SoftFail
Configurazione: Il record SPF utilizza ~all
Inviare da IP non autorizzato
Atteso: L'e-mail viene accettata ma contrassegnata
Intestazione attesa: Received-SPF: softfail
Scenario 4: Test Neutral
Configurazione: Il record SPF utilizza ?all
Intestazione attesa: Received-SPF: neutral
C.4 Test delle macro
C.4.1 Validazione dell'espansione delle macro
Caso di test:
Mittente: [email protected]
IP client: 192.0.2.100
Macro %\{s} → [email protected]
Macro %\{l} → user
Macro %\{o} → example.com
Macro %\{d} → example.com
Macro %\{i} → 192.0.2.100
Macro %\{ir} → 100.2.0.192
Macro %\{d2} → example.com
Macro %\{d1} → com
Test di macro complessa:
SPF: v=spf1 exists:%\{ir}.%\{l}._spf.%\{d} -all
Mittente: [email protected]
IP: 192.0.2.100
Espanso a: 100.2.0.192.user._spf.example.com
Validazione: Interrogare il record A per questo nome di dominio
C.4.2 Test di delimitatori delle macro
Mittente: [email protected]
%\{l} → user+tag
%\{l-} → user-tag (+ sostituito da -)
%\{lr} → gat+resu (invertito)
%\{lr-} → gat-resu (invertito e sostituito)
C.5 Test di prestazioni
C.5.1 Test di tempo di risposta
Test di riferimento:
# Testare il tempo di query DNS
time dig example.com TXT
# Testare la verifica SPF completa
time spf_check example.com 192.0.2.1 [email protected]
Obiettivi di prestazioni:
- Record semplice (1-2 meccanismi): < 100ms
- Record complesso (più include): < 500ms
- Tempo massimo consentito: 20 secondi
C.5.2 Test di carico
Simulare alta concorrenza:
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
# Testare 1000 verifiche SPF concorrenti
start = time.time()
results = check_spf_concurrent(1000)
end = time.time()
print(f"1000 verifiche completate in: {end - start} secondi")
print(f"Media per verifica: {(end - start) / 1000 * 1000}ms")
C.6 Test di regressione
C.6.1 Suite di test
Set di test minimo:
1. Scenari pass/fail di base
2. Tutti i 7 meccanismi (all, include, a, mx, ptr, ip4, ip6, exists)
3. Tutti i 4 qualificatori (+, -, ~, ?)
4. Entrambi i modificatori (redirect, exp)
5. Espansione delle macro
6. Gestione degli errori (temperror, permerror)
7. Test dei limiti (query DNS, timeout)
Suite di test RFC 7208:
# Utilizzare la suite di test ufficiale
git clone https://github.com/openspf/openspf.git
cd openspf/tests
./run_tests.sh
C.6.2 Integrazione continua
Esempio di configurazione 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 Test dei problemi comuni
C.7.1 Funzionalità di posta obsolete
Source Routing:
MAIL FROM:<@relay.example.com:[email protected]>
Atteso: Estrarre correttamente example.com come dominio
%-hack:
MAIL FROM:<user%[email protected]>
Atteso: Gestire correttamente o rifiutare
Bang Path:
MAIL FROM:<[email protected]>
Atteso: Gestire correttamente o rifiutare
C.7.2 Nomi di dominio internazionalizzati
Test IDN:
Dominio: münchen.de
A-label: xn--mnchen-3ya.de
Record SPF: Deve utilizzare A-label
Test: Assicurarsi della conversione e query corrette
C.7.3 Test IPv6
Formati di indirizzo IPv6:
Formato completo: 2001:0db8:0000:0000:0000:0000:0000:0001
Formato compresso: 2001:db8::1
IPv4-mapped: ::ffff:192.0.2.1
SPF: v=spf1 ip6:2001:db8::/32 -all
Test: Tutti i formati dovrebbero corrispondere correttamente
C.8 Tecniche di debug
C.8.1 Abilitare la registrazione dettagliata
Mittente:
# 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
Destinatario:
# Abilitare la registrazione di debug SPF
spf_debug_level = 5
tail -f /var/log/mail.log | grep SPF
C.8.2 Utilizzare il tracciamento dig
Tracciare le query SPF:
# Visualizzare il processo completo di risoluzione DNS
dig +trace example.com TXT
# Visualizzare la catena include
dig _spf.google.com TXT
dig _netblocks.google.com TXT
C.8.3 Utilizzare strumenti di debug SPF
Python pyspf:
import spf
result, explanation = spf.check2(
i='192.0.2.1',
s='[email protected]',
h='mail.example.com'
)
print(f"Risultato: {result}")
print(f"Spiegazione: {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 "Risultato: " . $result->code . "\n";
C.9 Monitoraggio dell'ambiente di produzione
C.9.1 Metriche di monitoraggio
Metriche chiave:
- Tasso di pass SPF
- Tasso di fail SPF
- Tasso di softfail SPF
- Tasso di temperror SPF (dovrebbe essere molto basso)
- Tasso di permerror SPF (dovrebbe essere 0)
- Tempo di verifica medio
- Tasso di timeout DNS
C.9.2 Configurazione degli avvisi
Regole di avviso consigliate:
- Tasso di permerror > 0%: Avviso immediato (errore record SPF)
- Tasso di temperror > 5%: Avvertimento (problemi DNS)
- Aumento improvviso del tasso di fail > 20%: Avviso (possibile cambio di configurazione o attacco)
- Tempo di verifica medio > 1 secondo: Avvertimento (problema di prestazioni)
C.9.3 Analisi dei log
Analizzare i fallimenti SPF:
# Estrarre indirizzi IP con SPF fail
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'client-ip=\K[0-9.]+' | \
sort | uniq -c | sort -rn
# Analizzare domini mittente con fail
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'envelope-from=\K[^;]+' | \
cut -d@ -f2 | sort | uniq -c | sort -rn
Generare un rapporto:
# Generatore di rapporto statistiche 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}%)")