Private
Public Access
2
0
This commit is contained in:
2026-01-20 12:27:03 +03:00
parent 2cd03478f9
commit f70924d910
29 changed files with 789 additions and 564 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.DS_Store
.ansible/ .ansible/
__pycache__/
.DS_Store

View File

@@ -67,58 +67,26 @@ Voir: https://github.com/coreruleset/plugin-registry
Fonctionnalités déjà implémentées: Fonctionnalités déjà implémentées:
- Déploiement et configuration avec Ansible
- Serveurs transient
- VIP (avec Keepalived)
- DNS dynamique (via Nomad)
- Cluster via Nomad
- OWASP CRS - OWASP CRS
- Multisite (cavec configuration des CRS par site) - Multisite (avec configuration complète des CRS par site)
- Redirection vers nom canonique - Alias avec redirection optionnelle vers nom canonique
- Rediction HTTP vers HTTPS - Redirection HTTP vers HTTPS
- Certificats SSL (automatiques via Let's Encrypt et customs) - Certificats SSL (personnalisés automatiques via Let's Encrypt)
- Redirect (SSL et www et autres)
- Filtrage par: - Filtrage par:
- adresses IP - adresses IP
- réseaux IP - réseaux IP
- Antibot par: - Antibot par:
- défi JavaScript - défi JavaScript
- Passive backend checks - Passive backend checks
- Compression Gzip et zstd - Compression Gzip et zstd
- Mieux tester le configuration des CRS
- Mode détection sans blocage - Mode détection sans blocage
- Filtrage par: - Filtrage par:
- méthode HTTP - méthode HTTP
Urgemment:
- Connexion à consul
- ACL consul
- Changement de format de configuration: Caddyfile -> JSON
- Lire doc. config JSON de Caddy
- Renommer tous les répertoires/utilisateurs caddy -> byow
- Collection Ansible
Fomctionnalités à ajouter:
- Active backend checks
- Antibot par:
- Cookie
- Captcha
- reCAPTCHA
- hCaptcha
- Turnstile
- mCaptcha
- Mise en œuvre d'un cluster Redis (ou Consul) pour le partage de informations entre les WAF d'un cluster
- Filtrage par:
- rDNS
- DNSBL
- ASN
- User-Agent (robots, crawlers, IA bots)
- 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é: - En-têtes de sécurité:
- HSTS - HSTS
- CSP - CSP
@@ -129,17 +97,54 @@ Fomctionnalités à ajouter:
- Keep Headers - Keep Headers
- Cookie Flags - Cookie Flags
- Custom headers - Custom headers
- suppression d'en-têtes
- Collecte des logs via OVH LDP
Urgemment:
- Connexion à consul, ACL consul
- Collection Ansible
- Gestion CORS
- Active backend checks
- Correction de la collecte des logs via OVH LDP
Fomctionnalités à ajouter:
- Rate limiting distribué
- Antibot par:
- Cookie
- Captcha
- reCAPTCHA
- hCaptcha
- Turnstile
- mCaptcha
- Filtrage par:
- rDNS
- DNSBL
- ASN
- User-Agent (robots, crawlers, IA bots)
- taille
- URI patterns
- pays
- (ip:danmeuk-tor-exit, ua:mitchellkrogza-bad-user-agents, /etc/hapee-1.9/blacklist.acl)
- Client cache
- Proxy cache
- Filtrage par pays
- Compression Brotli
- Reverse scan - Reverse scan
- Robots.txt (DarkVisitors API/Community Lists/Custom URLs/Manual Rules) - Robots.txt (DarkVisitors API/Community Lists/Custom URLs/Manual Rules)
- Sessions - Gestion des sessions
- Whitelist - Whitelist
- Bad behavior - Bad behavior
- Auth - Authentification
- Anti DDoS - Anti DDoS
- Greylist - Greylist
- HTML injection - HTML injection
- Metrics (Elasticsearch + Metabase) - Metrics (Elasticsearch + Metabase)
- Static files et FastCGI pour PHP - Static files et FastCGI pour PHP (est-ce une bonne idée ?!)
- Lire doc. config JSON de Caddy
- Crowdsec
- https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list
## Ressources ## Ressources

View File

@@ -0,0 +1,21 @@
#!/usr/bin/python
class FilterModule(object):
def filters(self):
return {
'dict2str': self.dict2str
}
def dict2str(self, d, sep1='=', sep2=';', sep3=''):
return sep2.join(
[ str(k) + (sep1+str(v) if v!=None and v!='' else '') for k, v in d.items() ]
) + sep3
if __name__ == "__main__":
# Tests
filters = FilterModule()
print(filters.filters())
print(filters.dict2str(
{"un": 1, "deux": 2, "str": "a string", "rien": None, "bool": True}
))

View File

@@ -16,9 +16,10 @@
- "DNS:{{ ansible_fqdn }}" - "DNS:{{ ansible_fqdn }}"
- "DNS:{{ ansible_hostname }}" - "DNS:{{ ansible_hostname }}"
- "IP:{{ ansible_default_ipv4.address }}" - "IP:{{ ansible_default_ipv4.address }}"
waf_config_sites: "{{ common.sites }}" caddy_config_only: true
caddy_sites: "{{ common.sites }}"
roles: roles:
- "local_ca_certs" - "local_ca_certs"
- "waf_config" - "caddy"
tags: tags:
- "waf" - "waf"

View File

@@ -98,15 +98,14 @@
unbound_stub_zones: unbound_stub_zones:
- name: "{{ common.consul.datacenter }}.{{ common.consul.domain }}" - name: "{{ common.consul.datacenter }}.{{ common.consul.domain }}"
stub_to: "127.0.0.1@8600" stub_to: "127.0.0.1@8600"
waf_config_sites: "{{ common.sites }}" caddy_sites: "{{ common.sites }}"
ovh_ldp_cluster: "gra2.logs.ovh.com" ovh_ldp_cluster: "gra2.logs.ovh.com"
ovh_ldp_token: ¨{{ secrets.ovh_ldp.token }} ovh_ldp_token: ¨{{ secrets.ovh_ldp.token }}
roles: roles:
- "local_ca_certs" - "local_ca_certs"
- "consul" - "consul"
- "waf_install" - "caddy"
# - "keepalived" # - "keepalived"
- "waf_config"
- "unbound" - "unbound"
- "ovh_ldp" - "ovh_ldp"
tags: tags:

View File

@@ -0,0 +1,407 @@
# Defaults
caddy_config_only: false
caddy_go_version: "1.25.5"
caddy_xcaddy_version: "0.4.5"
caddy_caddy_version: "2.10.2"
caddy_coraza_caddy_version: "2.1.0" # Coraza v3.3.3
caddy_owasp_crs_version: "4.22.0"
caddy_system_user: "byow"
caddy_system_group: "byow"
caddy_home_dir: "/opt/{{ caddy_system_user }}"
caddy_config_dir: "/etc/{{ caddy_system_user }}"
caddy_log_dir: "/var/log/{{ caddy_system_user }}"
# Cf.: https://github.com/coreruleset/plugin-registry
caddy_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"
has_after_config: true
- name: "roundcube-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"
- name: "sogo-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"
# Required unless not site.custom_cert
caddy_email: null
# Required if site.bot_barrier
caddy_bot_barrier_secret: null
# Optionnal
caddy_local_ca_name: null
caddy_ca_root: null
caddy_sites: []
# - name: ""
# site: ""
# paths:
# - path: "*"
# addrs:
# - ""
# blacklist:
# - ""
# aliases:
# - ""
# crs_exceptions:
# before_request:
# - ""
# after_response:
# - ""
caddy_waf_defaults:
redirect_from_aliases: true
custom_cert: false
custom_cert_file: ""
custom_key_file: ""
# Security headers
header_content_security_policy:
# https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html#detailed-csp-directives
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy#directives
## Fetch directives
child-src: "'self'"
connect-src: "'self'"
font-src: "'self'"
img-src: "'self'"
manifest-src: "'self'"
media-src: "'self'"
# Deprecated: prefetch-src: "'self'"
object-src: "'none'"
script-src: "'none'"
script-src-attr: "'none'"
script-src-elem: "'none'"
style-src: "'self'"
style-src-attr: "'self'"
style-src-elem: "'self'"
default-src: "'self'"
## Document directives
# base-uri
# plugin-types
# sandbox
## Navigation directives
form-action: "'self'"
frame-ancestors: "'self'"
# https://developer.mozilla.org/fr/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-src
frame-src: "'self'"
# https://developer.mozilla.org/fr/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/worker-src
worker-src: "'self'"
# https://developer.mozilla.org/fr/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/block-all-mixed-content
block-all-mixed-content:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/fenced-frame-src
fenced-frame-src: "'self'"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/upgrade-insecure-requests
upgrade-insecure-requests:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy
header_cross_origin_embedder_policy: "require-corp"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy
header_cross_origin_opener_policy: "same-origin"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Resource-Policy
header_cross_origin_resource_policy: "same-site"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy
header_permissions_policy:
accelerometer: "()"
ambient-light-sensor: "()"
aria-notify: "()"
attribution-reporting: "()"
autoplay: "()"
bluetooth: "()"
browsing-topics: "()"
camera: "()"
captured-surface-control: "()"
compute-pressure: "()"
cross-origin-isolated: "()"
deferred-fetch: "()"
deferred-fetch-minimal: "()"
display-capture: "()"
encrypted-media: "()"
fullscreen: "()"
gamepad: "()"
geolocation: "()"
gyroscope: "()"
hid: "()"
identity-credentials-get: "()"
idle-detection: "()"
language-detector: "()"
local-fonts: "()"
magnetometer: "()"
microphone: "()"
midi: "()"
on-device-speech-recognition: "()"
otp-credentials: "()"
payment: "()"
picture-in-picture: "()"
private-state-token-issuance: "()"
private-state-token-redemption: "()"
publickey-credentials-create: "()"
publickey-credentials-get: "()"
screen-wake-lock: "()"
serial: "()"
speaker-selection: "()"
storage-access: "()"
translator: "()"
summarizer: "()"
usb: "()"
web-share: "()"
window-management: "()"
xr-spatial-tracking: "()"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#floc-federated-learning-of-cohorts
interest-cohort: "()"
# ???
battery: "()"
document-domain: "()"
execution-while-not-rendered: "()"
execution-while-out-of-viewport: "()"
navigation-override: "()"
# https://www.geeksforgeeks.org/computer-networks/http-headers-feature-policy/
header_feature_policy:
accelerometer: "'none'"
ambient-light-sensor: "'none'"
autoplay: "'none'"
battery: "'none'"
camera: "'none'"
display-capture: "'none'"
document-domain: "'none'"
encrypted-media: "'none'"
fullscreen: "'none'"
geolocation: "'none'"
gyroscope: "'none'"
layout-animation: "'none'"
legacy-image-formats: "'none'"
magnetometer: "'none'"
microphone: "'none'"
midi: "'none'"
oversized-images: "'none'"
payment: "'none'"
picture-in-picture: "'none'"
publickey-credentials-get: "'none'"
sync-xhr: "'none'"
usb: "'none'"
screen-wake-lock: "'none'"
xr-spatial-tracking: "'none'"
# ???
execution-while-not-rendered: "'none'"
execution-while-out-of-viewport: "'none'"
navigation-override: "'none'"
speaker-selection: "'none'"
unoptimized-images: "'none'"
unsized-media: "'none'"
web-share: "'none'"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy
header_referrer_policy: "strict-origin-when-cross-origin"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.html
header_strict_transport_security: "max-age=2635200; includeSubDomains"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-content-type-options
header_x_content_type_options: "nosniff"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options
header_x_frame_options: "DENY"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-robots-tag
header_x_robots_tag: "noindex, nofollow"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#content-type
default_content_type: "text/html; charset=UTF-8"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#set-cookie
cookies_attributes: "SameSite=None; Secure;"
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-dns-prefetch-control
# FIXME
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#server
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-powered-by
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-aspnet-version
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-aspnetmvc-version
delete_headers:
- "Server"
- "X-Powered-By"
- "X-AspNet-Version"
- "X-AspNetMvc-Version"
- "Via"
custom_headers: {}
# - name: <string>
# value: <string>
# caddy-ratelimit
rate_events: 1000
rate_window: "1m"
# steffenbusch/caddy-bot-barrier
bot_barrier: false
# corazawaf/coraza-caddy
crs_plugins: []
log_only: false
paranoia_level: 1
detection_paranoia_level: null
enforce_bodyproc_urlencoded: 1
critical_anomaly_score: 5
error_anomaly_score: 4
warning_anomaly_score: 3
notice_anomaly_score: 2
inbound_anomaly_score_threshold: 5
outbound_anomaly_score_threshold: 4
reporting_level: 4
max_num_args: 255
arg_name_length: 100
arg_length: 1000
total_arg_length: 64000
max_file_size: 10485760 # 10 Mo
combined_file_sizes: 20970520 # 20 Mo
sampling_percentage: 100
allowed_methods:
- "GET"
- "HEAD"
- "POST"
- "OPTIONS"
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|"
restricted_extensions:
- ".ani/"
- ".asa/"
- ".asax/"
- ".ascx/"
- ".back/"
- ".backup/"
- ".bak/"
- ".bck/"
- ".bk/"
- ".bkp/"
- ".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/"
- ".sav/"
- ".save/"
- ".scr/"
- ".sct/"
- ".sh/"
- ".shs/"
- ".sql/"
- ".sqlite/"
- ".sqlite3/"
- ".swp/"
- ".sys/"
- ".temp/"
- ".tlb/"
- ".tmp/"
- ".vb/"
- ".vbe/"
- ".vbs/"
- ".vbproj/"
- ".vsdisco/"
- ".vxd/"
- ".webinfo/"
- ".ws/"
- ".wsc/"
- ".wsf/"
- ".wsh/"
- ".xsd/"
- ".xsx/"
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/"
restricted_headers_extended:
- "/accept-charset/"
allowed_http_versions:
- "HTTP/1.0"
- "HTTP/1.1"
- "HTTP/2"
- "HTTP/2.0"
- "HTTP/3"
- "HTTP/3.0"
allowed_request_content_type_charset:
- "|utf-8|"
- "|iso-8859-1|"
- "|iso-8859-15|"
- "|windows-1252|"

View File

@@ -1,4 +1,4 @@
- name: Restart Caddy - name: Restart Caddy
ansible.builtin.service: ansible.builtin.service:
name: "caddy" name: "{{ caddy_system_user }}"
state: "restarted" state: "restarted"

View File

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

View File

@@ -11,49 +11,50 @@
name: name:
- "git" - "git"
- "libnss3-tools" - "libnss3-tools"
- "ssl-cert"
- name: Create Go directory - name: Create Go directory
ansible.builtin.file: ansible.builtin.file:
path: "/usr/local/go-{{ waf_install_go_version }}" path: "/usr/local/go-{{ caddy_go_version }}"
state: "directory" state: "directory"
mode: "0755" mode: "0755"
- name: Install Go - name: Install Go
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "https://go.dev/dl/go{{ waf_install_go_version }}.linux-amd64.tar.gz" src: "https://go.dev/dl/go{{ caddy_go_version }}.linux-amd64.tar.gz"
dest: "/usr/local/go-{{ waf_install_go_version }}" dest: "/usr/local/go-{{ caddy_go_version }}"
remote_src: true remote_src: true
- name: Link Go - name: Link Go
ansible.builtin.file: ansible.builtin.file:
src: "/usr/local/go-{{ waf_install_go_version }}/go/bin/go" src: "/usr/local/go-{{ caddy_go_version }}/go/bin/go"
dest: "/usr/local/bin/go" dest: "/usr/local/bin/go"
state: "link" state: "link"
- name: Create xcaddy directory - name: Create xcaddy directory
ansible.builtin.file: ansible.builtin.file:
path: "/usr/local/xcaddy-{{ waf_install_xcaddy_version }}" path: "/usr/local/xcaddy-{{ caddy_xcaddy_version }}"
state: "directory" state: "directory"
mode: "0755" mode: "0755"
- name: Install xcaddy - name: Install xcaddy
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "{{ waf_install_xcaddy_download }}/v{{ waf_install_xcaddy_version }}/xcaddy_{{ waf_install_xcaddy_version }}_linux_amd64.tar.gz" src: "{{ caddy_xcaddy_download }}/v{{ caddy_xcaddy_version }}/xcaddy_{{ caddy_xcaddy_version }}_linux_amd64.tar.gz"
dest: "/usr/local/xcaddy-{{ waf_install_xcaddy_version }}" dest: "/usr/local/xcaddy-{{ caddy_xcaddy_version }}"
remote_src: true remote_src: true
- name: Link xcaddy - name: Link xcaddy
ansible.builtin.file: ansible.builtin.file:
src: "/usr/local/xcaddy-{{ waf_install_xcaddy_version }}/xcaddy" src: "/usr/local/xcaddy-{{ caddy_xcaddy_version }}/xcaddy"
dest: "/usr/local/bin/xcaddy" dest: "/usr/local/bin/xcaddy"
state: "link" state: "link"
- name: Create Caddy build command - name: Create Caddy build command
ansible.builtin.set_fact: ansible.builtin.set_fact:
waf_install_build_command: > caddy_build_command: >
xcaddy build v{{ waf_install_caddy_version }} xcaddy build v{{ caddy_caddy_version }}
--with github.com/pteich/caddy-tlsconsul --with github.com/pteich/caddy-tlsconsul
--with github.com/corazawaf/coraza-caddy/v2@v{{ waf_install_coraza_caddy_version }} --with github.com/corazawaf/coraza-caddy/v2@v{{ caddy_coraza_caddy_version }}
--with github.com/mholt/caddy-ratelimit --with github.com/mholt/caddy-ratelimit
--with github.com/steffenbusch/caddy-bot-barrier --with github.com/steffenbusch/caddy-bot-barrier
--output /usr/local/bin/caddy --output /usr/local/bin/caddy
@@ -61,74 +62,74 @@
- name: Test Caddy version - name: Test Caddy version
ansible.builtin.copy: ansible.builtin.copy:
dest: "/usr/local/bin/.caddy.build" dest: "/usr/local/bin/.caddy.build"
content: "{{ waf_install_build_command }}" content: "{{ caddy_build_command }}"
mode: "0644" mode: "0644"
register: waf_install_test_caddy_version register: caddy_test_caddy_version
- name: Build Caddy - name: Build Caddy
# noqa: no-handler # noqa: no-handler
when: "waf_install_test_caddy_version.changed" when: "caddy_test_caddy_version.changed"
ansible.builtin.command: ansible.builtin.command:
cmd: "{{ waf_install_build_command }}" cmd: "{{ caddy_build_command }}"
creates: "/usr/local/bin/caddy" creates: "/usr/local/bin/caddy"
notify: notify:
- "Restart Caddy" - "Restart Caddy"
- name: Create caddy system group - name: Create Caddy system group
ansible.builtin.group: ansible.builtin.group:
name: "caddy" name: "{{ caddy_system_group }}"
system: true system: true
- name: Create caddy system user - name: Create Caddy system user
ansible.builtin.user: ansible.builtin.user:
name: "caddy" name: "{{ caddy_system_user }}"
group: "caddy" group: "{{ caddy_system_group }}"
groups: ["ssl-cert"] groups: ["ssl-cert"]
password: "!" password: "!"
system: true system: true
home: "/opt/caddy" home: "{{ caddy_home_dir }}"
- name: Create caddy config directories - name: Create Caddy config directories
ansible.builtin.file: ansible.builtin.file:
path: "{{ item }}" path: "{{ item }}"
state: "directory" state: "directory"
group: "caddy" group: "{{ caddy_system_group }}"
mode: "0750" mode: "0750"
loop: loop:
- "/etc/caddy" - "{{ caddy_config_dir }}"
- "/etc/caddy/plugins" - "{{ caddy_config_dir }}/crs-plugins"
- "/etc/caddy/sites" - "{{ caddy_config_dir }}/sites"
- name: Create caddy log dir - name: Create Caddy log dir
ansible.builtin.file: ansible.builtin.file:
path: "/var/log/caddy" path: "{{ caddy_log_dir }}"
state: "directory" state: "directory"
group: "caddy" group: "{{ caddy_system_group }}"
mode: "0770" mode: "0770"
- name: Install OWASP CRS - name: Install OWASP CRS
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "https://github.com/coreruleset/coreruleset/archive/refs/tags/v{{ waf_install_owasp_crs_version }}.tar.gz" src: "https://github.com/coreruleset/coreruleset/archive/refs/tags/v{{ caddy_owasp_crs_version }}.tar.gz"
dest: "/etc/caddy" dest: "{{ caddy_config_dir }}"
group: "caddy" group: "{{ caddy_system_group }}"
remote_src: true remote_src: true
- name: Install plugins - name: Install plugins
loop: "{{ waf_install_crs_plugins }}" loop: "{{ caddy_crs_plugins }}"
loop_control: loop_control:
loop_var: "plugin" loop_var: "plugin"
ansible.builtin.include_tasks: "install_plugin.yml" ansible.builtin.include_tasks: "plugin_install.yml"
- name: Deploy caddy service file - name: Deploy caddy service file
ansible.builtin.template: ansible.builtin.template:
src: "templates/caddy.service" src: "templates/caddy.service"
dest: "/etc/systemd/system/caddy.service" dest: "/etc/systemd/system/{{ caddy_system_user }}.service"
mode: "0644" mode: "0644"
notify: notify:
- "Restart Caddy" - "Restart Caddy"
- name: Enable Caddy - name: Enable Caddy
ansible.builtin.service: ansible.builtin.service:
name: "caddy" name: "{{ caddy_system_user }}"
enabled: true enabled: true
daemon_reload: true daemon_reload: true

View File

@@ -0,0 +1,6 @@
- name: Install Caddy
when: "not(caddy_config_only)"
ansible.builtin.include_tasks: "caddy_install.yml"
- name: Config Caddy
ansible.builtin.include_tasks: "caddy_config.yml"

View File

@@ -3,31 +3,31 @@
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: "waf_install_plugin_downloaded" register: "caddy_plugin_downloaded"
- name: Deploy plugin {{ plugin.name }} - name: Deploy plugin {{ plugin.name }}
# noqa: no-handler # noqa: no-handler
when: "waf_install_plugin_downloaded.changed" when: "caddy_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: "waf_install_temp_plugin_dir" register: "caddy_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: "{{ waf_install_temp_plugin_dir.path }}" dest: "{{ caddy_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: "{{ waf_install_temp_plugin_dir.path }}/{{ plugin.name }}-plugin-{{ plugin.version }}/plugins/" src: "{{ caddy_temp_plugin_dir.path }}/{{ plugin.name }}-plugin-{{ plugin.version }}/plugins/"
dest: "/etc/caddy/plugins/" dest: "{{ caddy_config_dir }}/crs-plugins/"
mode: "0644" mode: "0644"
# directory_mode: "0750" # directory_mode: "0750"
group: "caddy" group: "{{ caddy_system_group }}"
remote_src: true remote_src: true
notify: notify:
- "Restart Caddy" - "Restart Caddy"
@@ -36,20 +36,20 @@
when: "not(plugin.has_after_config | default(false))" when: "not(plugin.has_after_config | default(false))"
ansible.builtin.copy: ansible.builtin.copy:
content: "" content: ""
dest: "/etc/caddy/plugins/{{ plugin.name }}-after.conf" dest: "{{ caddy_config_dir }}/crs-plugins/{{ plugin.name }}-after.conf"
mode: "0644" mode: "0644"
group: "caddy" group: "{{ caddy_system_group }}"
# FIXME: bad hack, why is it required? # FIXME: bad hack, why is it required?
- name: Adjust /etc/caddy/plugins permissions - name: Adjust plugins diretory permissions
ansible.builtin.file: ansible.builtin.file:
path: "/etc/caddy/plugins" path: "{{ caddy_config_dir }}/crs-plugins"
state: "directory" state: "directory"
group: "caddy" group: "{{ caddy_system_group }}"
mode: "0750" mode: "0750"
- name: Delete temp dir {{ plugin.name }} - name: Delete temp dir {{ plugin.name }}
when: "waf_install_temp_plugin_dir.path is defined" when: "caddy_temp_plugin_dir.path is defined"
ansible.builtin.file: ansible.builtin.file:
path: "{{ waf_install_temp_plugin_dir.path }}" path: "{{ caddy_temp_plugin_dir.path }}"
state: "absent" state: "absent"

View File

@@ -0,0 +1,152 @@
{
grace_period 10s
order coraza_waf first
{% if caddy_email | default(None) != None %}
email "{{ caddy_email }}"
{% endif %}
{% if caddy_ca_root | default(None) != None %}
tls {
ca_root "{{ caddy_ca_root }}"
}
{% endif %}
log waf {
format json
include "http.handlers.waf"
output stdout
}
{% if caddy_local_ca_name | default(None) != None %}
pki {
ca local {
name "{{ caddy_local_ca_name }}"
}
}
{% endif %}
}
{% for site in caddy_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 {{ caddy_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 {{ caddy_config_dir }}/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 "{{ caddy_config_dir }}/sites/{{ site.name }}/coraza.conf"
Include "{{ caddy_config_dir }}/sites/{{ site.name }}/crs-setup.conf"
{% for plugin in site.crs_plugins | default(caddy_waf_defaults.crs_plugins) %}
Include "{{ caddy_config_dir }}/crs-plugins/{{ plugin }}-config.conf"
Include "{{ caddy_config_dir }}/crs-plugins/{{ plugin }}-before.conf"
{% endfor %}
Include "{{ caddy_config_dir }}/sites/{{ site.name }}/exclusions-request-before.conf"
Include "{{ caddy_config_dir }}/coreruleset-{{ caddy_owasp_crs_version }}/rules/*.conf"
Include "{{ caddy_config_dir }}/sites/{{ site.name }}/exclusions-response-after.conf"
{% for plugin in site.crs_plugins | default(caddy_waf_defaults.crs_plugins) %}
Include "{{ caddy_config_dir }}/crs-plugins/{{ plugin }}-after.conf"
{% endfor %}
SecRuleEngine On
`
}
header {
Content-Security-Policy "{{ caddy_waf_defaults.header_content_security_policy | combine(site.header_content_security_policy | default({})) | dict2str(sep1=' ', sep2='; ', sep3=';') }}"
Cross-Origin-Embedder-Policy "{{ site.header_cross_origin_embedder_policy | default(caddy_waf_defaults.header_cross_origin_embedder_policy) }}"
Cross-Origin-Opener-Policy "{{ site.header_cross_origin_opener_policy | default(caddy_waf_defaults.header_cross_origin_opener_policy) }}"
Cross-Origin-Resource-Policy "{{ site.header_cross_origin_resource_policy | default(caddy_waf_defaults.header_cross_origin_resource_policy) }}"
Feature-Policy "{{ caddy_waf_defaults.header_feature_policy | combine(site.header_feature_policy | default({})) | dict2str(sep1=' ', sep2='; ', sep3=';') }}"
Permissions-Policy "{{ caddy_waf_defaults.header_permissions_policy | combine(site.header_permissions_policy | default({})) | dict2str(sep2=', ') }}"
Referrer-Policy "{{ site.header_referrer_policy | default(caddy_waf_defaults.header_referrer_policy) }}"
Strict-Transport-Security "{{ site.header_strict_transport_security | default(caddy_waf_defaults.header_strict_transport_security) }}"
X-Content-Type-Options "{{ site.header_x_content_type_options | default(caddy_waf_defaults.header_x_content_type_options) }}"
X-Frame-Options "{{ site.header_x_frame_options | default(caddy_waf_defaults.header_x_frame_options) }}"
X-Robots-Tag "{{ site.header_x_robots_tag | default(caddy_waf_defaults.header_x_robots_tag) }}"
?Content-Type "{{ site.default_content_type | default(caddy_waf_defaults.default_content_type) }}"
>Set-Cookie "(.*)" "$1; {{ site.cookies_attributes | default(caddy_waf_defaults.cookies_attributes) }}"
{% for header in site.delete_headers | default(caddy_waf_defaults.delete_headers) %}
-{{ header }}
{% endfor %}
{% for header in site.custom_headers | default(caddy_waf_defaults.custom_headers) %}
{{ header.name }} "{{ header.value }}"
{% endfor %}
}
handle_errors 403 {
header X-Blocked "true"
respond "Your request was blocked."
}
log {
format json
output file {{ caddy_log_dir }}/site_{{ site.name }}.log
}
}
{% endfor %}

View File

@@ -6,10 +6,10 @@ Requires=network-online.target
[Service] [Service]
Type=notify Type=notify
User=caddy User={{ caddy_system_user }}
Group=caddy Group={{ caddy_system_group }}
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile ExecStart=/usr/local/bin/caddy run --environ --config {{ caddy_config_dir }}/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force ExecReload=/usr/local/bin/caddy reload --config {{ caddy_config_dir }}/Caddyfile --force
TimeoutStopSec=15s TimeoutStopSec=15s
LimitNOFILE=1048576 LimitNOFILE=1048576
PrivateTmp=true PrivateTmp=true

View File

@@ -126,7 +126,7 @@ SecDataDir /tmp/
# 3: Info # 3: Info
# 4-8: Debug # 4-8: Debug
# 9: Trace (most verbose) # 9: Trace (most verbose)
SecDebugLog /var/log/caddy/coraza-debug.log SecDebugLog {{ caddy_log_dir }}/coraza-debug.log
SecDebugLogLevel 4 SecDebugLogLevel 4

View File

@@ -10,9 +10,9 @@ SecAction \
t:none,\ t:none,\
nolog,\ nolog,\
tag:'OWASP_CRS',\ tag:'OWASP_CRS',\
ver:'OWASP_CRS/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.blocking_paranoia_level={{ site.paranoia_level | default(waf_config_defaults.paranoia_level) }}" setvar:tx.blocking_paranoia_level={{ site.paranoia_level | default(caddy_waf_defaults.paranoia_level) }}"
{% if site.detection_paranoia_level | default(waf_config_defaults.detection_paranoia_level) != None %} {% if site.detection_paranoia_level | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.detection_paranoia_level={{ site.detection_paranoia_level | default(waf_config_defaults.detection_paranoia_level) }}" setvar:tx.detection_paranoia_level={{ site.detection_paranoia_level | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.enforce_bodyproc_urlencoded={{ site.enforce_bodyproc_urlencoded | default(waf_config_defaults.enforce_bodyproc_urlencoded) }}" setvar:tx.enforce_bodyproc_urlencoded={{ site.enforce_bodyproc_urlencoded | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.critical_anomaly_score={{ site.critical_anomaly_score | default(waf_config_defaults.critical_anomaly_score) }},\ setvar:tx.critical_anomaly_score={{ site.critical_anomaly_score | default(caddy_waf_defaults.critical_anomaly_score) }},\
setvar:tx.error_anomaly_score={{ site.error_anomaly_score | default(waf_config_defaults.error_anomaly_score) }},\ setvar:tx.error_anomaly_score={{ site.error_anomaly_score | default(caddy_waf_defaults.error_anomaly_score) }},\
setvar:tx.warning_anomaly_score={{ site.warning_anomaly_score | default(waf_config_defaults.warning_anomaly_score) }},\ setvar:tx.warning_anomaly_score={{ site.warning_anomaly_score | default(caddy_waf_defaults.warning_anomaly_score) }},\
setvar:tx.notice_anomaly_score={{ site.notice_anomaly_score | default(waf_config_defaults.notice_anomaly_score) }}" setvar:tx.notice_anomaly_score={{ site.notice_anomaly_score | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
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.inbound_anomaly_score_threshold={% if site.log_only | default(caddy_waf_defaults.log_only) %}1000{% else %}{{ site.inbound_anomaly_score_threshold | default(caddy_waf_defaults.inbound_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 %}" setvar:tx.outbound_anomaly_score_threshold={% if site.log_only | default(caddy_waf_defaults.log_only) %}1000{% else %}{{ site.outbound_anomaly_score_threshold | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.reporting_level={{ site.reporting_level | default(waf_config_defaults.reporting_level) }}" setvar:tx.reporting_level={{ site.reporting_level | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:'tx.allowed_methods={{ site.allowed_methods | default(waf_config_defaults.allowed_methods) | join(" ") }}'" setvar:'tx.allowed_methods={{ site.allowed_methods | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:'tx.allowed_request_content_type={{ site.allowed_request_content_type | default(waf_config_defaults.allowed_request_content_type) | join(" ") }}'" setvar:'tx.allowed_request_content_type={{ site.allowed_request_content_type | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:'tx.allowed_http_versions={{ site.allowed_http_versions | default(waf_config_defaults.allowed_http_versions) | join(" ") }}'" setvar:'tx.allowed_http_versions={{ site.allowed_http_versions | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:'tx.restricted_extensions={{ site.restricted_extensions | default(waf_config_defaults.restricted_extensions) | join(" ") }}'" setvar:'tx.restricted_extensions={{ site.restricted_extensions | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:'tx.restricted_headers_basic={{ site.restricted_headers_basic | default(waf_config_defaults.restricted_headers_basic) | join(" ") }}'" setvar:'tx.restricted_headers_basic={{ site.restricted_headers_basic | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:'tx.restricted_headers_extended={{ site.restricted_headers_extended | default(waf_config_defaults.restricted_headers_extended) | join(" ") }}'" setvar:'tx.restricted_headers_extended={{ site.restricted_headers_extended | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
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(" ") }}'" setvar:'tx.allowed_request_content_type_charset={{ caddy_waf_defaults.allowed_request_content_type_charset | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.max_num_args={{ site.max_num_args | default(waf_config_defaults.max_num_args) }}" setvar:tx.max_num_args={{ site.max_num_args | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.arg_name_length={{ site.arg_name_length | default(waf_config_defaults.arg_name_length) }}" setvar:tx.arg_name_length={{ site.arg_name_length | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.arg_length={{ site.arg_length | default(waf_config_defaults.arg_length) }}" setvar:tx.arg_length={{ site.arg_length | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.total_arg_length={{ site.total_arg_length | default(waf_config_defaults.total_arg_length) }}" setvar:tx.total_arg_length={{ site.total_arg_length | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.max_file_size={{ site.max_file_size | default(waf_config_defaults.max_file_size) }}" setvar:tx.max_file_size={{ site.max_file_size | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.combined_file_sizes={{ site.combined_file_sizes | default(waf_config_defaults.combined_file_sizes) }}" setvar:tx.combined_file_sizes={{ site.combined_file_sizes | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.sampling_percentage={{ site.sampling_percentage | default(waf_config_defaults.sampling_percentage) }}" setvar:tx.sampling_percentage={{ site.sampling_percentage | default(caddy_waf_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_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/{{ waf_config_owasp_crs_version }}',\ # ver:'OWASP_CRS/{{ caddy_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/{{ waf_config_owasp_crs_version }}',\ ver:'OWASP_CRS/{{ caddy_owasp_crs_version }}',\
setvar:tx.crs_setup_version={{ waf_config_owasp_crs_version | regex_replace('\.', '') }}" setvar:tx.crs_setup_version={{ caddy_owasp_crs_version | regex_replace('\.', '') }}"

View File

@@ -0,0 +1,3 @@
{% for exclusion in site.crs_exceptions.before_request | default([]) %}
{{ exclusion }}
{% endfor %}

View File

@@ -0,0 +1,3 @@
{% for exclusion in site.crs_exceptions.after_response | default([]) %}
{{ exclusion }}
{% endfor %}

View File

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

View File

@@ -1,196 +0,0 @@
# Defaults
waf_config_owasp_crs_version: "4.22.0"
# Required unless not site.custom_cert
waf_config_email: null
# Required if site.bot_barrier
waf_config_bot_barrier_secret: null
# Optionnal
waf_config_local_ca_name: null
waf_config_ca_root: null
waf_config_sites: []
# - name: ""
# site: ""
# paths:
# - path: "*"
# addrs:
# - ""
# blacklist:
# - ""
# aliases:
# - ""
# exceptions:
# before_request:
# - ""
# after_response:
# - ""
waf_config_defaults:
redirect_from_aliases: true
custom_cert: true
custom_cert_file: ""
custom_key_file: ""
plugins: []
# caddy-ratelimit
rate_events: 1000
rate_window: "1m"
# steffenbusch/caddy-bot-barrier
bot_barrier: false
# corazawaf/coraza-caddy
log_only: false
paranoia_level: 1
detection_paranoia_level: null
enforce_bodyproc_urlencoded: 1
critical_anomaly_score: 5
error_anomaly_score: 4
warning_anomaly_score: 3
notice_anomaly_score: 2
inbound_anomaly_score_threshold: 5
outbound_anomaly_score_threshold: 4
reporting_level: 4
max_num_args: 255
arg_name_length: 100
arg_length: 1000
total_arg_length: 64000
max_file_size: 10485760 # 10 Mo
combined_file_sizes: 20970520 # 20 Mo
sampling_percentage: 100
allowed_methods:
- "GET"
- "HEAD"
- "POST"
- "OPTIONS"
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|"
restricted_extensions:
- ".ani/"
- ".asa/"
- ".asax/"
- ".ascx/"
- ".back/"
- ".backup/"
- ".bak/"
- ".bck/"
- ".bk/"
- ".bkp/"
- ".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/"
- ".sav/"
- ".save/"
- ".scr/"
- ".sct/"
- ".sh/"
- ".shs/"
- ".sql/"
- ".sqlite/"
- ".sqlite3/"
- ".swp/"
- ".sys/"
- ".temp/"
- ".tlb/"
- ".tmp/"
- ".vb/"
- ".vbe/"
- ".vbs/"
- ".vbproj/"
- ".vsdisco/"
- ".vxd/"
- ".webinfo/"
- ".ws/"
- ".wsc/"
- ".wsf/"
- ".wsh/"
- ".xsd/"
- ".xsx/"
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/"
restricted_headers_extended:
- "/accept-charset/"
allowed_http_versions:
- "HTTP/1.0"
- "HTTP/1.1"
- "HTTP/2"
- "HTTP/2.0"
- "HTTP/3"
- "HTTP/3.0"
allowed_request_content_type_charset:
- "|utf-8|"
- "|iso-8859-1|"
- "|iso-8859-15|"
- "|windows-1252|"

View File

@@ -1,152 +0,0 @@
{
grace_period 10s
order coraza_waf first
{% if waf_config_email | default(None) != None %}
email "{{ waf_config_email }}"
{% endif %}
{% if waf_config_ca_root | default(None) != None %}
tls {
ca_root "{{ waf_config_ca_root }}"
}
{% endif %}
log waf {
format json
include "http.handlers.waf"
output stdout
}
{% if waf_config_local_ca_name | default(None) != None %}
pki {
ca local {
name "{{ waf_config_local_ca_name }}"
}
}
{% endif %}
}
{% for site in waf_config_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 {{ waf_config_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"
{% for plugin in site.plugins | default(waf_config_defaults.plugins) %}
Include "/etc/caddy/plugins/{{ plugin }}-config.conf"
Include "/etc/caddy/plugins/{{ plugin }}-before.conf"
{% endfor %}
Include "/etc/caddy/sites/{{ site.name }}/exclusions-request-before.conf"
Include "/etc/caddy/coreruleset-{{ waf_config_owasp_crs_version }}/rules/*.conf"
Include "/etc/caddy/sites/{{ site.name }}/exclusions-response-after.conf"
{% for plugin in site.plugins | default(waf_config_defaults.plugins) %}
Include "/etc/caddy/plugins/{{ plugin }}-after.conf"
{% endfor %}
SecRuleEngine On
`
}
# Cf. https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
header {
# Access-Control-Allow-Origin "https://secure.gravatar.com"
Content-Security-Policy "upgrade-insecure-requests; worker-src 'self' blob:; connect-src 'self'; media-src 'self'; img-src 'self' data: https://secure.gravatar.com/avatar/; font-src 'self' data:; style-src 'self' 'unsafe-inline'; object-src 'none'; frame-src 'self' blob:; child-src 'self'; form-action 'self'; frame-ancestors 'self'"
# Cross-Origin-Embedder-Policy "require-corp"
Cross-Origin-Opener-Policy "same-origin"
Cross-Origin-Resource-Policy "same-site"
Permissions-Policy "geolocation=(), camera=(), microphone=(), interest-cohort=()"
Referrer-Policy "strict-origin-when-cross-origin"
Strict-Transport-Security "max-age=2635200; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-Robots-Tag "noindex, nofollow"
?Content-Type "text/html; charset=UTF-8"
>Set-Cookie "(.*)" "$1; SameSite=None; Secure;"
-Server
-Via
-X-Powered-By
-X-AspNet-Version
-X-AspNetMvc-Version
}
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

@@ -1,3 +0,0 @@
{% for exclusion in site.exceptions.before_request | default([]) %}
{{ exclusion }}
{% endfor %}

View File

@@ -1,3 +0,0 @@
{% for exclusion in site.exceptions.after_response | default([]) %}
{{ exclusion }}
{% endfor %}

View File

@@ -1,25 +0,0 @@
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.22.0"
# 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"
has_after_config: true
- name: "roundcube-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"
- name: "sogo-rule-exclusions"
version: "1.0.4"
provider: "EsadCetiner"

View File

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

View File

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

View File

@@ -21,11 +21,21 @@ common:
custom_cert: true custom_cert: true
custom_cert_file: "/etc/ssl/certs/test.lab.crt" custom_cert_file: "/etc/ssl/certs/test.lab.crt"
custom_key_file: "/etc/ssl/private/test.lab.key" custom_key_file: "/etc/ssl/private/test.lab.key"
plugins: crs_plugins:
- "wordpress-rule-exclusions" - "wordpress-rule-exclusions"
exceptions: crs_exceptions:
# Cf. https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x) # Cf. https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v3.x)
# before_request: # before_request:
# - 'SecRule REQUEST_FILENAME "@endsWith /wp-admin/admin-ajax.php" "id:1,nolog,ctl:ruleRemoveById=932260"' # - 'SecRule REQUEST_FILENAME "@endsWith /wp-admin/admin-ajax.php" "id:1,nolog,ctl:ruleRemoveById=932260"'
after_response: after_response:
- 'SecRuleUpdateTargetById 932260 "!ARGS:tag-name"' - 'SecRuleUpdateTargetById 932260 "!ARGS:tag-name"'
header_cross_origin_embedder_policy: "unsafe-none"
header_content_security_policy:
worker-src: "'self' blob:"
img-src: "'self' data: https://secure.gravatar.com/avatar/"
font-src: "'self' data:"
script-src: "'self' 'unsafe-eval'"
script-src-elem: "'self' 'unsafe-inline'"
style-src-attr: "'self' 'unsafe-inline'"
style-src-elem: "'self' 'unsafe-inline'"
frame-src: "'self' blob:"