Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/_deploy-stack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ jobs:
AUTHENTIK_PANGOLIN_CLIENT_SECRET: ${{ secrets.AUTHENTIK_PANGOLIN_CLIENT_SECRET }}
AUTHENTIK_PULSE_CLIENT_ID: ${{ secrets.AUTHENTIK_PULSE_CLIENT_ID }}
AUTHENTIK_PULSE_CLIENT_SECRET: ${{ secrets.AUTHENTIK_PULSE_CLIENT_SECRET }}
AUTHENTIK_KRAUTWATCH_CLIENT_ID: ${{ secrets.AUTHENTIK_KRAUTWATCH_CLIENT_ID }}
AUTHENTIK_KRAUTWATCH_CLIENT_SECRET: ${{ secrets.AUTHENTIK_KRAUTWATCH_CLIENT_SECRET }}
# monitoring stack (#485). These four were never here because monitoring had no
# workflow to need them — every apply ran from a laptop that already had secrets.env,
# so nothing ever noticed. Adding deploy-monitoring.yml made the runner the first
Expand Down
4 changes: 4 additions & 0 deletions stacks/Core/authentik.lxc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,7 @@ spec:
# blueprint file, silently undoing every unrelated change in it.
authentik_pulse_client_id: AUTHENTIK_PULSE_CLIENT_ID
authentik_pulse_client_secret: AUTHENTIK_PULSE_CLIENT_SECRET
# Krautwatch's web UI (Krautwatch #48). The same pair is handed to the krautwatch-web
# container as Auth__Oidc__ClientId / Auth__Oidc__ClientSecret.
authentik_krautwatch_client_id: AUTHENTIK_KRAUTWATCH_CLIENT_ID
authentik_krautwatch_client_secret: AUTHENTIK_KRAUTWATCH_CLIENT_SECRET
66 changes: 66 additions & 0 deletions stacks/Core/authentik/assets/blueprints/00-homelab-identity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,72 @@ entries:
identifiers: { target: !KeyOf pulse-app, group: !KeyOf grp-monitoring-admins }
attrs: { order: 1, enabled: true }

# ── Krautwatch: OIDC provider ─────────────────────────────────────────────────────
# Krautwatch (CT 5114, the Media podman host) can hand its web UI's sign-in to an IdP as of
# v0.7.0. Registered here for the same reason Pangolin is: the client pair is fixed in
# Bitwarden Secrets Manager rather than minted by Authentik and copied out of a UI.
- model: authentik_providers_oauth2.oauth2provider
id: krautwatch-provider
identifiers: { name: Krautwatch }
attrs:
client_type: confidential
client_id: !Env AUTHENTIK_KRAUTWATCH_CLIENT_ID
client_secret: !Env AUTHENTIK_KRAUTWATCH_CLIENT_SECRET
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
# Krautwatch checks the groups claim itself as a second layer, and reads it from the ID
# token rather than depending on a userinfo round trip.
include_claims_in_id_token: true
# Same trap as Pangolin: an empty grant_types allow-list rejects every authorization
# request with a message that says nothing about grant types.
grant_types:
- authorization_code
- refresh_token
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
# Strict, not regex: ASP.NET Core's handler uses one fixed callback path, so there is
# nothing variable to match. Two URIs because the UI is LAN-only today (no hostname, no
# CF Access app) and would move to a name the day it is fronted — registering both now
# means that change is a compose edit rather than an identity change.
redirect_uris:
- matching_mode: strict
url: http://10.10.255.254:5099/signin-oidc
- matching_mode: strict
url: https://krautwatch.chrison.dev/signin-oidc

# ── Krautwatch: application ───────────────────────────────────────────────────────
- model: authentik_core.application
id: krautwatch-app
identifiers: { slug: krautwatch }
attrs:
name: Krautwatch
provider: !KeyOf krautwatch-provider
meta_launch_url: http://10.10.255.254:5099
meta_description: German public-TV indexer and download client for the *arr fleet.

# ── Who may sign in ───────────────────────────────────────────────────────────────
# Admins only. Krautwatch drives the download client and rewrites the media library's
# contents; family and guests have Plex for the output and no business with the machinery.
#
# Krautwatch re-checks the groups claim itself, so a binding removed here does not silently
# open the UI. That second check is configured with the group NAMES, not these entry ids:
#
# Auth__Oidc__RequiredGroups=homelab-admins,media-admins
#
# which is why the names have to be kept in step with #497's rename. Krautwatch splits that
# setting on commas only — never on whitespace — so a name containing a space would work
# there, but the convention holds anyway: consumers that split on whitespace exist (Pulse
# does), and a group name is a value every one of them re-parses with its own rules.
- model: authentik_policies.policybinding
identifiers: { target: !KeyOf krautwatch-app, group: !KeyOf grp-homelab-admins }
attrs: { order: 0, enabled: true }
- model: authentik_policies.policybinding
identifiers: { target: !KeyOf krautwatch-app, group: !KeyOf grp-media-admins }
attrs: { order: 1, enabled: true }

# ── Who is actually in a group ────────────────────────────────────────────────────
# Without this the whole model is inert: the four bindings above admit only members of
# those groups, and akadmin was in `authentik Admins` — which is Authentik's OWN admin
Expand Down
6 changes: 6 additions & 0 deletions stacks/Core/authentik/quadlets/authentik-server.container
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ Secret=authentik_pangolin_client_secret,type=env,target=AUTHENTIK_PANGOLIN_CLIEN
# being copied between two UIs.
Secret=authentik_pulse_client_id,type=env,target=AUTHENTIK_PULSE_CLIENT_ID
Secret=authentik_pulse_client_secret,type=env,target=AUTHENTIK_PULSE_CLIENT_SECRET
# Krautwatch's pair, same reason again: the blueprint declaring its provider is applied in this
# container, so `!Env AUTHENTIK_KRAUTWATCH_CLIENT_ID` reads it from here. Declaring the secret in
# authentik.lxc.yaml only CREATES it — without these two lines it is never injected, `!Env`
# resolves null, and authentik rejects the whole blueprint file (#497).
Secret=authentik_krautwatch_client_id,type=env,target=AUTHENTIK_KRAUTWATCH_CLIENT_ID
Secret=authentik_krautwatch_client_secret,type=env,target=AUTHENTIK_KRAUTWATCH_CLIENT_SECRET
# Must match ContainerName= on the postgres unit — resolved by aardvark-dns on the shared
# network, not by anything in this file.
Environment=AUTHENTIK_POSTGRESQL__HOST=authentik-postgresql
Expand Down
6 changes: 6 additions & 0 deletions stacks/Core/authentik/quadlets/authentik-worker.container
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ Secret=authentik_pangolin_client_secret,type=env,target=AUTHENTIK_PANGOLIN_CLIEN
# being copied between two UIs.
Secret=authentik_pulse_client_id,type=env,target=AUTHENTIK_PULSE_CLIENT_ID
Secret=authentik_pulse_client_secret,type=env,target=AUTHENTIK_PULSE_CLIENT_SECRET
# Krautwatch's pair, same reason again: the blueprint declaring its provider is applied in this
# container, so `!Env AUTHENTIK_KRAUTWATCH_CLIENT_ID` reads it from here. Declaring the secret in
# authentik.lxc.yaml only CREATES it — without these two lines it is never injected, `!Env`
# resolves null, and authentik rejects the whole blueprint file (#497).
Secret=authentik_krautwatch_client_id,type=env,target=AUTHENTIK_KRAUTWATCH_CLIENT_ID
Secret=authentik_krautwatch_client_secret,type=env,target=AUTHENTIK_KRAUTWATCH_CLIENT_SECRET
Environment=AUTHENTIK_POSTGRESQL__HOST=authentik-postgresql
Environment=AUTHENTIK_POSTGRESQL__NAME=authentik
Environment=AUTHENTIK_POSTGRESQL__USER=authentik
Expand Down