Last active
November 12, 2023 00:53
-
-
Save cyanide-burnout/7b817aad0fb6a3785491fafdc668a11f to your computer and use it in GitHub Desktop.
IP- and cookie-based authorization guard for haproxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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