Skip to content

Instantly share code, notes, and snippets.

@cyanide-burnout
Last active November 12, 2023 00:53
Show Gist options
  • Save cyanide-burnout/7b817aad0fb6a3785491fafdc668a11f to your computer and use it in GitHub Desktop.
Save cyanide-burnout/7b817aad0fb6a3785491fafdc668a11f to your computer and use it in GitHub Desktop.
IP- and cookie-based authorization guard for haproxy
--[[
IP- and cookie-based authorization guard for haproxy
https://www.haproxy.com/blog/5-ways-to-extend-haproxy-with-lua/
Guard checks permit in memcached, any value of key 'Allow-Address-<Address>' or 'Allow-Session-<Session>' will be accepted
Where:
<Address> is client's IP-address
<Session> is value of cookie 'XGuardKey'
Parameter 'action' can be:
pass - pass transaction, return result by using variables. Suitable for cases when connection has to be processed by application, such as returning HTTP 4xx or redicating to a captive portal.
drop - drop connection without response. Suitable for TCP connections.
Parameter 'scope' can be combination of following values, separated by dash:
local - allow bypass of private IP-addresses (RFC1918)
address - check IP address
cookie - check cookie
lua-load /opt/guard/guard.lua
tcp-request inspect-delay 5s
tcp-request content lua.guard drop local-address
http-request lua.guard pass local-cookie
http-request deny if { var(req.deny) -m bool }
]]
local function validateKey(key)
local socket = core.tcp()
socket:settimeout(5)
if socket:connect('127.0.0.1', 11211) then
socket:send('get ' .. key .. '\r\n')
local data, _ = socket:receive('*l')
socket:close()
return data ~= 'END'
end
return false
end
local function validateLocalAddress(address)
local patterns = { '^(10[.])', '^(127[.])', '^(172[.]1[6789][.])', '^(172[.]2[0123456789][.])', '^(172[.]3[01][.])', '^(192[.]168[.])' }
for _, pattern in pairs(patterns) do
if string.match(address, pattern) ~= nil then
return true
end
end
return false
end
local function validateCookie(transport, name, prefix)
if transport then
local headers = transport:req_get_headers()
for _, header in pairs(headers['cookie'] or { }) do
for key, value in header:gmatch('(%w+)=(%w+)') do
if key == name and validateKey(prefix .. value) then
return true
end
end
end
end
return false
end
local function handleAction(transaction, action, scope)
local address = transaction.f:src()
local result =
scope:match('local') and validateLocalAddress(address) or
scope:match('address') and validateKey('Allow-Address-' .. address) or
scope:match('cookie') and validateCookie(transaction.http, 'XGuardKey', 'Allow-Session-')
-- core.Info('Guard: ' .. address .. ': ' .. tostring(result))
if action == 'drop' and not result then
transaction:done()
return
end
transaction:set_var('req.allow', result)
transaction:set_var('req.deny', not result)
end
core.register_action('guard', { 'tcp-req', 'http-req' }, handleAction, 2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment