Commit Graph

14 Commits

Author SHA1 Message Date
unkin-agent 3396b399ce Jellyfin SSO: cheeztv OIDC hosts, media-group gating, LDAP outpost
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline failed
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.
2026-08-26 22:15:28 +10:00
benvin 132da3cfc8 Merge pull request 'Set explicit launch URL for the LiteLLM application' (#16) from benvin/litellm-app-visibility into main
ci/woodpecker/push/apply Pipeline was successful
Reviewed-on: #16
2026-07-31 22:00:02 +10:00
unkinben a93205bc82 Set explicit launch URL for the LiteLLM application
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
Ben (akR-global-admin) does not see the LiteLLM tile on the Authentik user
dashboard, while ArgoCD/Grafana/Rancher appear normally. The live API shows
LiteLLM is configured identically to those apps: the app exists, its access
binding akP-litellm-admin -> litellm is present, and akR-global-admin nests
akP-litellm-admin (bidirectionally, same as the others). A CI-style plan against
live state reports "No changes" -- so this is not terraform-correctable drift,
and a plain re-apply fixes nothing. Yet check_access for Ben returns
passing=false for litellm and passing=true for the rest: a stale cached access
policy result inside Authentik.

Add an optional per-app launch_url to the providers_oauth2 config (default null,
which keeps Authentik's redirect-derived URL) and wire it to the application's
meta_launch_url. Set it for LiteLLM to its UI. This makes the dashboard tile
deterministic and, on apply, re-saves the application -- invalidating the stale
access-policy cache so Ben's (already-correct) access re-evaluates and the tile
appears.

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
2026-07-31 20:46:35 +10:00
unkinben b6e9b6b6b9 Default OAuth2 providers to RS256 ID token signing
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
OAuth2 providers with no signing_key fall back to HS256, which RS256-only
RP clients (argocd confirmed, and the rest) reject with "unexpected
signature algorithm HS256; expected [RS256]", breaking OIDC login.

- Add data.authentik_certificate_key_pair.signing, resolving the estate's
  RSA keypair by name (var.oauth2_signing_key_name, default the built-in
  "authentik Self-signed Certificate").
- Default every provider's signing_key to that keypair via coalesce, so all
  providers sign with RS256 while keeping the per-yaml signing_key override.

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
2026-07-31 20:28:07 +10:00
unkinben 2b39450ae2 Set OAuth2 grant_types so authorization_code login works
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
## Why

All Authentik OIDC logins (ArgoCD, Grafana, Rancher, LiteLLM, NetBox) fail
with `invalid_request` / "The request is otherwise malformed". Authentik
2026.5 added an explicit `grant_types` allow-list to the OAuth2 provider
(model default = empty list). Our module never set it, so every provider has
`grant_types = []`, and `authorize.py` rejects the authorization_code grant
(`if self.grant_type not in self.provider.grant_types`) before any user auth.

## Change

- modules/authentik: add a `grant_types` field to the `providers_oauth2`
  variable, defaulting to `["authorization_code", "refresh_token"]` (the
  standard confidential web-app set), and wire it into
  `authentik_provider_oauth2`.

## Plan

`0 to add, 5 to change, 0 to destroy` — each existing oauth2 provider's
`grant_types` goes `[] -> ["authorization_code", "refresh_token"]`; no other
attributes change. Baseline plan (pre-change) was clean (no netbox/state drift).

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
2026-07-28 22:09:06 +10:00
benvin bd2ead5855 Merge branch 'main' into benvin/ceph-saml
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
2026-07-24 22:16:29 +10:00
unkinben 7610627168 Add LiteLLM RBAC: akP-litellm groups + role claim mapping
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
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.
2026-07-19 18:42:36 +10:00
unkinben 9c5937776e Add Ceph dashboard SAML provider
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline failed
Ceph dashboard SSO is SAML 2.0 (no native OIDC), so onboard it via an Authentik
SAML provider + application. Also resolve SAML authorization/invalidation flows
by slug and the signing keypair by name (mirrors the oauth2 handling), since the
SAML path had not been exercised before.

- config/providers_saml/ceph.yaml: SP entity id/ACS derived from the dashboard
  base URL (audience .../auth/saml2/metadata, acs .../auth/saml2, HTTP-POST),
  signed with the built-in self-signed keypair.

Ceph side (separate, Puppet): ceph dashboard sso setup saml2
  https://dashboard.ceph.unkin.net <authentik-idp-metadata-url>

Validated with `terragrunt plan`: 2 to add (provider + application).
2026-07-19 02:22:23 +10:00
unkinben 805ea48a36 Derive group name from filename; use distinct ak_groups claim
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
- 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.
2026-07-18 16:19:34 +10:00
unkinben 1dab2ecc6f Add two-tier RBAC: permission/role groups, access policies, group claim
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
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`.
2026-07-18 16:11:39 +10:00
unkinben 7636d45f21 Fix self-referential authentik_group parents
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline was successful
Adding the first managed group (argocd-admins) exposed a dormant bug: the
groups resource resolved `parents` by indexing authentik_group.this itself,
which OpenTofu rejects as a self-referential block. config/groups/ had been
empty, so `tofu plan` never hit it before.

Pass `parents` through as literal group PKs instead (the resource cannot
reference itself, so parent-by-map-key was never viable). Plan is clean:
3 to add (argocd provider, application, argocd-admins group).
2026-07-12 22:42:17 +10:00
unkinben 97be93a9ff authentik: add Grafana OAuth2/OIDC provider + application
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline failed
Adds an OIDC provider + application so the in-cluster Grafana
(grafana.k8s.syd1.au.unkin.net) can authenticate users against Authentik.

Extends the oauth2 module so provider config stays declarative and
secret-free:
- Resolve authorization/invalidation flows by slug (data.authentik_flow)
  and scope mappings by managed identifier
  (data.authentik_property_mapping_provider_scope).
- Read client_secret from Vault kv-v2 (data.vault_kv_secret_v2) instead of
  committing it; adds the hashicorp/vault provider (auth via VAULT_ADDR/
  VAULT_TOKEN from the Makefile).
- Support allowed_redirect_uris on the oauth2 provider.

config/providers_oauth2/grafana.yaml wires client_id `grafana`, the
openid/email/profile scopes, the login/generic_oauth redirect URI, and
points client_secret at kv/kubernetes/namespace/grafana/default/oauth-credentials.
2026-07-06 22:06:48 +10:00
unkinben 8aa2273dcf Fix provider schema for goauthentik/authentik 2026.5.0
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
- group: parent → parents (list)
- saml/oauth2: add required invalidation_flow
- oauth2: remove redirect_uris (use allowed_redirect_uris via config)
- ldap: replace authorization_flow/search_group with bind_flow/unbind_flow
- Add versions.tf with required_providers block
- Remove service_connection from outpost (auto-discovered)
2026-06-28 12:04:19 +10:00
unkinben 4042760a16 Initial scaffold
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline failed
- Terraform module for groups, SAML/OAuth2/LDAP providers, applications, and LDAP outposts
- Data-driven YAML config with Terragrunt config loader
- Environment: identity.unkin.net with Consul backend
- Provider: goauthentik/authentik 2026.5.0
- Woodpecker CI pipelines (pre-commit, plan, apply)
- Makefile with Vault AppRole and K8s auth support
2026-06-28 11:55:26 +10:00