HAProxy ACL With Variable In Substring

Hi,

I am attempting an ACL rule that will check if my src value from the client’s request or txn.source_ip can be found within my txn.token_payload variable if possible. Hardcoded values seems to be working, however variables seems to not be working.

I am currently using HAProxy 2.6 and I am not able to add a new LUA script at this point, so would like to perform this task via simple ACLs if possible.

Config that works:

frontend test-443
  # Binding
  bind *:443

  ## Validate Allowed Origins If Exists
  ### Decode Token Payload
  http-request set-var(txn.token_payload) req.hdr(Authorization),word(2,.),ub64dec
  ### Check If allowed-origins is available in token_payload packet
  acl allowed_origins_in_payload var(txn.token_payload) -m found -m sub allowed-origins
  ### Check if the src field can be found within the token_payload variable if the allowed-origins field exists
  acl src_found_in_allowed_origins var(txn.token_payload) -m found -m sub 172.1.1.1 if allowed_origins_in_payload
  ### Deny the request if the allowed-origins field exists but the client source can't be found within it
  http-request deny content-type 'text/html' string 'Host was not found in allowed_origins' if allowed_origins_in_payload !src_found_in_allowed_origins

  # Backend Endpoint
  use_backend backup
  
backend backup

  # Server Endpoint
  default-server check
  server test backup:12345

Config that does not work:

frontend test-443
  # Binding
  bind *:443

  ## Validate Allowed Origins If Exists
  ### Decode Token Payload
  http-request set-var(txn.token_payload) req.hdr(Authorization),word(2,.),ub64dec
  ### Check If allowed-origins is available in token_payload packet
  acl allowed_origins_in_payload var(txn.token_payload) -m found -m sub allowed-origins
  ### Check if the src field can be found within the token_payload variable if the allowed-origins field exists
  acl src_found_in_allowed_origins var(txn.token_payload) -m found -m sub src if allowed_origins_in_payload
  ### Deny the request if the allowed-origins field exists but the client source can't be found within it
  http-request deny content-type 'text/html' string 'Host was not found in allowed_origins' if allowed_origins_in_payload !src_found_in_allowed_origins

  # Backend Endpoint
  use_backend backup
  
backend backup

  # Server Endpoint
  default-server check
  server test backup:12345

Example Client Source:

172.1.1.1

Example JWT Token Payload:

{
  "exp": 1713905722,
  "iat": 1713905422,
  "jti": "12345",
  "iss": "http://172.2.2.2:443/realms/test",
  "aud": "/api/test",
  "sub": "wvwcwecwc-fbevopm-vwv-vwevew-vwvwevwev",
  "typ": "Bearer",
  "azp": "test",
  "allowed-origins": [
    "192.168.1.1",
    "172.1.1.1"
  ],
  "scope": "test-api",
  "clientHost": "172.2.2.2",
  "clientAddress": "172.2.2.2",
  "client_id": "test"
}

HAProxy Version:

2.6.5-987a4e2

Hi,

You cannot specify multiple -m on the same acl line: it gets rewritten each time you use it, thus only the last one will be considered.

For instance:

acl allowed_origins_in_payload var(txn.token_payload) -m found -m sub allowed-origins

Will be simply be evaluated as:

acl allowed_origins_in_payload var(txn.token_payload) -m sub allowed-origins

Moreover, -m sub will interpret the given patterns as raw strings, ie: it will not evaluate fetches, so if you use this:

acl src_found_in_allowed_origins var(txn.token_payload) -m sub src 

It will try to look for “src” keyword in txn.token_payload, not for the actual IP address that src sample fetch points to.

Unfortunately I can’t think of a trivial way to do this from pure config. Perhaps strcmp(), jwt_query() and json_query() converters could give you a hand, but it looks quite complicated to do this from pure config IMO.

If you’re not afraid about performance implications, another option could be to leverage Lua to implement your own fetch. Since Lua fetches have access to the TXN context, it should be pretty straightforward to extract allowed IP origins from the txn.token_payload variable and compare then with client’s IP address so that the fetch returns 1 on success and 0 on failure for instance.

match.lua:

function extract_allowed_ips_from_jwt(arg)
  local ips = { } -- array of strings ip
   -- your code to populate ips
  return ips
end

core.register_fetches("check_allowed", function(txn)
    -- Get source IP
    local clientip = txn.f:src()
    -- Get allowed ips
    local allowedips = extract_allowed_ips_from_jwt(txn:get_var("txn.token_payload"))

  for key,value in pairs(allowedips) do
      if string.match(value, clientip) then
          return true
      end
  end
  return false
end)

haproxy.conf:

global
  lua-load match.lua

frontend test-443
  # Binding
  bind *:443

  acl src_found_in_allowed_origins lua.check_allowed -m bool eq true
  http-request deny content-type 'text/html' string 'Host was not found in allowed_origins' if !src_found_in_allowed_origins