• Using a combination of http-request set-var and the log-format settings you can embed ACL actions that are taken in HAProxy into your logs
  • See my sample Docker-Compose file and HAProxy configuration.

As your HAProxy configuration gets larger and more complex you may end up having several ACLs that end up in manipulating traffic in a particular way. And inevitably, at some point, you’ll have someone asking what’s happening to their traffic. This is a quick and easy way to tag your logs depending on which ACL is triggered.

This method is one I have used a couple times, it’s pretty flexible since it triggers on the same ACL actions as any other action you can take in HAProxy. I mainly used it to discern between various denies when tracking customer traffic, but that’s not the only use.

I used the example configuration from the HAProxy 2.3 documentation as a base and added a couple lines in the web listener:

    maxconn 250
    log stdout format raw local0
    log global
    mode http
    option httplog
    timeout connect 5s
    timeout client 30s
    timeout server 30s
listen web
    bind *:80   
    mode http
    acl ip_is_internal src
    acl ip_is_internal src
    acl ip_is_internal src
    acl curl_user_agent hdr_reg(user-agent) -i ^.*curl.*$
    http-request set-var(txn.acl_trigger) str("Internal_IP") if ip_is_internal
    http-request set-var(txn.acl_trigger) str("cURL_User_Agent") if curl_user_agent
    http-request deny if curl_user_agent
    server web apache:80 check
    log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r ACL-Triggered:%{+E}[var(txn.acl_trigger)] "

The HAProxy configuration

The first thing we want to do is have a couple of ACLs to trigger on. I defined two here, one to track private IP address blocks and another to search for any user-agent that contains curl.

This type of logging functionality relies heavily on the http-request set-var proxy keyword. There’s two uses of it here, one for each ACL. What you’re looking to do is set the scope of the variable, I normally share this variable across the entire transaction (which includes request and response) so I can trigger this in either direction, but for most cases the request (req) scope is fine.

After that you perform your actions using the ACLs as normal. In the example configuration’s case, we’re just blocking any user-agent that contains curl and not doing anything with the private IP address ACL.

Finally, to expose this data into your log, you’ll want to specify a custom log format and include the variable that is written to. Section 8.2.4 of the HAProxy docs have more details on what each variable in the string means. The important bit is at the very end, {+E} is used to escape certain characters that could be contained in the variable while [var(txn.acl_trigger)] is used to write the variable contents into the log.

To test this, I put up a simple Docker-Compose file in a Gist as well as the HAProxy configuration.

If everything worked you should see some logs like this when you try to browse to and curl at localhost: [15/Jan/2022:06:27:16.155] web web/web 0/0/0/0/0 304 190 - - -— 1/1/0/0/0 0/0 "GET / HTTP/1.1" ACL-Triggered:Internal_IP [15/Jan/2022:06:27:16.692] web web/web 0/0/0/0/0 304 190 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1" ACL-Triggered:Internal_IP [15/Jan/2022:06:27:16.876] web web/web 0/0/0/0/0 304 190 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1" ACL-Triggered:Internal_IP [15/Jan/2022:06:27:19.909] web web/<NOSRV> 0/-1/-1/-1/0 403 197 - - PR-- 2/2/0/0/3 0/0 "GET / HTTP/1.1" ACL-Triggered:cURL_User_Agent [15/Jan/2022:06:27:20.425] web web/<NOSRV> 0/-1/-1/-1/0 403 197 - - PR– 2/2/0/0/3 0/0 "GET / HTTP/1.1" ACL-Triggered:cURL_User_Agent