Aller au contenu principal

Appendix C. Further Testing Advice (Conseils de test supplémentaires)

Appendix C. Further Testing Advice (Conseils de test supplémentaires)

Cette annexe fournit des recommandations détaillées pour tester et valider les implémentations SPF.

C.1 Test des enregistrements SPF

C.1.1 Validation de base

Test de requête DNS:

# Interroger l'enregistrement SPF
dig example.com TXT | grep "v=spf1"

# Ou utiliser nslookup
nslookup -type=TXT example.com

# Ou utiliser host
host -t TXT example.com

Points de validation:

  • ✓ L'enregistrement commence par v=spf1
  • ✓ Un seul enregistrement SPF
  • ✓ Syntaxe correcte (pas de fautes de frappe)
  • ✓ Taille de l'enregistrement < 512 octets

C.1.2 Outils de validation en ligne

Outils en ligne recommandés:

  1. Kitterman SPF Validator

    • URL: https://www.kitterman.com/spf/validate.html
    • Fonctionnalités: Vérification de syntaxe, comptage de requêtes DNS, détails d'analyse
  2. MXToolbox SPF Record Check

    • URL: https://mxtoolbox.com/spf.aspx
    • Fonctionnalités: Validation d'enregistrement, détection d'avertissements et d'erreurs
  3. DMARCIAN SPF Inspector

    • URL: https://dmarcian.com/spf-survey/
    • Fonctionnalités: Analyse approfondie, recommandations d'optimisation

Éléments à vérifier:

✓ Exactitude de la syntaxe
✓ Nombre de requêtes DNS (≤ 10)
✓ Nombre de void lookup (≤ 2)
✓ Longueur de l'enregistrement
✓ Validité des mécanismes
✓ Profondeur de la chaîne include

C.2 Test de l'implémentation de vérification SPF

C.2.1 Scénarios de tests unitaires

Cas de test 1: Correspondance IP de base

Enregistrement SPF: v=spf1 ip4:192.0.2.1 -all
IP de test: 192.0.2.1
Résultat attendu: pass

IP de test: 192.0.2.2
Résultat attendu: fail

Cas de test 2: Plage CIDR

Enregistrement SPF: v=spf1 ip4:192.0.2.0/24 -all
IP de test: 192.0.2.100
Résultat attendu: pass

IP de test: 192.0.3.1
Résultat attendu: fail

Cas de test 3: Mécanisme MX

example.com enregistrement MX: 10 mail.example.com
mail.example.com enregistrement A: 192.0.2.10

Enregistrement SPF: v=spf1 mx -all
IP de test: 192.0.2.10
Résultat attendu: pass

Cas de test 4: Mécanisme Include

example.com: v=spf1 include:_spf.example.org -all
_spf.example.org: v=spf1 ip4:192.0.2.0/24 -all

IP de test: 192.0.2.50
Résultat attendu: pass

Cas de test 5: Soft-fail

Enregistrement SPF: v=spf1 ip4:192.0.2.1 ~all
IP de test: 203.0.113.1
Résultat attendu: softfail

C.2.2 Tests de cas limites

MAIL FROM vide:

MAIL FROM: <>
HELO: mail.example.com
Attendu: Utiliser l'identité HELO, local-part est "postmaster"

Nom de domaine invalide:

MAIL FROM: [email protected]
Résultat attendu: none (ou permerror)

Timeout DNS:

Simuler un timeout DNS
Résultat attendu: temperror

Plusieurs enregistrements SPF:

example.com TXT "v=spf1 mx -all"
example.com TXT "v=spf1 a -all"
Résultat attendu: permerror

C.2.3 Tests de limites

Test de limite de requêtes DNS:

# Pseudo-code
def test_dns_lookup_limit():
# Créer un enregistrement SPF avec 11 includes
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 de limite d'enregistrements MX:

# Créer un domaine avec 11 enregistrements MX
# Attendu: le mécanisme mx retourne permerror

Test de Void Lookup:

# Créer un include qui retourne NXDOMAIN
# Le comptage devrait être inclus dans la limite void lookup

C.3 Envoi d'e-mails de test

C.3.1 Processus de test

Étape 1: Préparer le domaine de test

test.example.com IN TXT "v=spf1 ip4:YOUR_IP -all"

Étape 2: Envoyer un e-mail de test

# Utiliser l'outil swaks
swaks --to [email protected] \
--from [email protected] \
--server smtp.example.com

# Ou utiliser 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

Étape 3: Vérifier les en-têtes d'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 Tests de différents scénarios

Scénario 1: Test Pass

Configuration: Envoyer depuis une IP autorisée
En-tête attendu: Received-SPF: pass

Scénario 2: Test Fail

Configuration: Envoyer depuis une IP non autorisée
Attendu: L'e-mail est rejeté ou marqué
En-tête attendu: Received-SPF: fail

Scénario 3: Test SoftFail

Configuration: L'enregistrement SPF utilise ~all
Envoyer depuis une IP non autorisée
Attendu: L'e-mail est accepté mais marqué
En-tête attendu: Received-SPF: softfail

Scénario 4: Test Neutral

Configuration: L'enregistrement SPF utilise ?all
En-tête attendu: Received-SPF: neutral

C.4 Tests de macros

C.4.1 Validation d'expansion de macros

Cas de test:

Expéditeur: [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 de macro complexe:

SPF: v=spf1 exists:%\{ir}.%\{l}._spf.%\{d} -all
Expéditeur: [email protected]
IP: 192.0.2.100

Étendu à: 100.2.0.192.user._spf.example.com
Validation: Interroger l'enregistrement A pour ce nom de domaine

C.4.2 Test de délimiteurs de macros

Expéditeur: [email protected]

%\{l} → user+tag
%\{l-} → user-tag (+ remplacé par -)
%\{lr} → gat+resu (inversé)
%\{lr-} → gat-resu (inversé et remplacé)

C.5 Tests de performance

C.5.1 Tests de temps de réponse

Test de référence:

# Tester le temps de requête DNS
time dig example.com TXT

# Tester la vérification SPF complète
time spf_check example.com 192.0.2.1 [email protected]

Objectifs de performance:

  • Enregistrement simple (1-2 mécanismes): < 100ms
  • Enregistrement complexe (plusieurs includes): < 500ms
  • Temps maximum autorisé: 20 secondes

C.5.2 Tests de charge

Simuler une haute concurrence:

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

# Tester 1000 vérifications SPF concurrentes
start = time.time()
results = check_spf_concurrent(1000)
end = time.time()

print(f"1000 vérifications terminées en: {end - start} secondes")
print(f"Moyenne par vérification: {(end - start) / 1000 * 1000}ms")

C.6 Tests de régression

C.6.1 Suite de tests

Ensemble de tests minimal:

1. Scénarios pass/fail de base
2. Tous les 7 mécanismes (all, include, a, mx, ptr, ip4, ip6, exists)
3. Tous les 4 qualificateurs (+, -, ~, ?)
4. Les deux modificateurs (redirect, exp)
5. Expansion de macros
6. Gestion des erreurs (temperror, permerror)
7. Tests de limites (requêtes DNS, timeout)

Suite de tests RFC 7208:

# Utiliser la suite de tests officielle
git clone https://github.com/openspf/openspf.git
cd openspf/tests
./run_tests.sh

C.6.2 Intégration continue

Exemple de configuration 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 des problèmes courants

C.7.1 Fonctionnalités de messagerie anciennes

Source Routing:

MAIL FROM:&lt;@relay.example.com:[email protected]>
Attendu: Extraire correctement example.com comme domaine

%-hack:

MAIL FROM:&lt;user%[email protected]>
Attendu: Gérer correctement ou rejeter

Bang Path:

MAIL FROM:&lt;[email protected]>
Attendu: Gérer correctement ou rejeter

C.7.2 Noms de domaine internationalisés

Test IDN:

Domaine: münchen.de
A-label: xn--mnchen-3ya.de
Enregistrement SPF: Doit utiliser A-label

Test: S'assurer de la conversion et de la requête correctes

C.7.3 Tests IPv6

Formats d'adresse IPv6:

Format complet: 2001:0db8:0000:0000:0000:0000:0000:0001
Format compressé: 2001:db8::1
IPv4-mapped: ::ffff:192.0.2.1

SPF: v=spf1 ip6:2001:db8::/32 -all
Test: Tous les formats devraient correspondre correctement

C.8 Techniques de débogage

C.8.1 Activer la journalisation détaillée

Expéditeur:

# 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

Destinataire:

# Activer la journalisation de débogage SPF
spf_debug_level = 5
tail -f /var/log/mail.log | grep SPF

C.8.2 Utiliser le traçage dig

Tracer les requêtes SPF:

# Voir le processus complet de résolution DNS
dig +trace example.com TXT

# Voir la chaîne include
dig _spf.google.com TXT
dig _netblocks.google.com TXT

C.8.3 Utiliser les outils de débogage SPF

Python pyspf:

import spf

result, explanation = spf.check2(
i='192.0.2.1',
s='[email protected]',
h='mail.example.com'
)

print(f"Résultat: {result}")
print(f"Explication: {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 "Résultat: " . $result->code . "\n";

C.9 Surveillance de l'environnement de production

C.9.1 Métriques de surveillance

Métriques clés:

- Taux de pass SPF
- Taux de fail SPF
- Taux de softfail SPF
- Taux de temperror SPF (devrait être très faible)
- Taux de permerror SPF (devrait être 0)
- Temps de vérification moyen
- Taux de timeout DNS

C.9.2 Configuration des alertes

Règles d'alerte recommandées:

- Taux de permerror > 0%: Alerte immédiate (erreur d'enregistrement SPF)
- Taux de temperror > 5%: Avertissement (problèmes DNS)
- Augmentation soudaine du taux de fail > 20%: Alerte (changement de configuration possible ou attaque)
- Temps de vérification moyen > 1 seconde: Avertissement (problème de performance)

C.9.3 Analyse des journaux

Analyser les échecs SPF:

# Extraire les adresses IP avec SPF fail
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'client-ip=\K[0-9.]+' | \
sort | uniq -c | sort -rn

# Analyser les domaines d'expéditeur avec fail
grep "Received-SPF: fail" /var/log/mail.log | \
grep -oP 'envelope-from=\K[^;]+' | \
cut -d@ -f2 | sort | uniq -c | sort -rn

Générer un rapport:

# Générateur de rapport de statistiques 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}%)")