[MISC] Add coverage for Remote-User and Remote-Groups (#982)

* Fix dev workflow.

* Fix dev workflow.

* Cover Remote-User and Remote-Groups using Traefik.

* Cover Remote-User and Remote-Groups using HAProxy.

* Fix redirection after unauthorized response when using HAProxy.

Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
This commit is contained in:
Clément Michaud 2020-05-06 03:50:37 +02:00 committed by GitHub
parent cc06ab6c18
commit af5754bcab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 162 additions and 9 deletions

View File

@ -2,5 +2,6 @@ package server
import "aletheia.icu/broccoli/fs"
// Mock the embedded filesystem for unit tests.
var br = fs.New(false, []byte(""))
// Mock the embedded filesystem for unit tests. The bundle is built from an empty file and
// allows to run the dev workflow without failure.
var br = fs.New(false, []byte("\x1b~\x00\x80\x8d\x94n\xc2|\x84J\xf7\xbfn\xfd\xf7w;.\x8d m\xb2&\xd1Z\xec\xb2\x05\xb9\xc00\x8a\xf7(\x80^78\t(\f\f\xc3p\xc2\xc1\x06[a\xa2\xb3\xa4P\xe5\xa14\xfb\x19\xb2cp\xf6\x90-Z\xb2\x11\xe0l\xa1\x80\\\x95Vh\t\xc5\x06\x16\xfa\x8c\xc0\"!\xa5\xcf\xf7$\x9a\xb2\a`\xc6\x18\xc8~\xce8\r\x16Z\x9d\xc3\xe3\xff\x00"))

View File

@ -16,6 +16,7 @@ services:
labels:
# Traefik 1.x
- 'traefik.frontend.rule=Host:login.example.com;PathPrefix:/api'
- 'traefik.protocol=https'
# Traefik 2.x
- 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`)'
- 'traefik.http.routers.authelia_backend.entrypoints=https'

View File

@ -5,6 +5,5 @@ apk add --no-cache \
curl \
lua5.3-socket \
openssl && \
curl -Lfs -o /usr/local/etc/haproxy/auth-request.lua "https://raw.githubusercontent.com/TimWolla/haproxy-auth-request/master/auth-request.lua" && \
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=AU/ST=Victoria/L=Melbourne/O=Authelia/CN=*.example.com" -keyout haproxy.key -out haproxy.crt && \
cat haproxy.key haproxy.crt > /usr/local/etc/haproxy/haproxy.pem

View File

@ -0,0 +1,103 @@
-- 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.
local http = require("socket.http")
core.register_action("auth-request", { "http-req" }, function(txn, be, path)
txn:set_var("txn.auth_response_successful", false)
-- Check whether the given backend exists.
if core.backends[be] == nil then
txn:Alert("Unknown auth-request backend '" .. be .. "'")
txn:set_var("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 .. "'")
txn:set_var("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 ~= 'content-length' 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.
local b, c, h = http.request {
url = "http://" .. addr .. path,
headers = headers,
create = core.tcp,
-- Disable redirects, because DNS does not work here.
redirect = false,
-- We do not check body, so HEAD
method = "HEAD",
}
-- Check whether we received a valid HTTP response.
if b == nil then
txn:Warning("Failure in auth-request backend '" .. be .. "': " .. c)
txn:set_var("txn.auth_response_code", 500)
return
end
txn:set_var("txn.auth_response_code", c)
-- 2xx: Allow request.
if 200 <= c and c < 300 then
if h["remote-user"] then
txn:set_var("txn.auth_user", h["remote-user"])
end
if h["remote-groups"] then
txn:set_var("txn.auth_groups", h["remote-groups"])
end
txn:set_var("txn.auth_response_successful", true)
-- Don't allow other codes.
-- Codes with Location: Passthrough location at redirect.
elseif c == 301 or c == 302 or c == 303 or c == 307 or c == 308 then
txn:set_var("txn.auth_response_location", h["location"])
-- 401 / 403: Do nothing, everything else: log.
elseif c ~= 401 and c ~= 403 then
txn:Warning("Invalid status code in auth-request backend '" .. be .. "': " .. c)
end
end, 2)

View File

@ -4,6 +4,7 @@ services:
build: ./example/compose/haproxy/
volumes:
- ./example/compose/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- ./example/compose/haproxy/auth-request.lua:/usr/local/etc/haproxy/auth-request.lua
networks:
authelianet:
# Set the IP to be able to query on port 8080

View File

@ -22,6 +22,7 @@ frontend fe_http
acl host-authelia-portal hdr(host) -i login.example.com:8080
acl api-path path_beg -i /api
acl protected-frontends hdr(host) -m reg -i ^(admin|home|public|secure|singlefactor)\.example\.com
acl is_headers path -i -m end /headers
http-request set-var(req.scheme) str(https) if { ssl_fc }
http-request set-var(req.scheme) str(http) if !{ ssl_fc }
@ -37,9 +38,11 @@ frontend fe_http
# does not know how to handle it (see https://github.com/TimWolla/haproxy-auth-request/issues/12).
http-request lua.auth-request be_auth_request /api/verify if protected-frontends
http-request redirect location https://login.example.com:8080 if protected-frontends !{ var(txn.auth_response_successful) -m bool }
use_backend be_authelia if host-authelia-portal api-path
use_backend fe_authelia if host-authelia-portal !api-path
use_backend be_authelia if protected-frontends !{ var(txn.auth_response_successful) -m bool }
use_backend be_httpbin if protected-frontends is_headers
use_backend be_protected if protected-frontends
use_backend be_mail if { hdr(host) -i mail.example.com:8080 }
@ -63,3 +66,11 @@ backend be_mail
backend be_protected
server nginx-backend nginx-backend:80
backend be_httpbin
acl remote_user_exist var(txn.auth_user) -m found
acl remote_groups_exist var(txn.auth_groups) -m found
http-request set-header Remote-User %[var(txn.auth_user)] if remote_user_exist
http-request set-header Remote-Groups %[var(txn.auth_groups)] if remote_groups_exist
server httpbin-backend httpbin:8000

View File

@ -4,3 +4,20 @@ services:
image: citizenstig/httpbin
networks:
- authelianet
labels:
# Traefik 1.x
- 'traefik.frontend.rule=Host:public.example.com;Path:/headers'
- 'traefik.frontend.priority=120'
- 'traefik.frontend.auth.forward.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/'
- 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true'
- 'traefik.frontend.auth.forward.trustForwardHeader=true'
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups'
# Traefik 2.x
- 'traefik.http.routers.httpbin.rule=Host(`public.example.com`) && Path(`/headers`)'
- 'traefik.http.routers.httpbin.priority=150'
- 'traefik.http.routers.httpbin.tls=true'
- 'traefik.http.routers.httpbin.middlewares=authelia'
- 'traefik.http.middlewares.authelia.forwardauth.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/'
- 'traefik.http.middlewares.authelia.forwardauth.tls.insecureSkipVerify=true'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups'

View File

@ -167,10 +167,10 @@ http {
auth_request /auth_verify;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header Custom-Forwarded-User $user;
proxy_set_header Remote-User $user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Custom-Forwarded-Groups $groups;
proxy_set_header Remote-Groups $groups;
set $target_url $scheme://$http_host$request_uri;
error_page 401 =302 https://login.example.com:8080/?rd=$target_url;

View File

@ -60,12 +60,17 @@ func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticat
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
s.Assert().NoError(err)
s.WaitElementTextContains(ctx, s.T(), body, "httpbin:8000")
s.WaitElementTextContains(ctx, s.T(), body, "\"Host\"")
b, err := body.Text()
s.Assert().NoError(err)
s.Assert().NotContains(b, "john")
s.Assert().NotContains(b, "admins")
}
type Headers struct {
ForwardedGroups string `json:"Custom-Forwarded-Groups"`
ForwardedUser string `json:"Custom-Forwarded-User"`
ForwardedGroups string `json:"Remote-Groups"`
ForwardedUser string `json:"Remote-User"`
}
type HeadersPayload struct {

View File

@ -16,6 +16,7 @@ func init() {
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
"internal/suites/example/compose/haproxy/docker-compose.yml",
"internal/suites/example/compose/smtp/docker-compose.yml",
"internal/suites/example/compose/httpbin/docker-compose.yml",
})
setup := func(suitePath string) error {

View File

@ -22,6 +22,10 @@ func (s *HAProxySuite) TestTwoFactorScenario() {
suite.Run(s.T(), NewTwoFactorScenario())
}
func (s *HAProxySuite) TestCustomHeaders() {
suite.Run(s.T(), NewCustomHeadersScenario())
}
func TestHAProxySuite(t *testing.T) {
suite.Run(t, NewHAProxySuite())
}

View File

@ -16,6 +16,7 @@ func init() {
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
"internal/suites/example/compose/traefik/docker-compose.yml",
"internal/suites/example/compose/smtp/docker-compose.yml",
"internal/suites/example/compose/httpbin/docker-compose.yml",
})
setup := func(suitePath string) error {

View File

@ -16,6 +16,7 @@ func init() {
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
"internal/suites/example/compose/traefik2/docker-compose.yml",
"internal/suites/example/compose/smtp/docker-compose.yml",
"internal/suites/example/compose/httpbin/docker-compose.yml",
})
setup := func(suitePath string) error {

View File

@ -22,6 +22,10 @@ func (s *Traefik2Suite) TestTwoFactorScenario() {
suite.Run(s.T(), NewTwoFactorScenario())
}
func (s *Traefik2Suite) TestCustomHeaders() {
suite.Run(s.T(), NewCustomHeadersScenario())
}
func TestTraefik2Suite(t *testing.T) {
suite.Run(t, NewTraefik2Suite())
}

View File

@ -26,6 +26,10 @@ func (s *TraefikSuite) TestRedirectionURLScenario() {
suite.Run(s.T(), NewRedirectionURLScenario())
}
func (s *TraefikSuite) TestCustomHeaders() {
suite.Run(s.T(), NewCustomHeadersScenario())
}
func TestTraefikSuite(t *testing.T) {
suite.Run(t, NewTraefikSuite())
}