caddy+authentik tests: uniform Host-preserving :8016 proxy through embedded outpost
The redirect-to-flow design with an absolute cross-host next= was rejected by
the flow's open-redirect guard at completion ("Request has been denied:
Invalid next URL"), and meta-header routing does not exist in this build
(providers route on the ORIGINAL Host). :8016 now proxies every request whole
to authentik-server:9000 preserving Host; the embedded outpost serves login
in place and forwards authenticated traffic back to :8017. Requires one proxy
provider per guarded host (provisioned live: search/hermes/prometheus/romm/
files + www, flows backfilled). Tests flipped from the meta-header claim to
the upheld contract: Host preserved, no absolute next= redirects.
This commit is contained in:
+14
-44
@@ -234,53 +234,23 @@ prowlarr.franksdank.cloud {
|
||||
qbittorrent.franksdank.cloud {
|
||||
reverse_proxy 192.168.0.235:8080
|
||||
}
|
||||
# --- Internal listener: auth decision layer -------------------------------
|
||||
# Anubis dials http://caddy:8016 after verifying a client (TARGET in
|
||||
# dankclaw/anubis/quadlet/anubis.container). Host headers survive the hop, so
|
||||
# per-vhost routing resumes here: guarded vhosts proxy the WHOLE request
|
||||
# through Authentik (the meta headers tell Authentik which outpost, provider
|
||||
# and app are answering); unauthenticated clients get the login flow served
|
||||
# in place, authenticated traffic is forwarded back to http://:8017 and
|
||||
# routed straight to the app. Exempt vhosts never enter this block.
|
||||
# --- Internal listener: auth decision layer ---------------------------------
|
||||
# Anubis dials http://caddy:8016 after verifying a client (TARGET in
|
||||
# dankclaw/anubis/quadlet/anubis.container). Host headers survive the hop, so
|
||||
# per-vhost routing resumes here. This 2026.x build does not mount the outpost
|
||||
# URL space for proxy-provider meta-header routing (probed live: /ping answers
|
||||
# 204, every app path with meta headers 404s), so guarded vhosts route
|
||||
# whole-request to Authentik's flow endpoint instead: unauthenticated clients
|
||||
# get the login flow served in place; authenticated sessions complete instantly
|
||||
# and bounce back via next= straight to the app at http://:8017. Exempt vhosts
|
||||
# never enter this block.
|
||||
# dankclaw/anubis/quadlet/anubis.container). Host headers survive the hop.
|
||||
# Every request here is proxied whole-request to Authentik with Host
|
||||
# preserved. This build's embedded outpost routes ONLY on the ORIGINAL Host
|
||||
# against each proxy provider's external_host — meta headers are ignored and
|
||||
# cross-host absolute next= redirects are rejected by the flow's open-redirect
|
||||
# guard ("Request has been denied: Invalid next URL"), verified live.
|
||||
# Unauthenticated clients get the login flow served in place; authenticated
|
||||
# sessions are forwarded by the provider's internal_host back to
|
||||
# http://:8017 and routed straight to the app. Every host that may enter this
|
||||
# listener MUST have a proxy provider registered with the embedded outpost —
|
||||
# the Anubis guard set, the Caddy TLS edge, and the provider list stay in
|
||||
# lockstep. Exempt vhosts never enter this block.
|
||||
http://:8016 {
|
||||
@home host www.franksdank.cloud
|
||||
handle @home {
|
||||
redir https://auth.franksdank.cloud/if/flow/default-authentication-flow/?next=https://www.franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
@search host search.franksdank.cloud
|
||||
handle @search {
|
||||
redir https://auth.franksdank.cloud/if/flow/default-authentication-flow/?next=https://search.franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
@hermes host hermes.franksdank.cloud
|
||||
handle @hermes {
|
||||
redir https://auth.franksdank.cloud/if/flow/default-authentication-flow/?next=https://hermes.franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
@prometheus host prometheus.franksdank.cloud
|
||||
handle @prometheus {
|
||||
redir https://auth.franksdank.cloud/if/flow/default-authentication-flow/?next=https://prometheus.franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
@romm host romm.franksdank.cloud
|
||||
handle @romm {
|
||||
redir https://auth.franksdank.cloud/if/flow/default-authentication-flow/?next=https://romm.franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
@files host files.franksdank.cloud
|
||||
handle @files {
|
||||
redir https://auth.franksdank.cloud/if/flow/default-authentication-flow/?next=https://files.franksdank.cloud/ 302
|
||||
reverse_proxy authentik-server:9000 {
|
||||
header_up X-Forwarded-Proto https
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-11
@@ -2,14 +2,17 @@
|
||||
# Seam: Authentik (SSO / OIDC provider) — the homelab's auth layer.
|
||||
#
|
||||
# Authentik is a 3-service stack (PostgreSQL + server + worker) on dankclaw.
|
||||
# It provides OIDC, SAML, and Caddy forward_auth for the vhosts that do not
|
||||
# have their own application-level authentication (SearXNG, Hermes,
|
||||
# Prometheus, RomM, Copyparty, the homepage).
|
||||
# It protects the vhosts that have no application-level authentication
|
||||
# (SearXNG, Hermes, Prometheus, RomM, Copyparty, the homepage) with its
|
||||
# embedded proxy outpost.
|
||||
#
|
||||
# The Caddyfile carries an `auth.franksdank.cloud` vhost that proxies to the
|
||||
# Authentik server. Vhosts without their own auth are wrapped in a `route`
|
||||
# block that (1) proxies the outpost path to Authentik, (2) runs forward_auth,
|
||||
# (3) serves the app.
|
||||
# The Caddyfile carries an `auth.franksdank.cloud` vhost for the admin UI, and
|
||||
# an internal `:8016` listener that Anubis dials after verifying a client.
|
||||
# :8016 proxies every request whole to the Authentik server with the Host
|
||||
# header preserved; the embedded outpost routes on the ORIGINAL Host against
|
||||
# each proxy provider's external_host (meta headers do NOT route it —
|
||||
# disproved live, 2026-08-29) and forwards authenticated traffic back to the
|
||||
# post-auth listener `:8017`.
|
||||
#
|
||||
# See ../docs/Infrastructure/Authentik.md.
|
||||
|
||||
@@ -101,10 +104,12 @@ UNIT_DIR="$REPO_ROOT/dankclaw/authentik/quadlet"
|
||||
awk '/^http:\/\/:8016/,/^\}/' "$CADDYFILE" | grep -q 'reverse_proxy authentik-server:9000'
|
||||
}
|
||||
|
||||
@test "the auth decision carries the outpost meta headers" {
|
||||
grep -q 'X-authentik-meta-outpost' "$CADDYFILE"
|
||||
grep -q 'X-authentik-meta-provider' "$CADDYFILE"
|
||||
grep -q 'X-authentik-meta-app' "$CADDYFILE"
|
||||
@test "the internal listener preserves Host and never emits cross-host next=" {
|
||||
# The outpost routes on the original Host: no header_up Host override...
|
||||
! grep -q 'header_up Host' "$CADDYFILE"
|
||||
# ...and the rejected design (absolute cross-host next= into the flow UI)
|
||||
# must never return — it produces "Invalid next URL" at flow completion.
|
||||
! grep -qE 'if/flow/[^ ]*next=https' "$CADDYFILE"
|
||||
}
|
||||
|
||||
@test "authenticated traffic returns to Caddy on the post-auth listener" {
|
||||
|
||||
Reference in New Issue
Block a user