feat(registry): accept the Ory session in the zot UI #2119

Merged
pipeline-bot merged 1 commit from feat/zot-oidc-ui into trunk 2026-08-31 15:20:37 +00:00
Owner

Clicking Registry on the portal landed on a username/password form. Every other cluster UI accepts the Ory session, so the portal link dead-ended.

The cause: the live zot config had no http.auth.openid block. Three comments in this manifest recorded the wiring as deferred work. This is that flip — the shape is recovered from the retired XRegistry composition (platform-seanfarm, deleted 2026-07-23 in 74dad2d).

Rebased onto the zot-v2 storage migration (3cf59eb4/554f5cef): all edits re-anchor onto StatefulSet/zot-v2, and the guard now requires the registrar to stay wave-less (implicit wave 0: the Job mints zot-oidc alongside the StatefulSet, before the wave-1 Service switch).

What changes

  • hydra-zot-client-registrar registers the zot-platform Ory client and mints the zot-oidc Secret, following the argo-workflows / forgejo / argocd registrar pattern.
  • zot reads it via credentialsFile and runs the OIDC flow itself. No oathkeeper hop: an ext-auth hop on registry.sean.farm would gate /v2/ and break every pull.
  • sean@sean.farm joins adminPolicy — the identity zot derives from the email claim (verified against v2.1.15: username falls back to the userinfo email when no claimMapping is set).

What does not change

htpasswd, apikey and anonymousPolicy: ["read"] are untouched. No CI push, containerd mirror or bootc pull can complete an interactive redirect, so all /v2/ traffic keeps working exactly as before. tests/zot-binjovi-publisher.sh (including the new migration contract) passes unmodified.

Two details that are not cosmetic

secureSession is explicit. UseSecureSession auto-enables only when zot terminates TLS. zot serves plain HTTP behind the gateway, so without this the session cookie ships with no Secure attribute.

The registrar is a Sync hook, RBAC un-annotated, no waves. PreSync reads better and is a trap: this bundle declares its own Namespace, the Application sets CreateNamespace=false, and ArgoCD applies plain resources — and CreateNamespace — only after PreSync completes (argo-cd#4753, #3762); on a cold rebuild a PreSync registrar wedges the app permanently. A wave on the registrar would let the StatefulSet roll before its Secret exists. BeforeHookCreation means every registry sync re-runs the registrar and re-PUTs the Hydra client — idempotent, secret preserved by read-back, but each sync round-trips Hydra.

Deploy cost, honestly

This edits the pod template, so the singleton restarts once and rescans its repositories before serving — about 5 minutes live (that is what the 900s startup budget exists for, 32407d4e; the earlier "seconds" figure here covered only the Secret wait and was wrong). Do not merge mid-kernel-roll (the PDB comment explains why zot's host rolls last). Batching with #2121 (readiness-timeout fix) costs one restart instead of two.

Bootstrap edge

registry <- ory becomes a cold-rebuild ordering edge. It self-converges (the Job waits 60x5s for Hydra, backoffLimit: 20) and is not circular: scripts/k3s-create.sh keeps https://codeberg.org as the fallback endpoint behind registry.sean.farm.

Testing

  • tests/zot-oidc-ui.sh (new, CI-gated) — red on the parent commit, green here; guards the wiring, the untouched image path, the hook phase, and the wave-less registrar.
  • tests/check.sh — 143/143 guards pass, 0 skipped, on the rebased tree.
  • kubectl kustomize bundle/registry renders; zot-v2 carries the zot-oidc mount; the embedded config.json parses.

Not yet verified in-cluster: the browser login. Expect an "Ory" button on zot's login page, not the disappearance of that page — one click, no credentials, no consent screen. If login fails or lands read-only: kubectl -n registry logs zot-v2-0 | grep -i 'username\|callback\|claim' shows which username zot resolved; fix the adminPolicy literal or pin claimMapping.

https://claude.ai/code/session_019JVStQiFwbSLGHArU2sA4X

Clicking **Registry** on the portal landed on a username/password form. Every other cluster UI accepts the Ory session, so the portal link dead-ended. The cause: the live zot config had no `http.auth.openid` block. Three comments in this manifest recorded the wiring as deferred work. This is that flip — the shape is recovered from the retired XRegistry composition (`platform-seanfarm`, deleted 2026-07-23 in `74dad2d`). **Rebased onto the zot-v2 storage migration** (`3cf59eb4`/`554f5cef`): all edits re-anchor onto `StatefulSet/zot-v2`, and the guard now requires the registrar to stay wave-less (implicit wave 0: the Job mints `zot-oidc` alongside the StatefulSet, before the wave-1 Service switch). ### What changes - `hydra-zot-client-registrar` registers the `zot-platform` Ory client and mints the `zot-oidc` Secret, following the argo-workflows / forgejo / argocd registrar pattern. - zot reads it via `credentialsFile` and runs the OIDC flow **itself**. No oathkeeper hop: an ext-auth hop on `registry.sean.farm` would gate `/v2/` and break every pull. - `sean@sean.farm` joins `adminPolicy` — the identity zot derives from the `email` claim (verified against v2.1.15: username falls back to the userinfo email when no `claimMapping` is set). ### What does not change `htpasswd`, `apikey` and `anonymousPolicy: ["read"]` are untouched. No CI push, containerd mirror or bootc pull can complete an interactive redirect, so all `/v2/` traffic keeps working exactly as before. `tests/zot-binjovi-publisher.sh` (including the new migration contract) passes unmodified. ### Two details that are not cosmetic **`secureSession` is explicit.** `UseSecureSession` auto-enables only when zot terminates TLS. zot serves plain HTTP behind the gateway, so without this the session cookie ships with no `Secure` attribute. **The registrar is a `Sync` hook, RBAC un-annotated, no waves.** PreSync reads better and is a trap: this bundle declares its own Namespace, the Application sets `CreateNamespace=false`, and ArgoCD applies plain resources — and `CreateNamespace` — only **after** PreSync completes ([argo-cd#4753](https://github.com/argoproj/argo-cd/issues/4753), [#3762](https://github.com/argoproj/argo-cd/issues/3762)); on a cold rebuild a PreSync registrar wedges the app permanently. A wave on the registrar would let the StatefulSet roll before its Secret exists. `BeforeHookCreation` means every registry sync re-runs the registrar and re-PUTs the Hydra client — idempotent, secret preserved by read-back, but each sync round-trips Hydra. ### Deploy cost, honestly This edits the pod template, so **the singleton restarts once and rescans its repositories before serving — about 5 minutes live** (that is what the 900s startup budget exists for, `32407d4e`; the earlier "seconds" figure here covered only the Secret wait and was wrong). Do not merge mid-kernel-roll (the PDB comment explains why zot's host rolls last). Batching with #2121 (readiness-timeout fix) costs one restart instead of two. ### Bootstrap edge `registry <- ory` becomes a cold-rebuild ordering edge. It self-converges (the Job waits 60x5s for Hydra, `backoffLimit: 20`) and is **not** circular: `scripts/k3s-create.sh` keeps `https://codeberg.org` as the fallback endpoint behind `registry.sean.farm`. ### Testing - `tests/zot-oidc-ui.sh` (new, CI-gated) — red on the parent commit, green here; guards the wiring, the untouched image path, the hook phase, and the wave-less registrar. - `tests/check.sh` — 143/143 guards pass, 0 skipped, on the rebased tree. - `kubectl kustomize bundle/registry` renders; `zot-v2` carries the `zot-oidc` mount; the embedded `config.json` parses. Not yet verified in-cluster: the browser login. **Expect an "Ory" button on zot's login page, not the disappearance of that page** — one click, no credentials, no consent screen. If login fails or lands read-only: `kubectl -n registry logs zot-v2-0 | grep -i 'username\|callback\|claim'` shows which username zot resolved; fix the `adminPolicy` literal or pin `claimMapping`. https://claude.ai/code/session_019JVStQiFwbSLGHArU2sA4X
feat(registry): accept the Ory session in the zot UI
All checks were successful
pipeline/ci CI green @ 5bd721c90a22
5bd721c90a
The registry UI asked for a username and password. Every other cluster UI
takes the Ory session, so the portal link into the registry dead-ended on a
credential form.

The cause was a missing http.auth.openid block. Three comments in this
manifest recorded the wiring as deferred work. This is that flip.

A hydra-zot-client-registrar Job registers the `zot-platform` client and
mints the zot-oidc Secret. zot consumes it through credentialsFile and does
the OIDC flow itself, so no oathkeeper hop is added; an ext-auth hop here
would gate /v2/ and break every pull.

The change is additive. htpasswd, apikey and anonymous read are untouched,
because no CI push, containerd mirror or bootc pull can complete an
interactive redirect. `sean@sean.farm` joins adminPolicy, which is the
identity zot derives from the email claim.

Two details that are not cosmetic:

secureSession is explicit. UseSecureSession auto-enables only when zot
terminates TLS, and zot serves plain HTTP behind the gateway, so the session
cookie would otherwise ship without the Secure attribute.

The registrar is a PreSync hook, and so is its RBAC. ArgoCD applies plain
resources in the Sync phase, which begins only after PreSync ends, so a
PreSync Job whose ServiceAccount is an ordinary resource could never
schedule. A Sync hook instead would race the StatefulSet, which at
replicas: 1 with OrderedReady deletes the old pod first, and a pod stranded
in ContainerCreating on a missing Secret takes the fleet's bootc pull path
down. PreSync fails closed: no Hydra, no sync, no outage.

This makes `registry <- ory` a new ordering edge for a cold rebuild. It is
not circular: scripts/k3s-create.sh keeps codeberg.org as the fallback
endpoint behind registry.sean.farm, so ory's own images still pull while zot
is absent.

tests/zot-oidc-ui.sh guards the wiring and the untouched image path. It is
red on the parent commit.

Claude-Session: https://claude.ai/code/session_019JVStQiFwbSLGHArU2sA4X
sean force-pushed feat/zot-oidc-ui from 5bd721c90a
All checks were successful
pipeline/ci CI green @ 5bd721c90a22
to b3e12b5bac
All checks were successful
pipeline/ci CI green @ b3e12b5bac48
2026-08-31 13:32:52 +00:00
Compare
sean force-pushed feat/zot-oidc-ui from b3e12b5bac
All checks were successful
pipeline/ci CI green @ b3e12b5bac48
to 2df7b06847
All checks were successful
pipeline/ci CI green @ 2df7b0684789
2026-08-31 14:12:04 +00:00
Compare
sean changed target branch from main to trunk 2026-08-31 14:53:58 +00:00
pipeline-bot force-pushed feat/zot-oidc-ui from 2df7b06847
All checks were successful
pipeline/ci CI green @ 2df7b0684789
to 77806d1d51
All checks were successful
pipeline/ci CI green @ 77806d1d51ea
2026-08-31 15:19:59 +00:00
Compare
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/pipelines!2119
No description provided.