Skip to content

Commit 152fe33

Browse files
Merge pull request #202 from bunkerity/authentik-pr200-fixes
fix(authentik): harden identity-header handling, docs, and tests
2 parents 8e5a54c + b1e45fc commit 152fe33

9 files changed

Lines changed: 386 additions & 51 deletions

File tree

.github/workflows/tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ jobs:
4646
env:
4747
VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}
4848

49+
- name: Run Authentik tests
50+
run: ./.tests/authentik.sh
51+
4952
- name: Build and push APIs
5053
if: env.BW_TAG == '1.6.1'
5154
run: ./.tests/build-push.sh "${{ env.BW_TAG }}"

.tests/authentik.sh

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/bin/bash
2+
3+
# shellcheck disable=SC1091
4+
. .tests/utils.sh
5+
6+
echo "ℹ️ Starting Authentik tests ..."
7+
8+
# Create working directory
9+
if [ -d /tmp/bunkerweb-plugins ] ; then
10+
do_and_check_cmd sudo rm -rf /tmp/bunkerweb-plugins
11+
fi
12+
do_and_check_cmd mkdir -p /tmp/bunkerweb-plugins/authentik/bw-data/plugins
13+
do_and_check_cmd cp -r ./authentik /tmp/bunkerweb-plugins/authentik/bw-data/plugins
14+
do_and_check_cmd sudo chown -R 101:101 /tmp/bunkerweb-plugins/authentik/bw-data
15+
16+
# Copy compose + mock outpost config
17+
do_and_check_cmd cp .tests/authentik/docker-compose.yml /tmp/bunkerweb-plugins/authentik
18+
do_and_check_cmd cp .tests/authentik/mock-outpost.conf /tmp/bunkerweb-plugins/authentik
19+
20+
# Edit compose to use the locally built :tests images
21+
do_and_check_cmd sed -i "s@bunkerity/bunkerweb:.*\$@bunkerweb:tests@g" /tmp/bunkerweb-plugins/authentik/docker-compose.yml
22+
do_and_check_cmd sed -i "s@bunkerity/bunkerweb-scheduler:.*\$@bunkerweb-scheduler:tests@g" /tmp/bunkerweb-plugins/authentik/docker-compose.yml
23+
24+
# Do the tests
25+
cd /tmp/bunkerweb-plugins/authentik || exit 1
26+
echo "ℹ️ Running compose ..."
27+
do_and_check_cmd docker compose up --build -d
28+
29+
# Wait until the plugin is LIVE: while BunkerWeb is still applying config it serves a
30+
# 200 "Generating..." page and the plugin is inactive. An unauthenticated request only
31+
# becomes a 302 (gated) once config is applied -> use that as the readiness signal.
32+
echo "ℹ️ Waiting for BW (plugin live) ..."
33+
success="ko"
34+
retry=0
35+
while [ $retry -lt 120 ] ; do
36+
code="$(curl -s -o /dev/null -w "%{http_code}" -H "Host: app.example.com" http://localhost 2>/dev/null)"
37+
if [ "$code" = "302" ] ; then
38+
success="ok"
39+
break
40+
fi
41+
retry=$((retry + 1))
42+
sleep 2
43+
done
44+
if [ "$success" = "ko" ] ; then
45+
docker compose logs
46+
docker compose down -v
47+
echo "❌ Error: BunkerWeb / authentik plugin never became active"
48+
exit 1
49+
fi
50+
51+
fail=0
52+
53+
# T1: unauthenticated -> 302 to the outpost sign-in
54+
echo "ℹ️ T1: unauthenticated request is redirected to the outpost ..."
55+
loc="$(curl -s -D - -o /dev/null -H "Host: app.example.com" http://localhost | tr -d '\r' | awk -F': ' 'tolower($1)=="location"{print $2}')"
56+
if echo "$loc" | grep -q "/outpost.goauthentik.io/start?rd=" ; then
57+
echo "✔️ T1 ok ($loc)"
58+
else
59+
echo "❌ T1 failed (Location: $loc)" ; fail=1
60+
fi
61+
62+
# T2: authenticated -> 200 and identity header forwarded upstream (PASS=yes)
63+
echo "ℹ️ T2: authenticated request reaches upstream with identity header ..."
64+
body="$(curl -s -H "Host: app.example.com" -b "mock_session=valid" http://localhost)"
65+
if echo "$body" | grep -qi '"x-authentik-username": *"alice"' ; then
66+
echo "✔️ T2 ok"
67+
else
68+
echo "❌ T2 failed" ; fail=1
69+
fi
70+
71+
# T3: spoofed X-authentik-* are stripped (security); only Authentik's values survive
72+
echo "ℹ️ T3: spoofed identity headers are stripped ..."
73+
body="$(curl -s -H "Host: app.example.com" -b "mock_session=valid" -H "X-authentik-username: hacker" -H "X-authentik-uid: 0" http://localhost)"
74+
if echo "$body" | grep -qi '"x-authentik-username": *"alice"' \
75+
&& ! echo "$body" | grep -qi '"x-authentik-username": *"hacker"' \
76+
&& ! echo "$body" | grep -qi '"x-authentik-uid"' ; then
77+
echo "✔️ T3 ok (spoof stripped)"
78+
else
79+
echo "❌ T3 failed (spoof not stripped)" ; fail=1
80+
fi
81+
82+
# T4: PASS=no site strips spoofed identity headers and forwards none
83+
echo "ℹ️ T4: PASS=no site forwards no identity header ..."
84+
body="$(curl -s -H "Host: noheaders.example.com" -b "mock_session=valid" -H "X-authentik-username: hacker" http://localhost)"
85+
if ! echo "$body" | grep -qi "x-authentik-username" ; then
86+
echo "✔️ T4 ok"
87+
else
88+
echo "❌ T4 failed (identity header reached upstream)" ; fail=1
89+
fi
90+
91+
# T5: outpost path is proxied (not gated)
92+
echo "ℹ️ T5: outpost path is proxied to the outpost ..."
93+
body="$(curl -s -H "Host: app.example.com" http://localhost/outpost.goauthentik.io/start)"
94+
if echo "$body" | grep -q "MOCK AUTHENTIK OUTPOST" ; then
95+
echo "✔️ T5 ok"
96+
else
97+
echo "❌ T5 failed" ; fail=1
98+
fi
99+
100+
# T6: trailing-slash AUTHENTIK_URL still proxies the outpost (rstrip fix)
101+
echo "ℹ️ T6: trailing-slash AUTHENTIK_URL still proxies ..."
102+
body="$(curl -s -H "Host: noheaders.example.com" http://localhost/outpost.goauthentik.io/start)"
103+
if echo "$body" | grep -q "MOCK AUTHENTIK OUTPOST" ; then
104+
echo "✔️ T6 ok"
105+
else
106+
echo "❌ T6 failed (trailing-slash URL broke the outpost proxy)" ; fail=1
107+
fi
108+
109+
if [ "$fail" -ne 0 ] ; then
110+
docker compose logs
111+
docker compose down -v
112+
echo "❌ Authentik tests failed"
113+
exit 1
114+
fi
115+
116+
if [ "$1" = "verbose" ] ; then
117+
docker compose logs
118+
fi
119+
120+
docker compose down -v
121+
echo "✔️ Authentik tests succeeded"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
version: "3"
2+
3+
services:
4+
bunkerweb:
5+
image: bunkerity/bunkerweb:1.6.0
6+
ports:
7+
- 80:8080/tcp
8+
- 443:8443/tcp
9+
- 443:8443/udp
10+
environment:
11+
- API_WHITELIST_IP=127.0.0.0/8 10.20.30.0/24
12+
networks:
13+
- bw-universe
14+
- bw-services
15+
16+
bw-scheduler:
17+
image: bunkerity/bunkerweb-scheduler:1.6.0
18+
depends_on:
19+
- bunkerweb
20+
volumes:
21+
- ./bw-data/plugins:/data/plugins
22+
environment:
23+
- BUNKERWEB_INSTANCES=bunkerweb
24+
- MULTISITE=yes
25+
- SERVER_NAME=app.example.com noheaders.example.com
26+
- API_WHITELIST_IP=127.0.0.0/8 10.20.30.0/24
27+
- LOG_LEVEL=info
28+
- USE_BAD_BEHAVIOR=no
29+
- USE_LIMIT_REQ=no
30+
- USE_BUNKERNET=no
31+
- USE_BLACKLIST=no
32+
- USE_GREYLIST=no
33+
- USE_WHITELIST=no
34+
- USE_MODSECURITY=no
35+
- USE_ANTIBOT=no
36+
- SERVE_FILES=no
37+
- DISABLE_DEFAULT_SERVER=yes
38+
- AUTO_LETS_ENCRYPT=no
39+
- USE_LETS_ENCRYPT=no
40+
- REDIRECT_HTTP_TO_HTTPS=no
41+
- AUTO_REDIRECT_HTTP_TO_HTTPS=no
42+
# app.example.com : identity forwarding ON
43+
- app.example.com_USE_REVERSE_PROXY=yes
44+
- app.example.com_REVERSE_PROXY_HOST=http://echo:8080
45+
- app.example.com_REVERSE_PROXY_URL=/
46+
- app.example.com_USE_AUTHENTIK=yes
47+
- app.example.com_AUTHENTIK_URL=http://mock-outpost:9000
48+
- app.example.com_AUTHENTIK_SSL_VERIFY=no
49+
- app.example.com_AUTHENTIK_PASS_IDENTITY_HEADERS=yes
50+
# noheaders.example.com : forwarding OFF + TRAILING SLASH url (rstrip test)
51+
- noheaders.example.com_USE_REVERSE_PROXY=yes
52+
- noheaders.example.com_REVERSE_PROXY_HOST=http://echo:8080
53+
- noheaders.example.com_REVERSE_PROXY_URL=/
54+
- noheaders.example.com_USE_AUTHENTIK=yes
55+
- noheaders.example.com_AUTHENTIK_URL=http://mock-outpost:9000/
56+
- noheaders.example.com_AUTHENTIK_SSL_VERIFY=no
57+
- noheaders.example.com_AUTHENTIK_PASS_IDENTITY_HEADERS=no
58+
networks:
59+
- bw-universe
60+
61+
# Mock authentik outpost: 200 + X-authentik-* when cookie mock_session=valid, else 401.
62+
mock-outpost:
63+
image: nginx:alpine
64+
volumes:
65+
- ./mock-outpost.conf:/etc/nginx/conf.d/default.conf:ro
66+
networks:
67+
- bw-services
68+
69+
# Echo upstream: reflects request headers as JSON so assertions can inspect them.
70+
echo:
71+
image: mendhak/http-https-echo:31
72+
environment:
73+
- HTTP_PORT=8080
74+
networks:
75+
- bw-services
76+
77+
networks:
78+
bw-universe:
79+
name: bw-universe
80+
ipam:
81+
driver: default
82+
config:
83+
- subnet: 10.20.30.0/24
84+
bw-services:
85+
name: bw-services

.tests/authentik/mock-outpost.conf

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
map $http_cookie $mock_authed {
2+
default 0;
3+
"~*mock_session=valid" 1;
4+
}
5+
6+
server {
7+
listen 9000;
8+
server_name _;
9+
10+
# Authentik forward-auth decision endpoint:
11+
# 200 (+ identity headers) when the session cookie is present, else 401.
12+
location = /outpost.goauthentik.io/auth/nginx {
13+
default_type text/plain;
14+
add_header X-authentik-username "alice";
15+
add_header X-authentik-email "alice@example.com";
16+
add_header X-authentik-groups "admins";
17+
if ($mock_authed = 0) {
18+
return 401 "unauthorized";
19+
}
20+
return 200 "ok";
21+
}
22+
23+
# Browser-facing outpost endpoints (start, callback, sign_out, ...).
24+
location /outpost.goauthentik.io {
25+
default_type text/plain;
26+
return 200 "MOCK AUTHENTIK OUTPOST PATH";
27+
}
28+
29+
location / {
30+
default_type text/plain;
31+
return 404 "not found";
32+
}
33+
}

0 commit comments

Comments
 (0)