HAProxy SPOE doc: https://www.haproxy.com/blog/extending-haproxy-with-the-stream-processing-offload-engine https://github.com/haproxy/haproxy/blob/master/doc/SPOE.txt Coraza tutorial : https://coraza.io/connectors/coraza-spoa/ External Documentation (unreliable) : https://www.alldiscoveries.com/installation-and-configuration-haproxy-v2-4-22-with-waf-coraza-spoa-on-ubuntu-server-22-04-lts/ Structure implémenté : - srv1 (10.184.116.168) : host HAProxy & Coraza SPOA - srv2 (10.184.116.20) : le serveur à protéger (ici apache2, config par défault) Fichiers de configurations importants : - /etc/haproxy/haproxy.cfg - HAProxy Main Configuration - /etc/haproxy/coraza.cfg - HAProxy SPOE Configuration - /etc/coraza-spoa/config.yml - Coraza SPOA Main Configuration - /etc/coraza-spoa/coraza.conf - Coraza Engine Configuration On srv1 side : apt update && apt install -y haproxy git wget git clone https://github.com/corazawaf/coraza-spoa.git # install go to build coraza-spoa wget https://go.dev/dl/go1.24.0.linux-amd64.tar.gz rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.0.linux-amd64.tar.gz sudo echo "export PATH=$PATH:/usr/local/go/bin" >> /root/.profile source /root/.profile # build coraza-spoa cd coraza-spoa/ go run mage.go build # create coraza user and group addgroup --quiet --system coraza-spoa adduser --quiet --system --ingroup coraza-spoa --no-create-home --home /nonexistent --disabled-password coraza-spoa mkdir -p /etc/coraza-spoa cp build/coraza-spoa /usr/bin/coraza-spoa chmod 755 /usr/bin/coraza-spoa cp example/coraza-spoa.yaml /etc/coraza-spoa/config.yaml vim /etc/coraza-spoa/config.yaml change : bind: 0.0.0.0:9000 for : bind: 127.0.0.1:9000 change : - name: sample_app for : - name: haporxy_waf wget https://raw.githubusercontent.com/corazawaf/coraza/main/coraza.conf-recommended -O /etc/coraza-spoa/coraza.conf vim /etc/coraza-spoa/coraza.conf change : SecRuleEngine DetectionOnly for : SecRuleEngine On mkdir coraza-crs cd coraza-crs git clone https://github.com/coreruleset/coreruleset cp coreruleset/crs-setup.conf.example /etc/coraza-spoa/crs-setup.conf cp -R coreruleset/rules /etc/coraza-spoa cp -R coreruleset/plugins /etc/coraza-spoa # update files permissions chown -R coraza-spoa:coraza-spoa /etc/coraza-spoa/ chmod 755 /etc/coraza-spoa chmod -R 600 /etc/coraza-spoa/* chmod 700 /etc/coraza-spoa/rules chmod 700 /etc/coraza-spoa/plugins cd .. cp contrib/coraza-spoa.service /lib/systemd/system/coraza-spoa.service systemctl daemon-reload systemctl enable coraza-spoa.service cp example/haproxy/coraza.cfg /etc/haproxy/coraza.cfg cp example/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg vim /etc/haproxy/haproxy.cfg change : http-request set-var(txn.coraza.app) str(sample_app) for : http-request set-var(txn.coraza.app) str(haproxy_waf) change : filter spoe engine coraza config /usr/local/etc/haproxy/coraza.cfg for : filter spoe engine coraza config /etc/haproxy/coraza.cfg change : server backend $BACKEND_HOST for : server backend 10.184.116.20:80 check chnage : server coraza_spoa coraza-spoa:9000 for : server coraza_spoa 127.0.0.1:9000 systemctl restart haproxy systemctl restart coraza-spoa On srv2 side : apt update && apt install -y apache2 systemctl status apache2 Check installation : curl -I http://10.184.116.168/index.php?f=/etc/passwd should return : HTTP/1.1 403 Forbidden waf-block: request content-length: 0 ((========================================================================================================================================================================================)) ((=========================================================================================LARSEN=========================================================================================)) ((========================================================================================================================================================================================)) Optimisations possibles:* Regarder quelle règles sont utilisé souvent : journalctl -u coraza-spoa --no-pager | grep "id" dans /etc/coraza-spoa/coraza.conf enlver les regle non essentielles: par exemple SecRuleRemoveById 920350 Ou si incertain mettre la règle en mode DetectionOnly: SecRuleUpdateActionById 920350 "phase:2,log,pass" appliquer coraza uniquement sur les requêtes critiques: acl is_sensitive path_beg /admin /api /login /cart filter spoe engine coraza config /etc/haproxy/coraza.cfg if is_sensitive utiliser le mode détection pour les pages non sensibles: dans /etc/coraza-spoa/coraza.conf : SecRuleEngine On SecRule REQUEST_URI "@beginsWith /public" "id:1000,phase:1,pass" dans /etc/coraza-spoa/config.yaml on peut gerer l'allocation de ram et cpu: memory_limit: 256m worker_threads: 4 dans /etc/coraza-spoa/coraza.conf on peut limiter les logs aux req bloquées : SecAuditEngine RelevantOnly ou exclure les log tr On peut égallementy utiliser la directive SecAuditLogParts pour définir le logging de coraza (internez à modsecurity) : A: Audit log header (mandatory). B: Request headers. C: Request body (present only if the request body exists and Coraza is configured to intercept it. This would require SecRequestBodyAccess to be set to on). D: Reserved for intermediary response headers; not implemented yet. E: Intermediary response body (present only if Coraza is configured to intercept response bodies, and if the audit log engine is configured to record it. Intercepting response bodies requires SecResponseBodyAccess to be enabled). Intermediary response body is the same as the actual response body unless Coraza intercepts the intermediary response body, in which case the actual response body will contain the error message. F: Final response headers. G: Reserved for the actual response body; not implemented yet. H: Audit log trailer. I: This part is a replacement for part C. It will log the same data as C in all cases except when multipart/form-data encoding in used. In this case, it will log a fake application/x-www-form-urlencoded body that contains the information about parameters but not about the files. This is handy if you don’t want to have (often large) files stored in your audit logs. J: This part contains information about the files uploaded using multipart/form-data encoding. K: This part contains a full list of every rule that matched (one per line) in the order they were matched. The rules are fully qualified and will thus show inherited actions and default operators. Z: Final boundary, signifies the end of the entry (mandatory). frontend https_front bind *:443 ssl crt /etc/haproxy/certs/z-ciso.pem mode http option httplog acl api_path path_beg /api/ use_backend backend_ciso if api_path acl waf_trigger rand(100) lt 10 http-request set-var(txn.coraza.app) str(haproxy_waf) filter spoe engine coraza config /etc/haproxy/coraza.cfg http-request send-spoe-group coraza coraza-req if waf_trigger http-request redirect code 302 location %[var(txn.coraza.data)] if { var(txn.coraza.action) -m str redirect } http-response redirect code 302 location %[var(txn.coraza.data)] if { var(txn.coraza.action) -m str redirect } http-request deny deny_status 403 hdr waf-block "request" if { var(txn.coraza.action) -m str deny } http-response deny deny_status 403 hdr waf-block "response" if { var(txn.coraza.action) -m str deny } http-request silent-drop if { var(txn.coraza.action) -m str drop } http-response silent-drop if { var(txn.coraza.action) -m str drop } http-request deny deny_status 500 if { var(txn.coraza.error) -m int gt 0 } http-response deny deny_status 500 if { var(txn.coraza.error) -m int gt 0 } use_backend frontend_ciso #default_backend frontend_ciso backend coraza-spoa mode tcp server coraza_spoa 127.0.0.1:9000 acl skip_openvas_ipv4 src 10.184.116.20 acl skip_openvas_ipv6 src fd42:4cb:ac26:e6e1:216:3eff:fef6:99a8 http-request deny if skip_openvas_ipv4 http-request deny if skip_openvas_ipv6 185.14.128.171 2a03:a240:0:1dea::a2 # ansible local config # documentation: https://docs.ansible.com/ansible/latest/reference_appendices/config.html#ansible-configuration-settings [defaults] retry_files_enabled = False # this deactivate the network gather process to speed up the fact gathering process gather_subset = hardware,!network,virtual,ohai,facter remote_user = adm-nmilovanovic roles_path = ~/git/cosium/IT/ansible/roles host_key_checking = False forks = 10 # use for playbook 'virtual_ip_proxy.yml' with host 'center_proxy_gen_3' # default value: 104448 max_diff_size = 4177920000 display_args_to_stdout = True # enable timestamps when I run a playbook : profile_tasks # https://docs.ansible.com/ansible/latest/collections/ansible/posix/profile_tasks_callback.html callbacks_enabled = ansible.posix.profile_tasks,ansible.posix.timer,ansible.posix.profile_roles,community.general.counter_enabled stdout_callback=yaml # Callback settings # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/default_callback.html show_task_path_on_failure = True # When a task fails, display the path to the file containing the failed task and the line number. check_mode_markers = True # Toggle to control displaying markers when running in check mode. show_custom_stats = True # This adds the custom stats set via the set_stats plugin to the play recap display_failed_stderr = True # Toggle to control whether failed and unreachable tasks are displayed to STDERR (vs. STDOUT) #result_format = yaml #pretty_results = true [ssh_connection] pipelining = True [privilege_escalation] become = True [inventory] enable_plugins = constructed,ini [persistent_connection] connect_timeout = 100 command_timeout = 100 TASK [zabbix_agent : zabbix_agentd.conf src=zabbix_agentd.conf, dest={{ zabbix_path_config }}, backup=True] ******************************************************************************************************************************************************************************************************************************************************************************************************************************** Wednesday 21 May 2025 17:47:41 +0200 (0:00:00.034) 0:00:03.419 ********* Wednesday 21 May 2025 17:47:41 +0200 (0:00:00.034) 0:00:03.419 ********* ok: [pad-rp-1]