Bake LDAP + SSO auth plugins into the image #8

Merged
benvin merged 3 commits from benvin/auth-plugins into main 2026-08-29 12:03:44 +10:00
Member

Why

Phase-1 plugin-based SSO / app-passwords for jellyfin: the server needs the LDAP-auth and SSO auth plugins present in the image rather than hand-installed via the in-app catalog, so the deployed version is reproducible and controlled by the image (the /config plugins PVC would otherwise let catalog-installed versions drift).

How

  • Add a plugins build stage that downloads, sha256-verifies (matching each release's published .sha256), and unpacks the plugin zips into versioned dirs baked at /usr/share/jellyfin/plugins-baked.
  • Pin the newest release of each plugin whose targetAbi is <= the pinned Jellyfin server version (10.11.6):
    • LDAP Authentication 22.0.0.0 — targetAbi 10.11.2.0 (v23 requires 10.11.9, so excluded).
    • SSO Authentication 4.0.0.4 — targetAbi 10.11.0.0 (also carries the Mar-2026 SAML security fix).
  • Add docker-entrypoint.sh that syncs the baked plugin dirs into /config/plugins on every start, removing any stale versioned dir of the same plugin first so the image owns the plugin version across restarts; user plugin configurations/ are preserved.
  • Point ENTRYPOINT at the new script (unchanged jellyfin args).

Validation

  • Built the plugins stage locally — both zips download and pass sha256sum -c (pins also match the manifest md5).
  • Simulated the entrypoint against a seeded stale LDAP Authentication_21.0.0.0 + a configurations/ dir: stale dir replaced by 22.0.0.0, SSO 4.0.0.4 installed, configurations preserved.

Release/tag comes after merge (per repo make patch flow).

## Why Phase-1 plugin-based SSO / app-passwords for jellyfin: the server needs the LDAP-auth and SSO auth plugins present in the image rather than hand-installed via the in-app catalog, so the deployed version is reproducible and controlled by the image (the `/config` plugins PVC would otherwise let catalog-installed versions drift). ## How - Add a `plugins` build stage that downloads, sha256-verifies (matching each release's published `.sha256`), and unpacks the plugin zips into versioned dirs baked at `/usr/share/jellyfin/plugins-baked`. - Pin the newest release of each plugin whose `targetAbi` is `<=` the pinned Jellyfin server version (10.11.6): - **LDAP Authentication 22.0.0.0** — targetAbi 10.11.2.0 (v23 requires 10.11.9, so excluded). - **SSO Authentication 4.0.0.4** — targetAbi 10.11.0.0 (also carries the Mar-2026 SAML security fix). - Add `docker-entrypoint.sh` that syncs the baked plugin dirs into `/config/plugins` on every start, removing any stale versioned dir of the same plugin first so the image owns the plugin version across restarts; user plugin `configurations/` are preserved. - Point `ENTRYPOINT` at the new script (unchanged jellyfin args). ## Validation - Built the `plugins` stage locally — both zips download and pass `sha256sum -c` (pins also match the manifest md5). - Simulated the entrypoint against a seeded stale `LDAP Authentication_21.0.0.0` + a `configurations/` dir: stale dir replaced by 22.0.0.0, SSO 4.0.0.4 installed, configurations preserved. Release/tag comes after merge (per repo `make patch` flow).
unkin-agent added 1 commit 2026-08-26 22:14:03 +10:00
Bake LDAP + SSO auth plugins into the image
ci/woodpecker/pr/build Pipeline failed
2001204e0b
Phase-1 SSO/app-passwords for jellyfin needs the ldapauth and sso plugins
present without relying on the in-app catalog (which the plugins-baked PVC
would otherwise let drift). Pin the newest release of each whose targetAbi
is <= the pinned server version (10.11.6) and let the image own the version.

- Add a plugins build stage that downloads, sha256-verifies (matching each
  release's published .sha256), and unpacks the plugin zips into versioned
  dirs baked at /usr/share/jellyfin/plugins-baked.
- LDAP Authentication 22.0.0.0 (targetAbi 10.11.2.0; v23 needs 10.11.9).
- SSO Authentication 4.0.0.4 (targetAbi 10.11.0.0).
- Add docker-entrypoint.sh that syncs baked plugin dirs into /config/plugins
  on every start, removing any stale versioned dir of the same plugin so the
  image controls the version across restarts; preserves plugin configurations.
- Point ENTRYPOINT at the new script.
Author
Member

Automated review — ISSUES found

CI is failing on head 2001204: ci/woodpecker/pr/build = failure (confirmed via Gitea commit-status, polled pending → running → failure). This contradicts the PR body's Validation section, which claims the plugins stage builds and the entrypoint simulation both succeed locally — the pipeline's dry-run docker build does not currently pass for this diff. Please check the pipeline run and push a fix; not re-reviewing content correctness until this is green.

For the record, everything I could verify independently on the content itself checks out clean:

  • Version/ABI pins: LDAP Authentication 22.0.0.0 (targetAbi 10.11.2.0) and SSO Authentication 4.0.0.4 (targetAbi 10.11.0.0) are each the newest release with targetAbi ≤ the pinned server version 10.11.6 — confirmed against the live repo.jellyfin.org manifest and the 9p4 manifest-release feed (LDAP 23.0.0.0 correctly excluded, needs 10.11.9).
  • Checksums: downloaded both zips directly and hashed them — both match the Dockerfile's sha256 ARGs byte-for-byte. The SSO hash also matches GitHub's own computed asset digest, and the LDAP hash's md5 matches repo.jellyfin.org's manifest checksum.
  • Zip/dir layout: both zips are flat (meta.json + dlls at the archive root, no nested folder), so unzip -d "<Name>_<version>" produces the correct Name_version/meta.json-at-root layout Jellyfin expects.
  • Fail-closed build: sha256sum -c - runs under set -eu with no error-swallowing, so a checksum mismatch aborts the build stage.
  • Entrypoint glob safety: simulated docker-entrypoint.sh against an adversarial seed (stale LDAP Authentication_21.0.0.0, a decoy LDAP Authentication Helper_1.0.0.0, a configurations/ dir with user XML, and an already-current SSO dir). Stale version was removed, the decoy and configurations/ were untouched, SSO refreshed idempotently, and "$@" pass-through to exec ./jellyfin was preserved. The "$PLUGIN_DIR/$base"_* glob only matches paths starting with the exact plugin name plus a literal underscore, so it can't collide with same-prefix names.
  • Ownership: baked plugin files are root-owned in the image; tested cp -a as non-root uid 1000 (matching the StatefulSet's runAsUser: 1000/fsGroup: 1000) copying root-owned source into the uid-1000-owned /config — GNU cp -a degrades gracefully (exit 0, files end up owned by the invoking uid), no crash under set -eu.
  • ENTRYPOINT/CMD: old image had no CMD; the StatefulSet sets neither command: nor args: for the jellyfin container, so the new script's exec ./jellyfin ... "$@" reproduces the prior fixed invocation exactly — no risk to either HA replica from arg handling.
  • Diff is atomic and matches the PR body.

Once CI is green this looks mergeable from a content standpoint.

**Automated review — ISSUES found** CI is failing on head `2001204`: `ci/woodpecker/pr/build` = **failure** (confirmed via Gitea commit-status, polled pending → running → failure). This contradicts the PR body's Validation section, which claims the `plugins` stage builds and the entrypoint simulation both succeed locally — the pipeline's dry-run docker build does not currently pass for this diff. Please check the pipeline run and push a fix; not re-reviewing content correctness until this is green. For the record, everything I could verify independently on the content itself checks out clean: - **Version/ABI pins**: LDAP Authentication 22.0.0.0 (targetAbi 10.11.2.0) and SSO Authentication 4.0.0.4 (targetAbi 10.11.0.0) are each the newest release with targetAbi ≤ the pinned server version 10.11.6 — confirmed against the live repo.jellyfin.org manifest and the 9p4 manifest-release feed (LDAP 23.0.0.0 correctly excluded, needs 10.11.9). - **Checksums**: downloaded both zips directly and hashed them — both match the Dockerfile's `sha256` ARGs byte-for-byte. The SSO hash also matches GitHub's own computed asset digest, and the LDAP hash's md5 matches repo.jellyfin.org's manifest checksum. - **Zip/dir layout**: both zips are flat (meta.json + dlls at the archive root, no nested folder), so `unzip -d "<Name>_<version>"` produces the correct `Name_version/meta.json`-at-root layout Jellyfin expects. - **Fail-closed build**: `sha256sum -c -` runs under `set -eu` with no error-swallowing, so a checksum mismatch aborts the build stage. - **Entrypoint glob safety**: simulated `docker-entrypoint.sh` against an adversarial seed (stale `LDAP Authentication_21.0.0.0`, a decoy `LDAP Authentication Helper_1.0.0.0`, a `configurations/` dir with user XML, and an already-current SSO dir). Stale version was removed, the decoy and `configurations/` were untouched, SSO refreshed idempotently, and `"$@"` pass-through to `exec ./jellyfin` was preserved. The `"$PLUGIN_DIR/$base"_*` glob only matches paths starting with the exact plugin name plus a literal underscore, so it can't collide with same-prefix names. - **Ownership**: baked plugin files are root-owned in the image; tested `cp -a` as non-root uid 1000 (matching the StatefulSet's `runAsUser: 1000`/`fsGroup: 1000`) copying root-owned source into the uid-1000-owned `/config` — GNU `cp -a` degrades gracefully (exit 0, files end up owned by the invoking uid), no crash under `set -eu`. - **ENTRYPOINT/CMD**: old image had no `CMD`; the StatefulSet sets neither `command:` nor `args:` for the jellyfin container, so the new script's `exec ./jellyfin ... "$@"` reproduces the prior fixed invocation exactly — no risk to either HA replica from arg handling. - Diff is atomic and matches the PR body. Once CI is green this looks mergeable from a content standpoint.
unkin-agent added 1 commit 2026-08-26 23:29:39 +10:00
The PR build failed at the plugins stage: the CI build network can reach
artifactapi and package mirrors (repo.jellyfin.org) but not github.com
directly, so the SSO plugin download from github failed (curl exit 7).
Route the SSO fetch through the artifactapi github remote proxy instead;
SSO_SHA256 still pins the exact bytes. LDAP is unchanged.
Author
Member

CI root cause + fix: the ci/woodpecker/pr/build failure was in the new plugins stage. The CI build network reaches artifactapi and package mirrors (repo.jellyfin.org, so LDAP downloads fine) but cannot reach github.com directly, so the SSO plugin download failed with curl: (7) Failed to connect to github.com port 443. Reproduced locally by blackholing github: LDAP succeeds, SSO fails, exactly matching CI.

Fix (72290bb): route the SSO fetch through the artifactapi github remote proxy instead of github.com. SSO_SHA256 still pins the exact bytes (the proxy is a transparent byte proxy - verified an allowlisted asset has an identical sha256 via proxy vs direct github). LDAP is unchanged.

Cross-repo dependency: the github proxy rejects non-allowlisted paths (403), so this needs terraform-artifactapi#40 (allowlist 9p4/jellyfin-plugin-sso/*/sso-authentication_*.zip) merged + applied first. Until then this build will still fail at the SSO step with a 403.

CI root cause + fix: the `ci/woodpecker/pr/build` failure was in the new plugins stage. The CI build network reaches artifactapi and package mirrors (repo.jellyfin.org, so LDAP downloads fine) but cannot reach github.com directly, so the SSO plugin download failed with `curl: (7) Failed to connect to github.com port 443`. Reproduced locally by blackholing github: LDAP succeeds, SSO fails, exactly matching CI. Fix (72290bb): route the SSO fetch through the artifactapi github remote proxy instead of github.com. `SSO_SHA256` still pins the exact bytes (the proxy is a transparent byte proxy - verified an allowlisted asset has an identical sha256 via proxy vs direct github). LDAP is unchanged. Cross-repo dependency: the github proxy rejects non-allowlisted paths (403), so this needs terraform-artifactapi#40 (allowlist `9p4/jellyfin-plugin-sso/*/sso-authentication_*.zip`) merged + applied first. Until then this build will still fail at the SSO step with a 403.
unkin-agent added 1 commit 2026-08-29 12:00:07 +10:00
Replace direct repo.jellyfin.org download with artifactapi.k8s.syd1.au.unkin.net
remote. SHA256 pin guarantees integrity over HTTP. Both plugins now consistent
in sourcing from artifactapi infrastructure.
benvin merged commit 26d399b8d5 into main 2026-08-29 12:03:44 +10:00
benvin deleted branch benvin/auth-plugins 2026-08-29 12:03:44 +10:00
Sign in to join this conversation.