docs: simplify HAPROXY.md for existing setup
Config ciblée sur le backend n8n existant — juste le delta à appliquer. Token requis uniquement hors LAN/IP fixe, LAN reste en accès direct. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+58
-157
@@ -1,195 +1,96 @@
|
||||
# Configuration HAProxy pour n8n Pilot
|
||||
|
||||
## Architecture
|
||||
## Contexte
|
||||
|
||||
```
|
||||
Android App → HTTPS → HAProxy (443) → HTTP → n8n (5678)
|
||||
↓
|
||||
Validation X-App-Token
|
||||
Rate limiting
|
||||
TLS termination
|
||||
```
|
||||
|
||||
HAProxy agit comme reverse proxy devant n8n. Il :
|
||||
1. Termine TLS (certificat Let's Encrypt ou autosigné)
|
||||
2. Valide le header `X-App-Token` (token secret partagé)
|
||||
3. Applique du rate limiting pour protéger l'API
|
||||
4. Forward les requêtes vers n8n en HTTP interne
|
||||
n8n est déjà exposé via HAProxy à `https://n8n.gyozamancave.fr`.
|
||||
Il suffit d'ajouter la validation du `X-App-Token` dans le backend existant **sans toucher au frontend**.
|
||||
|
||||
---
|
||||
|
||||
## Configuration HAProxy complète
|
||||
## Modification à apporter — backend n8n uniquement
|
||||
|
||||
Remplacer le backend `n8n-backend` actuel par :
|
||||
|
||||
```haproxy
|
||||
#---------------------------------------------------------------------
|
||||
# global : paramètres du processus HAProxy
|
||||
#---------------------------------------------------------------------
|
||||
global
|
||||
log /dev/log local0
|
||||
log /dev/log local1 notice
|
||||
maxconn 2000
|
||||
daemon
|
||||
backend n8n-backend
|
||||
mode http
|
||||
balance source
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# defaults : valeurs par défaut pour tous les frontends/backends
|
||||
#---------------------------------------------------------------------
|
||||
defaults
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
timeout connect 5s
|
||||
timeout client 30s
|
||||
timeout server 30s
|
||||
# Timeout plus long pour les webhooks n8n qui peuvent prendre du temps
|
||||
timeout tunnel 3600s
|
||||
# IPs qui ne nécessitent pas le X-App-Token (LAN + IP fixe perso)
|
||||
acl is_local src 192.168.1.0/24 192.168.2.0/24 82.67.3.126/32
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# frontend : point d'entrée HTTPS sur le port 443
|
||||
#---------------------------------------------------------------------
|
||||
frontend n8n_pilot_frontend
|
||||
bind *:443 ssl crt /etc/ssl/n8n/fullchain.pem alpn h2,http/1.1
|
||||
|
||||
# Sécurité TLS : désactiver les versions obsolètes
|
||||
ssl-min-ver TLSv1.2
|
||||
|
||||
# Headers de sécurité injectés sur toutes les réponses
|
||||
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
|
||||
http-response set-header X-Content-Type-Options "nosniff"
|
||||
http-response set-header X-Frame-Options "DENY"
|
||||
|
||||
# ACL : vérification du X-App-Token
|
||||
# La valeur doit correspondre exactement au token configuré dans l'app
|
||||
# Validation du token pour les accès extérieurs (mobile app)
|
||||
acl valid_app_token req.hdr(X-App-Token) -m str "VOTRE_TOKEN_ICI"
|
||||
http-request deny deny_status 403 if !is_local !valid_app_token
|
||||
|
||||
# Refuser les requêtes sans token valide avec 403
|
||||
http-request deny deny_status 403 if !valid_app_token
|
||||
|
||||
# Rate limiting : max 60 requêtes par minute par IP
|
||||
# Protège contre les scripts de scan et les abus
|
||||
stick-table type ip size 100k expire 60s store http_req_rate(60s)
|
||||
http-request track-sc0 src
|
||||
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 60 }
|
||||
|
||||
# Redirection HTTP → HTTPS (optionnel si port 80 ouvert)
|
||||
# redirect scheme https if !{ ssl_fc }
|
||||
|
||||
default_backend n8n_backend
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# backend : instance n8n locale
|
||||
#---------------------------------------------------------------------
|
||||
backend n8n_backend
|
||||
balance roundrobin
|
||||
|
||||
# Supprimer le X-App-Token avant de forwarder à n8n (ne doit pas atteindre n8n)
|
||||
# Supprimer le token avant de forwarder à n8n (n8n ne doit pas le voir)
|
||||
http-request del-header X-App-Token
|
||||
|
||||
# Health check : n8n répond sur /healthz
|
||||
option httpchk GET /healthz
|
||||
http-check expect status 200
|
||||
http-request set-header X-Forwarded-Proto https if { ssl_fc }
|
||||
http-request set-header X-Real-IP %[src]
|
||||
http-request set-header X-Forwarded-For %[src]
|
||||
http-request set-header Host %[req.hdr(host)]
|
||||
option forwardfor
|
||||
server n8n 192.168.1.56:5678
|
||||
```
|
||||
|
||||
server n8n_local 127.0.0.1:5678 check inter 10s fall 3 rise 2
|
||||
**Logique** :
|
||||
- Depuis le LAN (`192.168.1/2.x`) ou ton IP fixe → accès direct, pas de token requis (navigation normale dans n8n)
|
||||
- Depuis l'extérieur (mobile app 4G/5G) → `X-App-Token` obligatoire, sinon 403
|
||||
|
||||
---
|
||||
|
||||
## Appliquer la modification
|
||||
|
||||
```bash
|
||||
# Valider la syntaxe avant rechargement
|
||||
haproxy -f /etc/haproxy/haproxy.cfg -c
|
||||
|
||||
# Rechargement gracieux (sans coupure)
|
||||
systemctl reload haproxy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotation du X-App-Token
|
||||
## Générer un token solide
|
||||
|
||||
Le token doit être tourné régulièrement (recommandé : tous les 90 jours minimum).
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
### Procédure de rotation sans downtime
|
||||
Copier la valeur générée dans :
|
||||
1. La config HAProxy (`VOTRE_TOKEN_ICI` ci-dessus)
|
||||
2. L'app Android : Paramètres > Token HAProxy > Sauvegarder
|
||||
|
||||
1. **Générer un nouveau token** :
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
# Exemple : a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
|
||||
```
|
||||
---
|
||||
|
||||
2. **Phase de transition — accepter les deux tokens** :
|
||||
```haproxy
|
||||
# Accepter l'ancien ET le nouveau pendant la transition
|
||||
acl valid_app_token req.hdr(X-App-Token) -m str "ANCIEN_TOKEN"
|
||||
acl valid_app_token_new req.hdr(X-App-Token) -m str "NOUVEAU_TOKEN"
|
||||
http-request deny deny_status 403 if !valid_app_token !valid_app_token_new
|
||||
```
|
||||
## Rotation du token
|
||||
|
||||
3. **Mettre à jour l'app** sur tous les appareils (Paramètres > Token HAProxy > Sauvegarder)
|
||||
1. Générer un nouveau token (`openssl rand -hex 32`)
|
||||
2. Ajouter temporairement les **deux** ACLs dans HAProxy pour éviter les coupures :
|
||||
|
||||
4. **Retirer l'ancien token** de HAProxy une fois tous les appareils mis à jour :
|
||||
```haproxy
|
||||
acl valid_app_token req.hdr(X-App-Token) -m str "NOUVEAU_TOKEN"
|
||||
http-request deny deny_status 403 if !valid_app_token
|
||||
```
|
||||
```haproxy
|
||||
acl valid_app_token req.hdr(X-App-Token) -m str "ANCIEN_TOKEN"
|
||||
acl valid_app_token_new req.hdr(X-App-Token) -m str "NOUVEAU_TOKEN"
|
||||
http-request deny deny_status 403 if !is_local !valid_app_token !valid_app_token_new
|
||||
```
|
||||
|
||||
5. **Recharger HAProxy** sans coupure :
|
||||
```bash
|
||||
haproxy -f /etc/haproxy/haproxy.cfg -c # Valider la config
|
||||
systemctl reload haproxy # Rechargement gracieux
|
||||
```
|
||||
3. Mettre à jour le token dans l'app (Paramètres > Token HAProxy)
|
||||
4. Retirer l'ancien token de la config HAProxy
|
||||
5. `systemctl reload haproxy`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Erreur 403 Forbidden
|
||||
|
||||
**Cause** : `X-App-Token` absent ou incorrect.
|
||||
|
||||
**Diagnostic** :
|
||||
**403 depuis l'app** → token absent ou incorrect. Tester :
|
||||
```bash
|
||||
# Tester manuellement avec curl
|
||||
curl -v -H "X-App-Token: VOTRE_TOKEN" https://n8n.votre-domaine.com/api/v1/workflows
|
||||
curl -v -H "X-App-Token: VOTRE_TOKEN" https://n8n.gyozamancave.fr/api/v1/workflows
|
||||
```
|
||||
|
||||
**Solutions** :
|
||||
- Vérifier que le token dans l'app (Paramètres) correspond exactement à la config HAProxy
|
||||
- Contrôler les espaces ou caractères invisibles dans le token
|
||||
- Vérifier les logs HAProxy : `journalctl -u haproxy -f`
|
||||
**403 depuis le navigateur** → vérifier que l'IP source est bien dans `is_local`. Depuis un VPN ou tethering, l'IP peut ne pas matcher.
|
||||
|
||||
---
|
||||
|
||||
### Erreur 429 Too Many Requests
|
||||
|
||||
**Cause** : rate limit dépassé (> 60 req/min).
|
||||
|
||||
**Solutions** :
|
||||
- Augmenter l'intervalle de polling dans l'app (Paramètres > Intervalle)
|
||||
- Ajuster la limite dans HAProxy si l'usage légitime est plus élevé
|
||||
- Vérifier qu'aucun processus tiers ne spam l'endpoint
|
||||
|
||||
---
|
||||
|
||||
### Timeout / connexion refusée
|
||||
|
||||
**Cause** : n8n inaccessible sur le port 5678, ou HAProxy mal configuré.
|
||||
|
||||
**Diagnostic** :
|
||||
**Logs HAProxy en direct** :
|
||||
```bash
|
||||
# Vérifier que n8n tourne
|
||||
curl http://127.0.0.1:5678/healthz
|
||||
|
||||
# Vérifier le statut HAProxy
|
||||
systemctl status haproxy
|
||||
haproxy -f /etc/haproxy/haproxy.cfg -c
|
||||
|
||||
# Logs HAProxy en temps réel
|
||||
journalctl -u haproxy -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Certificat TLS expiré
|
||||
|
||||
```bash
|
||||
# Renouveler avec Certbot
|
||||
certbot renew --quiet
|
||||
# Recharger HAProxy pour prendre en compte le nouveau certificat
|
||||
systemctl reload haproxy
|
||||
```
|
||||
|
||||
Automatiser le renouvellement :
|
||||
```bash
|
||||
# Ajouter dans crontab (root)
|
||||
0 3 * * 1 certbot renew --quiet && systemctl reload haproxy
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user