Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
9.4 KiB
08 — DNS, TLS et Cloudflare
Durée : environ 1 h 30, dont des délais d'attente incompressibles.
La référence complète des réglages Cloudflare est dans
infra/prod/cloudflare/README.md.
Ce document donne l'ordre d'exécution et les vérifications.
1. Ordre à respecter
L'ordre compte : émettre un certificat avant que le DNS ne réponde échoue, et Let's Encrypt limite la production à 5 échecs par heure.
1. Enregistrements DNS chez Cloudflare (propagation : quelques minutes)
2. Réglages SSL/TLS Cloudflare
3. Certificat de TEST (staging) ← valide la chaîne DNS-01
4. Certificat de PRODUCTION
5. Vérifications
6. Règles WAF et cache
2. Enregistrements DNS
cd infra/prod/terraform && terraform output dns_records_to_create
Créez dans Cloudflare, tous proxifiés (nuage orange) :
| Type | Nom | Valeur |
|---|---|---|
| A | xpeditis.com |
<app_public_ipv4> |
| A | www |
<app_public_ipv4> |
| A | app |
<app_public_ipv4> |
| A | api |
<app_public_ipv4> |
| A | grafana |
<app_public_ipv4> |
Aucun enregistrement ne pointe vers db-01.
Le proxy n'est pas optionnel : le firewall Hetzner n'accepte 80/443 que depuis les rangs Cloudflare. Un enregistrement en nuage gris (DNS only) donnerait un domaine injoignable — comportement voulu, mais déroutant si on l'a oublié.
E-mail — à faire maintenant
TXT xpeditis.com v=spf1 include:spf.brevo.com -all
TXT mail._domainkey <clé DKIM fournie par Brevo>
TXT _dmarc v=DMARC1; p=quarantine; rua=mailto:dmarc@xpeditis.com; pct=100
Sans SPF/DKIM/DMARC, les confirmations de réservation et les liens magiques transporteurs partent en indésirables. C'est un défaut de production silencieux et coûteux : personne ne se plaint, les clients pensent simplement que le service ne marche pas.
Vérifier
dig +short app.xpeditis.com # doit renvoyer des IP Cloudflare (104.x, 172.6x…)
dig +short TXT xpeditis.com
dig +short TXT _dmarc.xpeditis.com
3. Réglages SSL/TLS Cloudflare
SSL/TLS → Overview
| Réglage | Valeur |
|---|---|
| Mode de chiffrement | Full (strict) |
| Always Use HTTPS | Activé |
| Minimum TLS Version | 1.2 |
| TLS 1.3 | Activé |
| Automatic HTTPS Rewrites | Activé |
| HSTS | Pas encore — voir §6 |
« Full (strict) » et rien d'autre. En mode Flexible, Cloudflare parle en clair à votre serveur : le cadenas s'affiche dans le navigateur alors que le trafic circule en clair sur Internet. C'est pire que pas de HTTPS du tout, puisque personne ne s'en aperçoit.
4. Certificat de test
On valide d'abord la chaîne DNS-01 avec l'émetteur staging, qui n'a pas de quota serré.
export KUBECONFIG=~/.kube/xpeditis-prod.yaml
kubectl -n xpeditis-prod patch certificate xpeditis-wildcard --type=merge \
-p '{"spec":{"issuerRef":{"name":"letsencrypt-staging","kind":"ClusterIssuer"}}}'
kubectl -n xpeditis-prod delete secret xpeditis-wildcard-tls --ignore-not-found
# Suivre l'émission
kubectl -n xpeditis-prod get certificate -w
kubectl -n xpeditis-prod describe certificaterequest
kubectl -n cert-manager logs -l app=cert-manager --tail=50 -f
Deux à cinq minutes (propagation de l'enregistrement TXT _acme-challenge).
READY: True signifie que le jeton Cloudflare, les permissions et la zone sont
corrects. Si l'émission échoue :
| Message | Cause |
|---|---|
Cloudflare API error 10000 |
Jeton invalide ou permissions insuffisantes (Zone / DNS / Edit requis). |
could not find zone |
Le jeton ne couvre pas xpeditis.com, ou la zone n'est pas active. |
propagation check failed |
Attendre. Si cela persiste au-delà de 10 min, vérifier qu'aucun autre DNS ne fait autorité. |
5. Certificat de production
Une fois le test au vert :
kubectl -n xpeditis-prod patch certificate xpeditis-wildcard --type=merge \
-p '{"spec":{"issuerRef":{"name":"letsencrypt-prod","kind":"ClusterIssuer"}}}'
kubectl -n xpeditis-prod delete secret xpeditis-wildcard-tls --ignore-not-found
kubectl -n monitoring patch certificate grafana-tls --type=merge \
-p '{"spec":{"issuerRef":{"name":"letsencrypt-prod","kind":"ClusterIssuer"}}}'
kubectl -n monitoring delete secret grafana-tls --ignore-not-found
kubectl get certificate -A -w
Vérifiez l'émetteur réel du certificat servi :
echo | openssl s_client -connect api.xpeditis.com:443 -servername api.xpeditis.com 2>/dev/null \
| openssl x509 -noout -issuer -subject -dates
Attendu : issuer=C=US, O=Let's Encrypt, CN=R11 (ou équivalent).
Si vous lisez (STAGING), le certificat de test est encore en place :
supprimez le Secret et relancez.
Avec Cloudflare en proxy, le navigateur voit le certificat Cloudflare, pas le vôtre. La commande ci-dessus interroge Cloudflare. Pour vérifier le certificat d'origine, il faut se connecter depuis app-01 :
ssh deploy@<app_ip> \ "echo | openssl s_client -connect 127.0.0.1:443 -servername api.xpeditis.com 2>/dev/null | openssl x509 -noout -issuer -dates"
6. HSTS
À faire seulement quand tous les sous-domaines servent du HTTPS valide.
Traefik pose déjà l'en-tête (k8s/base/08-traefik-middlewares.yaml,
stsSeconds: 31536000). Activer HSTS aussi chez Cloudflare le fait
appliquer avant même que la requête n'atteigne le serveur.
SSL/TLS → Edge Certificates → HTTP Strict Transport Security → Enable
| Réglage | Valeur |
|---|---|
| Max Age | 12 mois |
| Include subdomains | Oui |
| Preload | Oui |
| No-Sniff | Oui |
C'est irréversible pendant un an. Une fois HSTS envoyé, les navigateurs refuseront tout accès HTTP à
xpeditis.comet à ses sous-domaines pendant la durée annoncée, même si vous désactivez le réglage. Un sous-domaine qui n'aurait pas de certificat valide deviendrait inaccessible sans recours.Commencez par
Max Age = 1 mois, sans preload. Passez à 12 mois et preload après deux semaines sans incident.
7. Règles WAF et cache
Appliquez les règles décrites dans
infra/prod/cloudflare/README.md :
- Bypass cache sur
api.xpeditis.com/*— la plus importante. Une réponse d'API mise en cache servirait les données d'un utilisateur à un autre. - Blocage de
/api/docs. - Limitation de débit sur
/api/v1/auth/login(10 requêtes/minute/IP). - Restriction du webhook Stripe aux IP de Stripe.
- Cache long sur
/_next/static/*.
8. Vérifications finales
# Les redirections
curl -sI http://xpeditis.com | head -3 # 301 → https
curl -sI https://www.xpeditis.com | head -1 # 200
curl -sI https://app.xpeditis.com | head -1 # 200
curl -sI https://api.xpeditis.com/api/v1/health | head -1 # 200
# Les en-têtes de sécurité
curl -sI https://app.xpeditis.com | grep -iE 'strict-transport|x-frame|x-content-type|referrer-policy'
# L'API n'est pas mise en cache
curl -sI https://api.xpeditis.com/api/v1/health | grep -i 'cf-cache-status'
# Attendu : BYPASS ou DYNAMIC, jamais HIT
# L'origine est-elle contournable ?
curl -sS --max-time 5 --connect-to app.xpeditis.com:443:<app_public_ipv4>:443 \
https://app.xpeditis.com/ 2>&1 | head -2
# Attendu : timeout — le firewall Hetzner bloque tout ce qui n'est pas Cloudflare
Analyse externe : ssllabs.com/ssltest sur
app.xpeditis.com — visez A ou A+.
9. Dépannage
Le certificat ne s'émet pas
kubectl -n xpeditis-prod describe certificate xpeditis-wildcard
kubectl -n xpeditis-prod get certificaterequest,order,challenge
kubectl -n cert-manager logs -l app=cert-manager --tail=100
Le Challenge indique précisément à quelle étape cela bloque.
Le certificat approche de l'expiration
cert-manager renouvelle 30 jours avant l'échéance. L'alerte
CertificatBientotExpire se déclenche à 15 jours — c'est-à-dire uniquement si
le renouvellement automatique a cessé de fonctionner.
Forcer un renouvellement :
kubectl -n xpeditis-prod delete secret xpeditis-wildcard-tls
# cert-manager réémet dans la minute
« Too many certificates already issued »
Vous avez atteint la limite Let's Encrypt (50 par domaine et par semaine).
Elle se réinitialise glissant sur 7 jours. En attendant, utilisez
letsencrypt-staging — et c'est exactement pourquoi la validation se fait
d'abord en staging.
Erreur 521 / 522 chez Cloudflare
Cloudflare n'atteint pas l'origine.
ssh deploy@<app_ip> 'sudo systemctl status k3s; sudo ss -tlnp | grep -E ":(80|443) "'
kubectl -n kube-system get pods -l app.kubernetes.io/name=traefik
Cause fréquente : les rangs d'IP Cloudflare ont changé.
bash infra/prod/scripts/refresh-cloudflare-ips.sh
10. Contrôle
[ ] 5 enregistrements A créés, tous proxifiés
[ ] Aucun enregistrement ne pointe vers db-01
[ ] SPF, DKIM, DMARC publiés
[ ] Mode SSL : Full (strict)
[ ] Certificat de test émis avec succès (chaîne DNS-01 validée)
[ ] Certificat de production émis, Ready: True, émetteur non-staging
[ ] HTTP redirige en 301 vers HTTPS
[ ] En-têtes de sécurité présents
[ ] cf-cache-status = BYPASS sur l'API
[ ] L'origine n'est pas joignable en contournant Cloudflare
[ ] Règles WAF appliquées
[ ] Note SSL Labs ≥ A
[ ] HSTS activé (après vérification, max-age court d'abord)
→ Suite : 09 — Déploiement applicatif