Multidomain installation and configuration of HAProxy with WAF Coraza SPOA and OWASP ModSecurity Core Rule Set 4.0 – WordPress Rule Exclusions on Ubuntu Server 22.04 LTS

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/

4 thoughts on “Multidomain installation and configuration of HAProxy with WAF Coraza SPOA and OWASP ModSecurity Core Rule Set 4.0 – WordPress Rule Exclusions on Ubuntu Server 22.04 LTS”

  1. Fantastic site you have here but I was curious if you knew
    of any forums that cover the same topics talked
    about in this article? I’d really like to be a part of online community where I can get responses from other experienced
    individuals that share the same interest. If you have any suggestions, please let me know.
    Thank you!

    Reply
    • Thank you for the compliments, but on some topics, even new ones, it is very difficult to find complete/synthetic information and functional example configurations, rather than trivial ones. Unfortunately, this also happens on the developer’s websites. I don’t have a specific reference forum; I usually check those of the developer. That’s why I wrote this guide and created scripts, specifically to help passionate people like you. To start with a functioning base of a software and then begin experimenting and learning about it. I’m very busy during this period, but I will try to publish more often.

      Reply

Leave a Comment