docs(jellyfin,authentik): the LDAP admin filter is inert; the auth door lives in Users
Diagnosed 'authentik login does not work in Jellyfin' 2026-11-04: nothing was broken. The Jellyfin admin account 'dank' is a LOCAL-provider account, and the operator's authentik identity (akadmin) was not in jellyfin_users, so the plugin search found nothing and Jellyfin fell back to local auth - same UI message, opposite fix. akadmin is now in jellyfin_users (directory data, DB-owned). - Jellyfin: the admin-filter law (DN match, so a group-OU base grants nothing; memberOf shape is the working one), the AuthenticationProviderId diagnostic, re-verified LDAP path (bind + memberOf search from Dankserver). - Authentik Outpost Reference: settle the token-intent argument (the plugin sends a password, never a token), verify the bind DN live, and record the instruments/traps: the outpost journal never shows a bind, Event is keyed by created/action, Outpost has no apps, and piping ak shell steals its heredoc. - Authentik: stop restating the LDAP outpost digest - the unit file owns it.
This commit is contained in:
1 parent
146c28da9f
commit
70be24e9d5
3 files changed
+81
-41
No files matched your search
@@ -188,7 +188,7 @@ Client access paths and the public-exposure policy (auth wall required before
|
||||
any public vhost; Plex-style claim tokens do not exist here — Jellyfin uses its
|
||||
own users/API keys) follow the fleet [[Authentik]]/[[Anubis]] chain pattern.
|
||||
|
||||
## Authentik LDAP authentication (2026-09-25)
|
||||
## Authentik LDAP authentication (live since 2026-09-25, re-verified 2026-11-04)
|
||||
|
||||
Jellyfin has no native external-auth, so it authenticates users through the
|
||||
**LDAP-Auth plugin** (`jellyfin-plugin-ldapauth`) against an [[Authentik]] LDAP
|
||||
@@ -204,17 +204,35 @@ user to verify the password.
|
||||
publishing `389`/`636` on the Dankclaw host for cross-host binds.
|
||||
- Service account `svc-jellyfin-ldap` (DN
|
||||
`cn=svc-jellyfin-ldap,ou=users,dc=franksdank,dc=cloud`).
|
||||
- Access groups `jellyfin_users` (search filter
|
||||
`(memberOf=cn=jellyfin_users,ou=groups,dc=franksdank,dc=cloud)`) and
|
||||
`jellyfin_admins` (admin filter `(cn=jellyfin_admins)`). Add a user to
|
||||
`jellyfin_users` to grant them Jellyfin access; `jellyfin_admins` grants
|
||||
the Jellyfin admin role.
|
||||
- Access group `jellyfin_users`: the plugin's search filter is
|
||||
`(memberOf=cn=jellyfin_users,ou=groups,dc=franksdank,dc=cloud)`, so a user
|
||||
who is not in that group is **invisible** to the plugin and the login fails
|
||||
as "Wrong username or password". Add members with
|
||||
`Group.objects.get(name="jellyfin_users").users.add(user)` in `ak shell`
|
||||
(directory data — the DB owns it, not this repo).
|
||||
`jellyfin_admins` is **inert as configured** — see the admin-filter law.
|
||||
|
||||
**Jellyfin side** (plugin config, set via admin → Plugins → LDAP-Auth):
|
||||
- Server `192.168.0.143:389` (plain LDAP over the trusted LAN), Base DN
|
||||
`dc=franksdank,dc=cloud`, bind user = the service-account DN, search
|
||||
attributes `uid,cn,mail,displayName`, uid attr `uid`, username attr `cn`
|
||||
`CreateUsersFromLdap=true`, all folders enabled.
|
||||
- Admin base `ou=groups,dc=franksdank,dc=cloud`, admin filter
|
||||
`(cn=jellyfin_admins)`, `EnableLdapAdminFilterMemberUid=false`.
|
||||
|
||||
**Law — the admin filter grants nothing unless it can return the user's own
|
||||
DN.** With `EnableLdapAdminFilterMemberUid=false` the plugin searches
|
||||
`LdapAdminBaseDn` with `LdapAdminFilter` and sets the admin permission only when
|
||||
a returned entry's DN equals the bound user's DN
|
||||
(`LDAP-Auth/LDAPAuthenticationProviderPlugin.cs`). A group-OU base returns the
|
||||
*group* entry, whose DN never matches, so **no LDAP user can ever become a
|
||||
Jellyfin admin** — measured 2026-11-04: the search answers
|
||||
`cn=jellyfin_admins,ou=groups,…` for every login. Working shape if group-granted
|
||||
admins are ever wanted: base `dc=franksdank,dc=cloud` +
|
||||
`(memberOf=cn=jellyfin_admins,ou=groups,dc=franksdank,dc=cloud)`. Deliberate
|
||||
today: the server's own admin account stays **local**, which is the break-glass
|
||||
door when authentik or the outpost is down. A *malformed* admin filter is not
|
||||
inert — the plugin throws for every login, so this knob is all-or-nothing.
|
||||
|
||||
**Gotcha — the bind credential is a PASSWORD, not a token.** The LDAP bind is
|
||||
verified by authentik's default authentication flow (standard-password
|
||||
@@ -224,10 +242,23 @@ password and the bind fails with `flow error password: Invalid`. The
|
||||
service-account password is stored only in the Jellyfin plugin config (the
|
||||
`LdapBindPassword` field) — it is never committed to this repo.
|
||||
|
||||
**Verification (2026-09-25):** `ldapsearch` bind + the `jellyfin_users`
|
||||
search both succeed from Dankclaw; a real user (`alex`, in `jellyfin_users`)
|
||||
logged into the web UI with their authentik password and the plugin created
|
||||
the matching Jellyfin user.
|
||||
**Verification (re-taken 2026-11-04, from Dankserver):** the container reaches
|
||||
`192.168.0.143:389`; the service-account bind plus the `jellyfin_users` search
|
||||
return every member; LDAP-Auth 23.0.0.0 loads on Jellyfin 12.4; the `alex` row
|
||||
still carries
|
||||
`AuthenticationProviderId=Jellyfin.Plugin.LDAP_Auth.LdapAuthenticationProviderPlugin`.
|
||||
|
||||
**Law — "the authentik login doesn't work" has two opposite causes; read the
|
||||
Users table before touching authentik.** Copy `config/data/jellyfin.db` with its
|
||||
`-wal`/`-shm` and read it read-only (never sqlite the live file):
|
||||
`Users.AuthenticationProviderId` names the door — `…Users.DefaultAuthentication
|
||||
Provider` = local password, `…LDAP_Auth.LdapAuthenticationProviderPlugin` =
|
||||
authentik. Because the LDAP search is scoped to `jellyfin_users`, a name that
|
||||
exists only as a local account is rejected by the plugin and Jellyfin then
|
||||
silently falls back to the local provider: same UI message, opposite fix. The
|
||||
instrument for "did a bind ever reach authentik" is the **event registry**, never
|
||||
the outpost journal — see [[Authentik Outpost Reference]].
|
||||
|
||||
**Gotcha — the login endpoint changed in Jellyfin 12.1.** The legacy
|
||||
`POST /Users/Auth/Password` route (and its `LegacyEmbyRouteRewriteMiddleware`)
|
||||
was removed; probing it now returns a silent 401 with no plugin log, which
|
||||
|
||||
Reference in new issue
Block a user