diff options
Diffstat (limited to 'internal/suites/example/compose/haproxy/auth-request.lua')
| -rw-r--r-- | internal/suites/example/compose/haproxy/auth-request.lua | 108 |
1 files changed, 100 insertions, 8 deletions
diff --git a/internal/suites/example/compose/haproxy/auth-request.lua b/internal/suites/example/compose/haproxy/auth-request.lua index 37b75f160..1504948d4 100644 --- a/internal/suites/example/compose/haproxy/auth-request.lua +++ b/internal/suites/example/compose/haproxy/auth-request.lua @@ -19,9 +19,42 @@ -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -- SOFTWARE. +-- +-- SPDX-License-Identifier: MIT local http = require("haproxy-lua-http") +core.register_action("auth-request", { "http-req" }, function(txn, be, path) + auth_request(txn, be, path, "HEAD", ".*", "-", "-") +end, 2) + +core.register_action("auth-intercept", { "http-req" }, function(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail) + hdr_req = globToLuaPattern(hdr_req) + hdr_succeed = globToLuaPattern(hdr_succeed) + hdr_fail = globToLuaPattern(hdr_fail) + auth_request(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail) +end, 6) + +function globToLuaPattern(glob) + if glob == "-" then + return "-" + end + -- magic chars: '^', '$', '(', ')', '%', '.', '[', ']', '*', '+', '-', '?' + -- https://www.lua.org/manual/5.4/manual.html#6.4.1 + -- + -- this chain is: + -- 1. escaping all the magic chars, adding a `%` in front of all of them, + -- except the chars being processed later in the chain; + -- 1.1. all the chars inside the [set] are magic chars and have special + -- meaning inside a set, so we're also escaping all of them to avoid + -- misbehavior; + -- 2. converting "match all" `*` and "match one" `?` to their Lua pattern + -- counterparts; + -- 3. adding start and finish boundaries outside the whole string and, + -- being a comma-separated list, between every single item as well. + return "^" .. glob:gsub("[%^%$%(%)%%%.%[%]%+%-]", "%%%1"):gsub("*", ".*"):gsub("?", "."):gsub(",", "$,^") .. "$" +end + function set_var_pre_2_2(txn, var, value) return txn:set_var(var, value) end @@ -44,8 +77,49 @@ function sanitize_header_for_variable(header) return header:gsub("[^a-zA-Z0-9]", "_") end +-- header_match checks whether the provided header matches the pattern. +-- pattern is a comma-separated list of Lua Patterns. +function header_match(header, pattern) + if header == "content-length" or header == "host" or pattern == "-" then + return false + end + for p in pattern:gmatch("[^,]*") do + if header:match(p) then + return true + end + end + return false +end -core.register_action("auth-request", { "http-req" }, function(txn, be, path) +-- Terminates the transaction and sends the provided response to the client. +-- hdr_fail filters header names that should be provided using Lua Patterns. +function send_response(txn, response, hdr_fail) + local reply = txn:reply() + if response then + reply:set_status(response.status_code) + for header, value in response:get_headers(false) do + if header_match(header, hdr_fail) then + reply:add_header(header, value) + end + end + if response.content then + reply:set_body(response.content) + end + else + reply:set_status(500) + end + txn:done(reply) +end + +-- auth_request makes the request to the external authentication service +-- and waits for the response. hdr_* params receive a comma-separated +-- list of Lua Patterns used to identify the headers that should be +-- copied between the requests and responses. A dash `-` in these params +-- mean that the headers shouldn't be copied at all. +-- Special values and behavior: +-- * method == "*": call the auth service using the same method used by the client. +-- * hdr_fail == "-": make the Lua script to not terminate the request. +function auth_request(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail) set_var(txn, "txn.auth_response_successful", false) -- Check whether the given backend exists. @@ -75,7 +149,7 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path) -- socket.http's format. local headers = {} for header, values in pairs(txn.http:req_get_headers()) do - if header ~= 'content-length' then + if header_match(header, hdr_req) then for i, v in pairs(values) do if headers[header] == nil then headers[header] = v @@ -87,28 +161,46 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path) end -- Make request to backend. - local response, err = http.head { + if method == "*" then + method = txn.sf:method() + end + local response, err = http.send(method:upper(), { url = "http://" .. addr .. path, headers = headers, - } + }) + + -- `terminate_on_failure == true` means that the Lua script should send the response + -- and terminate the transaction in the case of a failure. This will happen when + -- hdr_fail content isn't a dash `-`. + local terminate_on_failure = hdr_fail ~= "-" -- Check whether we received a valid HTTP response. if response == nil then txn:Warning("Failure in auth-request backend '" .. be .. "': " .. err) set_var(txn, "txn.auth_response_code", 500) + if terminate_on_failure then + send_response(txn) + end return end set_var(txn, "txn.auth_response_code", response.status_code) + local response_ok = 200 <= response.status_code and response.status_code < 300 for header, value in response:get_headers(true) do set_var(txn, "req.auth_response_header." .. sanitize_header_for_variable(header), value) + if response_ok and hdr_succeed ~= "-" and header_match(header, hdr_succeed) then + txn.http:req_set_header(header, value) + end end - -- 2xx: Allow request. - if 200 <= response.status_code and response.status_code < 300 then + -- response_ok means 2xx: allow request. + if response_ok then set_var(txn, "txn.auth_response_successful", true) - -- Don't allow other codes. + -- Don't allow codes < 200 or >= 300. + -- Forward the response to the client if required. + elseif terminate_on_failure then + send_response(txn, response, hdr_fail) -- Codes with Location: Passthrough location at redirect. 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 set_var(txn, "txn.auth_response_location", response:get_header("location", "last")) @@ -116,4 +208,4 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path) elseif response.status_code ~= 401 and response.status_code ~= 403 then txn:Warning("Invalid status code in auth-request backend '" .. be .. "': " .. response.status_code) end -end, 2) +end |
