Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
295 lines
9.4 KiB
Markdown
295 lines
9.4 KiB
Markdown
# 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`](../../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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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é.
|
|
|
|
```bash
|
|
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 :
|
|
|
|
```bash
|
|
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 :
|
|
|
|
```bash
|
|
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 :
|
|
> ```bash
|
|
> 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.com` et à 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`](../../infra/prod/cloudflare/README.md) :
|
|
|
|
1. **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.
|
|
2. Blocage de `/api/docs`.
|
|
3. Limitation de débit sur `/api/v1/auth/login` (10 requêtes/minute/IP).
|
|
4. Restriction du webhook Stripe aux IP de Stripe.
|
|
5. Cache long sur `/_next/static/*`.
|
|
|
|
---
|
|
|
|
## 8. Vérifications finales
|
|
|
|
```bash
|
|
# 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](https://www.ssllabs.com/ssltest/) sur
|
|
`app.xpeditis.com` — visez A ou A+.
|
|
|
|
---
|
|
|
|
## 9. Dépannage
|
|
|
|
### Le certificat ne s'émet pas
|
|
|
|
```bash
|
|
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 :
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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
|
|
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](./09-deploiement-application.md)**
|