xpeditis2.0/infra/prod/cloudflare/README.md
2026-09-07 21:40:50 +02:00

172 lines
6.1 KiB
Markdown

# Configuration Cloudflare — production
Cloudflare est la première ligne de défense : DNS, anti-DDoS, WAF, cache.
Le firewall Hetzner n'accepte 80/443 **que** depuis les rangs Cloudflare, donc
si le proxy est désactivé sur un enregistrement, ce domaine devient
inaccessible. C'est voulu : personne ne doit pouvoir taper l'IP d'origine.
Le plan Free suffit en phase 1 (≈ 100 utilisateurs). Le plan Pro (20 €/mois,
prévu au budget à partir de 1 000 utilisateurs) ajoute le WAF managé et
l'analyse de bots.
---
## 1. Enregistrements DNS
Toutes les valeurs `<IP_APP_01>` viennent de `make tf-output`.
| Type | Nom | Contenu | Proxy | TTL |
|---|---|---|---|---|
| A | `xpeditis.com` | `<IP_APP_01>` | **Proxifié** | Auto |
| A | `www` | `<IP_APP_01>` | **Proxifié** | Auto |
| A | `app` | `<IP_APP_01>` | **Proxifié** | Auto |
| A | `api` | `<IP_APP_01>` | **Proxifié** | Auto |
| A | `grafana` | `<IP_APP_01>` | **Proxifié** | Auto |
| AAAA | idem si IPv6 activée | `<IPv6_APP_01>` | **Proxifié** | Auto |
**Aucun enregistrement ne pointe vers db-01.** Son IP publique ne sert qu'au
SSH d'administration et ne doit apparaître nulle part en DNS.
### Messagerie (obligatoire pour que les e-mails arrivent)
| Type | Nom | Contenu |
|---|---|---|
| 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 spam — un défaut de production silencieux et coûteux.
Commencez à `p=quarantine`, passez à `p=reject` après deux semaines de rapports
propres.
---
## 2. Réglages SSL/TLS
| Réglage | Valeur | Pourquoi |
|---|---|---|
| Mode de chiffrement | **Full (strict)** | Cloudflare vérifie le certificat Let's Encrypt du serveur. En « Flexible », le trafic Cloudflare → origine serait en clair. |
| Always Use HTTPS | Activé | |
| Minimum TLS Version | **1.2** | TLS 1.0/1.1 sont cassés et refusés par la plupart des référentiels de conformité. |
| TLS 1.3 | Activé | |
| Automatic HTTPS Rewrites | Activé | |
| HSTS | **Activé après vérification** | Voir l'avertissement ci-dessous. |
> **HSTS est irréversible pendant un an.** Ne l'activez qu'une fois certain que
> *tous* les sous-domaines servent bien du HTTPS valide. Traefik pose déjà
> l'en-tête (`k8s/base/08-traefik-middlewares.yaml`) ; l'activer aussi côté
> Cloudflare ajoute la protection avant même que la requête n'atteigne le
> serveur.
---
## 3. Règles WAF
### 3.1 Bloquer l'accès direct aux chemins d'administration
```
(http.host eq "api.xpeditis.com" and starts_with(http.request.uri.path, "/api/docs"))
```
→ Action : **Block**
La documentation Swagger est déjà désactivée en production par `main.ts`, mais
une règle de bordure protège même en cas d'erreur de configuration.
### 3.2 Limiter les tentatives de connexion
Rate limiting (Security → WAF → Rate limiting rules) :
| Champ | Valeur |
|---|---|
| Expression | `starts_with(http.request.uri.path, "/api/v1/auth/login")` |
| Requêtes | 10 |
| Période | 1 minute |
| Par | Adresse IP |
| Action | Block, 10 minutes |
Cette limite s'ajoute à celle de Traefik (`rate-limit-auth`) et à celle de
NestJS (`CustomThrottlerGuard`). Trois couches indépendantes : une erreur de
configuration sur l'une ne laisse pas la porte ouverte.
### 3.3 Protéger le webhook Stripe
```
(http.host eq "api.xpeditis.com" and starts_with(http.request.uri.path, "/api/v1/subscriptions/webhook") and not ip.src in {IP_STRIPE})
```
→ Action : **Block**
Les rangs d'IP Stripe sont publiés sur
`https://stripe.com/files/ips/ips_webhooks.txt`. La signature du webhook est
déjà vérifiée côté applicatif ; ceci évite simplement que du bruit atteigne
l'application.
### 3.4 Filtrage géographique — à décider consciemment
Xpeditis sert des transitaires européens. Bloquer tout sauf l'Europe réduit
massivement le bruit, mais coupe aussi les transitaires asiatiques légitimes
(les connecteurs transporteurs partent du serveur, ils ne sont pas concernés).
**Recommandation** : ne pas bloquer par pays au lancement. Activer plutôt
« Bot Fight Mode » (gratuit) et surveiller les journaux un mois avant de
décider.
---
## 4. Cache
| Chemin | Règle |
|---|---|
| `api.xpeditis.com/*` | **Bypass cache** — une réponse d'API mise en cache servirait les données d'un utilisateur à un autre. |
| `app.xpeditis.com/_next/static/*` | Cache Everything, Edge TTL 1 an (contenu au nom versionné). |
| `app.xpeditis.com/*` | Standard (Next.js gère ses propres en-têtes). |
> La règle de bypass sur l'API est **critique**. Un cache mal placé sur une
> route authentifiée est une fuite de données, pas un problème de performance.
---
## 5. Jeton API pour cert-manager
`My Profile → API Tokens → Create Token → Edit zone DNS`
| Permission | Portée |
|---|---|
| Zone / DNS / Edit | Zone **xpeditis.com** uniquement |
| Zone / Zone / Read | Zone **xpeditis.com** uniquement |
N'utilisez **jamais** la clé globale du compte : elle permettrait à
cert-manager — ou à quiconque lirait le Secret — de rediriger tout votre trafic.
Le jeton alimente le Secret `cert-manager/cloudflare-api-token`
(`k8s/base/03-secrets.template.yaml`).
---
## 6. Accès à Grafana
Deux options, au choix :
- **Liste d'IP** (par défaut) — middleware Traefik `admin-ip-allowlist`,
à renseigner dans `k8s/base/08-traefik-middlewares.yaml`.
- **Cloudflare Access** (Zero Trust, gratuit jusqu'à 50 utilisateurs) —
authentification par e-mail à usage unique devant `grafana.xpeditis.com`.
Préférable si votre IP est dynamique.
---
## 7. Vérification
```bash
# Le proxy est-il bien actif ? (l'IP retournée doit être une IP Cloudflare)
dig +short app.xpeditis.com
# L'origine est-elle injoignable en direct ?
curl -sS --max-time 5 --connect-to app.xpeditis.com:443:<IP_APP_01>:443 \
https://app.xpeditis.com/ # doit échouer par timeout
# SPF / DKIM / DMARC
dig +short TXT xpeditis.com
dig +short TXT _dmarc.xpeditis.com
```