2424
2525local http = require (" haproxy-lua-http" )
2626
27+ core .register_action (" auth-request" , { " http-req" }, function (txn , be , path )
28+ auth_request (txn , be , path , " HEAD" , " .*" , " -" , " -" )
29+ end , 2 )
30+
31+ core .register_action (" auth-intercept" , { " http-req" }, function (txn , be , path , method , hdr_req , hdr_succeed , hdr_fail )
32+ hdr_req = globToLuaPattern (hdr_req )
33+ hdr_succeed = globToLuaPattern (hdr_succeed )
34+ hdr_fail = globToLuaPattern (hdr_fail )
35+ auth_request (txn , be , path , method , hdr_req , hdr_succeed , hdr_fail )
36+ end , 6 )
37+
38+ function globToLuaPattern (glob )
39+ if glob == " -" then
40+ return " -"
41+ end
42+ -- magic chars: '^', '$', '(', ')', '%', '.', '[', ']', '*', '+', '-', '?'
43+ -- https://www.lua.org/manual/5.4/manual.html#6.4.1
44+ --
45+ -- this chain is:
46+ -- 1. escaping all the magic chars, adding a `%` in front of all of them,
47+ -- except the chars being processed later in the chain;
48+ -- 1.1. all the chars inside the [set] are magic chars and have special
49+ -- meaning inside a set, so we're also escaping all of them to avoid
50+ -- misbehavior;
51+ -- 2. converting "match all" `*` and "match one" `?` to their Lua pattern
52+ -- counterparts;
53+ -- 3. adding start and finish boundaries outside the whole string and,
54+ -- being a comma-separated list, between every single item as well.
55+ return " ^" .. glob :gsub (" [%^%$%(%)%%%.%[%]%+%-]" , " %%%1" ):gsub (" *" , " .*" ):gsub (" ?" , " ." ):gsub (" ," , " $,^" ) .. " $"
56+ end
57+
2758function set_var_pre_2_2 (txn , var , value )
2859 return txn :set_var (var , value )
2960end
@@ -46,8 +77,49 @@ function sanitize_header_for_variable(header)
4677 return header :gsub (" [^a-zA-Z0-9]" , " _" )
4778end
4879
80+ -- header_match checks whether the provided header matches the pattern.
81+ -- pattern is a comma-separated list of Lua Patterns.
82+ function header_match (header , pattern )
83+ if header == " content-length" or header == " host" or pattern == " -" then
84+ return false
85+ end
86+ for p in pattern :gmatch (" [^,]*" ) do
87+ if header :match (p ) then
88+ return true
89+ end
90+ end
91+ return false
92+ end
4993
50- core .register_action (" auth-request" , { " http-req" }, function (txn , be , path )
94+ -- Terminates the transaction and sends the provided response to the client.
95+ -- hdr_fail filters header names that should be provided using Lua Patterns.
96+ function send_response (txn , response , hdr_fail )
97+ local reply = txn :reply ()
98+ if response then
99+ reply :set_status (response .status_code )
100+ for header , value in response :get_headers (true ) do
101+ if header_match (header , hdr_fail ) then
102+ reply :add_header (header , value )
103+ end
104+ end
105+ if response .content then
106+ reply :set_body (response .content )
107+ end
108+ else
109+ reply :set_status (500 )
110+ end
111+ txn :done (reply )
112+ end
113+
114+ -- auth_request makes the request to the external authentication service
115+ -- and waits for the response. hdr_* params receive a comma-separated
116+ -- list of Lua Patterns used to identify the headers that should be
117+ -- copied between the requests and responses. A dash `-` in these params
118+ -- mean that the headers shouldn't be copied at all.
119+ -- Special values and behavior:
120+ -- * method == "*": call the auth service using the same method used by the client.
121+ -- * hdr_fail == "-": make the Lua script to not terminate the request.
122+ function auth_request (txn , be , path , method , hdr_req , hdr_succeed , hdr_fail )
51123 set_var (txn , " txn.auth_response_successful" , false )
52124
53125 -- Check whether the given backend exists.
@@ -77,7 +149,7 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path)
77149 -- socket.http's format.
78150 local headers = {}
79151 for header , values in pairs (txn .http :req_get_headers ()) do
80- if header ~= ' content-length ' then
152+ if header_match ( header , hdr_req ) then
81153 for i , v in pairs (values ) do
82154 if headers [header ] == nil then
83155 headers [header ] = v
@@ -89,33 +161,51 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path)
89161 end
90162
91163 -- Make request to backend.
92- local response , err = http .head {
164+ if method == " *" then
165+ method = txn .sf :method ()
166+ end
167+ local response , err = http .send (method :upper (), {
93168 url = " http://" .. addr .. path ,
94169 headers = headers ,
95- }
170+ })
171+
172+ -- `terminate_on_failure == true` means that the Lua script should send the response
173+ -- and terminate the transaction in the case of a failure. This will happen when
174+ -- hdr_fail content isn't a dash `-`.
175+ local terminate_on_failure = hdr_fail ~= " -"
96176
97177 -- Check whether we received a valid HTTP response.
98178 if response == nil then
99179 txn :Warning (" Failure in auth-request backend '" .. be .. " ': " .. err )
100180 set_var (txn , " txn.auth_response_code" , 500 )
181+ if terminate_on_failure then
182+ send_response (txn )
183+ end
101184 return
102185 end
103186
104187 set_var (txn , " txn.auth_response_code" , response .status_code )
188+ local response_ok = 200 <= response .status_code and response .status_code < 300
105189
106190 for header , value in response :get_headers (true ) do
107191 set_var (txn , " req.auth_response_header." .. sanitize_header_for_variable (header ), value )
192+ if response_ok and hdr_succeed ~= " -" and header_match (header , hdr_succeed ) then
193+ txn .http :req_set_header (header , value )
194+ end
108195 end
109196
110- -- 2xx: Allow request.
111- if 200 <= response . status_code and response . status_code < 300 then
197+ -- response_ok means 2xx: allow request.
198+ if response_ok then
112199 set_var (txn , " txn.auth_response_successful" , true )
113- -- Don't allow other codes.
200+ -- Don't allow codes < 200 or >= 300.
201+ -- Forward the response to the client if required.
202+ elseif terminate_on_failure then
203+ send_response (txn , response , hdr_fail )
114204 -- Codes with Location: Passthrough location at redirect.
115205 elseif response .status_code == 301 or response .status_code == 302 or response .status_code == 303 or response .status_code == 307 or response .status_code == 308 then
116206 set_var (txn , " txn.auth_response_location" , response :get_header (" location" , " last" ))
117207 -- 401 / 403: Do nothing, everything else: log.
118208 elseif response .status_code ~= 401 and response .status_code ~= 403 then
119209 txn :Warning (" Invalid status code in auth-request backend '" .. be .. " ': " .. response .status_code )
120210 end
121- end , 2 )
211+ end
0 commit comments