[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>pull/984/head
parent
cc06ab6c18
commit
af5754bcab
|
@ -2,5 +2,6 @@ package server
|
||||||
|
|
||||||
import "aletheia.icu/broccoli/fs"
|
import "aletheia.icu/broccoli/fs"
|
||||||
|
|
||||||
// Mock the embedded filesystem for unit tests.
|
// Mock the embedded filesystem for unit tests. The bundle is built from an empty file and
|
||||||
var br = fs.New(false, []byte(""))
|
// 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"))
|
||||||
|
|
|
@ -16,6 +16,7 @@ services:
|
||||||
labels:
|
labels:
|
||||||
# Traefik 1.x
|
# Traefik 1.x
|
||||||
- 'traefik.frontend.rule=Host:login.example.com;PathPrefix:/api'
|
- 'traefik.frontend.rule=Host:login.example.com;PathPrefix:/api'
|
||||||
|
- 'traefik.protocol=https'
|
||||||
# Traefik 2.x
|
# Traefik 2.x
|
||||||
- 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`)'
|
- 'traefik.http.routers.authelia_backend.rule=Host(`login.example.com`) && PathPrefix(`/api`)'
|
||||||
- 'traefik.http.routers.authelia_backend.entrypoints=https'
|
- 'traefik.http.routers.authelia_backend.entrypoints=https'
|
||||||
|
|
|
@ -5,6 +5,5 @@ apk add --no-cache \
|
||||||
curl \
|
curl \
|
||||||
lua5.3-socket \
|
lua5.3-socket \
|
||||||
openssl && \
|
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 && \
|
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
|
cat haproxy.key haproxy.crt > /usr/local/etc/haproxy/haproxy.pem
|
|
@ -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)
|
|
@ -4,6 +4,7 @@ services:
|
||||||
build: ./example/compose/haproxy/
|
build: ./example/compose/haproxy/
|
||||||
volumes:
|
volumes:
|
||||||
- ./example/compose/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
- ./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:
|
networks:
|
||||||
authelianet:
|
authelianet:
|
||||||
# Set the IP to be able to query on port 8080
|
# Set the IP to be able to query on port 8080
|
||||||
|
|
|
@ -22,6 +22,7 @@ frontend fe_http
|
||||||
acl host-authelia-portal hdr(host) -i login.example.com:8080
|
acl host-authelia-portal hdr(host) -i login.example.com:8080
|
||||||
acl api-path path_beg -i /api
|
acl api-path path_beg -i /api
|
||||||
acl protected-frontends hdr(host) -m reg -i ^(admin|home|public|secure|singlefactor)\.example\.com
|
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(https) if { ssl_fc }
|
||||||
http-request set-var(req.scheme) str(http) 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).
|
# 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 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 be_authelia if host-authelia-portal api-path
|
||||||
use_backend fe_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_protected if protected-frontends
|
||||||
use_backend be_mail if { hdr(host) -i mail.example.com:8080 }
|
use_backend be_mail if { hdr(host) -i mail.example.com:8080 }
|
||||||
|
|
||||||
|
@ -63,3 +66,11 @@ backend be_mail
|
||||||
|
|
||||||
backend be_protected
|
backend be_protected
|
||||||
server nginx-backend nginx-backend:80
|
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
|
|
@ -4,3 +4,20 @@ services:
|
||||||
image: citizenstig/httpbin
|
image: citizenstig/httpbin
|
||||||
networks:
|
networks:
|
||||||
- authelianet
|
- 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'
|
||||||
|
|
|
@ -167,10 +167,10 @@ http {
|
||||||
auth_request /auth_verify;
|
auth_request /auth_verify;
|
||||||
|
|
||||||
auth_request_set $user $upstream_http_remote_user;
|
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;
|
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;
|
set $target_url $scheme://$http_host$request_uri;
|
||||||
error_page 401 =302 https://login.example.com:8080/?rd=$target_url;
|
error_page 401 =302 https://login.example.com:8080/?rd=$target_url;
|
||||||
|
|
|
@ -60,12 +60,17 @@ func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticat
|
||||||
|
|
||||||
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
|
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
|
||||||
s.Assert().NoError(err)
|
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 {
|
type Headers struct {
|
||||||
ForwardedGroups string `json:"Custom-Forwarded-Groups"`
|
ForwardedGroups string `json:"Remote-Groups"`
|
||||||
ForwardedUser string `json:"Custom-Forwarded-User"`
|
ForwardedUser string `json:"Remote-User"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadersPayload struct {
|
type HeadersPayload struct {
|
||||||
|
|
|
@ -16,6 +16,7 @@ func init() {
|
||||||
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
||||||
"internal/suites/example/compose/haproxy/docker-compose.yml",
|
"internal/suites/example/compose/haproxy/docker-compose.yml",
|
||||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||||
|
"internal/suites/example/compose/httpbin/docker-compose.yml",
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
|
|
|
@ -22,6 +22,10 @@ func (s *HAProxySuite) TestTwoFactorScenario() {
|
||||||
suite.Run(s.T(), NewTwoFactorScenario())
|
suite.Run(s.T(), NewTwoFactorScenario())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *HAProxySuite) TestCustomHeaders() {
|
||||||
|
suite.Run(s.T(), NewCustomHeadersScenario())
|
||||||
|
}
|
||||||
|
|
||||||
func TestHAProxySuite(t *testing.T) {
|
func TestHAProxySuite(t *testing.T) {
|
||||||
suite.Run(t, NewHAProxySuite())
|
suite.Run(t, NewHAProxySuite())
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ func init() {
|
||||||
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
||||||
"internal/suites/example/compose/traefik/docker-compose.yml",
|
"internal/suites/example/compose/traefik/docker-compose.yml",
|
||||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||||
|
"internal/suites/example/compose/httpbin/docker-compose.yml",
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
|
|
|
@ -16,6 +16,7 @@ func init() {
|
||||||
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
||||||
"internal/suites/example/compose/traefik2/docker-compose.yml",
|
"internal/suites/example/compose/traefik2/docker-compose.yml",
|
||||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||||
|
"internal/suites/example/compose/httpbin/docker-compose.yml",
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
|
|
|
@ -22,6 +22,10 @@ func (s *Traefik2Suite) TestTwoFactorScenario() {
|
||||||
suite.Run(s.T(), NewTwoFactorScenario())
|
suite.Run(s.T(), NewTwoFactorScenario())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Traefik2Suite) TestCustomHeaders() {
|
||||||
|
suite.Run(s.T(), NewCustomHeadersScenario())
|
||||||
|
}
|
||||||
|
|
||||||
func TestTraefik2Suite(t *testing.T) {
|
func TestTraefik2Suite(t *testing.T) {
|
||||||
suite.Run(t, NewTraefik2Suite())
|
suite.Run(t, NewTraefik2Suite())
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ func (s *TraefikSuite) TestRedirectionURLScenario() {
|
||||||
suite.Run(s.T(), NewRedirectionURLScenario())
|
suite.Run(s.T(), NewRedirectionURLScenario())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TraefikSuite) TestCustomHeaders() {
|
||||||
|
suite.Run(s.T(), NewCustomHeadersScenario())
|
||||||
|
}
|
||||||
|
|
||||||
func TestTraefikSuite(t *testing.T) {
|
func TestTraefikSuite(t *testing.T) {
|
||||||
suite.Run(t, NewTraefikSuite())
|
suite.Run(t, NewTraefikSuite())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue