Compare commits

..
Author SHA1 Message Date
claude-bot aac2599e5f docs(authentik): trim the blueprint comments, keep every ordering rule
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
559 -> 509 comment lines. A far smaller cut than the other files, and that is
the honest result: most of this file is per-entry rationale for a declarative
blueprint, where the comment is the only place the constraint is written down.

Everything load-bearing stays, and stays emphasised. Every ORDER MATTERS note
survives — Application before Provider, Application before Provider before
Outpost — because Application.provider is on_delete=PROTECT and getting the
order wrong leaves the blueprint failing to apply with no obvious cause. So do
both "git owns membership, add and remove people HERE not in the UI" notes,
since blueprint apply silently reverts UI edits to those groups. So does the
reason each gate exists at all: code-server runs --auth=none as root over
plaintext HA secrets, and shelly-manager has no authentication whatsoever.

Cut: the derivations. Why the dual-OAuth2 setup was collapsed into one
provider, the full W3C-spec account of credentials:'omit', a paragraph
reconstructing how the music-assistant gate failed. The instruction that came
out of that last one is kept and sharpened into "do not reintroduce a gate in
front of a PWA".

Two stale things went with it. The paperless proxy comment said the ingress
controller "only routes paperless.renner.uno to the outpost service" and named
ingress-nginx as that controller — there is no ingress-nginx left, Traefik is
the only IngressClass. And a leftover instruction to "delete via the Authentik
UI once everything's confirmed working" from a session long finished.

Verified beyond the usual: these comments live INSIDE the embedded blueprint
string, so a broken indent would render fine and only fail when Authentik
parses it. Extracted the rendered ConfigMap's k8s-outposts.yaml and parsed it
separately — valid, and still 74 entries.
2026-08-28 07:17:25 +02:00
k.renner 45ca838f22 Merge pull request 'docs(apps): trim the home-assistant manifest comments' (#687) from trim-comments-apps into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#687
2026-08-28 05:13:57 +00:00
@@ -25,20 +25,13 @@ data:
attrs:
local: true
# Retired OIDC layer. Paperless's URL-level access is gated by the
# paperless-gate ProxyProvider (below); user identity is forwarded
# from the gate to paperless via the X-authentik-* request headers
# and consumed by paperless via PAPERLESS_ENABLE_HTTP_REMOTE_USER
# (see kubernetes/apps/paperless/paperless.yaml). One Authentik
# Application, one Provider — the dual-OAuth2-flow setup with a
# separate paperless OIDC client + django-allauth callback was
# structurally fine but added a second consent screen on first
# login for no real defense-in-depth gain.
# Retired OIDC layer. Access is gated by the paperless-gate
# ProxyProvider below, which forwards identity as X-authentik-* headers
# consumed via PAPERLESS_ENABLE_HTTP_REMOTE_USER.
#
# Order matters: delete the Application BEFORE the OAuth2Provider
# because Application.provider is on_delete=PROTECT, so the
# OAuth2Provider can't be removed while the Application still
# references it.
# ORDER MATTERS: delete the Application BEFORE the OAuth2Provider.
# Application.provider is on_delete=PROTECT, so the provider cannot be
# removed while an Application still references it.
- model: authentik_core.application
identifiers:
slug: paperless
@@ -49,22 +42,15 @@ data:
name: Paperless
state: absent
# Also clean up the very-early orphan `paperless` ProxyProvider
# left behind from this session's first attempts. It's not bound
# to anything any more, just leftover state in the DB.
# Orphan ProxyProvider, unbound — leftover DB state.
- model: authentik_providers_proxy.proxyprovider
identifiers:
name: paperless
state: absent
# Dedicated forward-auth gate for paperless. This is a SEPARATE
# outpost (its own Deployment + Service `ak-outpost-paperless-
# gate`) running in front of paperless so every request is
# authenticated and authorized by Authentik before it reaches
# paperless's own attack surface. The gate sits on top of, and
# separate from, the OIDC OAuth2Provider above (which is what
# paperless's django-allauth uses for SSO once a request makes
# it through the gate).
# Dedicated forward-auth gate for paperless: its own outpost
# Deployment + Service, so every request is authenticated before it
# reaches paperless.
- id: paperless-gate-provider
model: authentik_providers_proxy.proxyprovider
identifiers:
@@ -73,16 +59,13 @@ data:
attrs:
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
# Proxy (not forward_domain) so the outpost itself terminates
# the request and forwards to paperless — moving the trust
# boundary off ingress-nginx and onto paperless-gate. ingress-
# nginx now only routes paperless.renner.uno to the outpost
# service; it never talks to paperless directly. The outpost
# is what populates X-Authentik-Username on the upstream
# request, and the NetworkPolicy in the paperless namespace
# only allows traffic to paperless from the gate outpost pod
# itself, so ingress-nginx (or any other in-cluster workload)
# cannot bypass the gate to inject headers.
# Proxy, NOT forward_domain: the outpost terminates the request
# itself, so the trust boundary sits here rather than at the ingress
# controller, which only routes to the outpost Service and never
# reaches paperless directly. The outpost populates
# X-Authentik-Username, and the paperless NetworkPolicy admits
# traffic only from this outpost — so nothing else in the cluster
# can bypass the gate and inject those headers.
mode: proxy
internal_host: http://paperless.paperless.svc.cluster.local
internal_host_ssl_validation: false
@@ -91,19 +74,12 @@ data:
access_token_validity: hours=24
refresh_token_validity: days=30
intercept_header_auth: true
# Pass paperless's entire static-asset tree through anonymously.
# Two reasons:
# 1. <link rel="manifest"> is fetched with `credentials: 'omit'`
# (W3C spec, no crossorigin attr) so the cookie is never sent
# — every page load and every SPA navigation turned into a
# 302 → auth.renner.uno round-trip with an orphan session JWT.
# Same root cause as the retired music-assistant-gate (see
# comment further below); paperless can't retire the gate so
# it carves out the offending paths instead.
# 2. Every CSS/JS/icon fetch otherwise validates the cookie at
# the outpost — pure overhead on assets that carry no
# authentication value. Django's STATIC tree is public by
# design; MEDIA (under /api/documents/*) stays gated.
# The static-asset tree passes anonymously, and must. A
# <link rel="manifest"> is fetched with `credentials: 'omit'` per
# the W3C spec, so the session cookie is never sent and every page
# load turns into a redirect loop through the login origin. Django's
# STATIC tree is public by design; MEDIA under /api/documents/*
# stays gated.
skip_path_regex: ^/static/.*
- model: authentik_core.application
@@ -130,18 +106,14 @@ data:
negate: false
failure_result: false
# Retired: the music-assistant-gate ProxyProvider, Application,
# and dedicated Outpost. The gate fundamentally conflicted with
# MA's PWA model — Chrome fetches manifest.webmanifest / sw.js /
# icons with `credentials: 'omit'` (per W3C spec, no crossorigin
# attribute on MA's <link rel="manifest">), and the cached
# service worker bypasses fresh navigations on PWA cold-start so
# the Authentik cookie never lands in the WebAPK's cookie jar.
# MA has its own auth (delegated to Home Assistant); the gate's
# marginal value didn't justify the recurring breakage.
# Order matters: Application before Provider before Outpost.
# Application.provider is on_delete=PROTECT, so the Provider
# can't be removed while the Application still references it.
# Retired: the music-assistant gate. A forward-auth gate cannot work in
# front of a PWA — the manifest and service worker are fetched with
# `credentials: 'omit'`, and a cached service worker bypasses fresh
# navigations, so the session cookie never reaches the installed app.
# Do not reintroduce one there; MA delegates auth to Home Assistant.
#
# ORDER MATTERS: Application before Provider before Outpost, because
# Application.provider is on_delete=PROTECT.
- model: authentik_core.application
identifiers:
slug: music-assistant-gate
@@ -157,18 +129,13 @@ data:
name: music-assistant-gate
state: absent
# Dedicated outpost: its ONLY job is to serve the in-cluster
# /outpost.goauthentik.io/auth/nginx endpoint that nginx
# auth_request hits for every request to paperless. The
# browser-facing legs of the OAuth flow (/start, /callback, the
# `/if/...` login UI, /application/o/authorize, /api, etc.) all
# need to happen on auth.renner.uno because that's the origin
# WebAuthn/FIDO/TOTP credentials are bound to. So we attach the
# ProxyProvider to BOTH outposts (see embedded outpost below) and
# set authentik_host_browser to auth.renner.uno here too — the
# nginx auth_request happens server-to-server, but if the
# dedicated outpost ever does emit a redirect URL, it points at
# the right host.
# Dedicated outpost, serving the in-cluster auth endpoint only.
#
# The browser-facing legs of the OAuth flow must happen on
# auth.renner.uno, because that is the origin WebAuthn credentials are
# bound to. Hence the ProxyProvider is attached to BOTH this outpost and
# the embedded one, and authentik_host_browser points at auth.renner.uno
# here too — so any redirect this outpost emits names the right host.
- model: authentik_outposts.outpost
identifiers:
name: paperless-gate
@@ -196,12 +163,7 @@ data:
kubernetes_ingress_secret_name: "authentik-outpost-tls"
kubernetes_json_patches: null
# Tear down the leftover paperless-proxy Application from an
# earlier experiment. The orphan `paperless` ProxyProvider that
# used to back paperless.app is left in place — it's PROTECTed
# by Application.provider as long as anything references it, and
# leftover orphans don't break anything. Delete via the Authentik
# UI once everything's confirmed working.
# Leftover Application from an earlier experiment.
- model: authentik_policies.policybinding
identifiers:
target: !Find [authentik_core.application, [slug, paperless-proxy]]
@@ -217,15 +179,11 @@ data:
# ------------------------------------------------------------------
# vscode config-editor gate
#
# code-server (the HA /config editor sidecar) runs with --auth=none,
# as root, mounting the whole HA /config incl. plaintext secrets.yaml
# and .storage. This gate moves it behind Authentik with a passkey
# step-up, mirroring the paperless-gate proxy-outpost pattern above:
# the dedicated ak-outpost-vscode-gate terminates the request,
# enforces the session, and forwards to the code-server Service.
# Traefik only ever routes vscode.home.renner.uno to the outpost; the
# home-assistant CNP is tightened so ONLY the gate outpost may reach
# :8080 (see home-assistant-ns.yaml + vscode-gate-egress.yaml).
# code-server runs with --auth=none, as root, mounting the whole HA
# /config including plaintext secrets.yaml and .storage. This gate is
# the only thing in front of it: the outpost terminates the request and
# enforces a passkey step-up, Traefik routes only to the outpost, and
# the home-assistant CNP admits :8080 from the gate alone.
# ------------------------------------------------------------------
- id: vscode-config-editors-group
model: authentik_core.group
@@ -265,17 +223,14 @@ data:
access_token_validity: hours=24
refresh_token_validity: days=30
intercept_header_auth: true
# code-server is a service-worker app: <link rel="manifest"> and
# the service worker are fetched with credentials:'omit', which
# turned into a /authorize redirect loop and forced the RETIREMENT
# of the music-assistant gate (see comment above). Carve its
# static/manifest/SW tree out so those cookieless fetches pass
# through instead of 302-looping; the editor's actual content
# (the /config tree) is served over authenticated websocket/API
# paths, which stay gated.
# NOTE: the exact skip set is code-server-version-specific and
# MUST be confirmed post-deploy against the real redirect-loop
# behaviour (see the PR validation checklist).
# code-server is a service-worker app, so its manifest and SW are
# fetched with credentials:'omit' and 302-loop unless carved out.
# The editor's actual content rides authenticated websocket/API
# paths and stays gated.
#
# This skip set is version-specific: re-check it after a
# code-server bump, because a renamed asset path reintroduces the
# loop silently.
skip_path_regex: ^/(_static/|manifest\.json|.*\.webmanifest|.*serviceWorker\.js).*
- model: authentik_core.application
@@ -331,16 +286,11 @@ data:
# ------------------------------------------------------------------
# shelly-manager gate
#
# shelly-manager has no authentication of its own: anything that can
# reach :8080 can rewrite Shelly device config, push scripts, and read
# the stored per-device AdminPassword values. Until now the only
# control was reachability of shelly-manager.home.renner.uno.
#
# Same shape as the vscode-gate above: a dedicated outpost terminates
# the request, enforces a passkey step-up, and forwards to the
# shelly-manager Service. Traefik only ever routes the hostname to the
# outpost; the home-assistant CNP allows ONLY this outpost to reach
# :8080 (see home-assistant-ns.yaml + shelly-manager-gate-egress.yaml).
# shelly-manager has no authentication of its own: anything that reaches
# :8080 can rewrite device config, push scripts and read the stored
# per-device AdminPassword values. Same shape as the vscode gate — the
# outpost is the only control, Traefik routes only to it, and the CNP
# admits :8080 from this outpost alone.
# ------------------------------------------------------------------
- id: shelly-managers-group
model: authentik_core.group