Repository: TimWolla/haproxy-auth-request Branch: main Commit: cdb891cf5299 Files: 39 Total size: 48.8 KB Directory structure: gitextract_3ykm4_1e/ ├── .github/ │ ├── dependabot.yml │ ├── vtest.json │ └── workflows/ │ └── test.yml ├── .gitignore ├── .gitmodules ├── .mailmap ├── LICENSE ├── Makefile ├── README.md ├── auth-request.lua ├── debian/ │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── haproxy-auth-request.docs │ ├── rules │ └── source/ │ └── format └── test/ ├── allow.vtc ├── allow_mt.vtc ├── deny.vtc ├── deny_close.vtc ├── deny_garbage.vtc ├── dynamic_method.vtc ├── expose_arbitrary_headers.vtc ├── expose_location.vtc ├── expose_location_last.vtc ├── expose_status_code.vtc ├── headers_complete.vtc ├── headers_fail.vtc ├── headers_fail_multiple.vtc ├── headers_filter.vtc ├── headers_request.vtc ├── headers_succeed.vtc ├── multiple_requests.vtc ├── multiple_requests_mt.vtc ├── no_variable_leak.lua ├── no_variable_leak.vtc ├── pass_headers_to_backend.vtc └── starts.vtc ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" ================================================ FILE: .github/vtest.json ================================================ { "problemMatcher": [ { "owner": "vtest", "pattern": [ { "regexp": "^#(\\s+top\\s+TEST\\s+(.*)\\s+FAILED.*)", "file": 2, "message": 1 } ] } ] } ================================================ FILE: .github/workflows/test.yml ================================================ # The MIT License (MIT) # # Copyright (c) 2020 Tim Düsterhus # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # 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 name: Test on: push: pull_request: schedule: - cron: '0 0 * * 0' jobs: VTest: runs-on: ubuntu-latest strategy: matrix: haproxy-versions: - "1.8" - "1.9" - "2.0" - "2.1" - "2.2" - "2.3" - "2.4" - "2.5" - "2.6" - "2.7" - "2.8" - "2.9" - "3.0" - "3.1" - "3.2" - "3.3" - "3.4" fail-fast: false steps: - name: Install HAProxy ${{ matrix.haproxy-versions }}. uses: timwolla/action-install-haproxy@main id: install-haproxy with: branch: ${{ matrix.haproxy-versions }} install_vtest: yes use_lua: yes - name: Install apt dependencies. run: sudo apt-get install -y lua-json - uses: actions/checkout@v6 with: submodules: recursive - name: Install haproxy-lua-http globally. run: | sudo install -m755 -d /usr/local/share/lua/5.3/ sudo install -m644 haproxy-lua-http/http.lua /usr/local/share/lua/5.3/haproxy-lua-http.lua sed -i 's/lua-prepend-path/#lua-prepend-path/g' test/*.vtc - name: Install problem matcher for VTest. run: echo "::add-matcher::.github/vtest.json" - name: Run tests using HAProxy ${{ steps.install-haproxy.outputs.version }}. run: | set -euo pipefail vtest -Dhaproxy_version=${{ steps.install-haproxy.outputs.version }} -k -t 10 test/*.vtc 2>&1 \ |tee >( awk ' $5=="passed"{icon=":heavy_check_mark:"} $5=="skipped"{icon=":grey_question:"} $5=="FAILED"{icon=":x:"} $1 == "#" && $3=="TEST"{ print "- " icon " " $4 } {line=$0} END{print "# " $0} ' \ |tac >> $GITHUB_STEP_SUMMARY ) ================================================ FILE: .gitignore ================================================ debian/debhelper-build-stamp debian/files debian/haproxy-auth-request.debhelper.log debian/haproxy-auth-request.substvars debian/haproxy-auth-request/ ================================================ FILE: .gitmodules ================================================ [submodule "haproxy-lua-http"] path = haproxy-lua-http url = https://github.com/haproxytech/haproxy-lua-http.git branch = master ================================================ FILE: .mailmap ================================================ Tim Düsterhus ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2018 Tim Düsterhus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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. ================================================ FILE: Makefile ================================================ # SPDX-License-Identifier: MIT default: install: auth-request.lua haproxy-lua-http/http.lua install -d "$(DESTDIR)/usr/share/haproxy" install -m644 haproxy-lua-http/http.lua "$(DESTDIR)/usr/share/haproxy" install -m644 auth-request.lua "$(DESTDIR)/usr/share/haproxy" .PHONY: install ================================================ FILE: README.md ================================================ # auth-request ![Test](https://github.com/TimWolla/haproxy-auth-request/workflows/Test/badge.svg) auth-request allows you to add access control to your HTTP services based on a subrequest to a configured HAProxy backend. The workings of this Lua script are loosely based on the [ngx_http_auth_request_module] module for nginx. ## Requirements - HAProxy 1.8.4+ (2.2.0+ recommended) - Only the latest version of each HAProxy branch is supported. - `USE_LUA=1` must be set at compile time. - [haproxy-lua-http] must be available within the Lua path. - A `json` library within the Lua path (dependency of haproxy-lua-http). - With HAProxy 2.1.3+ you can use the [`lua-prepend-path`] configuration option to specify the search path. ## Usage 1. Load this Lua script in the `global` section of your `haproxy.cfg`: ```haproxy global # *snip* lua-prepend-path /usr/share/haproxy/?/http.lua # If haproxy-lua-http is saved as /usr/share/haproxy/haproxy-lua-http/http.lua lua-load /usr/share/haproxy/auth-request.lua ``` 2. Define a backend that is used for the subrequests: ```haproxy backend auth_request mode http server auth_request 127.0.0.1:8080 check ``` 3. Execute the subrequest in your frontend (as early as possible): ```haproxy frontend http mode http bind :::80 v4v6 # *snip* # auth-request syntax: # Backend name Path to request http-request lua.auth-request auth_request /is-allowed # auth-intercept syntax: (Headers to copy) # Backend name Path Method Request Success Failure http-request lua.auth-intercept auth_request /is-allowed HEAD * - - ``` 4. Act on the results: ```haproxy frontend http # *snip* http-request deny if ! { var(txn.auth_response_successful) -m bool } ``` ### Parameters The scripts receive a list of parameters used to build the authentication request: * **Backend name**: is the name of an HAProxy backend. See the [Inner Workings](#inner-workings) section. * **Path to request**: the request URL sent to the auth-request backend. The following parameters are only available in the `auth-intercept` script: * **Method**: the HTTP method that should be used. Use an asterisk `*` to ask `auth-intercept` to copy the same method used by the client. `auth-request` uses the `HEAD` method. * **Headers to copy on Request**: a comma-separated list of a simplified glob pattern that should match the HTTP header names to copy from the client to the auth-intercept backend. Use a dash `-` to not copy any header. * **Headers to copy on Success**: a comma-separated list of a simplified glob pattern that should match the HTTP header names to copy from the auth-intercept backend to the protected backend server, if the auth-intercept backend respond with 2xx response code and the request succeed. All headers received from the auth-intercept will override headers with the same name provided by the client. Use `*` to copy all headers, or use a dash `-` to not copy any header. HAProxy variables are always created, see the [Available Variables](#available-variables) section. * **Headers to copy on Failure**: a comma-separated list of a simplified glob pattern that should match the HTTP header names to copy from the auth-intercept backend to the client, if the request failed. `auth-intercept` will use the same HTTP method and body sent by the auth-intercept backend to respond to the client, closing the transaction. The protected backend server will not be used. Use `*` to copy all headers. Use a dash `-` to not close the transaction and leave to the HAProxy configuration the task to deny the request based on the `txn.auth_response_successful` variable. HAProxy variables are always created, see the [Available Variables](#available-variables) section. Simplified glob pattern: use an asterisk `*` to match any sequence of characters and `?` to match a single char. `*` will match any header name. `x-*` will match all header names started with `x-`. `x-????` will match `x-user` but will not match neither `x-token` nor `x-id`. HAProxy 2.1 or older: the On Failure param (the last one) will close the transaction and respond to the client if the value is not a dash `-`, however this feature is only supported on HAProxy 2.2 or newer. The only supported option on 2.1 and older is a dash `-`. ### Available Variables auth-request uses HAProxy variables to communicate the results back to you. The [`var()` sample fetch] can be used to retrieve the variable contents. The following list of variables may be set.
txn.auth_response_successful
Set to true if the subrequest returns an HTTP status code in the 2xx range. false otherwise.
txn.auth_response_code
The HTTP status code of the subrequest. If the subrequest did not return a valid HTTP response the value will be 500.
txn.auth_response_location
The location response header of the subrequest. This variable is only set if the HTTP status code of the subrequest indicates a redirect (i.e. 301, 302, 303, 307, or 308).
req.auth_response_header.*
These variables store the subrequest’s response headers. The values of duplicate response headers will be merged with a comma. HAProxy variables may only contain alphanumeric characters, the dot (.), and an underscore _. Any non-alphanumeric characters will be replaced with an underscore to be representable. If the response contains duplicate response headers after normalizing the header name the result for these headers will be undefined. Normalization examples:
X-Authenticated-User
req.auth_response_header.x_authenticated_user
Success
req.auth_response_header.success
Please note: The scope of the response header variables is req compared to txn for the other variables. The contents will no longer be available during response processing to save memory. Copy the values of interest into a txn. variable if you need access them during response processing.
## Inner Workings The Lua script will make a HTTP request to the *first* server in the given backend that is either marked as `UP` or that does not have checks enabled. This allows for basic health checking of the auth-request backend. If you need more complex processing of the request forward the auth-request to a separate HAProxy *frontend* that performs the required modifications to the request and response. The requested URL is the one given in the second parameter. Any request headers will be forwarded as-is to the auth-request backend, with the exception of the `content-length` header which will be stripped, because the request body will not be forwarded. ## Known limitations - The Lua script only supports basic health checking, without redispatching or load balancing of any kind. - The backend must not be using TLS. [ngx_http_auth_request_module]: http://nginx.org/en/docs/http/ngx_http_auth_request_module.html [haproxy-lua-http]: https://github.com/haproxytech/haproxy-lua-http [`lua-prepend-path`]: http://cbonte.github.io/haproxy-dconv/2.1/configuration.html#lua-prepend-path [`var()` sample fetch]: http://cbonte.github.io/haproxy-dconv/2.2/configuration.html#7.3.2-var ================================================ FILE: auth-request.lua ================================================ -- The MIT License (MIT) -- -- Copyright (c) 2018 Tim Düsterhus -- -- Permission is hereby granted, free of charge, to any person obtaining a copy -- of this software and associated documentation files (the "Software"), to deal -- in the Software without restriction, including without limitation the rights -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -- copies of the Software, and to permit persons to whom the Software is -- furnished to do so, subject to the following conditions: -- -- The above copyright notice and this permission notice shall be included in all -- copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -- 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 function set_var_post_2_2(txn, var, value) return txn:set_var(var, value, true) end set_var = function(txn, var, value) local success = pcall(set_var_post_2_2, txn, var, value) if success then set_var = set_var_post_2_2 else set_var = set_var_pre_2_2 end return set_var(txn, var, value) end 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 -- 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. if core.backends[be] == nil then txn:Alert("Unknown auth-request backend '" .. be .. "'") set_var(txn, "txn.auth_response_code", 500) return end -- Check whether the given backend has servers that -- are not `DOWN`. local addr = nil for name, server in pairs(core.backends[be].servers) do local status = server:get_stats()['status'] if status == "no check" or status:find("UP") == 1 then addr = server:get_addr() break end end if addr == nil then txn:Warning("No servers available for auth-request backend: '" .. be .. "'") set_var(txn, "txn.auth_response_code", 500) return end -- Transform table of request headers from haproxy's to -- socket.http's format. local headers = {} for header, values in pairs(txn.http:req_get_headers()) do if header_match(header, hdr_req) then for i, v in pairs(values) do if headers[header] == nil then headers[header] = v else headers[header] = headers[header] .. ", " .. v end end end end -- Make request to backend. 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 -- response_ok means 2xx: allow request. if response_ok then set_var(txn, "txn.auth_response_successful", true) -- 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")) -- 401 / 403: Do nothing, everything else: log. 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 ================================================ FILE: debian/changelog ================================================ haproxy-auth-request (0) UNRELEASED; urgency=medium * Initial Release. -- Tim Düsterhus Mon, 08 Jan 2018 22:56:57 +0000 ================================================ FILE: debian/compat ================================================ 9 ================================================ FILE: debian/control ================================================ Source: haproxy-auth-request Section: net Priority: optional Maintainer: Tim Düsterhus Build-Depends: debhelper (>= 9) Standards-Version: 3.9.8 Homepage: https://github.com/TimWolla/haproxy-auth-request Vcs-Git: https://github.com/TimWolla/haproxy-auth-request.git Vcs-Browser: https://github.com/TimWolla/haproxy-auth-request.git Package: haproxy-auth-request Architecture: all Depends: ${misc:Depends}, lua-json Suggests: haproxy Description: HTTP access control using subrequests auth-request allows you to add access control to your HTTP services based on a subrequest to a configured haproxy backend. The workings of this Lua script are loosely based on the ngx_http_auth_request_module module for nginx. ================================================ FILE: debian/copyright ================================================ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: haproxy-auth-request Source: Files: * Copyright: Tim Düsterhus License: MIT Files: debian/* Copyright: 2018 Tim Düsterhus License: MIT License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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. # Please also look if there are files or directories which have a # different copyright/license attached and list them here. # Please avoid picking licenses with terms that are more restrictive than the # packaged work, as it may make Debian's contributions unacceptable upstream. ================================================ FILE: debian/haproxy-auth-request.docs ================================================ README.md ================================================ FILE: debian/rules ================================================ #!/usr/bin/make -f # See debhelper(7) (uncomment to enable) # output every command that modifies files on the build system. # export DH_VERBOSE = 1 %: dh $@ ================================================ FILE: debian/source/format ================================================ 3.0 (native) ================================================ FILE: test/allow.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that a successful request allows access." feature ignore_unknown_macro server s1 { rxreq txresp } -start server s_auth_backend { rxreq txresp \ -status 204 } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 200 } -run ================================================ FILE: test/allow_mt.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that a successful request allows access for multi-threaded Lua." feature ignore_unknown_macro feature cmd "dpkg --compare-versions ${haproxy_version} ge 2.4" server s1 { rxreq txresp } -start server s_auth_backend { rxreq txresp \ -status 204 } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load-per-thread ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 200 } -run ================================================ FILE: test/deny.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that a failing request denies access." feature ignore_unknown_macro server s1 { rxreq txresp } -start server s_auth_backend { rxreq txresp \ -status 403 } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 403 } -run ================================================ FILE: test/deny_close.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that a backend close denies access." feature ignore_unknown_macro server s1 { rxreq txresp } -start server s_auth_backend { rxreq } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 403 } -run ================================================ FILE: test/deny_garbage.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that a backend sending garbage denies access." feature ignore_unknown_macro server s1 { rxreq txresp } -repeat 7 -start server s_auth_backend { rxreq send "foo" accept rxreq send "foo\r\n" accept rxreq send "HTTP/1.0 200 Ok" accept rxreq send "HTTP\r\n" accept rxreq send "HTTP/1\r\n" accept rxreq send "HTTP/1.0 200 Ok\r\n" accept rxreq send "HTTP/1.0 XXX Fail\r\n\r\n" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 403 } -repeat 7 -run ================================================ FILE: test/dynamic_method.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that auth-request backend receives the client method." feature ignore_unknown_macro server s1 { rxreq txresp } -repeat 3 -start server s_auth_backend { rxreq expect req.method == "POST" txresp accept rxreq expect req.method == "GET" txresp accept rxreq expect req.method == "HEAD" txresp } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-intercept auth_backend / * * - - http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq -method "POST" rxresp txreq -method "GET" rxresp txreq -method "HEAD" rxresp } -run ================================================ FILE: test/expose_arbitrary_headers.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that arbitrary response headers are exposed." feature ignore_unknown_macro server s1 { rxreq txresp } -repeat 4 -start server s_auth_backend { rxreq txresp \ -status 200 \ -hdr "content-type: text/plain" \ -hdr "x-authenticated-email: guest@example.com" \ -hdr "x-authenticated-user: guest" accept rxreq txresp \ -status 200 \ -hdr "x-authenticated-user: root" accept rxreq txresp \ -status 200 \ -hdr "content-type: text/plain" \ -hdr "x-authenticated-email: root@example.com" \ -hdr "x-authenticated-user: root" accept rxreq txresp \ -status 200 \ -hdr "content-type: text/plain" \ -hdr "x-authenticated-email: root@example.com" \ -hdr "x-authenticated-email: root@example.net" \ -hdr "x-authenticated-user: root" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-request deny unless { var(req.auth_response_header.x_authenticated_user) -m str root } http-request set-header x-echo "%[var(req.auth_response_header.content_type)]|%[var(req.auth_response_header.x_authenticated_user)]|%[var(req.auth_response_header.x_authenticated_email)]" http-request set-var(txn.echo) req.fhdr(x-echo) http-response set-header x-echo %[var(txn.echo)] server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 403 } -run client c2 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 200 expect resp.http.x-echo == "|root|" txreq rxresp expect resp.status == 200 expect resp.http.x-echo == "text/plain|root|root@example.com" txreq rxresp expect resp.status == 200 expect resp.http.x-echo == "text/plain|root|root@example.com,root@example.net" } -run ================================================ FILE: test/expose_location.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that location header is exposed for redirects." feature ignore_unknown_macro server s1 { rxreq txresp } -repeat 4 -start server s_auth_backend { rxreq txresp \ -status 301 \ -hdr "location: https://example.com" accept rxreq txresp \ -status 303 \ -hdr "location: https://example.com" accept rxreq txresp \ -status 307 \ -hdr "location: https://example.com" accept rxreq txresp \ -status 200 \ -hdr "location: https://example.com" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-response set-header x-status %[var(txn.auth_response_code)] http-response set-header x-location %[var(txn.auth_response_location)] if { var(txn.auth_response_location) -m found } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.http.x-status == 301 expect resp.http.x-location == "https://example.com" txreq rxresp expect resp.http.x-status == 303 expect resp.http.x-location == "https://example.com" txreq rxresp expect resp.http.x-status == 307 expect resp.http.x-location == "https://example.com" txreq rxresp expect resp.http.x-status == 200 expect resp.http.x-location == "" } -run ================================================ FILE: test/expose_location_last.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that last location header is exposed for redirects." feature ignore_unknown_macro server s1 { rxreq txresp } -repeat 4 -start server s_auth_backend { rxreq txresp \ -status 301 \ -hdr "location: https://example.net" \ -hdr "location: https://example.com" accept rxreq txresp \ -status 301 \ -hdr "location: https://example.com" \ -hdr "location: https://example.net" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-response set-header x-status %[var(txn.auth_response_code)] http-response set-header x-location %[var(txn.auth_response_location)] if { var(txn.auth_response_location) -m found } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.http.x-status == 301 expect resp.http.x-location == "https://example.com" txreq rxresp expect resp.http.x-status == 301 expect resp.http.x-location == "https://example.net" } -run ================================================ FILE: test/expose_status_code.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that response status is exposed." feature ignore_unknown_macro server s1 { rxreq txresp } -repeat 4 -start server s_auth_backend { rxreq txresp \ -status 200 accept rxreq txresp \ -status 400 accept rxreq txresp \ -status 403 accept rxreq txresp \ -status 200 } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-response set-header x-status %[var(txn.auth_response_code)] server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.http.x-status == 200 txreq rxresp expect resp.http.x-status == 400 txreq rxresp expect resp.http.x-status == 403 txreq rxresp expect resp.http.x-status == 200 } -run ================================================ FILE: test/headers_complete.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that filtered request headers are passed to the auth-intercept backend." feature ignore_unknown_macro feature cmd "dpkg --compare-versions ${haproxy_version} ge 2.2" server s1 { rxreq expect req.http.x-user == "logan" expect req.http.x-app-data == "secret" expect req.http.token == "bearer value" txresp \ -status 201 \ -hdr "x-field: value" } -start server s_auth_backend { rxreq expect req.http.x-app-data == "" expect req.http.token == "bearer value" txresp \ -status 401 \ -hdr "x-reason: invalid pwd" accept rxreq expect req.http.x-app-data == "" expect req.http.token == "bearer value" txresp \ -status 200 \ -hdr "x-user: logan" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-intercept auth_backend / HEAD token x-user x-reason server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq \ -hdr "x-user: try-to-override" \ -hdr "x-app-data: secret" \ -hdr "token: bearer value" rxresp expect resp.status == 401 expect resp.http.x-reason == "invalid pwd" expect resp.http.x-field == "" txreq \ -hdr "x-user: try-to-override" \ -hdr "x-app-data: secret" \ -hdr "token: bearer value" rxresp expect resp.status == 201 expect resp.http.x-reason == "" expect resp.http.x-field == "value" } -run ================================================ FILE: test/headers_fail.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that filtered auth backend response headers are passed to the client." feature ignore_unknown_macro feature cmd "dpkg --compare-versions ${haproxy_version} ge 2.2" server s_auth_backend { rxreq txresp \ -status 401 \ -hdr "x-user: admin" \ -hdr "x-passwd: 123" \ -hdr "x-reason: invalid pwd" \ -hdr "token: asd" \ -hdr "other: value" \ -body "{\"msg\":\"invalid pwd\"}" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-intercept auth_backend / * * - x-user,x-reason backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 401 expect resp.http.x-user == "admin" expect resp.http.x-passwd == "" expect resp.http.x-reason == "invalid pwd" expect resp.http.token == "" expect resp.http.other == "" expect resp.body == "{\"msg\":\"invalid pwd\"}" } -run ================================================ FILE: test/headers_fail_multiple.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that multi-valued filtered auth backend response headers are passed to the client." feature ignore_unknown_macro feature cmd "dpkg --compare-versions ${haproxy_version} ge 2.2" server s_auth_backend { rxreq txresp \ -status 401 \ -hdr "x-user: admin" \ -hdr "x-passwd: 123" \ -hdr "x-reason: invalid pwd" \ -hdr "x-reason: account expired" \ -hdr "set-cookie: csrf=1234;" \ -hdr "set-cookie: session=;" \ -body "{\"msg\":\"invalid pwd\"}" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" # VTest only sees the first header with a given name, thus # we split the expected headers into separate headers that # can be checked independently. http-response set-header set-cookie1 %[res.fhdr(set-cookie,1)] http-response set-header set-cookie2 %[res.fhdr(set-cookie,2)] http-response set-header x-reason1 %[res.fhdr(x-reason,1)] http-response set-header x-reason2 %[res.fhdr(x-reason,2)] server be ${h1_fe2_addr}:${h1_fe2_port} listen fe2 mode http bind "fd@${fe2}" http-request lua.auth-intercept auth_backend / * * - x-user,x-reason,set-cookie backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 401 expect resp.http.x-user == "admin" expect resp.http.x-passwd == "" expect resp.http.x-reason !~ "," expect resp.http.x-reason1 == "invalid pwd" expect resp.http.x-reason2 == "account expired" expect resp.http.set-cookie !~ "," expect resp.http.set-cookie1 == "csrf=1234;" expect resp.http.set-cookie2 == "session=;" expect resp.body == "{\"msg\":\"invalid pwd\"}" } -run ================================================ FILE: test/headers_filter.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that headers are correctly filtered using a simplified glob pattern." feature ignore_unknown_macro server s1 { rxreq txresp -status 201 } -start server s_auth_backend { rxreq expect req.http.x-user == "logan" expect req.http.x-passwd == "top$secret" expect req.http.y-token == "" expect req.http.y-uid == "dead-...-beef" expect req.http.z-myapp == "" expect req.http.z-app == "another-data" txresp } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-intercept auth_backend / * x-*,y-???,*-app - - http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq \ -hdr "x-user: logan" \ -hdr "x-passwd: top$secret" \ -hdr "y-token: bearer value" \ -hdr "y-uid: dead-...-beef" \ -hdr "z-myapp: some-data" \ -hdr "z-app: another-data" rxresp expect resp.status == 201 } -run ================================================ FILE: test/headers_request.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that filtered request headers are passed to the auth-intercept backend." feature ignore_unknown_macro server s1 { rxreq txresp -status 201 } -start server s_auth_backend { rxreq expect req.http.x-user == "admin" expect req.http.x-passwd == "123" expect req.http.token == "asd" expect req.http.other == "" txresp } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-intercept auth_backend / * x-*,token - - http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq \ -hdr "x-user: admin" \ -hdr "x-passwd: 123" \ -hdr "token: asd" \ -hdr "other: value" rxresp expect resp.status == 201 } -run ================================================ FILE: test/headers_succeed.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that filtered auth backend response headers are passed to the protected backend server." feature ignore_unknown_macro server s1 { rxreq expect req.http.x-user == "admin" expect req.http.x-passwd == "" expect req.http.token == "asd" expect req.http.other == "" txresp -status 201 } -start server s_auth_backend { rxreq txresp \ -hdr "x-user: admin" \ -hdr "x-passwd: 123" \ -hdr "token: asd" \ -hdr "other: value" } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-intercept auth_backend / * * x-user,token - http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 201 } -run ================================================ FILE: test/multiple_requests.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that multiple clients work fine." feature ignore_unknown_macro server s1 { rxreq txresp } -repeat 150 -start server s_auth_backend_allow { rxreq txresp \ -status 204 } -repeat 100 -start server s_auth_backend_deny { rxreq txresp \ -status 403 } -repeat 50 -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend_allow /allow if { path /allow } http-request lua.auth-request auth_backend_deny /deny if { path /deny } http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend_allow mode http server auth_backend_allow ${s_auth_backend_allow_addr}:${s_auth_backend_allow_port} backend auth_backend mode http server auth_backend_deny ${s_auth_backend_deny_addr}:${s_auth_backend_deny_port} } -start client c1 -connect ${h1_fe1_sock} { txreq -url "/allow" rxresp expect resp.status == 200 } -repeat 75 -run client c2 -connect ${h1_fe1_sock} { txreq -url "/deny" rxresp expect resp.status == 403 } -repeat 50 -run client c3 -connect ${h1_fe1_sock} { txreq -url "/deny" rxresp expect resp.status == 403 } -repeat 50 -run client c1 -connect ${h1_fe1_sock} { txreq -url "/allow" rxresp expect resp.status == 200 } -repeat 25 -run ================================================ FILE: test/multiple_requests_mt.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that multiple clients work fine for multi-threaded Lua." feature ignore_unknown_macro feature cmd "dpkg --compare-versions ${haproxy_version} ge 2.4" server s1 { rxreq txresp } -repeat 150 -start server s_auth_backend_allow { rxreq txresp \ -status 204 } -repeat 100 -start server s_auth_backend_deny { rxreq txresp \ -status 403 } -repeat 50 -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load-per-thread ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend_allow /allow if { path /allow } http-request lua.auth-request auth_backend_deny /deny if { path /deny } http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend_allow mode http server auth_backend_allow ${s_auth_backend_allow_addr}:${s_auth_backend_allow_port} backend auth_backend mode http server auth_backend_deny ${s_auth_backend_deny_addr}:${s_auth_backend_deny_port} } -start client c1 -connect ${h1_fe1_sock} { txreq -url "/allow" rxresp expect resp.status == 200 } -repeat 75 -run client c2 -connect ${h1_fe1_sock} { txreq -url "/deny" rxresp expect resp.status == 403 } -repeat 50 -run client c3 -connect ${h1_fe1_sock} { txreq -url "/deny" rxresp expect resp.status == 403 } -repeat 50 -run client c1 -connect ${h1_fe1_sock} { txreq -url "/allow" rxresp expect resp.status == 200 } -repeat 25 -run ================================================ FILE: test/no_variable_leak.lua ================================================ -- SPDX-License-Identifier: MIT core.register_fetches("leak_check", function(txn, var) local result = txn:get_var(var) if result == nil then return "" end return result end) ================================================ FILE: test/no_variable_leak.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that we don't leak variables." feature ignore_unknown_macro feature cmd "dpkg --compare-versions ${haproxy_version} ge 2.2" feature cmd "dpkg --compare-versions ${haproxy_version} lt 2.5-dev6" server s1 { rxreq txresp } -start server s_auth_backend { rxreq txresp \ -status 301 \ -hdr "location: https://example.com" } -repeat 2 -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua lua-load ${testdir}/no_variable_leak.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-response set-header leak-check %[lua.leak_check(txn.auth_response_location)] server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.http.leak-check == "" } -run ================================================ FILE: test/pass_headers_to_backend.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that request headers are passed to the backend." feature ignore_unknown_macro server s1 { rxreq txresp } -start server s_auth_backend { rxreq expect req.http.x-foo == "bar" txresp \ -status 200 } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" http-request lua.auth-request auth_backend /allow http-request deny if ! { var(txn.auth_response_successful) -m bool } server s1 ${s1_addr}:${s1_port} backend auth_backend mode http server auth_backend ${s_auth_backend_addr}:${s_auth_backend_port} } -start client c1 -connect ${h1_fe1_sock} { txreq \ -hdr "x-foo: bar" rxresp expect resp.status == 200 } -run ================================================ FILE: test/starts.vtc ================================================ # SPDX-License-Identifier: MIT varnishtest "Verify that HAProxy starts with auth-request.lua loaded." feature ignore_unknown_macro server s1 { rxreq txresp } -start haproxy h1 -conf { global lua-prepend-path ${testdir}/../?/http.lua lua-load ${testdir}/../auth-request.lua listen fe1 mode http bind "fd@${fe1}" server s1 ${s1_addr}:${s1_port} } -start client c1 -connect ${h1_fe1_sock} { txreq rxresp expect resp.status == 200 } -run