Table des matières

Infrastructure Web & Mail Sécurisée

AKA Déployer une stack web et transactionnelle propre avec Cloudflare, Mailtrap & Nginx Multi-Vhosts

Quand on héberge plusieurs services web sur un même serveur, concilier isolation des hôtes virtuels (multi-vhosts), étanchéité du serveur d'origine derrière un CDN et délivrabilité des e-mails transactionnels devient vite un casse-tête si l'on ne pose pas un cadre strict dès le départ.

L'objectif ici n'est pas de faire du bricolage, mais d'établir un standard reproductible et durci :

1. Architecture & Séparation des Flux

Flux Trajet technique Rôle & Confidentialité
Web entrant (HTTPS) Visiteur→Cloudflare Edge (WAF/Proxy)→Nginx Origin L'IP réelle du serveur n'est jamais exposée publiquement.
Mail sortant (SMTP) App→Mailtrap SMTP →Destinataire L'IP source dans les headers `Received:` est celle de Mailtrap.
Mail entrant (MX) Émetteur tiers→Cloudflare MX →Email Routing → Boîte finale Aucun serveur de messagerie public n'écoute sur l'hôte d'origine.

1.1 Modèle de menace : Vecteurs de fuite d'IP

Pour préserver l'anonymat de l'infrastructure d'origine sur un serveur multi-vhosts :

2. Configuration de la Zone DNS (Cloudflare)

Les enregistrements suivants doivent être déployés dans la zone DNS Cloudflare du domaine :

Type Nom / Hôte Valeur / Cible Statut Proxy Commentaire
CNAME `@` (racine) `target.votre-cluster.org` Proxied (Orange) Proxy HTTP Cloudflare actif
CNAME `www` `target.votre-cluster.org` Proxied (Orange) Proxy HTTP Cloudflare actif
CNAME `mtxxxxxx` `smtp.mailtrap.live` DNS Only (Gris) Validation du domaine Mailtrap
CNAME `rwmt1._domainkey` `rwmt1.dkim.smtp.mailtrap.live` DNS Only (Gris) Clé DKIM #1 Mailtrap
CNAME `rwmt2._domainkey` `rwmt2.dkim.smtp.mailtrap.live` DNS Only (Gris) Clé DKIM #2 Mailtrap (Rotation)
MX `@` `route1.mx.cloudflare.net` (Prio 13) DNS Only (Gris) Cloudflare Email Routing
MX `@` `route2.mx.cloudflare.net` (Prio 27) DNS Only (Gris) Cloudflare Email Routing
MX `@` `route3.mx.cloudflare.net` (Prio 94) DNS Only (Gris) Cloudflare Email Routing
TXT `cfxxxxx-1._domainkey` `v=DKIM1; k=rsa; p=…` DNS Only (Gris) Clé DKIM Cloudflare (flux entrant)
TXT `_dmarc` `v=DMARC1; p=none; rua=mailto:dmarc@…` DNS Only (Gris) Politique DMARC + rapports
TXT `@` `v=spf1 include:_spf.mx.cloudflare.net include:_spf.mailtrap.io ~all` DNS Only (Gris) Autorisations SPF combinées

Les enregistrements techniques d'authentification (DKIM, SPF, CNAMEs de validation Mailtrap) doivent impérativement rester en DNS Only (Nuage Gris). L'activation du proxy Cloudflare sur ces entrées casserait la résolution DNS brute requise par les serveurs de messagerie.

3. Durcissement & Configuration Nginx

3.1 Vhost par défaut "Trou Noir" (Anti-Scan IP Direct)

Ce vhost intercepte toutes les requêtes directes par IP ou utilisant un nom de domaine inconnu/non configuré. Il présente un certificat auto-signé générique et coupe la connexion TCP sans émettre de réponse HTTP.

Génération du certificat factice :

user@host:~/ $ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
  -keyout /etc/ssl/private/dummy.key \
  -out /etc/ssl/certs/dummy.crt \
  -subj "/CN=null"

Configuration du vhost par défaut :

# /etc/nginx/conf.d/00-default-blackhole.conf
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
 
    server_name _;
 
    ssl_certificate /etc/ssl/certs/dummy.crt;
    ssl_certificate_key /etc/ssl/private/dummy.key;
 
    # Coupe immédiatement la connexion TCP
    return 444;
}

3.2 Restauration de l'IP réelle du visiteur

Pour que les logs et l'application reçoivent la véritable IP du client et non celle des relais Cloudflare :

# /etc/nginx/conf.d/cloudflare-realip.conf
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
 
# IPv6
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
 
real_ip_header CF-Connecting-IP;

3.3 Vhost Applicatif (Origin CA + Authenticated Origin Pulls)

Télécharger le certificat CA public Cloudflare pour Authenticated Origin Pulls :

user@host:~/ $ curl -s https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem -o /etc/ssl/certs/cloudflare-origin-pull.crt

Configuration du Vhost sécurisé :

# /etc/nginx/sites-available/app.domain.tld.conf
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
 
    server_name domain.tld www.domain.tld;
 
    # Certificat Cloudflare Origin CA (valide 15 ans, non listé dans les CT logs)
    ssl_certificate /etc/ssl/certs/cloudflare_origin.crt;
    ssl_certificate_key /etc/ssl/private/cloudflare_origin.key;
 
    # Authenticated Origin Pulls (mTLS) : Rejette tout client qui n'est pas Cloudflare
    ssl_client_certificate /etc/ssl/certs/cloudflare-origin-pull.crt;
    ssl_verify_client on;
 
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
 
    location / {
        proxy_pass http://127.0.0.1:8080; # Ou socket PHP-FPM / conteneur applicatif
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

4. Configuration Applicative & Relais SMTP

Pour router les notifications systèmes et applicatives sans divulguer l'infrastructure :

4.1 Paramètres de connexion SMTP

4.2 Bonnes pratiques applicatives

5. Audit & Validation Technique

5.1 Vérification de la propagation DNS

# Vérifier l'inclusion SPF
user@host:~/somedir $ dig TXT domain.tld +short

# Vérifier la résolution des clés DKIM Mailtrap
user@host:~/somedir $ dig CNAME rwmt1._domainkey.domain.tld +short
user@host:~/somedir $ dig CNAME rwmt2._domainkey.domain.tld +short

# Vérifier la politique DMARC
user@host:~/somedir $ dig TXT _dmarc.domain.tld +short

5.2 Test d'imperméabilité de l'IP d'origine

Exécuter un test depuis une machine externe vers l'IP publique directe :

# 1. Test direct par IP (Doit être fermé ou retourner HTTP 444 sans certificat exposé)
user@host:~/somedir $ curl -k -v https://<IP_PUBLIQUE_SERVEUR>/

# 2. Test avec en-tête Host forgé (Doit échouer car le mTLS Cloudflare rejette la connexion)
user@host:~/somedir $ curl -k -v -H "Host: domain.tld" https://<IP_PUBLIQUE_SERVEUR>/

*Résultat attendu :* La connexion TLS échoue immédiatement (rejet `ssl_verify_client`) ou retourne un statut vide `444`.

5.3 Durcissement DMARC progressif

Une fois la période d'observation des rapports Mailtrap passée sans détection de rejets légitimes :

  1. Passer l'enregistrement `_dmarc` de `p=none` à `p=quarantine; pct=100;`
  2. Passer ultérieurement à `p=reject; pct=100;` pour bloquer définitivement toute tentative d'usurpation d'identité sur votre nom de domaine.