I ended up building the following setup to solve this issue.
data_extractor.lua
-- Function to traverse the table and get a value based on the key.
-- @param input_tbl table: Table to traverse.
-- @param input_field_key string: Input string key to search for.
-- @param input_field_value string: Input string key value to search for.
-- @param input_key_ignore_case boolean: Match the key, regardless of the case of the strings.
-- @param input_value_ignore_case boolean: Match the value, regardless of the case of the strings.
-- @return True or False if key and value can be found
local function traverse_table_keys(input_tbl, input_field_key, input_field_value, input_key_ignore_case, input_value_ignore_case)
-- Loop through key value pairs in table
for key, value in pairs(input_tbl) do
if (input_key_ignore_case) then
key = string.lower(key)
input_field_key = string.lower(input_field_key)
end
-- If the key and the input field key matches then proceed with checking the type of the value
if (key ~= nil and key == input_field_key) then
-- If the value is not of type table and the value matches the input field value, then return true
if (type(value) ~= "table") then
if (input_value_ignore_case) then
value = string.lower(value)
input_field_value = string.lower(input_field_value)
end
if (value == input_field_value) then
return true
end
-- If the value is of type table, the proceed with decoding the table
else
-- Loop through key value pairs in table
for keyTbl, valueTbl in pairs(value) do
if (input_value_ignore_case) then
valueTbl = string.lower(valueTbl)
input_field_value = string.lower(input_field_value)
end
-- If the value in the table matches the input field value, then return true
if (valueTbl ~= nil and valueTbl == input_field_value) then
return true
end
end
end
-- If the value is of type table, run the table through this function again
elseif type(value) == "table" then
if (traverse_table_keys(value, input_field_key, input_field_value, input_key_ignore_case, input_value_ignore_case)) then
return true
end
end
end
-- If nothing found then return false
return false
end
-- Function to parse input object, retrieve specific key and value and compare it to the validation field
-- @param txn HaProxyObject: Object passed from HaProxy to get fetch samples.
-- @param input_object string: The variable containing the JSON packet that should be traveresed.
-- @param input_key string: The variable containing the key name that should be retrieved.
-- @param input_validation string: The variable containing the validation value that should exist within the value for the input_key.
-- @param input_key_ignore_case boolean: Match the key, regardless of the case of the strings.
-- @param input_value_ignore_case boolean: Match the value, regardless of the case of the strings.
-- @param input_debug boolean: If additional debug logging should be printed or not.
-- @return string: Boolean to confirm whether input_validation could be found within the value of the input_key field within the input_object JSON object.
local function json_extract_and_compare(txn, input_object, input_key, input_validation, input_key_ignore_case, input_value_ignore_case, input_debug)
local fe_name = txn.sf:fe_name()
local luaId = (txn.get_var(txn, "txn.luaId") or "N/A")
local inputObject = (txn:get_var(input_object) or "N/A")
local inputKey = (txn:get_var(input_key) or "N/A")
local inputValidation = (txn:get_var(input_validation) or "N/A")
-- Validate inputs
-- Argument 1: input_object / inputObject
if (inputObject == nil or type(inputObject) ~= "string" or string.len(inputObject) == 0) then
print("Argument 1 (input_object) is mandatory and has to be of type string and has to be a well formed JSON packet [" .. fe_name .. "]")
return ""
end
-- Argument 2: input_key / inputKey
if (inputKey == nil or type(inputKey) ~= "string") then
print("Argument 2 (input_key) is mandatory and has to be of type string [" .. fe_name .. "]")
return ""
end
-- Argument 3: input_validation / inputValidation
if (inputValidation == nil or type(inputValidation) ~= "string") then
print("Argument 3 (input_validation) is mandatory and has to be of type string [" .. fe_name .. "]")
return ""
end
-- Argument 4: input_key_ignore_case
if (input_key_ignore_case ~= nil) then
-- Convert from string to boolean representation
if (input_key_ignore_case == "true") then
input_key_ignore_case = true
elseif (input_key_ignore_case == "false") then
input_key_ignore_case = false
else
print("Argument 4 (input_key_ignore_case) has to be of type boolean [" .. fe_name .. "]")
return ""
end
else
-- Default to false
input_key_ignore_case = false
end
-- Argument 5: input_value_ignore_case
if (input_value_ignore_case ~= nil) then
-- Convert from string to boolean representation
if (input_value_ignore_case == "true") then
input_value_ignore_case = true
elseif (input_value_ignore_case == "false") then
input_value_ignore_case = false
else
print("Argument 5 (input_value_ignore_case) has to be of type boolean [" .. fe_name .. "]")
return ""
end
else
-- Default to false
input_value_ignore_case = false
end
-- Argument 6: input_debug
if (input_debug ~= nil) then
-- Convert from string to boolean representation
if (input_debug == "true") then
input_debug = true
elseif (input_debug == "false") then
input_debug = false
else
print("Argument 6 (input_debug) has to be of type boolean [" .. fe_name .. "]")
return ""
end
else
-- Default to false
input_debug = false
end
-- Print debug if required
if (input_debug) then
print("Frontend: " .. fe_name)
print("Input Object: " .. inputObject)
print("Input Key: " .. inputKey)
print("Input Validation: " .. inputValidation)
print("Input Key Ignore Case: " .. tostring(input_key_ignore_case))
print("Input Value Ignore Case: " .. tostring(input_value_ignore_case))
end
-- Decode as JSON and traverse table key and values
local extract_and_compare_output = traverse_table_keys(json.decode(inputObject), inputKey, inputValidation, input_key_ignore_case,
input_value_ignore_case)
-- Print debug if required
if (input_debug) then
print("Extract And Compare Output: " .. tostring(extract_and_compare_output))
end
return extract_and_compare_output
end
core.register_fetches("json_extract_and_compare", json_extract_and_compare)
haproxy.cfg:
global
lua-load-per-thread /opt/haproxy/lua/data_extractor.lua
frontend test-8080
bind *:8080
http-request set-var(txn.jwt_payload) req.hdr(Authorization),word(2,.),ub64dec
acl allowed_origins_in_payload var(txn.jwt_payload) -m found -m sub '"allowed-origins":'
http-request set-var(txn.jwt_payload_key) str("allowed-origins") if allowed_origins_in_payload
http-request set-var(txn.jwt_source_validation) src if allowed_origins_in_payload
acl wildcard_in_allowed_origins http_auth_bearer,jwt_payload_query('$.allowed-origins[0]') -m str '*' '/*'
http-request set-var(txn.allowed_origins_passed) lua.json_extract_and_compare(txn.jwt_payload,txn.jwt_payload_key,txn.jwt_source_validation,false) if allowed_origins_in_payload !wildcard_in_allowed_origins
http-request deny deny_status 403 content-type 'application/json' string '{ "error": "Access Forbidden"}' if allowed_origins_in_payload !{ var(txn.allowed_origins_passed) -m bool } !wildcard_in_allowed_origins