Adds config/providers_oauth2/vault.yaml so human logins to OpenBao go
through Authentik SSO (bao CLI and the web UI). Machine auth (approle,
kubernetes, CI) and break-glass are unchanged and stay on the OpenBao side.
Extends the oauth2 provider module so a config may generate its own client
secret instead of reading a pre-seeded one: client_secret_vault.generate
creates a random_password and writes {client_id, client_secret} to the given
kv-v2 path. Providers without the flag keep the existing read-only data source
behaviour. This is what lets the provider land with no manual Vault seed.
Gates the new application with akP-vault-admin and nests it in
akR-global-admin, matching how every other app in this repo is bound.
Add an OAuth2/OIDC provider + application for the repospawner operator tool,
fronted by oauth2-proxy, and gate it on a new akP-repospawner-admin permission
group nested under akR-global-admin (mirrors the watchstate precedent).
The terraform-authentik runner's Vault policy only grants read on
kv/data/kubernetes/namespace/+/default/oauth-credentials (literal trailing
filename), so the arrstack/default/mediamark-oauth-credentials path 403s at
plan time and reddens CI. mediamark deploys in its own `mediamark` namespace
(watchstate model), so point the data source at
kubernetes/namespace/mediamark/default/oauth-credentials, which the policy
covers. Hostnames are unchanged.
Rename the permission group to akP-mediamark-user to match the peer tier-suffix
convention (akP-watchstate-admin, akP-arrstack-user). The group name is derived
from the filename in config/config.hcl, so update the akR-media-adult
reference too.
Add an oauth2 provider/application for the mediamark kids-content marking UI,
a permission group gating it, and nest that permission in akR-media-adult.
Completes the Authentik-side plumbing for Jellyfin SSO across both media
instances -- fafflix (adults, jellyfin.k8s.syd1.au.unkin.net) and cheeztv
(kids, cheeztv.unkin.net + cheeztv.k8s.syd1.au.unkin.net) -- and adds an
LDAP outpost so native clients can authenticate with app passwords.
Why: the previously-merged jellyfin OIDC provider only covered the fafflix
host and gated on the generic jellyfin permission groups. cheeztv needs SSO
too, access should be limited to media users, and native (non-browser)
clients need a password-based path.
How:
- providers_oauth2/jellyfin.yaml: one shared confidential client now lists
strict redirect URIs for all three hosts using the verified
jellyfin-plugin-sso callback path /sso/OID/redirect/authentik. Client
secret moved to kv kubernetes/namespace/fafflix/default/oauth-credentials.
- Access is gated to media users only: akP-media-fafflix and akP-media-cheeztv
now carry `application: jellyfin` and bind to the app; akP-jellyfin-admin /
akP-jellyfin-user are demoted to pure role-claim groups (no app binding),
still mapped by the plugin for admin/user rights. Per-instance authz
(adults -> both, kids -> cheeztv only) stays with the media proxy.
- providers_ldap/jellyfin-ldap.yaml: new LDAP provider (base_dn
DC=ldap,DC=goauthentik,DC=io, direct bind/search) + application
(jellyfin-ldap) + outpost (jellyfin-ldap-outpost) via the existing module.
- modules/authentik/main.tf: resolve LDAP bind/unbind flow slugs to ids via
data.authentik_flow, matching the oauth2/saml convention.
watchstate is an internal media watch-state sync admin tool deployed at
watchstate.k8s.syd1.au.unkin.net behind oauth2-proxy (OIDC against
Authentik). Add the OAuth2/OIDC provider + application mirroring the
logviewer/traefik in-cluster admin pattern, gate it with the
akP-watchstate-admin permission group bound to the app, and nest that
permission into the akR-global-admin role so only the admin team can
authorize.
The media services are splitting into an adult (fafflix) and kids (cheeztv)
tier, and Authentik group membership will drive the media proxy's routing and
authorization. This adds the two-tier RBAC groups so users can be assigned the
right media access ahead of the provider/application wiring.
- Add akP-media-fafflix and akP-media-cheeztv per-service permission entitlements
(unbound, so they surface in the hierarchical ak_groups claim for the proxy)
- Add akR-media-adult role nesting both fafflix and cheeztv (adults reach both)
- Add akR-media-kids role nesting only cheeztv (kids reach kids services only)
The ClickHouse log UI (logviewer) is being exposed at
https://logviewer.unkin.net behind oauth2-proxy in the logging namespace;
this adds the Authentik side as the prerequisite.
- Adds config/providers_oauth2/logviewer.yaml mirroring traefik: confidential
client, client_id logviewer, secret from Vault kv
kubernetes/namespace/logging/default/oauth-credentials,
openid/email/profile scopes (ak_groups is attached to every oauth2
provider by the module), strict redirect URI for the
logviewer oauth2 callback.
- Adds config/permissions/akP-logviewer-admin.yaml bound to the logviewer
application and nests it under akR-global-admin.
The traefik dashboards (internal + external ingress classes) are being
exposed behind oauth2-proxy; this adds the Authentik side as the
prerequisite.
- Adds config/providers_oauth2/traefik.yaml mirroring arrstack: confidential
client, client_id traefik, secret from Vault kv
kubernetes/namespace/traefik-system/default/oauth-credentials,
openid/email/profile scopes (ak_groups is attached to every oauth2
provider by the module), strict redirect URIs for
traefik-internal/traefik-external oauth2 callbacks.
- Adds config/permissions/akP-traefik-admin.yaml bound to the traefik
application and nests it under akR-global-admin.
Jellyfin moves to Authentik SSO via jellyfin-plugin-sso (OIDC), keeping
native clients on Jellyfin local/API auth. Adds the oauth2 provider and
application for jellyfin.k8s.syd1.au.unkin.net plus the akP permission
groups gating access, wired into the standard-user and global-admin
roles per the two-tier RBAC model.
- Adds providers_oauth2/jellyfin.yaml: confidential client, secret read
from kv/kubernetes/namespace/jellyfin/default/oauth-credentials,
redirect URIs for the SSO plugin callback paths
- Adds akP-jellyfin-admin and akP-jellyfin-user bound to the app
- Nests akP-jellyfin-user under akR-standard-user and
akP-jellyfin-admin under akR-global-admin
Add the Authentik OIDC application that fronts the arrproxy media front door
at arrstack.unkin.net, plus the per-app entitlement groups arrproxy reads from
the user's groups claim to decide which backends (sonarr/radarr/prowlarr) a
user may reach.
- config/providers_oauth2/arrstack.yaml: confidential oauth2 client
client_id=arrstack, litellm-style auth/invalidation flows, client_secret
from Vault kv kubernetes/namespace/arrstack/default/oauth-credentials,
openid/email/profile scopes, redirect https://arrstack.unkin.net/oauth2/callback,
launch https://arrstack.unkin.net/. The module always attaches the estate's
hierarchical ak_groups scope mapping, so the front door emits the groups claim.
- config/permissions/akP-arrstack-user.yaml: front-door gate (application: arrstack).
- config/permissions/akP-arrstack-{sonarr,radarr,prowlarr}.yaml: per-app
entitlements, unbound (no application) so they only surface in the ak_groups
claim for arrproxy to authorize backends.
- config/roles/akR-arrstack-user.yaml: full media role nesting all four.
- akR-global-admin: also nests the arrstack front door + all per-app perms.
Bring LiteLLM into the two-tier RBAC and map groups to LiteLLM roles.
- akP-litellm-admin / akP-litellm-user permission groups (bound to the litellm
app for access); added to akR-global-admin / akR-standard-user roles.
- Generic per-provider role_mappings: emit an app role claim computed from
effective (hierarchical) group membership. LiteLLM: emits `litellm_role`
(proxy_admin for akP-litellm-admin, internal_user for akP-litellm-user, else
internal_user_view_only); LiteLLM reads it via GENERIC_USER_ROLE_ATTRIBUTE.
Validated: plan 5 to add, 3 to change; generated role expression renders correctly.
- Permission/role group name now comes from the config filename (the map key),
dropping the redundant `name` field from each YAML and the object types.
- The hierarchical mapping emits an `ak_groups` claim (scope `ak_groups`) instead
of `groups`, so it never collides with the direct-groups the default profile
mapping already emits under `groups` (Authentik overrides same-key claims in an
unpredictable order). Apps request the `ak_groups` scope and read that claim.
Introduce a user -> role -> [permissions] model for app access and roles,
managed declaratively.
- Permission groups (akP-<app>-<access>) under config/permissions/: atomic units,
each names the application it grants access to.
- Role groups (akR-<role>) under config/roles/: what users are assigned to;
each nests permission groups via parents (akR-global-admin -> all *-admin,
akR-standard-user -> all *-user). Split into a separate authentik_group
resource so roles can reference permission ids without self-reference.
- Policy bindings gate each application to its permission groups (and, via
child->parent membership propagation, the roles that nest them).
- Hierarchical `groups` scope mapping: walks user groups up through .parents so
the OIDC claim includes inherited permission groups (works around
goauthentik/authentik#15579). Inert until a provider requests the `groups`
scope, so no behaviour change to existing apps until they opt in.
Validated with `tofu validate`.