Private
Public Access
2
0

First commit

This commit is contained in:
2026-01-07 14:27:55 +03:00
commit 0ffc9385c3
47 changed files with 11658 additions and 0 deletions

1
ansible/roles/bgwaf/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/docs/

View File

@@ -0,0 +1,146 @@
# Web Application Firewall
Ce rôle Ansible déploie un WAF basé sur Caddy, Coraza et les OWASP Core Security Rules.
## Principes
### Go
Caddy, Coraza, le module Coraza pour Caddy et l'outil `xcaddy` sont
développés avec le langage Go.
L'outil `xcaddy` a besoin d'une version récente des outils de
développement Go. C'est pourquoi le rôle installe Go depuis
le site de téléchargement officiel et non via le gestionnaire
de paquets de la distribution Linux.
Go est installé dans le répertoire `/usr/local/go-1.25.5/go` et
un lien symbolique est créé de `/usr/local/bin/go` vers
`/usr/local/go-1.25.5/go/bin/go`.
### Caddy
Caddy est un serveur Web et reverse proxy HTTP puissant et versatile.
Étant développé en Go, son déploiement prend la forme d'un simble
binaire exécutable qui contient toutes les librairies requises. Ce
programme est construit avec l'outil `xcaddy` puis copié dans le
fichier `/usr/local/bin/caddy`. La ligne de commande utilisée pour
construire ce binaire est enregsitrée dans le fichier
`/usr/local/bin/.caddy.buil`.
Le fichier de configuration de caddy utilise le format `Caddyfile`.
Il est généré automatiquement à partir des paramètres du rôle puis
enregistré sous `/etc/caddy/Caddyfile`.
### Coraza
Coraza est directement inclus dans le binaire `caddy`
### Module Coraza pour Caddy
### xcaddy
qui permet de lier tous les modules Go requis
pour le WAF
### Plugins OWASP CRS
(À ne pas confondre avec les plugins Coraza.)
Voir: https://github.com/coreruleset/plugin-registry
- name: "drupal-rule-exclusions"
- name: "wordpress-rule-exclusions"
- name: "nextcloud-rule-exclusions"
- name: "dokuwiki-rule-exclusions"
- name: "phpmyadmin-rule-exclusions"
- name: "roundcube-rule-exclusions"
- name: "sogo-rule-exclusions"
## Paramètres du rôle Ansible
## TODO
Fonctionnalités déjà implémentées:
- Multisite (cavec configuration des CRS par site)
- Redirection vers nom canonique
- Rediction HTTP vers HTTPS
- Certificats SSL (autmotiques via Let's Encrypt et customs)
- OWASP CRS
- Redirect (SSL et www et autres)
- Filtrage par:
- adresses IP
- réseaux IP
- Antibot par:
- défi JavaScript
- Passive backend checks
- Compression Gzip et zstd
Fomctionnalités à ajouter:
- Active backend checks
- Antibot par:
- Cookie
- Captcha
- reCAPTCHA
- hCaptcha
- Turnstile
- mCaptcha
- Changement de format de configuration: Caddyfile -> JSON
- Mise en œuvre d'un cluster Redis (ou Consul) pour le partage de informations entre les WAF d'un cluster
- Mieux tester le configuration des CRS
- Mode détection sans blocage
- Filtrage par:
- rDNS
- DNSBL
- ASN
- User-Agent (robots, crawlers, IA bots)
- méthode HTTP
- taille
- URI patterns
- pays
- (ip:danmeuk-tor-exit, ua:mitchellkrogza-bad-user-agents, /etc/hapee-1.9/blacklist.acl)
- Gestion CORS
- Client cache
- Proxy cache
- Filtrage par pays
- Compression Brotli
- En-têtes de sécurité:
- HSTS
- CSP
- X-Frame-Options
- X-Content-Type-Options
- Referrer Policy
- Permissions Policy
- Keep Headers
- Cookie Flags
- Custom headers
- Reverse scan
- Robots.txt (DarkVisitors API/Community Lists/Custom URLs/Manual Rules)
- Sessions
- Whitelist
- Bad behavior
- Auth
- Anti DoDS
- Greylist
- HTML injection
- Metrics (Elasticsearch + Metabase)
- Static files et FastCGI pour PHP
## Ressources
- [Documentation officielle Caddy](https://caddyserver.com/docs/)
- [Site Officiel de Coraza](https://coraza.io)
- [Coraza WAF Caddy module](https://github.com/corazawaf/coraza-caddy)
- [Documentation AWOSP CRS](https://coreruleset.org/docs/)
Modules
- []()
- []()

View File

@@ -0,0 +1,63 @@
bgwaf_go_version: "1.25.5"
bgwaf_xcaddy_version: "0.4.5"
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
bgwaf_email: null
# Required if site.bot_barrier
bgwaf_bot_barrier_secret: null
# Optionnal
bgwaf_local_ca_name: null
bgwaf_ca_root: null
# Cf.: https://github.com/coreruleset/plugin-registry
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: ""
# site: ""
# paths:
# - path: "*"
# addrs:
# - ""
# blacklist:
# - ""
# aliases:
# - ""
# plugins: FIXME:TODO
# - ""
# exceptions:
# before_request:
# - ""
# after_response:
# - ""
# redirect_from_aliases: true
# custom_cert: true
# custom_cert_file: ""
# custom_key_file: ""
# rate_events: 1000
# rate_window: "1m"
# bot_barrier: false

View File

@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Check</title>
<style nonce="{{ .CSPNonce }}">
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background-color: #f4f4f9;
color: #333;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 {
font-size: 2em;
margin: 0.4em 0 0.3em;
}
p {
font-size: 1.2em;
margin: 0.6em 0;
}
.spinner {
margin: 2em 0;
width: 50px;
height: 50px;
border: 5px solid #ccc;
border-top: 5px solid #333;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="wrapper">
<!-- English Section -->
<h1>Just a moment...</h1>
<p>A small mathematical challenge is running<br>
in the background - fully automatic to confirm<br>
you're not a bot 🔒.</p>
<!-- Spinner -->
<div class="spinner"></div>
<p>⏳ You'll be redirected shortly.</p>
</div>
<script nonce="{{ .CSPNonce }}">{{ .Script }}</script>
</body>
</html>

View File

@@ -0,0 +1,10 @@
- name: Restart Caddy
ansible.builtin.service:
name: "caddy"
state: "restarted"
daemon_reload: true
- name: Reload Caddy
ansible.builtin.service:
name: "caddy"
state: "reloaded"

View File

@@ -0,0 +1,47 @@
- name: Download plugin {{ plugin.name }}
ansible.builtin.get_url:
url: "https://github.com/{{ plugin.provider | default('coreruleset') }}/{{ plugin.name }}-plugin/archive/refs/tags/v{{ plugin.version }}.tar.gz"
dest: "/usr/local/src/"
mode: "0644"
register: "bgwaf_plugin_downloaded"
- name: Deploy plugin {{ plugin.name }}
# noqa: no-handler
when: "bgwaf_plugin_downloaded.changed"
block:
- name: Get temp dir {{ plugin.name }}
ansible.builtin.tempfile:
state: "directory"
register: "bgwaf_temp_plugin_dir"
- name: Unarchive plugin {{ plugin.name }}
ansible.builtin.unarchive:
src: "/usr/local/src/{{ plugin.name }}-plugin-{{ plugin.version }}.tar.gz"
dest: "{{ bgwaf_temp_plugin_dir.path }}"
remote_src: true
- name: Copy plugin files {{ plugin.name }}
ansible.builtin.copy:
src: "{{ bgwaf_temp_plugin_dir.path }}/{{ plugin.name }}-plugin-{{ plugin.version }}/plugins/"
dest: "/etc/caddy/plugins/"
mode: "0644"
# directory_mode: "0750"
group: "caddy"
remote_src: true
notify:
- "Reload Caddy"
# FIXME: bad hack, why is it required?
- name: Adjust /etc/caddy/plugins permissions
ansible.builtin.file:
path: "/etc/caddy/plugins"
state: "directory"
group: "caddy"
mode: "0750"
- name: Delete temp dir {{ plugin.name }}
when: "bgwaf_temp_plugin_dir.path is defined"
ansible.builtin.file:
path: "{{ bgwaf_temp_plugin_dir.path }}"
state: "absent"

View File

@@ -0,0 +1,201 @@
- 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 }}"
vars:
bgwaf_site_exclusions: "{{ item.exceptions.before_request | default([]) }}"
ansible.builtin.template:
src: "templates/exclusions.conf"
dest: "/etc/caddy/sites/{{ item.name }}/exclusions-request-before.conf"
group: "caddy"
mode: "0640"
notify:
- "Reload Caddy"
- name: Generate sites exclusions after response file
loop: "{{ bgwaf_sites }}"
vars:
bgwaf_site_exclusions: "{{ item.exceptions.after_response | default([]) }}"
ansible.builtin.template:
src: "templates/exclusions.conf"
dest: "/etc/caddy/sites/{{ item.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

@@ -0,0 +1,127 @@
{
# storage redis
grace_period 10s
order coraza_waf first
{% if bgwaf_email | default(None) != None %}
email "{{ bgwaf_email }}"
{% endif %}
{% if bgwaf_ca_root | default(None) != None %}
tls {
ca_root "{{ bgwaf_ca_root }}"
}
{% endif %}
log waf {
format json
include "http.handlers.waf"
output file /var/log/caddy/waf.log
}
{% if bgwaf_local_ca_name | default(None) != None %}
pki {
ca local {
name "{{ bgwaf_local_ca_name }}"
}
}
{% endif %}
}
{% for site in bgwaf_sites %}
{% if (site.redirect_from_aliases | default(true)) and (site.aliases | default([]) | length) > 0 %}
{{ site.aliases | join(', ') }} {
redir https://{{ site.site }}{uri}
}
{% else %}
{% for alias in site.aliases | default([]) %}
{{ alias }},
{% endfor %}
{% endif %}
{{ site.site }} {
encode gzip zstd
{% for path in site.paths %}
handle {{ path.path | default('*') }} {
reverse_proxy {
to {{ path.addrs | join(' ') }}
header_up X-Real-IP {remote}
lb_policy ip_hash
# Active checks
# health_uri /
# health_interval 10s
# health_timeout 5s
# health_status 200
# Passive checks
fail_duration 30s
max_fails 3
unhealthy_latency 2000ms
transport http {
# tls_server_name {host}
# tls_insecure_skip_verify
}
}
}
{% endfor %}
{% if site.custom_cert | default (false) %}
tls {{ site.custom_cert_file }} {{ site.custom_key_file }}
{% endif %}
{% if site.blacklist | default([]) | length > 0 %}
@blocked {
{% for ip in site.blacklist | default([]) %}
remote_ip {{ ip }}
{% endfor %}
}
respond @blocked "Access Denied" 403
{% endif %}
{% if site.bot_barrier | default (false) %}
bot_barrier {
secret {{ bgwaf_bot_barrier_secret }}
complexity 18
valid_for 30m
seed_cookie_name __chall_{{ site.name }}_seed
solution_cookie_name __chall_{{ site.name }}_solution
mac_cookie_name __chall_{{ site.name }}_mac
template /etc/caddy/bot_barrier_template.html
}
{% endif %}
rate_limit {
# distributed
zone remote_ip {
key {remote.ip}
events {{ site.rate_events | default('1000') }}
window {{ site.rate_window | default('1m') }}
}
}
coraza_waf {
load_owasp_crs
directives `
Include "/etc/caddy/sites/{{ site.name }}/coraza.conf"
Include "/etc/caddy/sites/{{ site.name }}/crs-setup.conf"
Include "/etc/caddy/plugins/*-config.conf"
Include "/etc/caddy/plugins/*-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/sites/{{ site.name }}/exclusions-response-after.conf"
Include "/etc/caddy/plugins/*-after.conf"
SecRuleEngine On
`
}
handle_errors 403 {
header X-Blocked "true"
respond "Your request was blocked."
}
log {
format json
output file /var/log/caddy/site_{{ site.name }}.log
}
}
{% endfor %}

View File

@@ -0,0 +1,20 @@
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=15s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,158 @@
# -- Rule engine initialization ----------------------------------------------
# Enable Coraza, attaching it to every transaction. Use detection
# only to start with, because that minimises the chances of post-installation
# disruption.
SecRuleEngine DetectionOnly
# -- Request body handling ---------------------------------------------------
# Allow Coraza to access request bodies. If you don't, Coraza
# won't be able to see any POST parameters, which opens a large security
# hole for attackers to exploit.
SecRequestBodyAccess On
# Enable XML request body parser.
# Initiate XML Processor in case of xml content-type
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
# Enable JSON request body parser.
# Initiate JSON Processor in case of JSON content-type; change accordingly
# if your application does not use 'application/json'
SecRule REQUEST_HEADERS:Content-Type "^application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Enable JSON request body parser for more subtypes.
# Adapt this rule if you want to engage the JSON Processor for "+json" subtypes
SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \
"id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Maximum request body size we will accept for buffering. If you support
# file uploads, this value must has to be as large as the largest file
# you are willing to accept.
SecRequestBodyLimit 13107200
# Maximum request body size that Coraza will store in memory. If the body
# size exceeds this value, it will be saved to a temporary file on disk.
SecRequestBodyInMemoryLimit 131072
# Maximum request body size we will accept for buffering, with files excluded.
# You want to keep that value as low as practical.
# Note: SecRequestBodyNoFilesLimit is currently NOT supported by Coraza
# SecRequestBodyNoFilesLimit 131072
# What to do if the request body size is above our configured limit.
# Keep in mind that this setting will automatically be set to ProcessPartial
# when SecRuleEngine is set to DetectionOnly mode in order to minimize
# disruptions when initially deploying Coraza.
# Warning: Setting this directive to ProcessPartial introduces a potential bypass
# risk, as attackers could prepend junk data equal to or greater than the inspected body size.
SecRequestBodyLimitAction Reject
# Verify that we've correctly processed the request body.
# As a rule of thumb, when failing to process a request body
# you should reject the request (when deployed in blocking mode)
# or log a high-severity alert (when deployed in detection-only mode).
SecRule REQBODY_ERROR "!@eq 0" \
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
# By default be strict with what we accept in the multipart/form-data
# request body. If the rule below proves to be too strict for your
# environment consider changing it to detection-only.
# Do NOT remove it, as it will catch many evasion attempts.
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
"id:'200003',phase:2,t:none,log,deny,status:400, \
msg:'Multipart request body failed strict validation.'"
# -- Response body handling --------------------------------------------------
# Allow Coraza to access response bodies.
# You should have this directive enabled in order to identify errors
# and data leakage issues.
# Do keep in mind that enabling this directive does increases both
# memory consumption and response latency.
SecResponseBodyAccess On
# Which response MIME types do you want to inspect? You should adjust the
# configuration below to catch documents but avoid static files
# (e.g., images and archives).
SecResponseBodyMimeType text/plain text/html text/xml
# Buffer response bodies of up to 512 KB in length.
SecResponseBodyLimit 524288
# What happens when we encounter a response body larger than the configured
# limit? By default, we process what we have and let the rest through.
# That's somewhat less secure, but does not break any legitimate pages.
SecResponseBodyLimitAction ProcessPartial
# -- Filesystem configuration ------------------------------------------------
# The location where Coraza will keep its persistent data. This default setting
# is chosen due to all systems have /tmp available however, it
# too should be updated to a place that other users can't access.
SecDataDir /tmp/
# -- File uploads handling configuration -------------------------------------
# The location where Coraza stores intercepted uploaded files. This
# location must be private to Coraza. You don't want other users on
# the server to access the files, do you?
#SecUploadDir /opt/coraza/var/upload/
# If On, the WAF will store the uploaded files in the SecUploadDir
# directory.
# Note: SecUploadKeepFiles is currently NOT supported by Coraza
#SecUploadKeepFiles Off
# Uploaded files are by default created with permissions that do not allow
# any other user to access them. You may need to relax that if you want to
# interface Coraza to an external program (e.g., an anti-virus).
# Note: SecUploadFileMode is currently NOT supported by Coraza
#SecUploadFileMode 0600
# -- Debug log configuration -------------------------------------------------
# Default debug log path
# Debug levels:
# 0: No logging (least verbose)
# 1: Error
# 2: Warn
# 3: Info
# 4-8: Debug
# 9: Trace (most verbose)
SecDebugLog /var/log/caddy/coraza-debug.log
SecDebugLogLevel 4
# -- Audit log configuration -------------------------------------------------
# Log the transactions that are marked by a rule, as well as those that
# trigger a server error (determined by a 5xx or 4xx, excluding 404,
# level response status codes).
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:(5|4)(0|1)[0-9])$"
# Define which parts of the transaction are going to be recorded in the audit log
SecAuditLogParts ABIJDEFHZ
# Use a single file for logging. This is much easier to look at, but
# assumes that you will use the audit log only occasionally.
SecAuditLogType Serial
# The format used to write the audit log.
# Can be one of JSON|JsonLegacy|Native|OCSF
SecAuditLogFormat Native
# The following settings are not supported by Coraza
# SecCookieFormat 0
# SecArgumentSeparator &
# SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
# "id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
# SecRule TX:/^COR_/ "!@streq 0" \
# "id:'200005',phase:2,t:none,deny,msg:'Coraza internal error flagged: %{MATCHED_VAR_NAME}'"

View File

@@ -0,0 +1,876 @@
# ------------------------------------------------------------------------
# OWASP CRS ver.4.20.0
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2025 CRS project. All rights reserved.
#
# The OWASP CRS is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
#
# -- [[ Introduction ]] --------------------------------------------------------
#
# The OWASP CRS is a set of generic attack
# detection rules that provide a base level of protection for any web
# application. They are written for the open source, cross-platform
# ModSecurity Web Application Firewall.
#
# See also:
# https://coreruleset.org/
# https://github.com/coreruleset/coreruleset
# https://owasp.org/www-project-modsecurity-core-rule-set/
#
#
# -- [[ System Requirements ]] -------------------------------------------------
#
# CRS requires ModSecurity version 2.8.0 or above.
# We recommend to always use the newest ModSecurity version.
#
# The configuration directives/settings in this file are used to control
# the OWASP ModSecurity CRS. These settings do **NOT** configure the main
# ModSecurity settings (modsecurity.conf) such as SecRuleEngine,
# SecRequestBodyAccess, SecAuditEngine, SecDebugLog, and XML processing.
#
# The CRS assumes that modsecurity.conf has been loaded. It is bundled with
# ModSecurity. If you don't have it, you can get it from:
# 2.x: https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v2/master/modsecurity.conf-recommended
# 3.x: https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v3/master/modsecurity.conf-recommended
#
# The order of file inclusion in your webserver configuration should always be:
# 1. modsecurity.conf
# 2. crs-setup.conf (this file)
# 3. rules/*.conf (the CRS rule files)
#
# Please refer to the INSTALL file for detailed installation instructions.
#
#
# -- [[ Mode of Operation: Anomaly Scoring vs. Self-Contained ]] ---------------
#
# The CRS can run in two modes:
#
# -- [[ Anomaly Scoring Mode (default) ]] --
# In CRS3, anomaly mode is the default and recommended mode, since it gives the
# most accurate log information and offers the most flexibility in setting your
# blocking policies. It is also called "collaborative detection mode".
# In this mode, each matching rule increases an 'anomaly score'.
# At the conclusion of the inbound rules, and again at the conclusion of the
# outbound rules, the anomaly score is checked, and the blocking evaluation
# rules apply a disruptive action, by default returning an error 403.
#
# -- [[ Self-Contained Mode ]] --
# In this mode, rules apply an action instantly. This was the CRS2 default.
# It can lower resource usage, at the cost of less flexibility in blocking policy
# and less informative audit logs (only the first detected threat is logged).
# Rules inherit the disruptive action that you specify (i.e. deny, drop, etc).
# The first rule that matches will execute this action. In most cases this will
# cause evaluation to stop after the first rule has matched, similar to how many
# IDSs function.
#
# -- [[ Alert Logging Control ]] --
# In the mode configuration, you must also adjust the desired logging options.
# There are three common options for dealing with logging. By default CRS enables
# logging to the webserver error log (or Event viewer) plus detailed logging to
# the ModSecurity audit log (configured under SecAuditLog in modsecurity.conf).
#
# - To log to both error log and ModSecurity audit log file, use: "log,auditlog"
# - To log *only* to the ModSecurity audit log file, use: "nolog,auditlog"
# - To log *only* to the error log file, use: "log,noauditlog"
#
# Examples for the various modes follow.
# You must leave one of the following options enabled.
# Note that you must specify the same line for phase:1 and phase:2.
#
# Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
SecDefaultAction "phase:1,log,auditlog,pass"
SecDefaultAction "phase:2,log,auditlog,pass"
# Example: Anomaly Scoring mode, log only to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
# SecDefaultAction "phase:1,nolog,auditlog,pass"
# SecDefaultAction "phase:2,nolog,auditlog,pass"
# Example: Self-contained mode, return error 403 on blocking
# - In this configuration the default disruptive action becomes 'deny'. After a
# rule triggers, it will stop processing the request and return an error 403.
# - You can also use a different error status, such as 404, 406, et cetera.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
# SecDefaultAction "phase:1,log,auditlog,deny,status:403"
# SecDefaultAction "phase:2,log,auditlog,deny,status:403"
# Example: Self-contained mode, redirect back to homepage on blocking
# - In this configuration the 'tag' action includes the Host header data in the
# log. This helps to identify which virtual host triggered the rule (if any).
# - Note that this might cause redirect loops in some situations; for example
# if a Cookie or User-Agent header is blocked, it will also be blocked when
# the client subsequently tries to access the homepage. You can also redirect
# to another custom URL.
# SecDefaultAction "phase:1,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
# SecDefaultAction "phase:2,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
#
# -- [[ Paranoia Level Initialization ]] ---------------------------------------
#
# The Paranoia Level (PL) setting allows you to choose the desired level
# of rule checks that will add to your anomaly scores.
#
# With each paranoia level increase, the CRS enables additional rules
# giving you a higher level of security. However, higher paranoia levels
# also increase the possibility of blocking some legitimate traffic due to
# false alarms (also named false positives or FPs). If you use higher
# paranoia levels, it is likely that you will need to add some exclusion
# rules for certain requests and applications receiving complex input.
#
# - A paranoia level of 1 is default. In this level, most core rules
# are enabled. PL1 is advised for beginners, installations
# covering many different sites and applications, and for setups
# with standard security requirements.
# At PL1 you should face FPs rarely. If you encounter FPs, please
# open an issue on the CRS GitHub site and don't forget to attach your
# complete Audit Log record for the request with the issue.
# - Paranoia level 2 includes many extra rules, for instance enabling
# many regexp-based SQL and XSS injection protections, and adding
# extra keywords checked for code injections. PL2 is advised
# for moderate to experienced users desiring more complete coverage
# and for installations with elevated security requirements.
# PL2 comes with some FPs which you need to handle.
# - Paranoia level 3 enables more rules and keyword lists, and tweaks
# limits on special characters used. PL3 is aimed at users experienced
# at the handling of FPs and at installations with a high security
# requirement.
# - Paranoia level 4 further restricts special characters.
# The highest level is advised for experienced users protecting
# installations with very high security requirements. Running PL4 will
# likely produce a very high number of FPs which have to be
# treated before the site can go productive.
#
# All rules will log their PL to the audit log;
# example: [tag "paranoia-level/2"]. This allows you to deduct from the
# audit log how the WAF behavior is affected by paranoia level.
#
# It is important to also look into the variable
# tx.enforce_bodyproc_urlencoded (Enforce Body Processor URLENCODED)
# defined below. Enabling it closes a possible bypass of CRS.
#
# Uncomment this rule to change the default:
#
#SecAction \
# "id:900000,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.blocking_paranoia_level=1"
# It is possible to execute rules from a higher paranoia level but not include
# them in the anomaly scoring. This allows you to take a well-tuned system on
# paranoia level 1 and add rules from paranoia level 2 without having to fear
# the new rules would lead to false positives that raise your score above the
# threshold.
# This optional feature is enabled by uncommenting the following rule and
# setting the tx.detection_paranoia_level.
# Technically, rules up to the level defined in tx.detection_paranoia_level
# will be executed, but only the rules up to tx.blocking_paranoia_level affect the
# anomaly scores.
# By default, tx.detection_paranoia_level is set to tx.blocking_paranoia_level.
# tx.detection_paranoia_level must not be lower than tx.blocking_paranoia_level.
#
# Please notice that setting tx.detection_paranoia_level to a higher paranoia
# level results in a performance impact that is equally high as setting
# tx.blocking_paranoia_level to said level.
#
#SecAction \
# "id:900001,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.detection_paranoia_level=1"
#
# -- [[ Enforce Body Processor URLENCODED ]] -----------------------------------
#
# ModSecurity selects the body processor based on the Content-Type request
# header. But clients are not always setting the Content-Type header for their
# request body payloads. This will leave ModSecurity with limited vision into
# the payload. The variable tx.enforce_bodyproc_urlencoded lets you force the
# URLENCODED body processor in these situations. This is off by default, as it
# implies a change of the behaviour of ModSecurity beyond CRS (the body
# processor applies to all rules, not only CRS) and because it may lead to
# false positives already on paranoia level 1. However, enabling this variable
# closes a possible bypass of CRS so it should be considered.
#
# Uncomment this rule to change the default:
#
#SecAction \
# "id:900010,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.enforce_bodyproc_urlencoded=1"
#
# -- [[ Anomaly Scoring Mode Severity Levels ]] --------------------------------
#
# Each rule in the CRS has an associated severity level.
# These are the default scoring points for each severity level.
# These settings will be used to increment the anomaly score if a rule matches.
# You may adjust these points to your liking, but this is usually not needed.
#
# - CRITICAL severity: Anomaly Score of 5.
# Mostly generated by the application attack rules (93x and 94x files).
# - ERROR severity: Anomaly Score of 4.
# Generated mostly from outbound leakage rules (95x files).
# - WARNING severity: Anomaly Score of 3.
# Generated mostly by malicious client rules (91x files).
# - NOTICE severity: Anomaly Score of 2.
# Generated mostly by the protocol rules (92x files).
#
# In anomaly mode, these scores are cumulative.
# So it's possible for a request to hit multiple rules.
#
# (Note: In this file, we use 'phase:1' to set CRS configuration variables.
# In general, 'phase:request' is used. However, we want to make absolutely sure
# that all configuration variables are set before the CRS rules are processed.)
#
#SecAction \
# "id:900100,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.critical_anomaly_score=5,\
# setvar:tx.error_anomaly_score=4,\
# setvar:tx.warning_anomaly_score=3,\
# setvar:tx.notice_anomaly_score=2"
#
# -- [[ Anomaly Scoring Mode Blocking Threshold Levels ]] ----------------------
#
# Here, you can specify at which cumulative anomaly score an inbound request,
# or outbound response, gets blocked.
#
# Most detected inbound threats will give a critical score of 5.
# Smaller violations, like violations of protocol/standards, carry lower scores.
#
# [ At default value ]
# If you keep the blocking thresholds at the defaults, the CRS will work
# similarly to previous CRS versions: a single critical rule match will cause
# the request to be blocked and logged.
#
# [ Using higher values ]
# If you want to make the CRS less sensitive, you can increase the blocking
# thresholds, for instance to 7 (which would require multiple rule matches
# before blocking) or 10 (which would require at least two critical alerts - or
# a combination of many lesser alerts), or even higher. However, increasing the
# thresholds might cause some attacks to bypass the CRS rules or your policies.
#
# [ New deployment strategy: Starting high and decreasing ]
# It is a common practice to start a fresh CRS installation with elevated
# anomaly scoring thresholds (>100) and then lower the limits as your
# confidence in the setup grows. You may also look into the Sampling
# Percentage section below for a different strategy to ease into a new
# CRS installation.
#
# [ Anomaly Threshold / Paranoia Level Quadrant ]
#
# High Anomaly Limit | High Anomaly Limit
# Low Paranoia Level | High Paranoia Level
# -> Fresh Site | -> Experimental Site
# ------------------------------------------------------
# Low Anomaly Limit | Low Anomaly Limit
# Low Paranoia Level | High Paranoia Level
# -> Standard Site | -> High Security Site
#
# Uncomment this rule to change the defaults:
#
#SecAction \
# "id:900110,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.inbound_anomaly_score_threshold=5,\
# setvar:tx.outbound_anomaly_score_threshold=4"
#
# -- [[ Application Specific Rule Exclusions ]] --------------------------------
#
# CRS 3.x contained exclusion packages to tweak the CRS for use with common
# web applications, lowering the number of false positives.
#
# In CRS 4, these are no longer part of the CRS itself, but they are available
# as "CRS plugins". Some plugins improve support for web applications, and others
# may bring new functionality. Plugins are not installed by default, but can be
# downloaded from the plugin registry:
# https://github.com/coreruleset/plugin-registry
#
# For detailed information about using and installing plugins, please see:
# https://coreruleset.org/docs/concepts/plugins/
#
# -- [[ Anomaly Score Reporting Level ]] ---------------------------------------
#
# When a request is blocked due to the anomaly score meeting or exceeding the
# anomaly threshold then the blocking rule will also report the anomaly score.
# This applies to the separate inbound and outbound anomaly scores.
#
# In phase 5, there are additional rules that can perform additional reporting
# of anomaly scores with a verbosity that depends on the reporting level defined
# below.
#
# By setting the reporting level you control whether you want additional
# reporting beyond the blocking rule or not and, if yes, which requests should
# be covered. The higher the reporting level, the more verbose the reporting is.
#
# There are 6 reporting levels:
#
# 0 - Reporting disabled
# 1 - Reporting for requests with a blocking anomaly score >= a threshold
# 2 - Reporting for requests with a detection anomaly score >= a threshold
# 3 - Reporting for requests with a blocking anomaly score greater than 0
# 4 - Reporting for requests with a detection anomaly score greater than 0
# 5 - Reporting for all requests
#
# Note: Reporting levels 1 and 2 make it possible to differentiate between
# requests that are blocked and requests that are *not* blocked but would have
# been blocked if the blocking PL was equal to detection PL. This may be useful
# for certain FP tuning methodologies, for example moving to a higher PL.
#
# A value of 5 can be useful on platforms where you are interested in logging
# non-scoring requests, yet it is not possible to report this information in
# the request/access log. This applies to Nginx, for example.
#
#SecAction \
# "id:900115,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.reporting_level=4"
#
# -- [[ Early Anomaly Scoring Mode Blocking ]] ------------------------------
#
# The anomaly scores for the request and the responses are generally summed up
# and evaluated at the end of phase:2 and at the end of phase:4 respectively.
# However, it is possible to enable an early evaluation of these anomaly scores
# at the end of phase:1 and at the end of phase:3.
#
# If a request (or a response) hits the anomaly threshold in this early
# evaluation, then blocking happens immediately (if blocking is enabled) and
# the phase 2 (and phase 4 respectively) will no longer be executed.
#
# Enable the rule 900120 that sets the variable tx.early_blocking to 1 in order
# to enable early blocking. The variable tx.early_blocking is set to 0 by
# default. Early blocking is thus disabled by default.
#
# Please note that early blocking will hide potential alerts from you. This
# means that a payload that would appear in an alert in phase 2 (or phase 4)
# does not get evaluated if the request is being blocked early. So when you
# disabled early blocking again at some point in the future, then new alerts
# from phase 2 might pop up.
#SecAction \
# "id:900120,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.early_blocking=1"
#
# -- [[ Initialize Default Collections ]] -----------------------------------
#
# CRS provides a centralized option to initialize and populate collections
# meant to be used by plugins (E.g.DoS protection plugin).
# By default, Global and IP collections (see rule 901320),
# being not used by core rules, are not initialized.
#
# Uncomment this rule to change the default:
#
#SecAction \
# "id:900130,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.enable_default_collections=1"
#
# -- [[ HTTP Policy Settings ]] ------------------------------------------------
#
# This section defines your policies for the HTTP protocol, such as:
# - allowed HTTP versions, HTTP methods, allowed request Content-Types
# - forbidden file extensions (e.g. .bak, .sql) and request headers (e.g. Proxy)
#
# These variables are used in the following rule files:
# - REQUEST-911-METHOD-ENFORCEMENT.conf
# - REQUEST-920-PROTOCOL-ENFORCEMENT.conf
# HTTP methods that a client is allowed to use.
# Default: GET HEAD POST OPTIONS
# Example: for RESTful APIs, add the following methods: PUT PATCH DELETE
# Example: for WebDAV, add the following methods: CHECKOUT COPY DELETE LOCK
# MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK
# Uncomment this rule to change the default.
#
# The HTTP PUT method is normally used to upload data that is saved on the server at a user-supplied URL.
# If enabled, an attacker may be able to inject arbitrary, and potentially malicious, content into the application or on to the file system of the web server.
# Depending on the server's configuration, this may lead to compromise of other users (by uploading
# client-executable scripts), compromise of the server (by uploading server-executable code), or other attacks.
# For this reason, the PUT method is disabled by default.
# GET, HEAD, POST and OPTIONS are seen as the minimal set of HTTP methods
# from a security perspective. For static sites, removing the POST is
# recommended. Add other HTTP methods as seen fit (see above).
#
#SecAction \
# "id:900200,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:'tx.allowed_methods=GET HEAD POST OPTIONS'"
# Content-Types that a client is allowed to send in a request.
# Default: |application/x-www-form-urlencoded| |multipart/form-data| |text/xml|
# |application/xml| |application/soap+xml| |application/json| |application/reports+json| |application/csp-report|
#
# Please note, that the rule where CRS uses this variable (920420) evaluates it with operator
# `@within`, which is case sensitive, but uses t:lowercase. You must add your whole custom
# Content-Type with lowercase.
#
# Bypass Warning: some applications may not rely on the content-type request header in order
# to parse the request body. This could make an attacker able to send malicious URLENCODED/JSON/XML
# payloads without being detected by the WAF. Allowing request content-type that doesn't activate any
# body processor (for example: "text/plain", "application/x-amf", "application/octet-stream", etc..)
# could lead to a WAF bypass. For example, a malicious JSON payload submitted with a "text/plain"
# content type may still be interpreted as JSON by a backend application but would not trigger the
# JSON body parser at the WAF, leading to a bypass. To avoid bypasses, you must enable the appropriate
# body parser based on the expected data in the request bodies (For example JSON for JSON data, XML for XML data, etc).
#
# When additional JSON content types are legitimately used in a deployment,
# e.g. application/cloudevents+json, it is extremely important to ensure that a
# rule exists to enable the engine's JSON body processor for these additional
# JSON content types. Failure to do so can lead to a request body bypass. The
# default JSON rule in modsecurity.conf-recommended (200001) will only activate
# the JSON body processor for the specific content type application/json. The
# optional modsecurity.conf-recommended rule 200006 can be used to enable the
# JSON body processor for a wide variety of JSON content types.
#
# To prevent blocking request with not allowed content-type by default, you can create an exclusion
# rule that removes rule 920420. It's important that you enable the correct body parser when allowing
# an additional content type to prevent bypasses. For example, this rule enables the JSON body processor
# for the text/plain content type:
#SecRule REQUEST_HEADERS:Content-Type "@beginsWith text/plain" \
# "id:1234,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# chain"
# SecRule REQUEST_URI "@rx ^/foo/bar" \
# "t:none,\
# ctl:ruleRemoveById=920420,\
# ctl:requestBodyProcessor=JSON"
#
# See: https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v2.x)#ctl
# See: https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x)#ctl
#
# Uncomment this rule to change the default.
#
#SecAction \
# "id:900220,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/reports+json| |application/csp-report|'"
# Allowed HTTP versions.
# Default: HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 HTTP/3 HTTP/3.0
# Example for legacy clients: HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 HTTP/3 HTTP/3.0
# Note that some web server versions use 'HTTP/2', some 'HTTP/2.0', so
# we include both version strings by default.
# Uncomment this rule to change the default.
#SecAction \
# "id:900230,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 HTTP/3 HTTP/3.0'"
# Forbidden file extensions.
# Guards against unintended exposure of development/configuration files.
# Default: .ani/ .asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .cnf/ .com/ .compositefont/ .config/ .conf/ .copy/ .crt/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dist/ .dll/ .dos/ .dpkg-dist/ .drv/ .gadget/ .hta/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .inf/ .ini/ .jse/ .key/ .licx/ .lnk/ .log/ .mdb/ .msc/ .ocx/ .old/ .pass/ .pdb/ .pfx/ .pif/ .pem/ .pol/ .prf/ .printer/ .pwd/ .rdb/ .rdp/ .reg/ .resources/ .resx/ .save/ .scr/ .sct/ .shs/ .sql/ .sqlite/ .sqlite3/ .swp/ .sys/ .temp/ .tlb/ .tmp/ .vb/ .vbe/ .vbs/ .vbproj/ .vsdisco/ .vxd/ .webinfo/ .ws/ .wsc/ .wsf/ .wsh/ .xsd/ .xsx/
# Example: .bak/ .config/ .conf/ .db/ .ini/ .log/ .old/ .pass/ .pdb/ .rdb/ .sql/
# Note that .axd was removed due to false positives (see PR 1925).
#
# To additionally guard against configuration/install archive files from being
# accidentally exposed, common archive file extensions can be added to the
# restricted extensions list. An example list of common archive file extensions
# is presented below:
# .7z/ .br/ .bz/ .bz2/ .cab/ .cpio/ .gz/ .img/ .iso/ .jar/ .rar/ .tar/ .tbz2/ .tgz/ .txz/ .xz/ .zip/ .zst/
# (Source: https://en.wikipedia.org/wiki/List_of_archive_formats)
#
# Uncomment this rule to change the default.
#SecAction \
# "id:900240,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:'tx.restricted_extensions=.ani/ .asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .cnf/ .com/ .compositefont/ .config/ .conf/ .copy/ .crt/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dist/ .dll/ .dos/ .dpkg-dist/ .drv/ .gadget/ .hta/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .inf/ .ini/ .jse/ .key/ .licx/ .lnk/ .log/ .mdb/ .msc/ .ocx/ .old/ .pass/ .pdb/ .pfx/ .pif/ .pem/ .pol/ .prf/ .printer/ .pwd/ .rdb/ .rdp/ .reg/ .resources/ .resx/ .save/ .scr/ .sct/ .shs/ .sql/ .sqlite/ .sqlite3/ .swp/ .sys/ .temp/ .tlb/ .tmp/ .vb/ .vbe/ .vbs/ .vbproj/ .vsdisco/ .vxd/ .webinfo/ .ws/ .wsc/ .wsf/ .wsh/ .xsd/ .xsx/'"
# Restricted request headers.
# The HTTP request headers that CRS restricts are split into two categories:
# basic (always forbidden) and extended (may be forbidden). All header names
# should be lowercase and enclosed by /slashes/ as delimiters.
#
# [ Basic ]
# Includes deprecated headers and headers with known security risks. Always
# forbidden.
# Default: /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/ /x-http-method-override/ /x-http-method/ /x-method-override/ /x-middleware-subrequest/ /expect/
#
# /content-encoding/
# Used to list any encodings that have been applied to the original payload.
# Only used for compression, which isn't supported by CRS by default since CRS
# blocks newlines and null bytes inside the request body. Most compression
# algorithms require at least null bytes per RFC. Blocking Content-Encoding
# shouldn't break anything and increases security since WAF engines, including
# ModSecurity, are typically incapable of properly scanning compressed request
# bodies.
#
# /proxy/
# Blocking this prevents the 'httpoxy' vulnerability: https://httpoxy.org
#
# /lock-token/
#
# /content-range/
#
# /if/
#
# /x-http-method-override/
# /x-http-method/
# /x-method-override/
# Blocking these headers prevents method override attacks, as described here:
# https://www.sidechannel.blog/en/http-method-override-what-it-is-and-how-a-pentester-can-use-it
#
# /x-middleware-subrequest/
# CVE-2025-29927 (Next.js)
#
# /expect/
# Blocking this header prevents Expect-based desync attacks
# https://portswigger.net/research/http1-must-die#expect-based-desync-attacks
#
# Uncomment this rule to change the default.
#SecAction \
# "id:900250,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:'tx.restricted_headers_basic=/content-encoding/ /proxy/ /lock-token/ /content-range/ /if/ /x-http-method-override/ /x-http-method/ /x-method-override/ /x-middleware-subrequest/ /expect/'"
#
# [ Extended ]
# Includes deprecated headers that are still in use (so false positives are
# possible) and headers with possible security risks. Forbidden at a higher
# paranoia level.
# Default: /accept-charset/
#
# /accept-charset/
# Deprecated header that should not be used by clients and should be ignored
# by servers. Can be used for a response WAF bypass by asking for a charset
# that the WAF cannot decode. Considered to be a good indicator of suspicious
# behavior but produces too many false positives to be forbidden by default.
# References:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
# https://github.com/coreruleset/coreruleset/issues/3140
#
# Uncomment this rule to change the default.
#SecAction \
# "id:900255,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:'tx.restricted_headers_extended=/accept-charset/'"
# Content-Types charsets that a client is allowed to send in a request.
# The content-types are enclosed by |pipes| as delimiters to guarantee exact matches.
#
# You can add additional character sets if something more exotic is required. One caveat: you will also need to edit 'regex-assembly/include/allowed-charsets.ra' and rebuild all the associated regular expressions using `crs-toolchain regex update --all`. See https://coreruleset.org/docs/6-development/6-2-crs-toolchain/.
#
# Warning: If the WAF engine is unable to fully and correctly decode a newly added character encoding then this can lead to a full request body or response body bypass. Additional permitted character encodings should be added with caution and tested to ensure inspection is not affected.
#
# Default: |utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|
# Uncomment this rule to change the default.
#SecAction \
# "id:900280,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:'tx.allowed_request_content_type_charset=|utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|'"
#
# -- [[ HTTP Argument/Upload Limits ]] -----------------------------------------
#
# Here you can define optional limits on HTTP get/post parameters and uploads.
# This can help to prevent application specific DoS attacks.
#
# These values are checked in REQUEST-920-PROTOCOL-ENFORCEMENT.conf.
# Beware of blocking legitimate traffic when enabling these limits.
#
# Block request if number of arguments is too high
# Default: unlimited
# Example: 255
# Note that a hard limit by the engine may also apply here (SecArgumentsLimit).
# This would override this soft limit.
# Uncomment this rule to set a limit.
#SecAction \
# "id:900300,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.max_num_args=255"
# Block request if the length of any argument name is too high
# Default: unlimited
# Example: 100
# Uncomment this rule to set a limit.
#SecAction \
# "id:900310,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.arg_name_length=100"
# Block request if the length of any argument value is too high
# Default: unlimited
# Example: 400
# Uncomment this rule to set a limit.
#SecAction \
# "id:900320,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.arg_length=400"
# Block request if the total length of all combined arguments is too high
# Default: unlimited
# Example: 64000
# Uncomment this rule to set a limit.
#SecAction \
# "id:900330,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.total_arg_length=64000"
# Block request if the file size of any individual uploaded file is too high
# Default: unlimited
# Example: 1048576
# Uncomment this rule to set a limit.
#SecAction \
# "id:900340,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.max_file_size=1048576"
# Block request if the total size of all combined uploaded files is too high
# Default: unlimited
# Example: 1048576
# Uncomment this rule to set a limit.
#SecAction \
# "id:900350,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.combined_file_sizes=1048576"
#
# -- [[ Easing In / Sampling Percentage ]] -------------------------------------
#
# Adding the CRS to an existing productive site can lead to false
# positives, unexpected performance issues and other undesired side effects.
#
# It can be beneficial to test the water first by enabling the CRS for a
# limited number of requests only and then, when you have solved the issues (if
# any) and you have confidence in the setup, to raise the ratio of requests
# being sent into the ruleset.
#
# Adjust the percentage of requests that are funnelled into the Core Rules by
# setting TX.sampling_percentage below. The default is 100, meaning that every
# request gets checked by the CRS. The selection of requests, which are going
# to be checked, is based on a pseudo random number generated by ModSecurity.
#
# If a request is allowed to pass without being checked by the CRS, there is no
# entry in the audit log (for performance reasons), but an error log entry is
# written. If you want to disable the error log entry, then issue the
# following directive somewhere after the inclusion of the CRS
# (E.g., RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf).
#
#SecRuleUpdateActionById 901450 "nolog"
#
# ATTENTION: If this TX.sampling_percentage is below 100, then some of the
# requests will bypass the Core Rules completely and you lose the ability to
# protect your service with ModSecurity.
#
# Uncomment this rule to enable this feature:
#
#SecAction \
# "id:900400,\
# phase:1,\
# pass,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.sampling_percentage=100"
#
# -- [[ Check UTF-8 encoding ]] ------------------------------------------------
#
# The CRS can optionally check request contents for invalid UTF-8 encoding.
# We only want to apply this check if UTF-8 encoding is actually used by the
# site; otherwise it will result in false positives.
#
# Uncomment this rule to use this feature:
#
#SecAction \
# "id:900950,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.crs_validate_utf8_encoding=1"
# -- [[ Skip Checking Responses ]] ------------------------------------------------
#
# CRS will perform analysis of the response contents if this is enabled and you have
# the directive `SecResponseBodyAccess On`.
#
# Warning: this feature is _enabled_ by default, but depending on your applications
# you might be targeted in a Request Filter Denial of Service (RFDoS) attack.
#
# References: https://blog.sicuranext.com/response-filter-denial-of-service-a-new-way-to-shutdown-a-website/
#
# Uncomment this rule to _skip checking responses_.
#
#SecAction \
# "id:900500,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# tag:'OWASP_CRS',\
# ver:'OWASP_CRS/4.20.0',\
# setvar:tx.crs_skip_response_analysis=1"
#
# -- [[ End of setup ]] --------------------------------------------------------
#
# The CRS checks the tx.crs_setup_version variable to ensure that the setup
# has been loaded. If you are not planning to use this setup template,
# you must manually set the tx.crs_setup_version variable before including
# the CRS rules/* files.
#
# The variable is a numerical representation of the CRS version number.
# E.g., v3.0.0 is represented as 300.
#
SecAction \
"id:900990,\
phase:1,\
pass,\
t:none,\
nolog,\
tag:'OWASP_CRS',\
ver:'OWASP_CRS/4.21.0',\
setvar:tx.crs_setup_version=4210"

View File

@@ -0,0 +1,3 @@
{% for exclusion in bgwaf_site_exclusions %}
{{ exclusion }}
{% endfor %}

View File

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

View File

@@ -0,0 +1,10 @@
# 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

@@ -0,0 +1,2 @@
- name: Reboot host
ansible.builtin.reboot:

View File

@@ -0,0 +1,53 @@
- 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,2 @@
- name: Setup proxy client
ansible.builtin.include_tasks: "setup_proxy_client.yml"

View File

@@ -0,0 +1,24 @@
- name: Create 00proxy.conf file
ansible.builtin.template:
src: "templates/apt_proxy.conf"
dest: "/etc/apt/apt.conf.d/00proxy.conf"
mode: "644"
owner: "root"
group: "root"
- name: Add http_proxy and https_proxy variable to file /etc/environment
ansible.builtin.lineinfile:
path: "/etc/environment"
regexp: '^{{ item.key }}='
line: '{{ item.key }}="{{ item.value }}"'
create: true
mode: "644"
owner: "root"
group: "root"
with_items:
- key: 'http_proxy'
value: '{{ common_proxy_server }}'
- key: 'https_proxy'
value: '{{ common_proxy_server }}'
- key: 'no_proxy'
value: 'localhost,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'

View File

@@ -0,0 +1,2 @@
Acquire::http::Proxy "http://{{ common_proxy_server }}";
Acquire::https::Proxy "https://{{ common_proxy_server }}";

View File

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

View File

@@ -0,0 +1,10 @@
- name: Restart Consul
ansible.builtin.service:
name: "consul"
state: "restarted"
daemon_reload: true
- name: Reload Consul
ansible.builtin.service:
name: "consul"
state: "reloaded"

View File

@@ -0,0 +1,78 @@
- name: Install requirements
ansible.builtin.apt:
name:
- "unzip"
- name: Create Consul directory
ansible.builtin.file:
path: "/usr/local/consul-{{ consul_version }}"
state: "directory"
mode: "0755"
- name: Install Consul
ansible.builtin.unarchive:
src: "https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_linux_amd64.zip"
dest: "/usr/local/consul-{{ consul_version }}"
remote_src: true
notify:
- "Restart Consul"
- name: Link Consul
ansible.builtin.file:
src: "/usr/local/consul-{{ consul_version }}/consul"
dest: "/usr/local/bin/consul"
state: "link"
notify:
- "Restart Consul"
- name: Create consul system group
ansible.builtin.group:
name: "consul"
system: true
- name: Create consul system user
ansible.builtin.user:
name: "consul"
group: "consul"
groups: ["ssl-cert"]
password: "!"
system: true
home: "/opt/consul"
- name: Create consul config directory
ansible.builtin.file:
path: "/etc/consul"
state: "directory"
group: "consul"
mode: "0750"
- name: Create consul log dir
ansible.builtin.file:
path: "/var/log/consul"
state: "directory"
group: "consul"
mode: "0770"
- name: Generate Consul config
ansible.builtin.template:
src: "templates/config.json"
dest: "/etc/consul/config.json"
group: "consul"
mode: "0640"
notify:
- "Reload Consul"
- name: Deploy Consul service file
ansible.builtin.template:
src: "templates/consul.service"
dest: "/etc/systemd/system/consul.service"
mode: "0644"
notify:
- "Restart Consul"
- name: Enable and start Consul
ansible.builtin.service:
name: "consul"
state: "started"
enabled: true
daemon_reload: true

View File

@@ -0,0 +1,26 @@
{
"log_level": "INFO",
"node_name": "{{ ansible_hostname }}",
"domain": "{{ consul_domain }}",
"server": true,
"key_file": "{{ consul_key_file }}",
"cert_file": "{{ consul_cert_file }}",
"ca_file": "{{ consul_ca_file }}",
"verify_incoming": true,
"verify_outgoing": true,
"verify_server_hostname": true,
"encrypt": "{{ consul_secret_key }}",
"ui": true,
"leave_on_terminate": true,
"data_dir": "/opt/consul",
"datacenter": "{{ consul_datacenter | mandatory }}",
"client_addr": "{{ consul_client_addr }}",
"bind_addr": "{{ consul_bind_addr }}",
"advertise_addr": "{{ consul_advertise_addr }}",
"bootstrap_expect": {{ consul_nodes | length }},
"retry_join": ["{{ consul_nodes | join('", "') }}"],
"enable_syslog": true,
"performance": {
"raft_multiplier": 1
}
}

View File

@@ -0,0 +1,19 @@
[Unit]
Description="HashiCorp Consul - A service mesh solution"
Documentation=https://www.consul.io/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul/config.json
[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-file=/etc/consul/config.json
ExecReload=/usr/local/bin/consul reload
KillMode=process
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,11 @@
# Defaults
local_ca_key_size: 4096
local_ca_key_type: "RSA"
# Required
local_ca_common_name: None
local_ca_passphrase: None
local_ca_country_name: None
local_ca_locality_name: None
local_ca_organization_name: None
local_ca_email_address: None

View File

@@ -0,0 +1,51 @@
- name: Create CA directory
delegate_to: "localhost"
run_once: true
ansible.builtin.file:
path: "ca"
state: "directory"
mode: "0700"
- name: Create private key with password protection
delegate_to: "localhost"
run_once: true
community.crypto.openssl_privatekey:
path: "ca/ca-certificate.key"
passphrase: "{{ local_ca_passphrase }}"
size: "{{ local_ca_key_size }}"
type: "{{ local_ca_key_type }}"
mode: "0640"
- name: Create certificate signing request (CSR) for CA certificate
delegate_to: "localhost"
run_once: true
community.crypto.openssl_csr:
path: "ca/ca-certificate.csr"
privatekey_path: "ca/ca-certificate.key"
privatekey_passphrase: "{{ vault.ca.passphrase }}"
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
country_name: "{{ local_ca_country_name }}"
locality_name: "{{ local_ca_locality_name }}"
organization_name: "{{ local_ca_organization_name }}"
email_address: "{{ local_ca_email_address }}"
basic_constraints:
- "CA:TRUE"
basic_constraints_critical: true
key_usage:
- "keyCertSign"
key_usage_critical: true
return_content: true
register: local_ca_csr
- name: Create self-signed CA certificate from CSR
delegate_to: "localhost"
run_once: true
# noqa: no-handler
when: "local_ca_csr.changed"
community.crypto.x509_certificate:
path: "ca/ca-certificate.crt"
csr_content: "{{ local_ca_csr.csr }}"
privatekey_path: "ca/ca-certificate.key"
privatekey_passphrase: "{{ vault.ca.passphrase }}"
provider: "selfsigned"

View File

@@ -0,0 +1,2 @@
squid_authorized_networks: ["127.0.0.1"]
squid_cache_size: 10240

View File

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

View File

@@ -0,0 +1,23 @@
- name: Install squid package
ansible.builtin.apt:
name: "squid"
notify:
- "Restart squid"
- name: Deploy squid configuration file
ansible.builtin.template:
src: "templates/squid.conf"
dest: "/etc/squid/squid.conf"
owner: "root"
group: "root"
mode: "0644"
notify:
- "Restart squid"
- name: Enable squid
ansible.builtin.systemd:
name: "squid"
enabled: true
daemon_reload: true
notify:
- "Restart squid"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
- name: Install Apache 2
ansible.builtin.apt:
name:
- "apache2"