xpeditis2.0/docs/deployment/hetzner/10-ingress-tls-cloudflare.md
2026-03-26 18:08:28 +01:00

241 lines
6.3 KiB
Markdown

# 10 — Ingress, TLS et Cloudflare
---
## Architecture TLS
Deux approches possibles, que vous pouvez combiner :
```
Option 1 — TLS Cloudflare uniquement (plus simple)
Browser → Cloudflare (TLS terminé) → HTTP vers Hetzner LB → Pods
Option 2 — TLS de bout en bout (plus sécurisé)
Browser → Cloudflare → HTTPS vers Hetzner LB → cert-manager TLS → Pods
Recommandation : Option 2 avec Cloudflare en "Full (strict)" mode
```
---
## Configuration Cloudflare
### 1. Ajouter les entrées DNS
Dans votre dashboard Cloudflare → Votre domaine → DNS → Records :
```
Type Name Content Proxy TTL
A api <IP_LB_HETZNER> ✅ ON Auto
A app <IP_LB_HETZNER> ✅ ON Auto
A @ <IP_LB_HETZNER> ✅ ON Auto
```
Pour obtenir l'IP du Load Balancer Hetzner :
```bash
hcloud load-balancer list
# ID NAME TYPE LOCATION PUBLIC NET PRIVATE NET
# 12345 xpeditis-lb lb11 fsn1 1.2.3.4 / 2001::... 10.0.0.2
```
### 2. SSL/TLS Mode
Cloudflare → Votre domaine → SSL/TLS → Overview :
- Sélectionnez **Full (strict)** ← obligatoire si cert-manager gère les certicats côté Hetzner
### 3. Page Rules / Transform Rules
Cloudflare → Votre domaine → Rules → Page Rules :
```
Rule 1 : Force HTTPS
If URL matches: http://api.xpeditis.com/*
Then: Always Use HTTPS
Rule 2 : Force HTTPS frontend
If URL matches: http://app.xpeditis.com/*
Then: Always Use HTTPS
```
### 4. WAF Rules (optionnel mais recommandé)
Cloudflare → Security → WAF → Managed Rules :
- Activer **Cloudflare Managed Ruleset** (gratuit)
- Activer **Cloudflare OWASP Core Ruleset** (gratuit)
Custom Rules pour Xpeditis :
```
Rule: Block rate search abuse
If: (http.request.uri.path contains "/api/v1/rates/search") AND (rate(1m) > 60)
Then: Block
Rule: Protect Stripe webhook
If: (http.request.uri.path eq "/api/v1/subscriptions/webhook") AND (not ip.src in {151.101.0.0/17})
Then: Block ← Autorise uniquement les IPs Stripe
```
### 5. Cache Rules (pour les assets frontend)
Cloudflare → Caching → Cache Rules :
```
Rule: Cache Next.js static assets
If: (http.request.uri.path contains "/_next/static/")
Then: Cache Everything, TTL 1 year
```
---
## Vérification du certificat TLS (cert-manager)
Après le déploiement de l'Ingress :
```bash
# Vérifier l'état du certificat
kubectl get certificate -n xpeditis-prod
# NAME READY SECRET AGE
# xpeditis-tls-prod True xpeditis-tls-prod 5m ← READY=True = succès
# Si READY=False, debugger :
kubectl describe certificate xpeditis-tls-prod -n xpeditis-prod
kubectl describe certificaterequest -n xpeditis-prod
kubectl logs -n cert-manager deployment/cert-manager | tail -50
# Voir les challenges ACME en cours
kubectl get challenge -n xpeditis-prod
# Si des challenges sont en attente, vérifier que le DNS Cloudflare pointe bien vers le LB
```
### Tester la chaîne TLS
```bash
# Tester le certificat
curl -I https://api.xpeditis.com/api/v1/health
# HTTP/2 200
# server: traefik
# content-type: application/json
# Détails du certificat
openssl s_client -connect api.xpeditis.com:443 -servername api.xpeditis.com 2>/dev/null | openssl x509 -noout -dates
# notBefore=Apr 1 00:00:00 2026 GMT
# notAfter=Jun 30 00:00:00 2026 GMT ← Let's Encrypt = 90 jours, renouvellement auto à 60 jours
```
---
## Configuration WebSocket Socket.IO
Socket.IO nécessite une configuration spécifique pour fonctionner derrière Traefik + Cloudflare.
### Cloudflare WebSocket
Cloudflare → Votre domaine → Network → WebSockets :
- **Activer WebSockets** (désactivé par défaut sur le plan Free)
> Note : Sur le plan Free Cloudflare, les WebSockets sont supportés mais avec un timeout de 100s. Pour les connexions persistantes Socket.IO, configurez des reconnexions côté client.
### Traefik Sticky Sessions
La configuration des sticky sessions dans `k8s/07-ingress.yaml` garantit que les reconnexions WebSocket retombent sur le même pod (important pour Socket.IO avant l'implémentation Redis adapter) :
```yaml
annotations:
traefik.ingress.kubernetes.io/service.sticky.cookie: "true"
traefik.ingress.kubernetes.io/service.sticky.cookie.name: "XPEDITIS_BACKEND"
traefik.ingress.kubernetes.io/service.sticky.cookie.secure: "true"
```
### Test WebSocket
```bash
# Test avec wscat (npm install -g wscat)
wscat -c "wss://api.xpeditis.com/notifications" \
-H "Authorization: Bearer <JWT_TOKEN>"
# La connexion doit s'établir et recevoir :
# {"event":"unread_count","data":{"count":0}}
# {"event":"recent_notifications","data":[...]}
```
---
## Traefik Dashboard (accès restreint)
```bash
# Traefik a un dashboard utile pour debugger les routes
# Activer l'accès avec authentification
# Générer un mot de passe htpasswd
htpasswd -nb admin <MOT_DE_PASSE> | base64
# Créer un Middleware BasicAuth
cat > /tmp/traefik-auth.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: traefik-dashboard-auth
namespace: kube-system
type: kubernetes.io/basic-auth
stringData:
username: admin
password: <MOT_DE_PASSE>
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: dashboard-auth
namespace: kube-system
spec:
basicAuth:
secret: traefik-dashboard-auth
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: traefik-dashboard
namespace: kube-system
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
traefik.ingress.kubernetes.io/router.middlewares: "kube-system-dashboard-auth@kubernetescrd"
spec:
ingressClassName: traefik
tls:
- hosts:
- traefik.xpeditis.com
secretName: traefik-tls
rules:
- host: traefik.xpeditis.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: traefik
port:
number: 9000
EOF
kubectl apply -f /tmp/traefik-auth.yaml
```
---
## Checklist TLS
```bash
echo "=== Test endpoints ==="
curl -sf https://api.xpeditis.com/api/v1/health | jq .
curl -sf https://app.xpeditis.com/ | head -5
echo "=== Certificats ==="
kubectl get certificate -n xpeditis-prod
kubectl get certificaterequest -n xpeditis-prod
echo "=== Ingress ==="
kubectl get ingress -n xpeditis-prod
echo "=== Test HTTPS force ==="
curl -L http://api.xpeditis.com/api/v1/health
# Doit être redirigé vers HTTPS
```