Private
Public Access
2
0
This commit is contained in:
2026-01-14 11:03:59 +03:00
parent de92065d22
commit 562f9511db
43 changed files with 619 additions and 422 deletions

View File

@@ -1,5 +1,7 @@
[defaults] [defaults]
inventory = ./inventory/ inventory = ./inventory/
gathering = smart
vault_password_file = ./.vault_pass
[ssh_connection] [ssh_connection]
pipelining = True pipelining = True

View File

@@ -0,0 +1,5 @@
ANSIBLE_STRATEGY_PLUGINS=/home/seb/.local/share/pipx/venvs/ansible/lib/python3.13/site-packages/ansible_mitogen/plugins/strategy
export ANSIBLE_STRATEGY_PLUGINS
ANSIBLE_STRATEGY=mitogen_linear
export ANSIBLE_STRATEGY

24
ansible/play-config.yml Normal file
View File

@@ -0,0 +1,24 @@
- name: Web Application Firewalls (config)
hosts: waf
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
vars:
certs_ca_passphrase: "{{ secrets.ca.passphrase }}"
certs_default_country_name: "{{ common.certificates.country_name }}"
certs_default_locality_name: "{{ common.certificates.locality_name }}"
certs_default_organization_name: "{{ common.certificates.organization_name }}"
certs_default_email_address: "{{ common.certificates.email_address }}"
certs_list:
- cn: "test.lab"
domains:
- "DNS:test.lab"
- "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}"
waf_config_sites: "{{ common.sites }}"
roles:
- "certs"
- "waf_config"
tags:
- "waf"

View File

@@ -10,14 +10,15 @@
hosts: all hosts: all
gather_facts: false gather_facts: false
vars_files: vars_files:
- "vars.yml" - "vars/common.yml"
- "vars/secrets.yml"
vars: vars:
local_ca_common_name: "Lab CA" local_ca_common_name: "Lab CA"
local_ca_passphrase: "{{ vault.ca.passphrase }}" local_ca_passphrase: "{{ secrets.ca.passphrase }}"
local_ca_country_name: "FR" local_ca_country_name: "{{ common.certificates.country_name }}"
local_ca_locality_name: "Vannes" local_ca_locality_name: "{{ common.certificates.locality_name }}"
local_ca_organization_name: "iTik" local_ca_organization_name: "{{ common.certificates.organization_name }}"
local_ca_email_address: "ca@itik.fr" local_ca_email_address: "{{ common.certificates.email_address }}"
roles: roles:
- "local_ca" - "local_ca"
tags: tags:
@@ -26,6 +27,9 @@
- name: Proxy server - name: Proxy server
hosts: proxy hosts: proxy
gather_facts: false gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
vars: vars:
squid_authorized_networks: squid_authorized_networks:
- "{{ ansible_default_ipv4.network }}/24" - "{{ ansible_default_ipv4.network }}/24"
@@ -38,7 +42,8 @@
hosts: all hosts: all
gather_facts: false gather_facts: false
vars_files: vars_files:
- "vars.yml" - "vars/common.yml"
- "vars/secrets.yml"
vars: vars:
common_proxy_server: "{{ hostvars['proxy1'].ansible_default_ipv4.address }}:3128" common_proxy_server: "{{ hostvars['proxy1'].ansible_default_ipv4.address }}:3128"
roles: roles:
@@ -49,39 +54,66 @@
- name: HTTP Servers - name: HTTP Servers
hosts: www hosts: www
gather_facts: false gather_facts: false
vars_files:
- "vars/common.yml"
- "vars/secrets.yml"
roles: roles:
- "www" - "www"
tags: tags:
- "www" - "www"
- name: Web Application Firewalls - name: Web Application Firewalls (install)
hosts: waf hosts: waf
gather_facts: false gather_facts: false
vars_files: vars_files:
- "vars.yml" - "vars/common.yml"
- "vars/secrets.yml"
vars: vars:
cert_domains: certs_ca_passphrase: "{{ secrets.ca.passphrase }}"
- "DNS:test.lab" certs_default_country_name: "{{ common.certificates.country_name }}"
- "DNS:{{ ansible_fqdn }}" certs_default_locality_name: "{{ common.certificates.locality_name }}"
- "DNS:{{ ansible_hostname }}" certs_default_organization_name: "{{ common.certificates.organization_name }}"
- "IP:{{ ansible_default_ipv4.address }}" certs_default_email_address: "{{ common.certificates.email_address }}"
- "DNS:server.{{ consul_datacenter }}.{{ consul_domain }}" certs_list:
cert_country_name: "FR" # Consul
cert_locality_name: "Vannes" - cn: "server.{{ common.consul.datacenter }}.{{ common.consul.domain }}"
cert_organization_name: "iTik" domains:
cert_email_address: "ca@itik.fr" - "DNS:server.{{ common.consul.datacenter }}.{{ common.consul.domain }}"
consul_domain: "itik.fr" # Sites
consul_datacenter: "lab" - cn: "test.lab"
consul_secret_key: "{{ vault.consul.secret_key }}" domains:
- "DNS:test.lab"
- "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}"
consul_domain: "{{ common.consul.domain }}"
consul_datacenter: "{{ common.consul.datacenter }}"
consul_secret_key: "{{ secrets.consul.secret_key }}"
consul_nodes: consul_nodes:
- "waf1" - "waf1"
- "waf2" - "waf2"
- "waf3" - "waf3"
bgwaf_cluster: false # FIXME: -> Cady modules
bgwaf_sites: "{{ waf.sites }}" waf_install_cluster: true
vip_script_user: "caddy"
vip_monitored_process: "caddy"
vip_vrrp_password: "{{ secrets.keepalived.secret }}"
vip_vrrp_ips:
- master: "waf1"
virtual_router_id: 11
addr: "{{ {{ ansible_default_ipv4.address }} | split('.')[0-3] | join('.') }}.250"
- master: "waf2"
virtual_router_id: 12
addr: "{{ {{ ansible_default_ipv4.address }} | split('.')[0-3] | join('.') }}.251"
- master: "waf3"
virtual_router_id: 13
addr: "{{ {{ ansible_default_ipv4.address }} | split('.')[0-3] | join('.') }}.252"
waf_config_sites: "{{ common.sites }}"
roles: roles:
- "cert" - "certs"
- "consul" - "consul"
- "bgwaf" - "waf_install"
- "vip"
- "waf_config"
tags: tags:
- "bgwaf" - "waf"

View File

@@ -1,201 +0,0 @@
- name: Remove packaged Go
ansible.builtin.apt:
name:
- "golang"
state: "absent"
autoremove: true
purge: true
- name: Install requirements
ansible.builtin.apt:
name:
- "git"
- "libnss3-tools"
- name: Create Go directory
ansible.builtin.file:
path: "/usr/local/go-{{ bgwaf_go_version }}"
state: "directory"
mode: "0755"
- name: Install Go
ansible.builtin.unarchive:
src: "https://go.dev/dl/go{{ bgwaf_go_version }}.linux-amd64.tar.gz"
dest: "/usr/local/go-{{ bgwaf_go_version }}"
remote_src: true
- name: Link Go
ansible.builtin.file:
src: "/usr/local/go-{{ bgwaf_go_version }}/go/bin/go"
dest: "/usr/local/bin/go"
state: "link"
- name: Install xcaddy
when: |
'xcaddy' not in ansible_facts.packages
or ansible_facts.packages['xcaddy'][0]['version'] != bgwaf_xcaddy_version
ansible.builtin.apt:
deb: "{{ bgwaf_xcaddy_download }}/v{{ bgwaf_xcaddy_version }}/xcaddy_{{ bgwaf_xcaddy_version }}_linux_amd64.deb"
force: true
- name: Create Caddy build command
ansible.builtin.set_fact:
bgwaf_build_command: >
xcaddy build v{{ bgwaf_caddy_version }}
{% if bgwaf_cluster %} --with github.com/pteich/caddy-tlsconsul{% endif %}
--with github.com/corazawaf/coraza-caddy/v2@v{{ bgwaf_coraza_caddy_version }}
--with github.com/mholt/caddy-ratelimit
--with github.com/steffenbusch/caddy-bot-barrier
--output /usr/local/bin/caddy
- name: Test Caddy version
ansible.builtin.copy:
dest: "/usr/local/bin/.caddy.build"
content: "{{ bgwaf_build_command }}"
mode: "0644"
register: bgwaf_test_caddy_version
- name: Build Caddy
# noqa: no-handler
when: "bgwaf_test_caddy_version.changed"
ansible.builtin.command:
cmd: "{{ bgwaf_build_command }}"
creates: "/usr/local/bin/caddy"
notify:
- "Restart Caddy"
- name: Create caddy system group
ansible.builtin.group:
name: "caddy"
system: true
- name: Create caddy system user
ansible.builtin.user:
name: "caddy"
group: "caddy"
groups: ["ssl-cert"]
password: "!"
system: true
home: "/opt/caddy"
- name: Create caddy config directories
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
group: "caddy"
mode: "0750"
loop:
- "/etc/caddy"
- "/etc/caddy/plugins"
- "/etc/caddy/sites"
- name: Create caddy log dir
ansible.builtin.file:
path: "/var/log/caddy"
state: "directory"
group: "caddy"
mode: "0770"
- name: Install OWASP CRS
ansible.builtin.unarchive:
src: "https://github.com/coreruleset/coreruleset/archive/refs/tags/v{{ bgwaf_owasp_crs_version }}.tar.gz"
dest: "/etc/caddy"
group: "caddy"
remote_src: true
- name: Install plugins
loop: "{{ bgwaf_crs_plugins }}"
loop_control:
loop_var: "plugin"
ansible.builtin.include_tasks: "install_plugin.yml"
- name: Create sites config directories
loop: "{{ bgwaf_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.file:
path: "/etc/caddy/sites/{{ site.name }}"
state: "directory"
group: "caddy"
mode: "0750"
- name: Generate sites coraza.conf config file
loop: "{{ bgwaf_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/coraza.conf"
dest: "/etc/caddy/sites/{{ site.name }}/coraza.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate sites crs-setup config file
loop: "{{ bgwaf_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/crs-setup.conf"
dest: "/etc/caddy/sites/{{ site.name }}/crs-setup.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate sites exclusions before request file
loop: "{{ bgwaf_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/exclusions-request-before.conf"
dest: "/etc/caddy/sites/{{ site.name }}/exclusions-request-before.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate sites exclusions after response file
loop: "{{ bgwaf_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/exclusions-response-after.conf"
dest: "/etc/caddy/sites/{{ site.name }}/exclusions-response-after.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate Caddyfile
ansible.builtin.template:
src: "templates/Caddyfile"
dest: "/etc/caddy/Caddyfile"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Copy bot barrier template
ansible.builtin.copy:
src: "files/bot_barrier_template.html"
dest: "/etc/caddy/bot_barrier_template.html"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Deploy caddy service file
ansible.builtin.template:
src: "templates/caddy.service"
dest: "/etc/systemd/system/caddy.service"
mode: "0644"
notify:
- "Restart Caddy"
- name: Enable and start Caddy
ansible.builtin.service:
name: "caddy"
state: "started"
enabled: true
daemon_reload: true

View File

@@ -1 +0,0 @@
bgwaf_xcaddy_download: "https://github.com/caddyserver/xcaddy/releases/download"

View File

@@ -1,10 +0,0 @@
# Defaults
cert_not_after: "+365d" # valid for one year
cert_not_before: "-1d" # valid since yesterday
# Required
cert_domains: []
cert_country_name: None
cert_locality_name: None
cert_organization_name: None
cert_email_address: None

View File

@@ -1,53 +0,0 @@
- name: Install requirements
ansible.builtin.apt:
name:
- "ssl-cert"
- name: Copy CA certificate file on server
ansible.builtin.copy:
src: "ca/ca-certificate.crt"
dest: "/etc/ssl/certs/local-ca.crt"
mode: "0644"
- name: Create private key for new certificate
community.crypto.openssl_privatekey:
path: "/etc/ssl/private/local.key"
mode: "0640"
group: "ssl-cert"
- name: Create CSR for new certificate
community.crypto.openssl_csr:
path: "/etc/ssl/certs/local.csr"
privatekey_path: "/etc/ssl/private/local.key"
common_name: "{{ cert_domains[0] }}"
subject_alt_name: "{{ cert_domains }}"
country_name: "{{ cert_country_name }}"
locality_name: "{{ cert_locality_name }}"
organization_name: "{{ cert_organization_name }}"
email_address: "{{ cert_email_address }}"
return_content: true
register: cert_csr
- name: Sign certificate with our CA
delegate_to: "localhost"
# noqa: no-handler
when: "cert_csr.changed"
community.crypto.x509_certificate_pipe:
csr_content: "{{ cert_csr.csr }}"
provider: "ownca"
ownca_path: "ca/ca-certificate.crt"
ownca_privatekey_path: "ca/ca-certificate.key"
ownca_privatekey_passphrase: "{{ vault.ca.passphrase }}"
ownca_not_after: "{{ cert_not_after }}"
ownca_not_before: "{{ cert_not_before }}"
register: cert_certificate
- name: Write certificate file on server
# noqa: no-handler
when: "cert_csr.changed"
ansible.builtin.copy:
dest: "/etc/ssl/certs/local.crt"
content: "{{ cert_certificate.certificate }}"
mode: "0644"
notify:
- "Reboot host"

View File

@@ -0,0 +1,19 @@
# Defaults
certs_default_country_name: null
certs_default_locality_name: null
certs_default_organization_name: null
certs_default_email_address: null
certs_default_not_after: "+365d" # valid for one year
certs_default_not_before: "-1d" # valid since yesterday
# Required
certs_ca_passphrase: null
certs_list: {}
# - cn: <string>
# domains: <array of strings>
# country_name: <string>
# locality_name: <string>
# organization_name: <string>
# email_address: <string>
# not_after: <string>
# not_before: <string>

View File

@@ -0,0 +1,42 @@
- name: Create private key for new certificate
community.crypto.openssl_privatekey:
path: "/etc/ssl/private/{{ cert.cn }}.key"
mode: "0640"
group: "ssl-cert"
- name: Create CSR for new certificate
community.crypto.openssl_csr:
path: "/etc/ssl/certs/{{ cert.cn }}.csr"
privatekey_path: "/etc/ssl/private/{{ cert.cn }}.key"
common_name: "{{ cert.cn }}"
subject_alt_name: "{{ cert.domains }}"
country_name: "{{ cert.country_name | default(certs_default_country_name) | mandatory }}"
locality_name: "{{ cert.locality_name | default(certs_default_locality_name) | mandatory }}"
organization_name: "{{ cert.organization_name | default(certs_default_organization_name) | mandatory }}"
email_address: "{{ cert.email_address | default(certs_default_email_address) | mandatory }}"
return_content: true
register: certs_csr
- name: Sign certificate with our CA
delegate_to: "localhost"
# noqa: no-handler
when: "certs_csr.changed"
community.crypto.x509_certificate_pipe:
csr_content: "{{ certs_csr.csr }}"
provider: "ownca"
ownca_path: "ca/ca-certificate.crt"
ownca_privatekey_path: "ca/ca-certificate.key"
ownca_privatekey_passphrase: "{{ certs_ca_passphrase }}"
ownca_not_after: "{{ cert.not_after | default(certs_default_not_after) }}"
ownca_not_before: "{{ cert.not_before | default(certs_default_not_before) }}"
register: certs_certificate
- name: Write certificate file on server
# noqa: no-handler
when: "certs_csr.changed"
ansible.builtin.copy:
dest: "/etc/ssl/certs/{{ cert.cn }}.crt"
content: "{{ certs_certificate.certificate }}"
mode: "0644"
notify:
- "Reboot host"

View File

@@ -0,0 +1,16 @@
- name: Install requirements
ansible.builtin.apt:
name:
- "ssl-cert"
- name: Copy CA certificate file on server
ansible.builtin.copy:
src: "ca/ca-certificate.crt"
dest: "/etc/ssl/certs/local-ca.crt"
mode: "0644"
- name: Create certs
loop: "{{ certs_list }}"
loop_control:
loop_var: "cert"
ansible.builtin.include_tasks: "create_cert.yml"

View File

@@ -1,13 +1,13 @@
# Required # Required
consul_datacenter: null consul_datacenter: null
consul_domain: null
consul_secret_key: null consul_secret_key: null
consul_nodes: [] consul_nodes: []
consul_domain: None
# Defaults # Defaults
consul_version: "1.22.2" consul_version: "1.22.2"
consul_key_file: "/etc/ssl/private/local.key" consul_key_file: "/etc/ssl/private/server.{{ consul_datacenter }}.{{ consul_domain }}.key"
consul_cert_file: "/etc/ssl/certs/local.crt" consul_cert_file: "/etc/ssl/certs/server.{{ consul_datacenter }}.{{ consul_domain }}.crt"
consul_ca_file: "/etc/ssl/certs/local-ca.crt" consul_ca_file: "/etc/ssl/certs/local-ca.crt"
consul_client_addr: "0.0.0.0" consul_client_addr: "0.0.0.0"
consul_bind_addr: "{{ ansible_default_ipv4.address }}" consul_bind_addr: "{{ ansible_default_ipv4.address }}"

View File

@@ -3,9 +3,9 @@ local_ca_key_size: 4096
local_ca_key_type: "RSA" local_ca_key_type: "RSA"
# Required # Required
local_ca_common_name: None local_ca_common_name: null
local_ca_passphrase: None local_ca_passphrase: null
local_ca_country_name: None local_ca_country_name: null
local_ca_locality_name: None local_ca_locality_name: null
local_ca_organization_name: None local_ca_organization_name: null
local_ca_email_address: None local_ca_email_address: null

View File

@@ -22,7 +22,7 @@
community.crypto.openssl_csr: community.crypto.openssl_csr:
path: "ca/ca-certificate.csr" path: "ca/ca-certificate.csr"
privatekey_path: "ca/ca-certificate.key" privatekey_path: "ca/ca-certificate.key"
privatekey_passphrase: "{{ vault.ca.passphrase }}" privatekey_passphrase: "{{ local_ca_passphrase }}"
common_name: "{{ local_ca_common_name }}" common_name: "{{ local_ca_common_name }}"
use_common_name_for_san: false # since we do not specify SANs, don't use CN as a SAN use_common_name_for_san: false # since we do not specify SANs, don't use CN as a SAN
country_name: "{{ local_ca_country_name }}" country_name: "{{ local_ca_country_name }}"
@@ -47,5 +47,5 @@
path: "ca/ca-certificate.crt" path: "ca/ca-certificate.crt"
csr_content: "{{ local_ca_csr.csr }}" csr_content: "{{ local_ca_csr.csr }}"
privatekey_path: "ca/ca-certificate.key" privatekey_path: "ca/ca-certificate.key"
privatekey_passphrase: "{{ vault.ca.passphrase }}" privatekey_passphrase: "{{ local_ca_passphrase }}"
provider: "selfsigned" provider: "selfsigned"

View File

@@ -0,0 +1,16 @@
# Required
vip_script_user: null
vip_monitored_process: null
vip_vrrp_password: null # Max 8 characters
vip_vrrp_ips: null
# - master: <string>
# virtual_router_id: <int>
# addr: <string>
# Optionnal
vip_alerts_to: null
# - <string>
# Required if length vip_alerts_to > 0
vip_smtp_from: null
vip_smtp_relay: null

View File

@@ -0,0 +1,4 @@
- name: Restart keepalived
ansible.builtin.service:
name: "keepalived"
state: "restarted"

View File

@@ -0,0 +1,28 @@
- name: Installation des paquetages
ansible.builtin.apt:
name:
- "keepalived"
notify:
- "Restart keepalived"
- name: Tell kernel to allow binding non-local IP
ansible.posix.sysctl:
name: "net.ipv4.ip_nonlocal_bind"
value: "1"
sysctl_set: true
notify:
- "Restart keepalived"
- name: Generate keepalived.conf file
ansible.builtin.template:
src: "templates/keepalived.conf"
dest: "/etc/keepalived/keepalived.conf"
mode: "0640"
notify:
- "Restart keepalived"
- name: Activate and start keepalived
ansible.builtin.service:
name: "keepalived"
state: "started"
enabled: true

View File

@@ -0,0 +1,45 @@
global_defs {
enable_script_security
script_user "{{ vip_script_user }}"
{% if vip_alerts_to | default([]) | length > 0 %}
notification_email {
{% for dest in vip_alerts_to %}
{{ dest }}
{% endfor %}
}
notification_email_from {{ vip_smtp_from }}
smtp_server {{ vip_smtp_relay }}
smtp_connect_timeout 30
router_id {{ ansible_hostname }}
{% endif %}
}
vrrp_script chk_{{ vip_monitored_process }} {
script "/usr/bin/killall -0 {{ vip_monitored_process }}" # Check if process is running
interval 2 # Check every 2 seconds
weight 2 # Weight to influence master election
}
{% for ip in vip_vrrp_ips %}
# master: {{ ip.master }}
vrrp_instance VI_{{ ip.virtual_router_id }} {
state {{ (ansible_hostname==ip.master) | ternary('MASTER', 'BACKUP') }}
interface eth0
virtual_router_id {{ ip.virtual_router_id }}
priority {{ (ansible_hostname==ip.master) | ternary('101', '100') }}
advert_int 1
promote_secondaries
smtp_alert true
authentication {
auth_type PASS
auth_pass {{ vip_vrrp_password }}
}
virtual_ipaddress {
{{ ip.addr }}
}
track_script {
chk_{{ vip_monitored_process }}
}
}
{% endfor %}

View File

@@ -1,42 +1,17 @@
bgwaf_go_version: "1.25.5" # Defaults
bgwaf_xcaddy_version: "0.4.5" waf_config_owasp_crs_version: "4.21.0"
bgwaf_caddy_version: "2.10.2"
bgwaf_coraza_caddy_version: "2.1.0" # Coraza v3.3.3
bgwaf_owasp_crs_version: "4.21.0"
# Cluster mode (e.g. with consul)
bgwaf_cluster: false
# Required unless not site.custom_cert # Required unless not site.custom_cert
bgwaf_email: null waf_config_email: null
# Required if site.bot_barrier # Required if site.bot_barrier
bgwaf_bot_barrier_secret: null waf_config_bot_barrier_secret: null
# Optionnal # Optionnal
bgwaf_local_ca_name: null waf_config_local_ca_name: null
bgwaf_ca_root: null waf_config_ca_root: null
# Cf.: https://github.com/coreruleset/plugin-registry waf_config_sites: []
bgwaf_crs_plugins:
- name: "drupal-rule-exclusions"
version: "1.0.0"
- name: "wordpress-rule-exclusions"
version: "1.2.0"
- name: "nextcloud-rule-exclusions"
version: "1.5.0"
- name: "dokuwiki-rule-exclusions"
version: "1.0.0"
- name: "phpmyadmin-rule-exclusions"
version: "1.0.0"
- name: "roundcube-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"
- name: "sogo-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"
bgwaf_sites: []
# - name: "" # - name: ""
# site: "" # site: ""
# paths: # paths:
@@ -55,15 +30,19 @@ bgwaf_sites: []
# after_response: # after_response:
# - "" # - ""
bgwaf_defaults: waf_config_defaults:
redirect_from_aliases: true redirect_from_aliases: true
custom_cert: true custom_cert: true
custom_cert_file: "" custom_cert_file: ""
custom_key_file: "" custom_key_file: ""
rate_events: 1000 rate_events: 1000
rate_window: "1m" rate_window: "1m"
bot_barrier: false bot_barrier: false
log_only: true
# CRS defaults
log_only: false
paranoia_level: 1 paranoia_level: 1
detection_paranoia_level: null detection_paranoia_level: null
enforce_bodyproc_urlencoded: 1 enforce_bodyproc_urlencoded: 1
@@ -100,8 +79,12 @@ bgwaf_defaults:
- ".asa/" - ".asa/"
- ".asax/" - ".asax/"
- ".ascx/" - ".ascx/"
- ".back/"
- ".backup/" - ".backup/"
- ".bak/" - ".bak/"
- ".bck/"
- ".bk/"
- ".bkp/"
- ".bat/" - ".bat/"
- ".cdx/" - ".cdx/"
- ".cer/" - ".cer/"
@@ -158,9 +141,11 @@ bgwaf_defaults:
- ".reg/" - ".reg/"
- ".resources/" - ".resources/"
- ".resx/" - ".resx/"
- ".sav/"
- ".save/" - ".save/"
- ".scr/" - ".scr/"
- ".sct/" - ".sct/"
- ".sh/"
- ".shs/" - ".shs/"
- ".sql/" - ".sql/"
- ".sqlite/" - ".sqlite/"

View File

@@ -0,0 +1,4 @@
- name: Reload Caddy
ansible.builtin.service:
name: "caddy"
state: "reloaded"

View File

@@ -0,0 +1,75 @@
- name: Create sites config directories
loop: "{{ waf_config_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.file:
path: "/etc/caddy/sites/{{ site.name }}"
state: "directory"
group: "caddy"
mode: "0750"
- name: Generate sites coraza.conf config file
loop: "{{ waf_config_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/coraza.conf"
dest: "/etc/caddy/sites/{{ site.name }}/coraza.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate sites crs-setup config file
loop: "{{ waf_config_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/crs-setup.conf"
dest: "/etc/caddy/sites/{{ site.name }}/crs-setup.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate sites exclusions before request file
loop: "{{ waf_config_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/exclusions-request-before.conf"
dest: "/etc/caddy/sites/{{ site.name }}/exclusions-request-before.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate sites exclusions after response file
loop: "{{ waf_config_sites }}"
loop_control:
loop_var: "site"
ansible.builtin.template:
src: "templates/exclusions-response-after.conf"
dest: "/etc/caddy/sites/{{ site.name }}/exclusions-response-after.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate Caddyfile
ansible.builtin.template:
src: "templates/Caddyfile"
dest: "/etc/caddy/Caddyfile"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Copy bot barrier template
ansible.builtin.copy:
src: "files/bot_barrier_template.html"
dest: "/etc/caddy/bot_barrier_template.html"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"

View File

@@ -2,13 +2,13 @@
# storage redis # storage redis
grace_period 10s grace_period 10s
order coraza_waf first order coraza_waf first
{% if bgwaf_email | default(None) != None %} {% if waf_config_email | default(None) != None %}
email "{{ bgwaf_email }}" email "{{ waf_config_email }}"
{% endif %} {% endif %}
{% if bgwaf_ca_root | default(None) != None %} {% if waf_config_ca_root | default(None) != None %}
tls { tls {
ca_root "{{ bgwaf_ca_root }}" ca_root "{{ waf_config_ca_root }}"
} }
{% endif %} {% endif %}
@@ -17,17 +17,17 @@
include "http.handlers.waf" include "http.handlers.waf"
output file /var/log/caddy/waf.log output file /var/log/caddy/waf.log
} }
{% if bgwaf_local_ca_name | default(None) != None %} {% if waf_config_local_ca_name | default(None) != None %}
pki { pki {
ca local { ca local {
name "{{ bgwaf_local_ca_name }}" name "{{ waf_config_local_ca_name }}"
} }
} }
{% endif %} {% endif %}
} }
{% for site in bgwaf_sites %} {% for site in waf_config_sites %}
{% if (site.redirect_from_aliases | default(true)) and (site.aliases | default([]) | length) > 0 %} {% if (site.redirect_from_aliases | default(true)) and (site.aliases | default([]) | length) > 0 %}
{{ site.aliases | join(', ') }} { {{ site.aliases | join(', ') }} {
redir https://{{ site.site }}{uri} redir https://{{ site.site }}{uri}
@@ -79,7 +79,7 @@
{% endif %} {% endif %}
{% if site.bot_barrier | default (false) %} {% if site.bot_barrier | default (false) %}
bot_barrier { bot_barrier {
secret {{ bgwaf_bot_barrier_secret }} secret {{ waf_config_bot_barrier_secret }}
complexity 18 complexity 18
valid_for 30m valid_for 30m
seed_cookie_name __chall_{{ site.name }}_seed seed_cookie_name __chall_{{ site.name }}_seed
@@ -106,7 +106,7 @@
Include "/etc/caddy/plugins/*-config.conf" Include "/etc/caddy/plugins/*-config.conf"
Include "/etc/caddy/plugins/*-before.conf" Include "/etc/caddy/plugins/*-before.conf"
Include "/etc/caddy/sites/{{ site.name }}/exclusions-request-before.conf" Include "/etc/caddy/sites/{{ site.name }}/exclusions-request-before.conf"
Include "/etc/caddy/coreruleset-{{ bgwaf_owasp_crs_version }}/rules/*.conf" Include "/etc/caddy/coreruleset-{{ waf_config_owasp_crs_version }}/rules/*.conf"
Include "/etc/caddy/sites/{{ site.name }}/exclusions-response-after.conf" Include "/etc/caddy/sites/{{ site.name }}/exclusions-response-after.conf"
Include "/etc/caddy/plugins/*-after.conf" Include "/etc/caddy/plugins/*-after.conf"
SecRuleEngine On SecRuleEngine On

View File

@@ -10,9 +10,9 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.blocking_paranoia_level={{ site.paranoia_level | default(bgwaf_defaults.paranoia_level) }}" setvar:tx.blocking_paranoia_level={{ site.paranoia_level | default(waf_config_defaults.paranoia_level) }}"
{% if site.detection_paranoia_level | default(bgwaf_defaults.detection_paranoia_level) != None %} {% if site.detection_paranoia_level | default(waf_config_defaults.detection_paranoia_level) != None %}
## Detection paranoia level ## Detection paranoia level
SecAction \ SecAction \
@@ -22,8 +22,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.detection_paranoia_level={{ site.detection_paranoia_level | default(bgwaf_defaults.detection_paranoia_level) }}" setvar:tx.detection_paranoia_level={{ site.detection_paranoia_level | default(waf_config_defaults.detection_paranoia_level) }}"
{% endif %} {% endif %}
## Enforce Body Processor URLENCODED ## Enforce Body Processor URLENCODED
@@ -34,8 +34,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.enforce_bodyproc_urlencoded={{ site.enforce_bodyproc_urlencoded | default(bgwaf_defaults.enforce_bodyproc_urlencoded) }}" setvar:tx.enforce_bodyproc_urlencoded={{ site.enforce_bodyproc_urlencoded | default(waf_config_defaults.enforce_bodyproc_urlencoded) }}"
## Anomaly Scoring Mode Severity Levels ## Anomaly Scoring Mode Severity Levels
SecAction \ SecAction \
@@ -45,11 +45,11 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.critical_anomaly_score={{ site.critical_anomaly_score | default(bgwaf_defaults.critical_anomaly_score) }},\ setvar:tx.critical_anomaly_score={{ site.critical_anomaly_score | default(waf_config_defaults.critical_anomaly_score) }},\
setvar:tx.error_anomaly_score={{ site.error_anomaly_score | default(bgwaf_defaults.error_anomaly_score) }},\ setvar:tx.error_anomaly_score={{ site.error_anomaly_score | default(waf_config_defaults.error_anomaly_score) }},\
setvar:tx.warning_anomaly_score={{ site.warning_anomaly_score | default(bgwaf_defaults.warning_anomaly_score) }},\ setvar:tx.warning_anomaly_score={{ site.warning_anomaly_score | default(waf_config_defaults.warning_anomaly_score) }},\
setvar:tx.notice_anomaly_score={{ site.notice_anomaly_score | default(bgwaf_defaults.notice_anomaly_score) }}" setvar:tx.notice_anomaly_score={{ site.notice_anomaly_score | default(waf_config_defaults.notice_anomaly_score) }}"
## Anomaly Scoring Mode Blocking Threshold Levels ## Anomaly Scoring Mode Blocking Threshold Levels
SecAction \ SecAction \
@@ -59,9 +59,9 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.inbound_anomaly_score_threshold={% if site.log_only | default(bgwaf_defaults.log_only) %}1000{% else %}{{ site.inbound_anomaly_score_threshold | default(bgwaf_defaults.inbound_anomaly_score_threshold) }}{% endif %},\ setvar:tx.inbound_anomaly_score_threshold={% if site.log_only | default(waf_config_defaults.log_only) %}1000{% else %}{{ site.inbound_anomaly_score_threshold | default(waf_config_defaults.inbound_anomaly_score_threshold) }}{% endif %},\
setvar:tx.outbound_anomaly_score_threshold={% if site.log_only | default(bgwaf_defaults.log_only) %}1000{% else %}{{ site.outbound_anomaly_score_threshold | default(bgwaf_defaults.outbound_anomaly_score_threshold) }}{% endif %}" setvar:tx.outbound_anomaly_score_threshold={% if site.log_only | default(waf_config_defaults.log_only) %}1000{% else %}{{ site.outbound_anomaly_score_threshold | default(waf_config_defaults.outbound_anomaly_score_threshold) }}{% endif %}"
## Application Specific Rule Exclusions ## Application Specific Rule Exclusions
# FIXME # FIXME
@@ -87,8 +87,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.reporting_level={{ site.reporting_level | default(bgwaf_defaults.reporting_level) }}" setvar:tx.reporting_level={{ site.reporting_level | default(waf_config_defaults.reporting_level) }}"
## Early Anomaly Scoring Mode Blocking ## Early Anomaly Scoring Mode Blocking
SecAction \ SecAction \
@@ -98,7 +98,7 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.early_blocking=0" setvar:tx.early_blocking=0"
## Initialize Default Collections ## Initialize Default Collections
@@ -109,7 +109,7 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.enable_default_collections=1" setvar:tx.enable_default_collections=1"
## HTTP Policy Settings ## HTTP Policy Settings
@@ -120,8 +120,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:'tx.allowed_methods={{ site.allowed_methods | default(bgwaf_defaults.allowed_methods) | join(" ") }}'" setvar:'tx.allowed_methods={{ site.allowed_methods | default(waf_config_defaults.allowed_methods) | join(" ") }}'"
# Content-Types that a client is allowed to send in a request # Content-Types that a client is allowed to send in a request
SecAction \ SecAction \
@@ -131,8 +131,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:'tx.allowed_request_content_type={{ site.allowed_request_content_type | default(bgwaf_defaults.allowed_request_content_type) | join(" ") }}'" setvar:'tx.allowed_request_content_type={{ site.allowed_request_content_type | default(waf_config_defaults.allowed_request_content_type) | join(" ") }}'"
## Allowed HTTP versions ## Allowed HTTP versions
SecAction \ SecAction \
@@ -142,8 +142,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:'tx.allowed_http_versions={{ site.allowed_http_versions | default(bgwaf_defaults.allowed_http_versions) | join(" ") }}'" setvar:'tx.allowed_http_versions={{ site.allowed_http_versions | default(waf_config_defaults.allowed_http_versions) | join(" ") }}'"
## Forbidden file extensions ## Forbidden file extensions
SecAction \ SecAction \
@@ -153,8 +153,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:'tx.restricted_extensions={{ site.restricted_extensions | default(bgwaf_defaults.restricted_extensions) | join(" ") }}'" setvar:'tx.restricted_extensions={{ site.restricted_extensions | default(waf_config_defaults.restricted_extensions) | join(" ") }}'"
## Restricted request headers ## Restricted request headers
SecAction \ SecAction \
@@ -164,8 +164,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:'tx.restricted_headers_basic={{ site.restricted_headers_basic | default(bgwaf_defaults.restricted_headers_basic) | join(" ") }}'" setvar:'tx.restricted_headers_basic={{ site.restricted_headers_basic | default(waf_config_defaults.restricted_headers_basic) | join(" ") }}'"
## Extended restricted request headers (forbidden at a higher paranoia level) ## Extended restricted request headers (forbidden at a higher paranoia level)
SecAction \ SecAction \
@@ -175,8 +175,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:'tx.restricted_headers_extended={{ site.restricted_headers_extended | default(bgwaf.restricted_headers_extended) | join(" ") }}'" setvar:'tx.restricted_headers_extended={{ site.restricted_headers_extended | default(waf_config_defaults.restricted_headers_extended) | join(" ") }}'"
# Content-Types charsets that a client is allowed to send in a request # Content-Types charsets that a client is allowed to send in a request
SecAction \ SecAction \
@@ -186,8 +186,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:'tx.allowed_request_content_type_charset={{ bgwaf_defaults.allowed_request_content_type_charset | default(bgwaf_defaults.allowed_request_content_type_charset) | join(" ") }}'" setvar:'tx.allowed_request_content_type_charset={{ waf_config_defaults.allowed_request_content_type_charset | default(waf_config_defaults.allowed_request_content_type_charset) | join(" ") }}'"
# Block request if number of arguments is too high # Block request if number of arguments is too high
SecAction \ SecAction \
@@ -197,8 +197,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.max_num_args={{ site.max_num_args | default(bgwaf_defaults.max_num_args) }}" setvar:tx.max_num_args={{ site.max_num_args | default(waf_config_defaults.max_num_args) }}"
# Block request if the length of any argument name is too high # Block request if the length of any argument name is too high
SecAction \ SecAction \
@@ -208,8 +208,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.arg_name_length={{ site.arg_name_length | default(bgwaf_defaults.arg_name_length) }}" setvar:tx.arg_name_length={{ site.arg_name_length | default(waf_config_defaults.arg_name_length) }}"
# Block request if the length of any argument value is too high # Block request if the length of any argument value is too high
SecAction \ SecAction \
@@ -219,8 +219,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.arg_length={{ site.arg_length | default(bgwaf_defaults.arg_length) }}" setvar:tx.arg_length={{ site.arg_length | default(waf_config_defaults.arg_length) }}"
# Block request if the total length of all combined arguments is too high # Block request if the total length of all combined arguments is too high
SecAction \ SecAction \
@@ -230,8 +230,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.total_arg_length={{ site.total_arg_length | default(bgwaf_defaults.total_arg_length) }}" setvar:tx.total_arg_length={{ site.total_arg_length | default(waf_config_defaults.total_arg_length) }}"
# Block request if the file size of any individual uploaded file is too high # Block request if the file size of any individual uploaded file is too high
SecAction \ SecAction \
@@ -241,8 +241,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.max_file_size={{ site.max_file_size | default(bgwaf_defaults.max_file_size) }}" setvar:tx.max_file_size={{ site.max_file_size | default(waf_config_defaults.max_file_size) }}"
# Block request if the total size of all combined uploaded files is too high # Block request if the total size of all combined uploaded files is too high
SecAction \ SecAction \
@@ -252,8 +252,8 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.combined_file_sizes={{ site.combined_file_sizes | default(bgwaf_defaults.combined_file_sizes) }}" setvar:tx.combined_file_sizes={{ site.combined_file_sizes | default(waf_config_defaults.combined_file_sizes) }}"
## Easing In / Sampling Percentage ## Easing In / Sampling Percentage
SecAction \ SecAction \
@@ -262,8 +262,8 @@ SecAction \
pass,\ pass,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.sampling_percentage={{ site.sampling_percentage | default(bgwaf_defaults.sampling_percentage) }}" setvar:tx.sampling_percentage={{ site.sampling_percentage | default(waf_config_defaults.sampling_percentage) }}"
## Check UTF-8 encoding ## Check UTF-8 encoding
SecAction \ SecAction \
@@ -273,7 +273,7 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.crs_validate_utf8_encoding=1" setvar:tx.crs_validate_utf8_encoding=1"
## Skip Checking Responses ## Skip Checking Responses
@@ -290,7 +290,7 @@ SecAction \
# t:none,\ # t:none,\
# nolog,\ # nolog,\
# tag:'OWASP_CRS',\ # tag:'OWASP_CRS',\
# ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ # ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
# setvar:tx.crs_skip_response_analysis=1" # setvar:tx.crs_skip_response_analysis=1"
## End of setup ## End of setup
@@ -301,5 +301,5 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ bgwaf_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\
setvar:tx.crs_setup_version={{ bgwaf_owasp_crs_version | regex_replace('\.', '') }}" setvar:tx.crs_setup_version={{ waf_config_owasp_crs_version | regex_replace('\.', '') }}"

View File

@@ -0,0 +1,27 @@
waf_install_go_version: "1.25.5"
waf_install_xcaddy_version: "0.4.5"
waf_install_caddy_version: "2.10.2"
waf_install_coraza_caddy_version: "2.1.0" # Coraza v3.3.3
waf_install_owasp_crs_version: "4.21.0"
# Cluster mode (e.g. with consul)
waf_install_cluster: false
# Cf.: https://github.com/coreruleset/plugin-registry
waf_install_crs_plugins:
- name: "drupal-rule-exclusions"
version: "1.0.0"
- name: "wordpress-rule-exclusions"
version: "1.2.0"
- name: "nextcloud-rule-exclusions"
version: "1.5.0"
- name: "dokuwiki-rule-exclusions"
version: "1.0.0"
- name: "phpmyadmin-rule-exclusions"
version: "1.0.0"
- name: "roundcube-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"
- name: "sogo-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"

View File

@@ -3,8 +3,3 @@
name: "caddy" name: "caddy"
state: "restarted" state: "restarted"
daemon_reload: true daemon_reload: true
- name: Reload Caddy
ansible.builtin.service:
name: "caddy"
state: "reloaded"

View File

@@ -3,27 +3,27 @@
url: "https://github.com/{{ plugin.provider | default('coreruleset') }}/{{ plugin.name }}-plugin/archive/refs/tags/v{{ plugin.version }}.tar.gz" url: "https://github.com/{{ plugin.provider | default('coreruleset') }}/{{ plugin.name }}-plugin/archive/refs/tags/v{{ plugin.version }}.tar.gz"
dest: "/usr/local/src/" dest: "/usr/local/src/"
mode: "0644" mode: "0644"
register: "bgwaf_plugin_downloaded" register: "waf_install_plugin_downloaded"
- name: Deploy plugin {{ plugin.name }} - name: Deploy plugin {{ plugin.name }}
# noqa: no-handler # noqa: no-handler
when: "bgwaf_plugin_downloaded.changed" when: "waf_install_plugin_downloaded.changed"
block: block:
- name: Get temp dir {{ plugin.name }} - name: Get temp dir {{ plugin.name }}
ansible.builtin.tempfile: ansible.builtin.tempfile:
state: "directory" state: "directory"
register: "bgwaf_temp_plugin_dir" register: "waf_install_temp_plugin_dir"
- name: Unarchive plugin {{ plugin.name }} - name: Unarchive plugin {{ plugin.name }}
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "/usr/local/src/{{ plugin.name }}-plugin-{{ plugin.version }}.tar.gz" src: "/usr/local/src/{{ plugin.name }}-plugin-{{ plugin.version }}.tar.gz"
dest: "{{ bgwaf_temp_plugin_dir.path }}" dest: "{{ waf_install_temp_plugin_dir.path }}"
remote_src: true remote_src: true
- name: Copy plugin files {{ plugin.name }} - name: Copy plugin files {{ plugin.name }}
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ bgwaf_temp_plugin_dir.path }}/{{ plugin.name }}-plugin-{{ plugin.version }}/plugins/" src: "{{ waf_install_temp_plugin_dir.path }}/{{ plugin.name }}-plugin-{{ plugin.version }}/plugins/"
dest: "/etc/caddy/plugins/" dest: "/etc/caddy/plugins/"
mode: "0644" mode: "0644"
# directory_mode: "0750" # directory_mode: "0750"
@@ -41,7 +41,7 @@
mode: "0750" mode: "0750"
- name: Delete temp dir {{ plugin.name }} - name: Delete temp dir {{ plugin.name }}
when: "bgwaf_temp_plugin_dir.path is defined" when: "waf_install_temp_plugin_dir.path is defined"
ansible.builtin.file: ansible.builtin.file:
path: "{{ bgwaf_temp_plugin_dir.path }}" path: "{{ waf_install_temp_plugin_dir.path }}"
state: "absent" state: "absent"

View File

@@ -0,0 +1,125 @@
- name: Remove packaged Go
ansible.builtin.apt:
name:
- "golang"
state: "absent"
autoremove: true
purge: true
- name: Install requirements
ansible.builtin.apt:
name:
- "git"
- "libnss3-tools"
- name: Create Go directory
ansible.builtin.file:
path: "/usr/local/go-{{ waf_install_go_version }}"
state: "directory"
mode: "0755"
- name: Install Go
ansible.builtin.unarchive:
src: "https://go.dev/dl/go{{ waf_install_go_version }}.linux-amd64.tar.gz"
dest: "/usr/local/go-{{ waf_install_go_version }}"
remote_src: true
- name: Link Go
ansible.builtin.file:
src: "/usr/local/go-{{ waf_install_go_version }}/go/bin/go"
dest: "/usr/local/bin/go"
state: "link"
# FIXME: no .deb
- name: Install xcaddy
when: |
'xcaddy' not in ansible_facts.packages
or ansible_facts.packages['xcaddy'][0]['version'] != waf_install_xcaddy_version
ansible.builtin.apt:
deb: "{{ waf_install_xcaddy_download }}/v{{ waf_install_xcaddy_version }}/xcaddy_{{ waf_install_xcaddy_version }}_linux_amd64.deb"
force: true
- name: Create Caddy build command
ansible.builtin.set_fact:
waf_install_build_command: >
xcaddy build v{{ waf_install_caddy_version }}
{% if waf_install_cluster %} --with github.com/pteich/caddy-tlsconsul{% endif %}
--with github.com/corazawaf/coraza-caddy/v2@v{{ waf_install_coraza_caddy_version }}
--with github.com/mholt/caddy-ratelimit
--with github.com/steffenbusch/caddy-bot-barrier
--output /usr/local/bin/caddy
- name: Test Caddy version
ansible.builtin.copy:
dest: "/usr/local/bin/.caddy.build"
content: "{{ waf_install_build_command }}"
mode: "0644"
register: waf_install_test_caddy_version
- name: Build Caddy
# noqa: no-handler
when: "waf_install_test_caddy_version.changed"
ansible.builtin.command:
cmd: "{{ waf_install_build_command }}"
creates: "/usr/local/bin/caddy"
notify:
- "Restart Caddy"
- name: Create caddy system group
ansible.builtin.group:
name: "caddy"
system: true
- name: Create caddy system user
ansible.builtin.user:
name: "caddy"
group: "caddy"
groups: ["ssl-cert"]
password: "!"
system: true
home: "/opt/caddy"
- name: Create caddy config directories
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
group: "caddy"
mode: "0750"
loop:
- "/etc/caddy"
- "/etc/caddy/plugins"
- "/etc/caddy/sites"
- name: Create caddy log dir
ansible.builtin.file:
path: "/var/log/caddy"
state: "directory"
group: "caddy"
mode: "0770"
- name: Install OWASP CRS
ansible.builtin.unarchive:
src: "https://github.com/coreruleset/coreruleset/archive/refs/tags/v{{ waf_install_owasp_crs_version }}.tar.gz"
dest: "/etc/caddy"
group: "caddy"
remote_src: true
- name: Install plugins
loop: "{{ waf_install_crs_plugins }}"
loop_control:
loop_var: "plugin"
ansible.builtin.include_tasks: "install_plugin.yml"
- name: Deploy caddy service file
ansible.builtin.template:
src: "templates/caddy.service"
dest: "/etc/systemd/system/caddy.service"
mode: "0644"
notify:
- "Restart Caddy"
- name: Enable Caddy
ansible.builtin.service:
name: "caddy"
enabled: true
daemon_reload: true

View File

@@ -0,0 +1 @@
waf_install_xcaddy_download: "https://github.com/caddyserver/xcaddy/releases/download"

View File

@@ -1,10 +1,15 @@
vault: common:
ca:
passphrase: "plopplop" certificates:
consul: country_name: "FR"
secret_key: "MZu7pu72IxVkGzSjkB0ZGd+RTp5n34ByxOoxB+BKaSc=" locality_name: "Vannes"
organization_name: "iTik"
email_address: "ca@itik.fr"
consul:
domain: "itik.fr"
datacenter: "lab"
waf:
sites: sites:
- name: "test" - name: "test"
site: "test.lab" site: "test.lab"
@@ -13,12 +18,9 @@ waf:
- "http://www1" - "http://www1"
- "http://www2" - "http://www2"
- "http://www3" - "http://www3"
log_only: true
# paranoia_level: 1
# detection_paranoia_level: null
custom_cert: true custom_cert: true
custom_cert_file: "/etc/ssl/certs/local.crt" custom_cert_file: "/etc/ssl/certs/test.lab.crt"
custom_key_file: "/etc/ssl/private/local.key" custom_key_file: "/etc/ssl/private/test.lab.key"
# exceptions: # exceptions:
# before_request: # before_request:
# - 'SecRule REQUEST_URI "@beginsWith /?bin/sh" "id:1,nolog,ctl:ruleRemoveById=932160"' # - 'SecRule REQUEST_URI "@beginsWith /?bin/sh" "id:1,nolog,ctl:ruleRemoveById=932160"'

13
ansible/vars/secrets.yml Normal file
View File

@@ -0,0 +1,13 @@
$ANSIBLE_VAULT;1.1;AES256
61643639653530613066383464333939383034643565386434616361653331656237303835653837
3431646133336265393237383366653364373039376365340a396438643530383965343865383837
62636338366437356661356561356138383032353133396461643362323536303965353564666232
3735363038323139620a386364633933303139623436626337346535663737386264303036323866
31303762336638313961383034386137376566383662323961336536303230303063396661333135
66646133333565656233303461356166303862303037663133383534613263383130316261643738
65656165346432636363393436646532643531393066346332326561656230316232383866643665
36626333656335626534333737633231396561633831653464383036323431616431306433623139
61346630663636313463376237353036343264613936366539393165623265656233303864386563
63386661343061373930383139376666353035373531386230306432663132653130633039616665
38383735326339353738383166623932316564636634343830326432613563306436666532313562
65326431346639636338

2
tofu/.gitignore vendored
View File

@@ -1,2 +1,4 @@
/.terraform/ /.terraform/
/.terraform.lock.hcl /.terraform.lock.hcl
/terraform.tfstate
/terraform.tfstate.backup

View File

@@ -1 +1 @@
{"version":4,"terraform_version":"1.11.2","serial":31,"lineage":"5ba1e29d-946a-a982-e845-5fae39bcee6e","outputs":{},"resources":[{"mode":"managed","type":"multipass_instance","name":"this","provider":"provider[\"registry.opentofu.org/larstobi/multipass\"]","instances":[{"index_key":"proxy1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"20g","image":"24.04","memory":"8g","name":"proxy1"},"sensitive_attributes":[]},{"index_key":"waf1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf1"},"sensitive_attributes":[]},{"index_key":"waf2","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf2"},"sensitive_attributes":[]},{"index_key":"waf3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf3"},"sensitive_attributes":[]},{"index_key":"www1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www1"},"sensitive_attributes":[]},{"index_key":"www2","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www2"},"sensitive_attributes":[]},{"index_key":"www3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www3"},"sensitive_attributes":[]}]}],"check_results":null} {"version":4,"terraform_version":"1.11.2","serial":37,"lineage":"5ba1e29d-946a-a982-e845-5fae39bcee6e","outputs":{},"resources":[{"mode":"managed","type":"multipass_instance","name":"this","provider":"provider[\"registry.opentofu.org/larstobi/multipass\"]","instances":[{"index_key":"proxy1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"20g","image":"24.04","memory":"8g","name":"proxy1"},"sensitive_attributes":[]},{"index_key":"waf1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf1"},"sensitive_attributes":[]},{"index_key":"waf2","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf2"},"sensitive_attributes":[]},{"index_key":"waf3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf3"},"sensitive_attributes":[]},{"index_key":"www1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www1"},"sensitive_attributes":[]},{"index_key":"www2","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www2"},"sensitive_attributes":[]},{"index_key":"www3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www3"},"sensitive_attributes":[]}]}],"check_results":null}

View File

@@ -0,0 +1 @@
{"version":4,"terraform_version":"1.11.2","serial":36,"lineage":"5ba1e29d-946a-a982-e845-5fae39bcee6e","outputs":{},"resources":[{"mode":"managed","type":"multipass_instance","name":"this","provider":"provider[\"registry.opentofu.org/larstobi/multipass\"]","instances":[{"index_key":"proxy1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"20g","image":"24.04","memory":"8g","name":"proxy1"},"sensitive_attributes":[]},{"index_key":"waf3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf3"},"sensitive_attributes":[]},{"index_key":"www1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www1"},"sensitive_attributes":[]},{"index_key":"www2","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www2"},"sensitive_attributes":[]},{"index_key":"www3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www3"},"sensitive_attributes":[]}]}],"check_results":null}

View File

@@ -1 +0,0 @@
{"version":4,"terraform_version":"1.11.2","serial":30,"lineage":"5ba1e29d-946a-a982-e845-5fae39bcee6e","outputs":{},"resources":[{"mode":"managed","type":"multipass_instance","name":"this","provider":"provider[\"registry.opentofu.org/larstobi/multipass\"]","instances":[{"index_key":"proxy1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"20g","image":"24.04","memory":"8g","name":"proxy1"},"sensitive_attributes":[]},{"index_key":"waf2","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf2"},"sensitive_attributes":[]},{"index_key":"waf3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"waf3"},"sensitive_attributes":[]},{"index_key":"www1","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www1"},"sensitive_attributes":[]},{"index_key":"www2","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www2"},"sensitive_attributes":[]},{"index_key":"www3","schema_version":0,"attributes":{"cloudinit_file":"cloud-init.yml","cpus":2,"disk":"12g","image":"24.04","memory":"2g","name":"www3"},"sensitive_attributes":[]}]}],"check_results":null}