Passa al contenuto principale

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:

  1. Kitterman SPF Validator

    • URL: https://www.kitterman.com/spf/validate.html
    • Funzionalità: Verifica della sintassi, conteggio query DNS, dettagli di analisi
  2. MXToolbox SPF Record Check

    • URL: https://mxtoolbox.com/spf.aspx
    • Funzionalità: Validazione record, rilevamento avvisi ed errori
  3. DMARCIAN SPF Inspector

    • URL: https://dmarcian.com/spf-survey/
    • Funzionalità: Analisi approfondita, raccomandazioni di ottimizzazione

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:`&lt;[email protected]&gt;`
RCPT TO:`&lt;[email protected]&gt;`
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:&lt;@relay.example.com:[email protected]>
Atteso: Estrarre correttamente example.com come dominio

%-hack:

MAIL FROM:&lt;user%[email protected]>
Atteso: Gestire correttamente o rifiutare

Bang Path:

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