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:
Frank committed 2026-11-04 12:25:57 -08:00
1 parent 146c28da9f
commit 70be24e9d5
3 files changed
+81 -41

No files matched your search

+41 -10
View File
@@ -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