Search code examples
haproxysha256hmacsonarcloud

How to verify HMAC in HAProxy


Is it possible to check HMAC validity in HAProxy? Ideally I'd like to set an acl if the HMAC is valid so I can use this in rules.

Our Ubuntu 18.04 build server (running Jenkins) sits behind a firewall with only specific IP ranges white-listed.

We have an HAProxy (1.8) instance receiving all inbound requests and routing to the appropriate backend service.

The problem is SonarCloud have changed their webhooks from a defined set of IP addresses to using HMAC to validate authenticity. This means the webhooks are blocked by the firewall unless we open it to all internet traffic.

https://sonarcloud.io/documentation/project-administration/webhooks/#securing-your-webhooks

If we can congifure HAProxy to validate the HMAC then we can open the server to all traffic & use HAProxy to validate these requests (as well as other existing IP whitelisted ranges).


Solution

  • Thanks to Michael for the pointer to HAProxy/Lua integration. My solution noted here for reference.

    Created the following Lua script (hmac_validate.lua):

    hmac = require('openssl.hmac')
    
    local function tohex(s)
        return (string.gsub(s, ".", function (c)
            return string.format("%.2x", string.byte(c))
        end))
    end -- tohex
    
    function validate_sonar_hmac(txn, hmac_header_key, hmac_secret)
        local payload = txn.req:dup() -- take a copy of the request content
        local body = string.sub(payload,string.find(payload,"\r\n\r\n")+4) -- strip off the headers
        local signature = txn.sf:req_fhdr(hmac_header_key) -- get the HMAC signature sent on the request
    
        -- calculate hmac from body & secret
        local sc_hmac = hmac.new(hmac_secret, "sha256")
        local calculated_signature = tohex(sc_hmac:final(body))
    
        local signatures_match = calculated_signature == signature
        if not signatures_match then
            core.Alert("Sonar Cloud HMAC signature mismatch - received '"..signature.."' but calculated '"..calculated_signature.."'")
        end
    
        txn:set_var("req.sonar_request_valid", signatures_match)
    end;
    
    core.register_action("validate-sonar-hmac", {"http-req"}, validate_sonar_hmac, 2)
    

    HA Proxy config changed to add these lines:

    global
        lua-load /etc/haproxy/hmac_validate.lua
    
    frontend
        acl sonarcloud hdr(X-Sonar-Webhook-HMAC-SHA256) -m found
        http-request lua.validate-sonar-hmac X-Sonar-Webhook-HMAC-SHA256 {{ sonarcloud_hmac_secret }} if sonarcloud
        http-request deny if sonarcloud !{ var(req.sonar_request_valid) -m bool }