diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2023-04-08 14:48:55 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-08 14:48:55 +1000 |
| commit | 2dcfc0b04c3fbe57ecc11322487089bc8970e79f (patch) | |
| tree | 54538032cbe1cdd9220d1418251d1c848c987519 /internal/suites/example/compose/haproxy/auth-request.lua | |
| parent | fa250ea7ddb902132f4df74c407be84015577fa3 (diff) | |
feat(handlers): authz authrequest authelia url (#5181)
This adjusts the AuthRequest Authz implementation behave similarly to the other implementations in as much as Authelia can return the relevant redirection to the proxy and the proxy just utilizes it if possible. In addition it swaps the HAProxy examples over to the ForwardAuth implementation as that's now supported.
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
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 |
