Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
350ebd7248 | ||
|
|
a9fad4634d | ||
|
|
55f99d416c | ||
|
|
a3fff845a3 | ||
|
|
dde4eb0439 |
@@ -0,0 +1,271 @@
|
|||||||
|
<!--
|
||||||
|
Brouillon vblog.io, pas publié.
|
||||||
|
Titre retenu par Cédric : "Test de BunkerWeb, le WAF Open source".
|
||||||
|
Catégories pressenties : Sécu, Docker, Ansible, Proxmox.
|
||||||
|
À faire avant publication : rendre public (ou non) le dépôt bunkerwebpoc sur gitea.vlab.bzh,
|
||||||
|
exporter docs/architecture.png en image d'article, ajouter des captures de l'UI (Reports, Bans), choisir le GIF final.
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Test de BunkerWeb, le WAF Open source
|
||||||
|
|
||||||
|
Bonjour bonjour !
|
||||||
|
|
||||||
|
Je vous l'avais teasé dans mon [test de Maintenant](https://vblog.io/test-de-maintenant-loutil-de-benjamin/) ("un Waf Open Source, dont je vous parlerai très bientôt"), eh bien le voilà ! BunkerWeb, c'est un reverse proxy basé sur nginx, avec un WAF intégré (ModSecurity et l'OWASP Core Rule Set), de l'antibot, du bannissement automatique, une interface web, le tout open source et développé en France par Bunkerity... sur le papier, c'est pile ce qu'il nous faut quand on expose des applis qu'on ne maîtrise pas complètement (et soyons honnêtes, c'est à peu près toutes les applis ^^).
|
||||||
|
|
||||||
|
Sauf qu'entre lire une doc et savoir ce que l'outil bloque VRAIMENT, il y a un monde. Donc plutôt qu'un énième "j'ai lancé le docker run et ça marche", j'ai monté un vrai PoC : trois VM sur mon Proxmox, des applis volontairement trouées derrière, un poste "attaquant" et un script qui rejoue les attaques avec et sans WAF. Le tout déployé de bout en bout par Ansible, histoire de pouvoir tout casser et tout reconstruire en quelques minutes (et croyez-moi, j'ai beaucoup cassé).
|
||||||
|
|
||||||
|
## BunkerWeb, c'est quoi exactement ?
|
||||||
|
|
||||||
|
BunkerWeb, c'est un nginx "durci par défaut", piloté par une grosse liste de réglages (plusieurs centaines, tous documentés) plutôt que par des fichiers de conf nginx écrits à la main. On lui dit quel nom de site sert quel backend, on active ou non chaque protection, et il génère la configuration nginx qui va bien.
|
||||||
|
|
||||||
|
Côté protections, on trouve dans la version communautaire : ModSecurity avec le CRS 4 ; des en-têtes de sécurité posés automatiquement (HSTS, CSP, X-Frame-Options...) ; l'antibot (challenge JavaScript, cookie, captcha maison, ou hCaptcha/reCAPTCHA/Turnstile) ; la limitation de débit et de connexions ; le "bad behavior", qui bannit une IP qui accumule trop d'erreurs 4xx ; des listes noires (IP, User-Agent, URI, listes communautaires, DNSBL, pays) ; Let's Encrypt intégré, HTTP/3, compression gzip et brotli. Il existe aussi une version PRO payante (anti DDoS, monitoring Prometheus, rapports...), que je n'ai pas testée ici.
|
||||||
|
|
||||||
|
Petit aparté pour ceux qui ne connaissent pas : le Core Rule Set de l'OWASP (le CRS pour les intimes), c'est un jeu de règles open source pour ModSecurity (et son cousin Coraza), qui repère de façon générique les grands classiques des attaques web, injections SQL, scripts injectés dans les pages, inclusion de fichiers, injection de commandes, scanners, requêtes HTTP bancales... sans rien savoir de votre appli. Le principe est malin : chaque règle qui se déclenche ajoute des points à la requête, et au-delà d'un seuil, elle est bloquée. Un "niveau de paranoïa" (de 1 à 4) permet ensuite de régler le curseur entre "je laisse passer un peu plus" et "je bloque tout ce qui bouge, faux positifs compris". C'est cette brique que BunkerWeb embarque par défaut, en version 4 dans mon PoC.
|
||||||
|
|
||||||
|
L'architecture depuis la 1.6 est découpée en briques :
|
||||||
|
|
||||||
|
- `bunkerweb` : l'instance nginx elle-même, celle qui reçoit le trafic sur 80/443 ;
|
||||||
|
- `bw-scheduler` : le cerveau, qui lit la configuration, génère la conf nginx, la pousse aux instances par une API interne et gère les tâches planifiées (renouvellement des certificats, téléchargement des listes noires...) ;
|
||||||
|
- `bw-ui` : l'interface web d'administration ;
|
||||||
|
- une base de données (MariaDB dans mon cas, SQLite par défaut) qui stocke la configuration et l'état ;
|
||||||
|
- Redis, optionnel, pour partager bans, sessions et compteurs entre plusieurs instances.
|
||||||
|
|
||||||
|
Il existe plusieurs intégrations : Docker, Docker "autoconf" (configuration par labels sur les conteneurs, à la Traefik), Swarm, Kubernetes, ou paquets Linux. J'y reviens plus bas, parce que le choix a des conséquences.
|
||||||
|
|
||||||
|
## L'architecture du PoC
|
||||||
|
|
||||||
|

|
||||||
|
<!-- schéma Excalicloud : Claude Code/bunkerweb-poc/architecture-poc-bunkerweb.excalidraw -->
|
||||||
|
|
||||||
|
Trois VM Debian 13 sur Proxmox VE 9, sur un VLAN de labo en DHCP :
|
||||||
|
|
||||||
|
| VM | Rôle | Conteneurs |
|
||||||
|
|---|---|---|
|
||||||
|
| `bw-waf` | point d'entrée unique, 80/443 (TCP, et UDP pour HTTP/3) | `bunkerweb`, `bw-scheduler`, `bw-ui`, `bw-db` (MariaDB 11), `bw-redis` |
|
||||||
|
| `bw-apps` | les sites de test, joignables UNIQUEMENT depuis le WAF | `poc-www` (nginx), `poc-shop` (OWASP Juice Shop), `poc-api` (go-httpbin) |
|
||||||
|
| `bw-client` | le poste "attaquant" | `curl`, `sqlmap`, `nmap`, `ab` et le script de tests |
|
||||||
|
|
||||||
|
Si j'ai séparé le WAF et les applis sur deux VM, c'est parce qu'en vrai, on aura un WAF en DMZ et des backends ailleurs. Et ça oblige à se poser une question qu'on oublie souvent : qu'est-ce qui empêche un attaquant de contourner le WAF en tapant directement sur l'appli ? Ici, un filtrage iptables dans la chaîne `DOCKER-USER` sur `bw-apps`, qui n'autorise que l'IP du WAF à joindre les ports publiés des applis :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
iptables -N BWPOC 2>/dev/null || iptables -F BWPOC
|
||||||
|
iptables -A BWPOC -s <IP de bw-waf> -j RETURN
|
||||||
|
iptables -A BWPOC -j DROP
|
||||||
|
|
||||||
|
RULE="-i ens18 -p tcp -m conntrack --ctstate NEW --ctorigdstport 8081:8083 -j BWPOC"
|
||||||
|
iptables -C DOCKER-USER $RULE 2>/dev/null || iptables -I DOCKER-USER $RULE
|
||||||
|
```
|
||||||
|
|
||||||
|
Petite subtilité au passage : Docker réécrit le port de destination (DNAT) avant que le paquet n'arrive dans `DOCKER-USER`, donc un `--dport 8081` classique ne matche rien. Il faut filtrer sur le port d'origine avec `--ctorigdstport`. Le genre de détail qui fait perdre une heure si on ne le sait pas ^^
|
||||||
|
|
||||||
|
Une variante tout-en-un (une seule VM) existe aussi dans le dépôt, pour ceux qui veulent juste jouer : le mode est détecté tout seul, et dans ce cas BunkerWeb joint les applis par un réseau Docker interne sans publier aucun port applicatif.
|
||||||
|
|
||||||
|
## Trois sites de test, trois terrains de jeu
|
||||||
|
|
||||||
|
Une page "Hello World" derrière un WAF, ça ne montre rien. J'ai donc pris trois applis, et chacune sert à faire réagir un groupe de protections.
|
||||||
|
|
||||||
|
`www` est une vitrine nginx statique avec un formulaire de contact. C'est là que j'ai mis l'antibot en mode challenge JavaScript : un navigateur passe sans s'en rendre compte, un `curl` reste coincé sur `/challenge`. On y vérifie aussi les en-têtes de sécurité et la redirection HTTPS.
|
||||||
|
|
||||||
|
`shop` est [OWASP Juice Shop](https://owasp.org/www-project-juice-shop/), l'appli volontairement truffée de failles qu'on utilise en formation sécu. C'est le terrain de jeu de ModSecurity et du CRS (injections SQL, XSS, traversée de répertoires). Elle utilise aussi des websockets (socket.io), ce qui permet de vérifier au passage que le WAF ne casse pas l'appli.
|
||||||
|
|
||||||
|
`api` est [go-httpbin](https://github.com/mccutchen/go-httpbin), une API qui renvoie ce qu'on lui envoie. Idéal pour tester les contraintes protocolaires (méthodes autorisées, taille maximale du corps), la limitation de débit, le bad behavior et une règle ModSecurity écrite à la main. Et comme elle renvoie les en-têtes reçus, on voit tout de suite si le backend reçoit bien le `X-Forwarded-For`.
|
||||||
|
|
||||||
|
Et enfin `bwui`, l'interface de BunkerWeb, publiée... derrière BunkerWeb, évidemment.
|
||||||
|
|
||||||
|
## Intégration Docker, pas autoconf : pourquoi
|
||||||
|
|
||||||
|
L'autoconf est très séduisant quand on vient de Traefik (mon chouchou, vous le savez) : on pose des labels sur les conteneurs et BunkerWeb s'adapte. Sauf qu'il ne voit que les conteneurs du même hôte Docker. Avec des backends sur une autre VM, ça ne colle pas.
|
||||||
|
|
||||||
|
J'ai donc pris l'intégration Docker "classique" : toute la configuration est portée par les variables d'environnement du `bw-scheduler`. Avec `MULTISITE=yes`, chaque réglage peut être préfixé par le nom du site. Un extrait du compose généré (secrets remplacés) :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
bw-scheduler:
|
||||||
|
image: bunkerity/bunkerweb-scheduler:1.6.17
|
||||||
|
environment:
|
||||||
|
BUNKERWEB_INSTANCES: "bunkerweb"
|
||||||
|
SERVER_NAME: "www.bwpoc.internal shop.bwpoc.internal api.bwpoc.internal bwui.bwpoc.internal"
|
||||||
|
# --- Réglages globaux ---
|
||||||
|
MULTISITE: "yes"
|
||||||
|
DISABLE_DEFAULT_SERVER: "yes"
|
||||||
|
AUTO_REDIRECT_HTTP_TO_HTTPS: "yes"
|
||||||
|
USE_MODSECURITY: "yes"
|
||||||
|
USE_MODSECURITY_CRS: "yes"
|
||||||
|
MODSECURITY_CRS_VERSION: "4"
|
||||||
|
USE_BAD_BEHAVIOR: "yes"
|
||||||
|
BAD_BEHAVIOR_THRESHOLD: "10"
|
||||||
|
BAD_BEHAVIOR_COUNT_TIME: "1m"
|
||||||
|
BAD_BEHAVIOR_BAN_TIME: "5m"
|
||||||
|
BLACKLIST_USER_AGENT: "PoC-EvilBot"
|
||||||
|
USE_REDIS: "yes"
|
||||||
|
# --- api.bwpoc.internal ---
|
||||||
|
api.bwpoc.internal_USE_REVERSE_PROXY: "yes"
|
||||||
|
api.bwpoc.internal_REVERSE_PROXY_HOST: "http://<IP de bw-apps>:8083"
|
||||||
|
api.bwpoc.internal_ALLOWED_METHODS: "GET|POST|HEAD"
|
||||||
|
api.bwpoc.internal_MAX_CLIENT_SIZE: "1m"
|
||||||
|
api.bwpoc.internal_LIMIT_REQ_RATE: "5r/s"
|
||||||
|
```
|
||||||
|
|
||||||
|
Le scheduler parle à l'instance `bunkerweb` par une API interne sur un réseau Docker dédié (`bw-universe`), et seul ce réseau est autorisé (`API_WHITELIST_IP`). Les réglages posés par variables apparaissent dans l'UI en lecture seule ; ce qu'on modifie dans l'UI est stocké en base et s'ajoute par-dessus. C'est pratique pour tester, mais en production je garderais la conf dans le code et l'UI pour la consultation.
|
||||||
|
|
||||||
|
Côté Ansible, on ne touche pas au compose : tout est décrit dans un dictionnaire par site, et le template fait le reste. Ajouter une règle ModSecurity maison, c'est trois lignes :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
bw_sites:
|
||||||
|
api:
|
||||||
|
upstream: api
|
||||||
|
settings:
|
||||||
|
ALLOWED_METHODS: "GET|POST|HEAD"
|
||||||
|
MAX_CLIENT_SIZE: "1m"
|
||||||
|
LIMIT_REQ_RATE: "5r/s"
|
||||||
|
custom_confs:
|
||||||
|
MODSEC_poc-rule: >-
|
||||||
|
SecRule ARGS:poc "@streq attack"
|
||||||
|
"id:10001,phase:2,deny,status:403,log,msg:'PoC BunkerWeb : regle maison'"
|
||||||
|
```
|
||||||
|
|
||||||
|
Redis est activé dès le départ : bans et compteurs sont partagés, ce qui prépare le jour où on veut une deuxième instance `bunkerweb` derrière un VIP keepalived, sans rien changer au reste.
|
||||||
|
|
||||||
|
## Le déploiement : Ansible de bout en bout
|
||||||
|
|
||||||
|
Le dépôt fait tout, de la VM jusqu'aux tests, en cinq playbooks enchaînés par un `make deploy` : création des VM sur Proxmox ; socle Debian et Docker CE ; stack des applis et filtrage ; stack BunkerWeb (avec attente que l'API réponde au travers du WAF) ; poste client et script de tests.
|
||||||
|
|
||||||
|
Pour Proxmox, j'ai voulu faire propre et ne pas tout lancer en `root@pam`. Le SSH root ne sert qu'une seule fois, pour fabriquer le modèle de VM. Ensuite tout passe par l'API avec un compte dédié `bwpoc@pve`, un jeton, et un rôle taillé au plus juste, limité à un pool `bwpoc`, au stockage utilisé et au seul VLAN du labo :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pveum pool add bwpoc --comment "PoC BunkerWeb"
|
||||||
|
pveum role add BWPoC --privs "VM.Allocate,VM.Clone,VM.Audit,VM.PowerMgmt,VM.Config.CPU,VM.Config.Memory,VM.Config.Disk,VM.Config.Network,VM.Config.Cloudinit,VM.Config.Options,VM.Config.HWType,VM.Config.CDROM,VM.GuestAgent.Audit,Pool.Audit"
|
||||||
|
pveum user add bwpoc@pve --comment "PoC BunkerWeb (Ansible)"
|
||||||
|
pveum user token add bwpoc@pve ansible --privsep 0
|
||||||
|
pveum acl modify /pool/bwpoc --users bwpoc@pve --roles BWPoC
|
||||||
|
pveum acl modify /storage/zfs --users bwpoc@pve --roles PVEDatastoreUser
|
||||||
|
pveum acl modify /sdn/zones/localnetwork/vmbr0/200 --users bwpoc@pve --roles PVESDNUser
|
||||||
|
pveum acl modify /nodes/lancelot --users bwpoc@pve --roles PVEAuditor
|
||||||
|
```
|
||||||
|
|
||||||
|
Petite note PVE 9 : la lecture des infos de l'agent invité passe par le nouveau privilège `VM.GuestAgent.Audit` (sur PVE 8, c'était `VM.Monitor`). Et l'ACL réseau sur `/sdn/zones/localnetwork/vmbr0/200` permet de n'autoriser que le VLAN 200 sur le pont, ce qui est bien pratique et assez peu connu.
|
||||||
|
|
||||||
|
Les VM sont en DHCP, pas d'IP fixe dans l'inventaire. Le playbook démarre les clones, attend l'agent QEMU, lui demande l'IP obtenue, l'écrit dans `inventory/host_vars/<vm>/dhcp.yml` et recharge l'inventaire. Les playbooks suivants utilisent ces IP. Seul conseil : réservez les baux DHCP des trois VM, parce que l'IP du WAF sert au filtrage côté applis.
|
||||||
|
|
||||||
|
## Les pièges rencontrés (parce qu'il y en a toujours)
|
||||||
|
|
||||||
|
Je ne vais pas vous mentir, ça n'est pas passé du premier coup.
|
||||||
|
|
||||||
|
Première galère, le modèle sans agent QEMU. Mon modèle cloud-init Debian 13 existant n'avait pas `qemu-guest-agent`. Sans agent, impossible de lire l'IP DHCP par l'API. J'ai donc fabriqué un modèle dédié (clone du modèle existant, ajout de l'agent avec `virt-customize`), une fois pour toutes.
|
||||||
|
|
||||||
|
Deuxième galère, et pas la moins drôle : au premier déploiement, les trois VM obtiennent... la même IP DHCP. Le coupable : le `machine-id` hérité du modèle, dont le client DHCP dérive son identifiant. Même machine-id, même identifiant, même bail. La parade : un `cloud-init clean --machine-id` dans le modèle, et le playbook régénère le machine-id de chaque clone au premier démarrage par l'agent QEMU (API `/agent/exec`, sans SSH), avant de redemander un bail.
|
||||||
|
|
||||||
|
Troisième galère, le reload silencieux de BunkerWeb, et celle-là m'a bien eu. J'ai ajouté une règle ModSecurity chaînée (deux lignes `SecRule` liées par `chain`) mal formée sur la deuxième ligne. Résultat : le reload nginx échoue, et BunkerWeb... ne dit rien de visible, il reste sur l'ancienne configuration (son mécanisme de failover). Tout a l'air de tourner, sauf que la nouvelle règle n'existe pas. Dans mon cas, effet de bord rigolo, mon client de test s'est fait bannir en boucle par l'ancienne conf. Moralité, après chaque changement de conf, regardez les logs du scheduler. Et si vous vous bannissez vous-même (ça arrive plus vite qu'on ne croit) :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec bw-scheduler bwcli unban <IP>
|
||||||
|
```
|
||||||
|
|
||||||
|
Et pour finir, le coup du DNAT dans `DOCKER-USER` dont je parlais plus haut (`--ctorigdstport` et pas `--dport`, je le répète parce que je me suis fait avoir ^^).
|
||||||
|
|
||||||
|
## Les tests : 26 sur 26
|
||||||
|
|
||||||
|
Le dépôt embarque un script `bwpoc-tests` qui rejoue une série de requêtes avec `curl --resolve` (donc pas besoin de DNS) et compare le code HTTP obtenu à celui attendu. Sortie réelle, lancée depuis `bw-client` :
|
||||||
|
|
||||||
|
```
|
||||||
|
debian@bw-client:~$ bwpoc-tests
|
||||||
|
|
||||||
|
PoC BunkerWeb : WAF <IP de bw-waf>, sites <site>-bwpoc.vlab.bzh
|
||||||
|
|
||||||
|
== Transport et socle ==
|
||||||
|
[OK] HTTP redirigé vers HTTPS (api) 301
|
||||||
|
[OK] Host inconnu refusé (DISABLE_DEFAULT_SERVER) 403
|
||||||
|
[OK] Requête légitime sur l'API 200
|
||||||
|
|
||||||
|
== En-têtes et compression ==
|
||||||
|
[OK] En-tête strict-transport-security ajouté yes
|
||||||
|
[OK] En-tête x-frame-options ajouté yes
|
||||||
|
[OK] En-tête x-content-type-options ajouté yes
|
||||||
|
[OK] En-tête content-security-policy ajouté yes
|
||||||
|
[OK] En-tête referrer-policy ajouté yes
|
||||||
|
[OK] En-tête Server masqué masqué
|
||||||
|
[OK] Le backend reçoit X-Forwarded-For (<IP de bw-client>) yes
|
||||||
|
[OK] Compression br br
|
||||||
|
[OK] Compression gzip gzip
|
||||||
|
|
||||||
|
== Antibot (www) ==
|
||||||
|
[OK] Un client sans JavaScript reste bloqué sur /challenge yes
|
||||||
|
|
||||||
|
== WAF ModSecurity + OWASP CRS 4 (shop) ==
|
||||||
|
[OK] Page d'accueil Juice Shop 200
|
||||||
|
[OK] SQLi : recherche produits ')) OR 1=1-- 403
|
||||||
|
[OK] XSS : <script>alert(1)</script> 403
|
||||||
|
[OK] Traversée de répertoire ../../etc/passwd 403
|
||||||
|
[OK] Injection de commande ;cat /etc/passwd 403
|
||||||
|
[OK] Règle ModSecurity maison ?poc=attack 403
|
||||||
|
|
||||||
|
== Listes noires ==
|
||||||
|
[OK] User-Agent maison PoC-EvilBot (BLACKLIST_USER_AGENT) 403
|
||||||
|
[OK] User-Agent sqlmap (liste communautaire) 403
|
||||||
|
|
||||||
|
== Contraintes protocolaires (api) ==
|
||||||
|
[OK] Méthode PUT interdite (ALLOWED_METHODS) 405
|
||||||
|
[OK] Corps de 2 Mo refusé (MAX_CLIENT_SIZE=1m) 413
|
||||||
|
|
||||||
|
== Limitation de débit puis bad behavior (api) ==
|
||||||
|
Envoi de 40 requêtes en parallèle (LIMIT_REQ_RATE=5r/s)...
|
||||||
|
Répartition des codes : 1 200 39 429
|
||||||
|
[OK] Des requêtes ont été limitées (429) yes
|
||||||
|
Envoi de 12 requêtes en erreur 404 (BAD_BEHAVIOR_THRESHOLD=10)...
|
||||||
|
[OK] IP bannie : requête légitime désormais refusée 403
|
||||||
|
[OK] Bannissement limité au service api (shop répond) 200
|
||||||
|
|
||||||
|
Bilan : 26 OK, 0 KO, 0 info
|
||||||
|
```
|
||||||
|
|
||||||
|
Le bannissement par bad behavior est par site : mon client est banni de `api` mais continue d'accéder à `shop`, et je trouve ça plutôt malin (une appli bavarde en 404 ne pénalise pas les autres). La limitation de débit, elle, est volontairement sévère (5 requêtes/s sur l'API, et sur une rafale de 40 il n'en passe qu'une !), à ajuster selon l'appli bien sûr. Et les derniers tests bannissent volontairement le client pendant 5 minutes ; j'ai raccourci le `BAD_BEHAVIOR_BAN_TIME` (1 jour par défaut !) pour pouvoir rejouer.
|
||||||
|
|
||||||
|
### Avec et sans WAF
|
||||||
|
|
||||||
|
Le test le plus parlant, c'est de rejouer les mêmes attaques en direct sur les applis, en ouvrant temporairement le filtrage au client (`apps_direct_access_sources` dans l'inventaire, puis `bwpoc-tests --direct`). La SQLi de Juice Shop renvoie alors gentiment la liste complète des produits en 200 au lieu d'un 403, et le `PUT` passe sans broncher. Rien de surprenant, mais le voir noir sur blanc dans sa console, ça parle plus qu'une plaquette commerciale.
|
||||||
|
|
||||||
|
### Aller plus loin à la main
|
||||||
|
|
||||||
|
Le script ne fait qu'effleurer le sujet, et voici ce que je vous conseille de tester à la main :
|
||||||
|
|
||||||
|
- `sqlmap` contre Juice Shop avec son User-Agent par défaut : bloqué tout de suite par la liste communautaire. Avec `--random-agent`, c'est le CRS qui prend le relais, et c'est là qu'on voit sa vraie valeur ;
|
||||||
|
- l'antibot en mode `captcha` ou `cookie` sur `www`, à tester dans un navigateur ;
|
||||||
|
- les faux positifs : naviguez normalement dans Juice Shop (connexion, panier, avis). Ce que le CRS bloque à tort remonte dans l'UI (menu Reports) avec l'ID de la règle, qu'on exclut par un `SecRuleRemoveById` dans un `custom_confs` de type `MODSEC_CRS` ;
|
||||||
|
- le mode `MODSECURITY_SEC_RULE_ENGINE: "DetectionOnly"` sur un site, pour observer avant de bloquer. Clairement, c'est comme ça que je démarrerais en production ;
|
||||||
|
- les journaux : `docker logs -f bunkerweb`, et l'audit ModSecurity dans `/var/log/bunkerweb/modsec_audit.log` du conteneur.
|
||||||
|
|
||||||
|
## Et pour l'exposer ?
|
||||||
|
|
||||||
|
Pour le rendre accessible de l'extérieur, j'ai simplement mis le WAF derrière mon chouchou Traefik, déjà en place chez moi, qui ne fait que l'accessibilité et le Let's Encrypt (sites en `www-bwpoc.vlab.bzh` et compagnie). Le seul point à retenir côté BunkerWeb : déclarer l'IP du reverse proxy en amont (`USE_REAL_IP`, `REAL_IP_FROM`, `REAL_IP_HEADER: X-Forwarded-For`), sinon BunkerWeb voit toutes les requêtes venir de Traefik... et le premier bannissement bannit tout le monde ^^. L'interface d'admin, elle, est réservée aux IP privées par une petite règle ModSecurity par site. Le dépôt sait aussi faire du Let's Encrypt directement par BunkerWeb si vous n'avez pas de proxy devant.
|
||||||
|
|
||||||
|
## Les limites du PoC
|
||||||
|
|
||||||
|
Pour être honnête jusqu'au bout : une seule instance BunkerWeb, donc pas de haute dispo (Redis est là pour la suite) ; certificats auto-signés en interne ; les fonctions PRO ne sont pas couvertes ; et le géoblocage comme les DNSBL ne se testent pas proprement depuis des IP privées.
|
||||||
|
|
||||||
|
## Alors, verdict ?
|
||||||
|
|
||||||
|
Franchement, je suis bluffé par le rapport effort/résultat. Avec une poignée de variables, on a un reverse proxy correctement durci, le CRS 4 actif partout, de l'antibot, du bannissement, et une UI qui permet enfin de voir ce qui se passe (ce qui n'a jamais été le point fort d'un ModSecurity monté "à la main", on ne va pas se mentir). La doc est riche, les réglages sont nombreux mais cohérents, et l'intégration Docker se pilote très bien par Ansible.
|
||||||
|
|
||||||
|
Il y a quand même des points d'attention : le reload silencieux en cas de conf invalide (à surveiller dans les logs, vraiment), la cohabitation UI/variables d'environnement qu'il faut décider dès le départ, et comme tout WAF, le travail sur les faux positifs qui ne se fait pas tout seul. On ne le pose pas pour l'oublier ensuite. Mais pour des besoins "classiques", je le vois tout à fait remplacer des WAF propriétaires, et en plus c'est français, ce qui ne gâche rien (souveraineté, tout ça... vous me connaissez ^^).
|
||||||
|
|
||||||
|
La suite logique, ce sera une deuxième instance derrière un VIP pour tester la haute dispo, et un vrai test de charge. Si certains d'entre vous l'utilisent déjà en production, je suis TRES preneur de vos retours en commentaire !
|
||||||
|
|
||||||
|
<!-- TODO Cédric : paragraphe transparence IA (PoC et dépôt construits avec Claude Code / Jarvis), à ta façon -->
|
||||||
|
|
||||||
|
Bonne découverte !
|
||||||
|
|
||||||
|
Références sur vBlog.io :
|
||||||
|
|
||||||
|
- [Test de Maintenant, l'outil de Benjamin](https://vblog.io/test-de-maintenant-loutil-de-benjamin/) (où je vous teasais déjà ce WAF ^^) <!-- d'autres liens vBlog à ajouter ? -->
|
||||||
|
|
||||||
|
Autres liens utiles :
|
||||||
|
|
||||||
|
- **BunkerWeb** : [documentation](https://docs.bunkerweb.io/latest/)
|
||||||
|
- **BunkerWeb sur GitHub** : [fichiers d'intégration officiels](https://github.com/bunkerity/bunkerweb/tree/v1.6.17/misc/integrations)
|
||||||
|
- **OWASP** : [Core Rule Set](https://coreruleset.org/)
|
||||||
|
- **OWASP CRS** : [documentation](https://coreruleset.org/docs/)
|
||||||
|
- **OWASP** : [projet ModSecurity Core Rule Set](https://owasp.org/www-project-modsecurity-core-rule-set/)
|
||||||
|
- **OWASP** : [Juice Shop](https://owasp.org/www-project-juice-shop/)
|
||||||
|
- **go-httpbin** : [dépôt GitHub](https://github.com/mccutchen/go-httpbin)
|
||||||
|
- **Le PoC** : [dépôt Ansible + docker-compose](https://gitea.vlab.bzh/vLab-BZH/bunkerwebpoc) <!-- à rendre public avant publication -->
|
||||||
|
|
||||||
|
<!-- GIF final à choisir -->
|
||||||
Reference in new issue
Block a user