Welcome, after having integrated the Waf Coraza 3.0.1 with HAProxy v2.4.2 via the coraza-spoa component with my previous guide “Installation and configuration HAProxy v2.4.22 with WAF Coraza SPOA on Ubuntu Server 22.04 LTS” and the OWASP ModSecurity Core Rule Set (CRS) v4.0, let’s proceed with the simulation of a real multi-domain environment.
We will configure a default Applications with the default rules enabled, valid for all domains with an unspecified configuration, a domain with only SecRuleEngine DetectionOnly, and finally a domain with WordPress installed on which we will enable OWASP ModSecurity Core Rule Set v4.0 – WordPress Rule Exclusions Plugin
All pre-configured configuration files are available for download on my github at: https://github.com/thelogh/haproxy-coraza
Assuming we have a basic installation of HAProxy and Coraza Spoa as in my guide, we proceed to expand the configuration.
We define multiple Applications in Coraza-Spoa, edit the main configuration file /etc/coraza-spoa/config.yaml
In default_application: the name of our default application is specified (haproxy_waf), the one that will be used by HAProxy if we do not specify exceptions.
In applications: the Applications (configurations) that Coraza-spoa can use are inserted, later we will see how we will distinguish the different applications based on the domain.
Let’s proceed with entering the specific configuration for our first domain. Assuming we do not want to enable blocking of requests based on the rules, but only want to see the behavior of the rules without creating problems and then creating exclusions, we should set SecRuleEngine to DetectionOnly.
Configure our domain www.example1.com in SecRuleEngine DetectionOnly.
To simplify things and maintain the formatting, let’s duplicate the haproxy_waf configuration and change the name.
Attention, the name of the application must be the root name of the domain without the www, this is because we will distinguish our applications based on the domain.
Example website www.example1.com, application name example1.com.
This is to simplify otherwise if the site can be used both in https://example1.com and https://www.example1.com we would have to create 2 different applications for the same domain, and it is not efficient.
So the name of our application will be: example1.com
Create a folder where we will store the configurations of all our domains, in this case called “sites“
mkdir /etc/coraza-spoa/sites
We will then create a configuration folder inside /etc/coraza-spoa/sites with the name of our root domain, in this case it will be example1.com
mkdir /etc/coraza-spoa/sites/example1.com
We copy the configuration files and base directories into the domain configuration folder
cp -a /etc/coraza-spoa/coraza.conf /etc/coraza-spoa/sites/example1.com
cp -a /etc/coraza-spoa/crs-setup.conf /etc/coraza-spoa/sites/example1.com
cp -aR /etc/coraza-spoa/plugins/ /etc/coraza-spoa/sites/example1.com
cp -aR /etc/coraza-spoa/rules/ /etc/coraza-spoa/sites/example1.com
We copy the configuration files and base directories into the domain configuration folder
Once we have duplicated our basic configuration files, we will change the path in the “directives” and specify the new location.
Attention, do not change the order of the files to be loaded in the directives, they must maintain a precise loading order otherwise the rules are not processed correctly. We also change the name of the log file to perform in the case of checks.
example1.com:
# Get the coraza.conf from https://github.com/corazawaf/coraza
#
# Download the OWASP CRS from https://github.com/coreruleset/coreruleset/releases
# and copy crs-setup.conf & the rules, plugins directories to /etc/coraza-spoa
directives: |
Include /etc/coraza-spoa/sites/example1.com/coraza.conf
Include /etc/coraza-spoa/sites/example1.com/crs-setup.conf
Include /etc/coraza-spoa/sites/example1.com/plugins/*-config.conf
Include /etc/coraza-spoa/sites/example1.com/plugins/*-before.conf
Include /etc/coraza-spoa/sites/example1.com/rules/*.conf
Include /etc/coraza-spoa/sites/example1.com/plugins/*-after.conf
# HAProxy configured to send requests only, that means no cache required
# NOTE: there are still some memory & caching issues, so use this with care
no_response_check: true
# The transaction cache lifetime in milliseconds (60000ms = 60s)
transaction_ttl_ms: 60000
# The maximum number of transactions which can be cached
transaction_active_limit: 100000
# The log level configuration, one of: debug/info/warn/error/panic/fatal
log_level: info
# The log file path
log_file: /var/log/coraza-spoa/coraza-agent-example1.com.log
Edit the configuration file /etc/coraza-spoa/sites/example1.com/coraza.conf and set SecRuleEngine to DetectionOnly.
The configuration for our first site is complete, let’s now proceed to the configuration of our second domain www.example2.com with the WordPress 6.x CSM installed with the plugin for exclusions officially released by OWASP (OWASP ModSecurity Core Rule Set – WordPress Rule Exclusions Plugin).
You can find the list of plugins compatible with the ModSecurity Core Rule Set v4 here https://github.com/coreruleset/plugin-registry
As for the example1.com site, we configure a new example2.com application in the /etc/coraza-spoa/config.yaml file.
example2.com:
# Get the coraza.conf from https://github.com/corazawaf/coraza
#
# Download the OWASP CRS from https://github.com/coreruleset/coreruleset/releases
# and copy crs-setup.conf & the rules, plugins directories to /etc/coraza-spoa
directives: |
Include /etc/coraza-spoa/sites/example2.com/coraza.conf
Include /etc/coraza-spoa/sites/example2.com/crs-setup.conf
Include /etc/coraza-spoa/sites/example2.com/plugins/*-config.conf
Include /etc/coraza-spoa/sites/example2.com/plugins/*-before.conf
Include /etc/coraza-spoa/sites/example2.com/rules/*.conf
Include /etc/coraza-spoa/sites/example2.com/plugins/*-after.conf
# HAProxy configured to send requests only, that means no cache required
# NOTE: there are still some memory & caching issues, so use this with care
no_response_check: true
# The transaction cache lifetime in milliseconds (60000ms = 60s)
transaction_ttl_ms: 60000
# The maximum number of transactions which can be cached
transaction_active_limit: 100000
# The log level configuration, one of: debug/info/warn/error/panic/fatal
log_level: info
# The log file path
log_file: /var/log/coraza-spoa/coraza-agent-example2.com.log
Copy the basic configuration files with the directories.
mkdir /etc/coraza-spoa/sites/example2.com
cp -a /etc/coraza-spoa/coraza.conf /etc/coraza-spoa/sites/example2.com
cp -a /etc/coraza-spoa/crs-setup.conf /etc/coraza-spoa/sites/example2.com
cp -aR /etc/coraza-spoa/plugins/ /etc/coraza-spoa/sites/example2.com
cp -aR /etc/coraza-spoa/rules/ /etc/coraza-spoa/sites/example2.com
Now download the plugin.
Use git to get them from the official repository.
git clone https://github.com/coreruleset/wordpress-rule-exclusions-plugin
Once downloaded, we copy the plugin into the directory designated for our site
cp -a ./wordpress-rule-exclusions-plugin/plugins/* /etc/coraza-spoa/sites/example2.com/plugins/
Set permissions for folders and directories
#OWNER
chown -R coraza-spoa:coraza-spoa /etc/coraza-spoa/
#DIRECTORY
chmod 700 $(find /etc/coraza-spoa -type d)
#FILE
chmod 600 $(find /etc/coraza-spoa -type f)
We restart the service to update the configuration
systemctl restart coraza-spoa
Now let’s configure HAProxy. Let’s edit the configuration file /etc/haproxy/haproxy.cfg
And we insert the backends for the two example domains, with their respective acl:
# https://www.haproxy.com/documentation/hapee/latest/onepage/#home
global
log stdout format raw local0
defaults
log global
option httplog
timeout client 1m
timeout server 1m
timeout connect 10s
timeout http-keep-alive 2m
timeout queue 15s
timeout tunnel 4h # for websocket
frontend test
mode http
bind *:80
unique-id-format %[uuid()]
unique-id-header X-Unique-ID
filter spoe engine coraza config /etc/haproxy/coraza.cfg
# Currently haproxy cannot use variables to set the code or deny_status, so this needs to be manually configured here
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 }
# Deny in case of an error, when processing with the Coraza SPOA
http-request deny deny_status 504 if { var(txn.coraza.error) -m int gt 0 }
http-response deny deny_status 504 if { var(txn.coraza.error) -m int gt 0 }
acl host_example1.com hdr(host) -i example1.com www.example1.com
acl host_example2.com hdr(host) -i example2.com www.example2.com
use_backend example1.com if host_example1.com
use_backend example2.com if host_example2.com
use_backend test_backend
backend test_backend
mode http
http-request return status 200 content-type "text/plain" string "Welcome!\n"
backend example1.com
mode http
http-request return status 200 content-type "text/plain" string "Welcome! example1.com\n"
backend example2.com
mode http
http-request return status 200 content-type "text/plain" string "Welcome! example2.com\n"
backend coraza-spoa
mode tcp
balance roundrobin
timeout connect 5s # greater than hello timeout
timeout server 3m # greater than idle timeout
server s1 127.0.0.1:9000
Let’s now edit the coraza-spoa configuration file in /etc/haproxy/coraza.cfg.
We will identify the application (configuration) to be used in coraza present in the /etc/coraza-spoa/config.yaml file, using the name of the requested domain.
We will change the passed variable to “app”
In spoe-message coraza-req
args app=req.hdr(host),regsub("^www.",,i)
We will pass the variable of the requested domain into HAProxy and through the regex (regsub) we will remove the www prefix, to standardize the configuration for both www.example1.com and example1.com
In spoe-message coraza-res
args app=str(txn.app_name)
The final file should look like this
# https://github.com/haproxy/haproxy/blob/master/doc/SPOE.txt
# /etc/haproxy/coraza.cfg
[coraza]
spoe-agent coraza-agent
# Process HTTP requests only (the responses are not evaluated)
messages coraza-req
# Comment the previous line and add coraza-res, to process responses also.
# NOTE: there are still some memory & caching issues, so use this with care
#messages coraza-req coraza-res
option var-prefix coraza
option set-on-error error
timeout hello 2s
timeout idle 2m
timeout processing 500ms
use-backend coraza-spoa
log global
spoe-message coraza-req
args app=req.hdr(host),regsub("^www.",,i) id=unique-id src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
event on-frontend-http-request
spoe-message coraza-res
args app=str(txn.app_name) id=unique-id version=res.ver status=status headers=res.hdrs body=res.body
event on-http-response
Test the configuration with the command
haproxy -c -f /etc/haproxy/haproxy.cfg
Now remember to edit your hosts file to point your example domain to your server for testing.
The logs for the various applications will be present in the /var/log/coraza-spoa/ folder.
Good fun!
If you need help or want to give suggestions, feel free to contact me on My Linkedin profile https://www.linkedin.com/in/valerio-puglia-332873125/