# {{ ansible_managed }} # Stack BunkerWeb {{ bunkerweb_version }} : WAF + scheduler + UI + MariaDB + Redis # Mode {{ poc_mode }}{% if poc_mode == 'split' %}, applis sur {{ apps_ip }}{% endif %} {% macro upstream(name, site) -%} {%- if site.upstream is match('^https?://') -%} {{ site.upstream }} {%- elif poc_mode == 'single' -%} http://poc-{{ site.upstream }}:{{ apps_internal_ports[site.upstream] }} {%- else -%} http://{{ apps_ip }}:{{ apps_ports[site.upstream] }} {%- endif -%} {%- endmacro %} {% macro val(v) %}{{ (v | string | replace('$', '$$')) | to_json }}{% endmacro %} x-db-env: &db-env DATABASE_URI: {{ val('mariadb+pymysql://bunkerweb:' ~ bw_db_password ~ '@bw-db:3306/db') }} x-api-env: &api-env API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24" services: bunkerweb: image: bunkerity/bunkerweb:{{ bunkerweb_version }} container_name: bunkerweb restart: unless-stopped ports: - "80:8080/tcp" - "443:8443/tcp" - "443:8443/udp" # HTTP/3 (QUIC) labels: - "bunkerweb.INSTANCE=yes" environment: <<: [*db-env, *api-env] networks: - bw-universe - bw-services bw-scheduler: image: bunkerity/bunkerweb-scheduler:{{ bunkerweb_version }} container_name: bw-scheduler restart: unless-stopped depends_on: - bunkerweb - bw-db - bw-redis volumes: - bw-storage:/data environment: <<: [*db-env, *api-env] BUNKERWEB_INSTANCES: "bunkerweb" SERVER_NAME: {{ val(bw_sites.keys() | map('regex_replace', '$', '.' ~ poc_domain) | join(' ')) }} # --- Réglages globaux --- {% for k, v in bw_global_settings.items() %} {{ k }}: {{ val(v) }} {% endfor %} {% for name, site in bw_sites.items() %} {% set fqdn = name ~ '.' ~ poc_domain %} # --- {{ fqdn }} --- {{ fqdn }}_USE_REVERSE_PROXY: "yes" {{ fqdn }}_REVERSE_PROXY_URL: "/" {{ fqdn }}_REVERSE_PROXY_HOST: {{ val(upstream(name, site)) }} {{ fqdn }}_SELF_SIGNED_SSL_SUBJ: {{ val('/CN=' ~ fqdn ~ '/') }} {% for k, v in (site.settings | default({})).items() %} {{ fqdn }}_{{ k }}: {{ val(v) }} {% endfor %} {% 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/" "t:none"') }} {% endif %} {% endfor %} networks: - bw-universe - bw-db bw-ui: image: bunkerity/bunkerweb-ui:{{ bunkerweb_version }} container_name: bw-ui restart: unless-stopped depends_on: - bw-db volumes: - bw-ui-data:/data # secrets Flask, clés TOTP et Biscuit environment: <<: *db-env ADMIN_USERNAME: {{ val(bw_ui_admin_user) }} ADMIN_PASSWORD: {{ val(bw_ui_admin_password) }} TZ: "Europe/Paris" networks: - bw-universe - bw-db bw-db: image: {{ mariadb_image }} container_name: bw-db restart: unless-stopped command: --max-allowed-packet=67108864 environment: MYSQL_RANDOM_ROOT_PASSWORD: "yes" MYSQL_DATABASE: "db" MYSQL_USER: "bunkerweb" MYSQL_PASSWORD: {{ val(bw_db_password) }} volumes: - bw-data:/var/lib/mysql networks: - bw-db bw-redis: image: {{ redis_image }} container_name: bw-redis restart: unless-stopped command: ["redis-server", "--requirepass", {{ val(bw_redis_password) }}, "--save", "60", "1"] volumes: - bw-redis:/data networks: - bw-universe volumes: bw-data: bw-storage: bw-ui-data: bw-redis: networks: bw-universe: name: bw-universe ipam: driver: default config: - subnet: 10.20.30.0/24 # seul réseau autorisé à parler à l'API BunkerWeb bw-services: name: bw-services # réseau des applis (mode single), créé par Ansible external: true bw-db: name: bw-db