6.3 KiB
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 :
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 :
# 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
# 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) :
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
# 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)
# 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
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