Comment mettre en place un WAF avec HAProxy
Guide complet pour ajouter une protection de type pare-feu applicatif web à HAProxy avec coraza-spoa et l'OWASP Core Rule Set, complétée par les défenses natives que sont les ACL et les stick-tables.
HAProxy est le load balancer open source le plus utilisé au monde, mais à lui seul il n'est pas un pare-feu applicatif web. HAProxy open source ne comporte aucun module WAF natif ; il inspecte et achemine le trafic, termine le TLS et applique des ACL et des limitations de débit, mais il n'effectue pas, en standard, de détection d'attaques applicatives fondée sur des signatures ou des règles.
Pour doter HAProxy open source d'une véritable capacité WAF, vous déléguez l'inspection des requêtes à un moteur externe via le Stream Processing Offload Protocol (SPOP), au moyen d'un Stream Processing Offload Agent (SPOA). L'option open source la plus pratique est coraza-spoa, qui exécute le moteur OWASP Coraza et l'OWASP Core Rule Set (CRS) sous la forme d'un SPOA que HAProxy consulte pour chaque requête. HAProxy Enterprise propose une autre voie : un WAF intégré, propulsé par du machine learning, qui s'exécute au sein du processus, sans agent distinct à gérer.
Ce guide vous accompagne à travers ces deux couches de défense. Vous durcissez d'abord HAProxy avec les ACL et les stick-tables natives pour la limitation de débit et le filtrage de base, puis vous déployez coraza-spoa avec l'OWASP CRS pour une protection applicative complète. À la fin, vous disposerez d'un WAF fonctionnel et réglable devant vos backends, sans aucun coût de licence.
Prérequis
- HAProxy 2.6+ recommandé (SPOE est disponible depuis la version 1.7, mais utilisez une branche LTS récente)
- Accès root ou sudo à votre serveur
- Ubuntu 22.04+, Debian 12+ ou RHEL/Rocky 9+ (les autres distributions peuvent varier)
- Une application backend fonctionnelle déjà mise en proxy via HAProxy
- Go 1.25+ et Git installés (le go.mod de coraza-spoa exige une chaîne d'outils Go récente pour compiler depuis les sources)
- Connaissances de base de la configuration HAProxy (frontends, backends, ACL)
Guide étape par étape
Choisir votre approche WAF pour HAProxy
HAProxy open source ne fournit aucun module WAF. Il existe trois véritables options pour en ajouter un, et il vaut la peine de choisir délibérément avant d'installer quoi que ce soit :
- coraza-spoa (open source, recommandé ici) : exécute le moteur OWASP Coraza et l'OWASP Core Rule Set sous la forme d'un Stream Processing Offload Agent. HAProxy transmet chaque requête à l'agent via SPOP et applique le verdict rendu. Mêmes règles SecLang que ModSecurity.
- SPOA ModSecurity (open source) : un SPOA plus ancien qui embarque le moteur ModSecurity. Il fonctionne, mais il est moins activement maintenu que Coraza et se trouve de fait supplanté par coraza-spoa pour les nouveaux déploiements.
- WAF HAProxy Enterprise (commercial) : un WAF intégré, propulsé par du machine learning, qui s'exécute dans le même processus que le load balancer, avec un mode de compatibilité OWASP CRS optionnel. Aucun agent distinct, une latence ajoutée quasi nulle, mais il nécessite une licence Enterprise.
Quel que soit le WAF retenu, les ACL et les stick-tables natives de HAProxy assurent la limitation de débit et un filtrage grossier. Ce guide utilise coraza-spoa parce qu'il est gratuit, open source et exécute les mêmes règles CRS que ModSecurity.
Ajouter une protection de base avec les ACL et les stick-tables
Avant d'ajouter un moteur WAF, servez-vous des fonctionnalités natives de HAProxy pour la limitation de débit et le filtrage de requêtes de base. Les stick-tables suivent l'état par client ; les ACL permettent de bloquer ou de brider en fonction de cet état. Ajoutez ceci à votre frontend dans /etc/haproxy/haproxy.cfg :
frontend web
mode http
bind :80
# Bufferiser le corps de la requête pour que le WAF puisse inspecter les charges utiles POST et JSON par la suite
option http-buffer-request
# Suivre le débit de requêtes par IP source sur une fenêtre de 10 s
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request track-sc0 src
# Refuser les clients dépassant 100 requêtes par tranche de 10 secondes
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
# Exemple de filtrage de base fondé sur le chemin
acl block_path path_beg /.git /.env /wp-login.php
http-request deny deny_status 403 if block_path
default_backend web-backend
Rechargez et vérifiez que la configuration est valide :
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
Installer coraza-spoa
coraza-spoa est un programme Go qui exécute le moteur WAF Coraza et l'expose à HAProxy via SPOP. Compilez-le depuis les sources :
# Cloner le SPOA
cd /opt
sudo git clone https://github.com/corazawaf/coraza-spoa.git
cd coraza-spoa
# Compiler le binaire avec Mage (il n'y a pas de Makefile ; nécessite Go 1.25+ selon go.mod)
sudo go run mage.go build
# Installer le binaire compilé dans votre PATH (il est écrit dans build/)
sudo cp build/coraza-spoa /usr/local/bin/
Créez un répertoire pour héberger la configuration et les règles de l'agent :
sudo mkdir -p /etc/coraza-spoa/rules
Télécharger et configurer l'OWASP Core Rule Set
Coraza exécute les règles OWASP CRS standard, les mêmes règles SecLang que celles utilisées par ModSecurity. Récupérez le CRS et sa configuration de base :
cd /etc/coraza-spoa/rules
sudo git clone https://github.com/coreruleset/coreruleset.git
cd coreruleset
# Créer le fichier de configuration du CRS à partir de l'exemple
sudo cp crs-setup.conf.example crs-setup.conf
Vous avez également besoin d'une configuration de base Coraza, qui définit le mode du moteur de règles et la gestion du corps des requêtes. Créez /etc/coraza-spoa/rules/coraza.conf avec les directives essentielles :
# Démarrer en mode détection jusqu'à ce que vous ayez réglé les faux positifs
SecRuleEngine DetectionOnly
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyLimitAction Reject
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml application/json
SecAuditEngine RelevantOnly
SecAuditLogParts ABIJDEFHZ
SecAuditLog /var/log/coraza-spoa/audit.log
Configurer l'agent coraza-spoa
L'agent a besoin de sa propre configuration, qui lui indique quelles règles charger et sur quelle adresse écouter HAProxy. Créez /etc/coraza-spoa/config.yaml :
# configuration de l'agent coraza-spoa
bind: 127.0.0.1:9000
log_level: info
# Doit correspondre au nom d'une application définie dans la liste ci-dessous
default_application: default
# applications est une liste YAML ; chaque entrée est un objet doté d'un nom
applications:
- name: default
directives: |
Include /etc/coraza-spoa/rules/coraza.conf
Include /etc/coraza-spoa/rules/coreruleset/crs-setup.conf
Include /etc/coraza-spoa/rules/coreruleset/rules/*.conf
# Activer l'inspection en phase de réponse pour que le message coraza-res et
# la règle http-response deny s'exécutent réellement (false par défaut)
response_check: true
transaction_ttl_ms: 60000
log_level: info
log_file: /var/log/coraza-spoa/coraza.log
Créez le répertoire de journaux :
sudo mkdir -p /var/log/coraza-spoa
Vérifiez que l'agent démarre et charge les règles sans erreur :
sudo coraza-spoa -config /etc/coraza-spoa/config.yaml
Configurer le filtre SPOE de HAProxy
HAProxy communique avec l'agent via un fichier de configuration Stream Processing Offload Engine (SPOE). Celui-ci définit quelles données de requête sont envoyées à Coraza et dans quelles variables le verdict est réécrit. Créez /etc/haproxy/coraza.cfg :
[coraza]
spoe-agent coraza-agent
messages coraza-res
groups coraza-req
option var-prefix coraza
option set-on-error error
timeout hello 2s
timeout idle 2m
timeout processing 500ms
use-backend coraza-spoa
log global
spoe-message coraza-req
args app=var(txn.coraza.app) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body exportRuleIDs=bool(false)
spoe-message coraza-res
args app=var(txn.coraza.app) id=var(txn.coraza.id) version=res.ver status=status headers=res.hdrs body=res.body exportRuleIDs=bool(false) detect-only=bool(false)
event on-http-response
spoe-group coraza-req
messages coraza-req
La requête est envoyée via un spoe-group que le frontend déclenche avec send-spoe-group ; le message de réponse se déclenche automatiquement sur l'événement on-http-response. L'ordre des arguments correspond exactement à celui de l'exemple officiel coraza.cfg, et il doit impérativement respecter cet ordre.
Définissez ensuite un backend pointant vers l'agent afin que HAProxy sache où le joindre. Ajoutez ceci à /etc/haproxy/haproxy.cfg :
backend coraza-spoa
mode tcp
option spop-check
server coraza 127.0.0.1:9000 check
Intégrer la décision du WAF dans votre frontend
Rattachez maintenant le filtre SPOE à votre frontend et agissez sur le verdict renvoyé par Coraza. Mettez à jour le frontend de l'étape 2 dans /etc/haproxy/haproxy.cfg :
frontend web
mode http
bind :80
# Bufferiser le corps de la requête pour que Coraza puisse inspecter les charges utiles POST et JSON
option http-buffer-request
# Limitation de débit native (issue de l'étape 2)
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
# Sélectionner l'application coraza-spoa utilisée par ce frontend
http-request set-var(txn.coraza.app) str(default)
# Rattacher le filtre WAF Coraza et envoyer la requête à l'agent
filter spoe engine coraza config /etc/haproxy/coraza.cfg
http-request send-spoe-group coraza coraza-req
# Refuser les requêtes et réponses que Coraza signale comme des attaques
http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny }
http-response deny deny_status 403 if { var(txn.coraza.action) -m str deny }
default_backend web-backend
La ligne option http-buffer-request n'est pas facultative si les attaques transmises dans le corps des requêtes POST vous préoccupent. Sans elle, HAProxy ne renseigne pas de façon fiable l'échantillon req.body avant l'envoi de la requête à l'agent ; les charges utiles d'injection SQL ou de XSS véhiculées dans les corps de requêtes de formulaire ou JSON ne sont donc jamais envoyées à Coraza et échappent à l'inspection, même si SecRequestBodyAccess vaut On. L'inspection du corps des requêtes est bornée par tune.bufsize (16 Ko par défaut) : HAProxy ne peut bufferiser qu'environ un bufsize, si bien que les corps plus volumineux ne sont pas envoyés intégralement à Coraza. Si vous devez inspecter des corps de formulaire ou JSON plus gros, augmentez tune.bufsize modérément (par exemple à 64 Ko) plutôt que de l'aligner sur un SecRequestBodyLimit de plusieurs mégaoctets, ce qui multiplierait la mémoire par connexion et risquerait de provoquer une saturation mémoire.
Validez l'ensemble de la configuration avant de recharger :
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
Exécuter coraza-spoa en tant que service
Exécutez l'agent sous systemd pour qu'il démarre au boot et redémarre en cas de défaillance. Créez /etc/systemd/system/coraza-spoa.service :
[Unit]
Description=Coraza SPOA WAF agent for HAProxy
After=network.target
[Service]
ExecStart=/usr/local/bin/coraza-spoa -config /etc/coraza-spoa/config.yaml
Restart=on-failure
User=haproxy
Group=haproxy
[Install]
WantedBy=multi-user.target
Activez et démarrez l'agent, puis rechargez HAProxy :
sudo systemctl daemon-reload
sudo systemctl enable --now coraza-spoa
sudo systemctl reload haproxy
# Vérifier que l'agent tourne et écoute
sudo systemctl status coraza-spoa
Tester le WAF
Passez temporairement SecRuleEngine On dans /etc/coraza-spoa/rules/coraza.conf, redémarrez l'agent et envoyez des charges utiles d'attaque connues. Elles doivent être bloquées avec un code 403 :
sudo systemctl restart coraza-spoa
# Tentative d'injection SQL (doit être bloquée)
curl -I 'http://your-server.com/?id=1%20OR%201=1'
# Tentative de XSS (doit être bloquée)
curl -I 'http://your-server.com/?q=<script>alert(1)</script>'
# Tentative d'injection SQL dans le corps de la requête (doit être bloquée si http-buffer-request est activé)
curl -I -X POST -d 'id=1 OR 1=1' 'http://your-server.com/login'
# Une requête normale (doit passer avec un code 200)
curl -I 'http://your-server.com/'
Vérifiez que les blocages apparaissent dans le journal d'audit de Coraza :
sudo tail -f /var/log/coraza-spoa/audit.log
Vous devriez voir des entrées indiquant les identifiants de règles CRS déclenchées et le score d'anomalie ayant provoqué le refus.
Régler les faux positifs
Faites fonctionner le mode DetectionOnly pendant une à deux semaines et passez en revue le journal d'audit à la recherche de requêtes légitimes qui auraient été bloquées. Effectuez le réglage à l'aide des mécanismes CRS standard dans un fichier personnalisé chargé après les règles, par exemple /etc/coraza-spoa/rules/custom-exclusions.conf :
# Désactiver une règle précise qui se déclenche à tort
SecRuleRemoveById 942100
# Désactiver une règle uniquement pour un chemin précis
SecRule REQUEST_URI "@beginsWith /api/upload" \
"id:1000,phase:1,pass,nolog,ctl:ruleRemoveById=920420"
Vous pouvez également abaisser le niveau de paranoïa du CRS dans crs-setup.conf pour réduire les faux positifs (1 = le moins de faux positifs, 4 = le plus strict) :
SecAction "id:900000,phase:1,pass,t:none,nolog,\
setvar:tx.blocking_paranoia_level=1"
Une fois le journal propre, positionnez SecRuleEngine On et redémarrez l'agent pour commencer à appliquer les blocages.
Conclusion et étapes suivantes
Votre instance HAProxy dispose désormais d'une protection en couches : les ACL et les stick-tables natives pour la limitation de débit et le filtrage grossier, ainsi qu'un WAF applicatif complet via coraza-spoa exécutant l'OWASP Core Rule Set. Cela confère à HAProxy open source la capacité WAF qui lui fait nativement défaut, sans aucun coût de licence.
Prochaines étapes :
- Surveiller
/var/log/coraza-spoa/audit.logpour les attaques bloquées et les faux positifs - Après une à deux semaines en mode DetectionOnly, passer SecRuleEngine sur On
- Mettre en place la rotation des journaux d'audit et de l'agent
- Expédier les journaux d'audit Coraza vers votre SIEM (ELK, Splunk, Grafana) à des fins de corrélation
- Planifier des mises à jour régulières de l'OWASP CRS et refaire des tests après chaque montée de version
- Si la performance en processus et la détection fondée sur le machine learning comptent davantage que la transparence des règles, évaluer le WAF intégré de HAProxy Enterprise comme alternative à l'approche SPOA
Dépannage
HAProxy se recharge mais le WAF ne bloque jamais rien
Vérifiez que l'agent est en cours d'exécution (systemctl status coraza-spoa) et que SecRuleEngine est positionné sur On et non sur DetectionOnly. En mode DetectionOnly, Coraza journalise les correspondances mais ne rend jamais le verdict deny, si bien que HAProxy n'a rien sur quoi agir.
Les attaques dans la chaîne de requête sont bloquées mais pas celles dans le corps POST
Cela signifie que les corps de requêtes n'atteignent pas Coraza. Ajoutez option http-buffer-request au frontend pour que HAProxy bufferise et renseigne req.body avant l'envoi de la requête à l'agent. Sans cela, SecRequestBodyAccess On n'a aucune donnée à inspecter. Notez que la taille du corps bufferisé est plafonnée par tune.bufsize (16 Ko par défaut) ; augmentez-la modérément si vous devez inspecter des corps plus volumineux.
La variable txn.coraza.action n'est jamais définie
Le var-prefix dans /etc/haproxy/coraza.cfg doit correspondre au nom de variable de votre ACL http-request deny. Si var-prefix vaut coraza, la variable est txn.coraza.action. Une incohérence fait que l'ACL ne correspond jamais, sans le moindre message.
Tout le trafic est refusé après l'activation du blocage
Il s'agit généralement d'une avalanche de faux positifs due à un CRS non réglé fonctionnant à un niveau de paranoïa élevé. Repassez SecRuleEngine sur DetectionOnly, abaissez le niveau de paranoïa à 1 et examinez le journal d'audit pour repérer les identifiants de règles fautives avant de réactiver le blocage.
HAProxy laisse passer le trafic (fail open) lorsque l'agent est arrêté
Avec option set-on-error error, HAProxy laisse passer le trafic s'il ne parvient pas à joindre l'agent. Cela évite qu'un crash de l'agent ne mette votre site hors ligne, mais cela signifie aussi qu'une panne d'agent non surveillée désactive la protection. Mettez en place des alertes sur l'état du service coraza-spoa et sur les compteurs d'erreurs SPOE dans les statistiques de HAProxy.
L'agent ne démarre pas à cause d'une erreur d'analyse de règle
Lisez la sortie de démarrage ; elle indique le fichier et la ligne de la première règle non analysable. Les causes fréquentes sont un chemin Include manquant, un checkout incomplet du CRS ou une version de coraza-spoa dont les clés de configuration diffèrent de celles présentées dans ce guide.
Latence élevée après l'activation du WAF
Chaque requête effectue désormais un aller-retour vers l'agent. Maintenez coraza-spoa sur le même hôte que HAProxy (127.0.0.1), resserrez le timeout de traitement (processing) et envisagez d'abaisser SecRequestBodyLimit ou de réduire l'inspection du corps des réponses si vous n'en avez pas besoin.
Questions fréquentes
HAProxy open source inclut-il un WAF intégré ?
Non. HAProxy open source est un load balancer et un reverse proxy ; il ne comporte aucun module WAF natif. Il assure un filtrage fondé sur les ACL et une limitation de débit via les stick-tables, mais pas de détection d'attaques au niveau applicatif. Pour disposer d'un WAF, vous déléguez l'inspection à un moteur externe via SPOP (par exemple coraza-spoa), ou vous utilisez la version commerciale HAProxy Enterprise, qui intègre un WAF.
Qu'est-ce qu'un SPOA et pourquoi HAProxy en a-t-il besoin pour un WAF ?
SPOA signifie Stream Processing Offload Agent. Il s'agit d'un programme externe avec lequel HAProxy communique via le Stream Processing Offload Protocol (SPOP) pour déléguer un travail qu'il ne peut pas réaliser lui-même. Comme HAProxy open source ne dispose d'aucun moteur WAF, un SPOA tel que coraza-spoa exécute le WAF dans un processus distinct ; HAProxy envoie chaque requête à l'agent pour inspection et applique le verdict renvoyé.
Puis-je utiliser l'OWASP Core Rule Set avec HAProxy ?
Oui. coraza-spoa exécute le moteur OWASP Coraza, entièrement compatible avec le langage de règles SecLang de ModSecurity et avec l'OWASP CRS. Vos règles CRS et vos éventuelles règles SecLang personnalisées fonctionnent exactement comme sous ModSecurity. HAProxy Enterprise propose également un mode de compatibilité CRS optionnel qui exécute les règles CRS via son moteur propulsé par du machine learning.
Dois-je utiliser coraza-spoa ou le SPOA ModSecurity ?
Pour les nouveaux déploiements, coraza-spoa est le meilleur choix. Il exécute le moteur OWASP Coraza, activement développé, en Go pur, utilise les mêmes règles CRS et est plus simple à compiler et à conteneuriser. Le SPOA ModSecurity, plus ancien, embarque le moteur ModSecurity et se trouve de fait supplanté. Notez que coraza-spoa est encore au statut préliminaire (preview) ; validez donc les formats de configuration par rapport à la version que vous installez.
Comment mettre en place une limitation de débit dans HAProxy sans WAF ?
Servez-vous des stick-tables et des ACL, qui sont des fonctionnalités natives de HAProxy. Définissez une stick-table qui suit une métrique telle que http_req_rate par IP source, suivez les connexions avec http-request track-sc0 src, puis refusez les requêtes qui dépassent un seuil. Cela s'exécute au sein du processus, sans agent externe, et constitue votre première couche de défense, en complément de l'inspection plus poussée qu'offre un WAF.
Le WAF ajoute-t-il de la latence à HAProxy ?
L'approche SPOA ajoute un aller-retour de HAProxy vers l'agent pour chaque requête ; il existe donc un surcoût mesurable par rapport au traitement natif. Maintenez coraza-spoa sur le même hôte (127.0.0.1) et ajustez le timeout de traitement (processing) pour le réduire au minimum. Le WAF intégré de HAProxy Enterprise évite cela en s'exécutant au sein du processus, ce qui constitue son principal avantage de performance par rapport à la voie SPOA open source.
Que se passe-t-il si l'agent coraza-spoa plante ?
Cela dépend de votre gestion des erreurs SPOE. Avec option set-on-error error, HAProxy laisse passer le trafic (fail open) lorsque l'agent est injoignable, ce qui maintient votre site en ligne mais désactive la protection WAF pendant la panne. Pour cette raison, surveillez le service coraza-spoa et les compteurs d'erreurs SPOE de HAProxy, et mettez en place des alertes sur l'état de l'agent afin qu'une défaillance silencieuse ne vous laisse pas sans protection.
Guides associés
Guide des bonnes pratiques de sécurité WAF
Bonnes pratiques essentielles pour configurer et maintenir votre pare-feu applicatif web pour une sécurité optimale.
Comment installer et configurer ModSecurity avec NGINX
Guide complet pour déployer ModSecurity 3.x avec NGINX afin d'obtenir une protection WAF gratuite et open source, basée sur l'OWASP Core Rule Set.