Jellyfin SSO: cheeztv OIDC hosts, media-group gating, LDAP outpost #28
@@ -1,3 +1,6 @@
|
||||
# Permission group akP-jellyfin-admin (name = filename). Grants admin
|
||||
# access to jellyfin: bound to the jellyfin application and mapped to its admin role.
|
||||
application: jellyfin
|
||||
# Permission group akP-jellyfin-admin (name = filename). Does NOT gate the
|
||||
# jellyfin application (SSO access is gated by the media groups akP-media-fafflix
|
||||
# / akP-media-cheeztv). It exists purely as an admin role-claim group: the
|
||||
# jellyfin-plugin-sso matches the hierarchical `ak_groups` claim against this
|
||||
# group name to grant Jellyfin administrator rights. Nested under akR-global-admin.
|
||||
attributes: {}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Permission group akP-jellyfin-user (name = filename). Grants user
|
||||
# access to jellyfin: bound to the jellyfin application and mapped to its user role.
|
||||
application: jellyfin
|
||||
# Permission group akP-jellyfin-user (name = filename). Does NOT gate the
|
||||
# jellyfin application (SSO access is gated by the media groups akP-media-fafflix
|
||||
# / akP-media-cheeztv). It exists purely as a user role-claim group: the
|
||||
# jellyfin-plugin-sso matches the hierarchical `ak_groups` claim against this
|
||||
# group name for regular (non-admin) Jellyfin access. Nested under akR-standard-user.
|
||||
attributes: {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Permission group akP-media-cheeztv (name = filename). Per-service entitlement
|
||||
# for the kids ("cheeztv") media tier: NOT bound to any application (no
|
||||
# `application` field), so it does not gate OIDC. It exists purely to appear in
|
||||
# the user's hierarchical `ak_groups` claim, which the media proxy reads to
|
||||
# decide whether to route/authorize the cheeztv backend.
|
||||
attributes: {}
|
||||
# for the kids ("cheeztv") media tier. Bound to the jellyfin application, so it
|
||||
# gates Jellyfin SSO access: only members (via akR-media-adult or akR-media-kids)
|
||||
# may authorize. It also appears in the user's hierarchical `ak_groups` claim,
|
||||
# which the media proxy reads to route/authorize the cheeztv backend.
|
||||
application: jellyfin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Permission group akP-media-fafflix (name = filename). Per-service entitlement
|
||||
# for the adult ("fafflix") media tier: NOT bound to any application (no
|
||||
# `application` field), so it does not gate OIDC. It exists purely to appear in
|
||||
# the user's hierarchical `ak_groups` claim, which the media proxy reads to
|
||||
# decide whether to route/authorize the fafflix backend.
|
||||
attributes: {}
|
||||
# for the adult ("fafflix") media tier. Bound to the jellyfin application, so it
|
||||
# gates Jellyfin SSO access: only members (via akR-media-adult) may authorize.
|
||||
# It also appears in the user's hierarchical `ak_groups` claim, which the media
|
||||
# proxy reads to route/authorize the fafflix backend.
|
||||
application: jellyfin
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# LDAP provider + outpost for Jellyfin native-client app-password validation.
|
||||
#
|
||||
# Jellyfin's web UI signs in via OIDC (see providers_oauth2/jellyfin.yaml), but
|
||||
# native clients (mobile/TV apps, DLNA) cannot do a browser OIDC dance. Those
|
||||
# clients authenticate against this Authentik LDAP outpost using their username
|
||||
# plus an Authentik "App password" token as the bind password. The Jellyfin LDAP
|
||||
# plugin binds as the user; a successful bind == valid app-password.
|
||||
#
|
||||
# bind_mode "direct": the outpost runs the bind_flow (default-authentication-flow)
|
||||
# on every bind request, so app-password revocation takes effect immediately.
|
||||
# search_mode "direct": entries are read live from the Authentik API. Search is
|
||||
# gated by Authentik's directory permissions -- the bound user is NOT granted the
|
||||
# "Search full LDAP directory" permission, so it can only read its own entry,
|
||||
# which is all the Jellyfin plugin needs to resolve the user after bind.
|
||||
#
|
||||
# base_dn is the Authentik default LDAP tree. The module creates the matching
|
||||
# authentik_application (slug jellyfin-ldap) and authentik_outpost
|
||||
# (name jellyfin-ldap-outpost, type ldap). The outpost's API token is generated
|
||||
# by Authentik AFTER apply and must be seeded into KV for the k8s outpost
|
||||
# Deployment to consume: kv/kubernetes/namespace/authentik/default/outpost-token (key: token).
|
||||
name: Jellyfin LDAP
|
||||
bind_flow: default-authentication-flow
|
||||
unbind_flow: default-invalidation-flow
|
||||
base_dn: DC=ldap,DC=goauthentik,DC=io
|
||||
bind_mode: direct
|
||||
search_mode: direct
|
||||
mfa_support: true
|
||||
@@ -1,29 +1,45 @@
|
||||
# OAuth2/OIDC provider + application for Jellyfin
|
||||
# (jellyfin.k8s.syd1.au.unkin.net), consumed by jellyfin-plugin-sso (OIDC) so
|
||||
# the web UI signs in via Authentik while native clients keep Jellyfin local/API
|
||||
# auth. client_secret is read from Vault, not committed. The plugin requests the
|
||||
# `ak_groups` scope and matches the hierarchical groups claim against
|
||||
# akP-jellyfin-admin / akP-jellyfin-user for its admin/user role mapping, so no
|
||||
# role_mappings claim is needed here. The path segment "authentik" is the SSO
|
||||
# provider name configured in the plugin — it must match on the Jellyfin side.
|
||||
# OAuth2/OIDC provider + application for the Jellyfin web UI SSO, consumed by
|
||||
# jellyfin-plugin-sso (OIDC) so the browser signs in via Authentik while native
|
||||
# clients use the LDAP outpost (see providers_ldap/jellyfin-ldap.yaml).
|
||||
#
|
||||
# ONE shared confidential client (client_id jellyfin) serves BOTH Jellyfin
|
||||
# instances -- fafflix (adults, jellyfin.k8s.syd1.au.unkin.net) and cheeztv
|
||||
# (kids, cheeztv.unkin.net + cheeztv.k8s.syd1.au.unkin.net). Each instance runs
|
||||
# the SSO plugin with provider name "authentik", so its callback is
|
||||
# https://<host>/sso/OID/redirect/authentik (verified plugin path shape); all
|
||||
# three hosts are listed as strict redirect URIs.
|
||||
#
|
||||
# Access is gated to media users only: akP-media-fafflix and akP-media-cheeztv
|
||||
# carry `application: jellyfin` and bind to this app, so only members of the
|
||||
# media roles (akR-media-adult / akR-media-kids) can authorize. Per-instance
|
||||
# authorization (adults -> fafflix + cheeztv, kids -> cheeztv only) is enforced
|
||||
# downstream by the media proxy reading the hierarchical ak_groups claim.
|
||||
# Admin vs. user role inside Jellyfin is still mapped by the plugin matching the
|
||||
# ak_groups claim against akP-jellyfin-admin / akP-jellyfin-user.
|
||||
#
|
||||
# client_secret is read from Vault (seeded out of band), never committed. Both
|
||||
# instances share this one secret; terraform reads it from the fafflix namespace
|
||||
# path, and the cheeztv Deployment reads the same value from its own namespace.
|
||||
name: Jellyfin
|
||||
authorization_flow: default-provider-authorization-implicit-consent
|
||||
invalidation_flow: default-provider-invalidation-flow
|
||||
client_type: confidential
|
||||
client_id: jellyfin
|
||||
# Explicit dashboard tile launch URL (the Jellyfin web UI).
|
||||
# Explicit dashboard tile launch URL (the fafflix Jellyfin web UI).
|
||||
launch_url: https://jellyfin.k8s.syd1.au.unkin.net/
|
||||
client_secret_vault:
|
||||
mount: kv
|
||||
path: kubernetes/namespace/jellyfin/default/oauth-credentials
|
||||
path: kubernetes/namespace/fafflix/default/oauth-credentials
|
||||
scope_mappings:
|
||||
- goauthentik.io/providers/oauth2/scope-openid
|
||||
- goauthentik.io/providers/oauth2/scope-email
|
||||
- goauthentik.io/providers/oauth2/scope-profile
|
||||
redirect_uris:
|
||||
# jellyfin-plugin-sso OIDC callback (plugin >= 3.5.2).
|
||||
# fafflix (adults)
|
||||
- matching_mode: strict
|
||||
url: https://jellyfin.k8s.syd1.au.unkin.net/sso/OID/redirect/authentik
|
||||
# Legacy plugin callback path, kept so older plugin builds also work.
|
||||
# cheeztv (kids) -- external + in-cluster hostnames
|
||||
- matching_mode: strict
|
||||
url: https://jellyfin.k8s.syd1.au.unkin.net/sso/OID/r/authentik
|
||||
url: https://cheeztv.unkin.net/sso/OID/redirect/authentik
|
||||
- matching_mode: strict
|
||||
url: https://cheeztv.k8s.syd1.au.unkin.net/sso/OID/redirect/authentik
|
||||
|
||||
@@ -169,12 +169,24 @@ resource "authentik_provider_oauth2" "this" {
|
||||
grant_types = each.value.grant_types
|
||||
}
|
||||
|
||||
# Resolve LDAP bind/unbind flows by slug (mirrors the oauth2/saml handling) so
|
||||
# configs reference human-readable flow slugs instead of Authentik UUIDs.
|
||||
data "authentik_flow" "ldap_bind" {
|
||||
for_each = var.providers_ldap
|
||||
slug = each.value.bind_flow
|
||||
}
|
||||
|
||||
data "authentik_flow" "ldap_unbind" {
|
||||
for_each = var.providers_ldap
|
||||
slug = each.value.unbind_flow
|
||||
}
|
||||
|
||||
resource "authentik_provider_ldap" "this" {
|
||||
for_each = var.providers_ldap
|
||||
|
||||
name = each.value.name
|
||||
bind_flow = each.value.bind_flow
|
||||
unbind_flow = each.value.unbind_flow
|
||||
bind_flow = data.authentik_flow.ldap_bind[each.key].id
|
||||
unbind_flow = data.authentik_flow.ldap_unbind[each.key].id
|
||||
base_dn = each.value.base_dn
|
||||
certificate = each.value.certificate
|
||||
tls_server_name = each.value.tls_server_name
|
||||
|
||||
Reference in New Issue
Block a user