From 0e5426b7c3b56d93418dd8619f2d56336dedb11e Mon Sep 17 00:00:00 2001 From: Cedric Date: Tue, 27 Oct 2026 15:32:41 +0100 Subject: [PATCH] =?UTF-8?q?Option=20domaine=20public=20:=20Let's=20Encrypt?= =?UTF-8?q?=20(poc=5Ftls)=20et=20acc=C3=A8s=20r=C3=A9serv=C3=A9=20par=20IP?= =?UTF-8?q?=20(poc=5Fallowed=5Fsources)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 19 +++++++++++++++++++ inventory/group_vars/all.yml | 13 +++++++++++++ inventory/group_vars/bunkerweb.yml | 8 ++++++-- roles/bunkerweb/templates/compose.yml.j2 | 5 +++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 25c3461..e5767fd 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,25 @@ La SQLi de Juice Shop renvoie alors la liste complète des produits (200) au lie - **Mode détection seule** : `MODSECURITY_SEC_RULE_ENGINE: "DetectionOnly"` sur un site, pour observer avant de bloquer, comme on le ferait en production. - **Journaux** : `docker logs -f bunkerweb` sur `bw-waf`, et l'audit ModSecurity dans `/var/log/bunkerweb/modsec_audit.log` du conteneur. +## Exposer le PoC sur un domaine public (Let's Encrypt) + +Par défaut tout est en `bwpoc.internal` avec des certificats auto-signés. Pour un vrai domaine (exemple : `lab.naoned.net`) : + +1. DNS : `www`, `shop`, `api` et `bwui` du domaine pointent sur l'IP publique NATée vers le WAF. +2. Pare-feu : 80 et 443 (TCP, plus 443/UDP pour HTTP/3) vers l'IP du WAF ; réservation DHCP sur la MAC du WAF pour que l'IP ne bouge pas. +3. Variables (inventaire ou `-e`) : + +```yaml +poc_domain: lab.naoned.net +poc_tls: letsencrypt +poc_letsencrypt_email: vous@example.org +poc_allowed_sources: ["203.0.113.10"] # IP autorisées sur les sites de poc_restricted_sites (bwui par défaut) +``` + +4. `make waf client tests`. + +La restriction par IP est une règle ModSecurity par site (id 10002) qui laisse passer le challenge ACME. Le client de test est toujours autorisé. Ajoutez `shop` à `poc_restricted_sites` pour ne pas exposer Juice Shop à tout Internet. + ## Exploitation ```bash diff --git a/inventory/group_vars/all.yml b/inventory/group_vars/all.yml index 546b834..fdad2ab 100644 --- a/inventory/group_vars/all.yml +++ b/inventory/group_vars/all.yml @@ -9,6 +9,19 @@ # ".internal" est réservé par l'ICANN pour l'usage privé : pas de collision possible. poc_domain: bwpoc.internal # A ADAPTER si vous avez une zone DNS de labo +# --- TLS ---------------------------------------------------------------- +# selfsigned : certificats auto-signés (labo fermé, domaine non public) +# letsencrypt : ACME HTTP-01, les 4 noms doivent pointer sur le WAF et le port 80 être joignable d'Internet +poc_tls: selfsigned +poc_letsencrypt_email: "" +poc_letsencrypt_staging: false # true pour tester sans consommer les quotas Let's Encrypt + +# --- Exposition publique ------------------------------------------------ +# Sites réservés à certaines IP sources (403 pour les autres, sauf challenge ACME). +# Le client de test du PoC est toujours autorisé. Liste vide = pas de restriction. +poc_restricted_sites: [bwui] +poc_allowed_sources: [] # ex. ["203.0.113.10", "198.51.100.0/24"] + # --- Proxmox ---------------------------------------------------------- pve_node: lancelot pve_storage: zfs # stockage ZFS de lancelot diff --git a/inventory/group_vars/bunkerweb.yml b/inventory/group_vars/bunkerweb.yml index 72b6548..7ea2b10 100644 --- a/inventory/group_vars/bunkerweb.yml +++ b/inventory/group_vars/bunkerweb.yml @@ -21,8 +21,12 @@ bw_global_settings: MULTISITE: "yes" DISABLE_DEFAULT_SERVER: "yes" # Host inconnu = connexion fermée (444) SERVE_FILES: "no" - # TLS : certificats auto-signés par site (pas d'ACME en labo fermé) - GENERATE_SELF_SIGNED_SSL: "yes" + # TLS : auto-signé (labo fermé) ou Let's Encrypt (domaine public), voir poc_tls + GENERATE_SELF_SIGNED_SSL: "{{ 'yes' if poc_tls == 'selfsigned' else 'no' }}" + AUTO_LETS_ENCRYPT: "{{ 'yes' if poc_tls == 'letsencrypt' else 'no' }}" + EMAIL_LETS_ENCRYPT: "{{ poc_letsencrypt_email }}" + LETS_ENCRYPT_CHALLENGE: "http" + USE_LETS_ENCRYPT_STAGING: "{{ 'yes' if poc_letsencrypt_staging | bool else 'no' }}" AUTO_REDIRECT_HTTP_TO_HTTPS: "yes" # WAF : ModSecurity + OWASP CRS v4 actifs partout (défaut BunkerWeb) USE_MODSECURITY: "yes" diff --git a/roles/bunkerweb/templates/compose.yml.j2 b/roles/bunkerweb/templates/compose.yml.j2 index 10a84e1..0b07b75 100644 --- a/roles/bunkerweb/templates/compose.yml.j2 +++ b/roles/bunkerweb/templates/compose.yml.j2 @@ -66,6 +66,11 @@ services: {% for k, v in (site.custom_confs | default({})).items() %} {{ fqdn }}_CUSTOM_CONF_{{ k }}: {{ val(v) }} {% endfor %} +{% if name in poc_restricted_sites and poc_allowed_sources | length > 0 %} + # Accès réservé (poc_allowed_sources + client de test), challenge ACME toujours permis + {{ fqdn }}_CUSTOM_CONF_MODSEC_poc-allowlist: {{ val('SecRule REMOTE_ADDR "!@ipMatch ' ~ ((poc_allowed_sources + [client_ip]) | join(',')) ~ '" "id:10002,phase:1,deny,status:403,log,msg:\'PoC BunkerWeb : source non autorisee\',chain" +SecRule REQUEST_URI "!@beginsWith /.well-known/acme-challenge/"') }} +{% endif %} {% endfor %} networks: - bw-universe