From 281ffb7c0c18c25d19463f685dbec99896e7b458 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 21 Feb 2026 12:05:25 -0800 Subject: [PATCH] Update zot API key 1Password path and add rotation docs - Fix op read path to use Forgejo Secrets item field zot-ci-api (was zot-ci-apikey/credential) - Rewrite zot reference card security model for OIDC + API key auth - Add API key rotation procedure with impersonation steps and op oneliner - Document 90-day key expiry in wire-ci-registry-auth how-to Co-Authored-By: Claude Opus 4.6 --- ansible/playbooks/indri.yml | 2 +- docs/how-to/zot/wire-ci-registry-auth.md | 4 ++-- docs/reference/services/zot.md | 26 +++++++++++++++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index 25271b8..c2bb67a 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -110,7 +110,7 @@ - name: Fetch Zot CI API key for Forgejo Actions ansible.builtin.command: - cmd: op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/zot-ci-apikey/credential" + cmd: op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/w3663ffnvkewbftncqxtcpeavy/zot-ci-api" delegate_to: localhost register: _zot_ci_api_key changed_when: false diff --git a/docs/how-to/zot/wire-ci-registry-auth.md b/docs/how-to/zot/wire-ci-registry-auth.md index 2857918..cce0655 100644 --- a/docs/how-to/zot/wire-ci-registry-auth.md +++ b/docs/how-to/zot/wire-ci-registry-auth.md @@ -16,7 +16,7 @@ How CI pipelines authenticate to the zot registry after OIDC + apikey auth is en The `zot-ci` service account (created in [[register-zot-oidc-client]]) belongs to the `artifact-workloads` group, granting `["read", "create"]` permissions — CI can push new tags but cannot overwrite or delete existing ones. -Authentication uses a zot API key generated after the service account's first OIDC login. The key is stored in 1Password (`zot-ci-apikey` item in blumeops vault) and synced to Forgejo Actions secrets via the `forgejo_actions_secrets` ansible role. +Authentication uses a zot API key generated after the service account's first OIDC login. The key is stored in 1Password (`Forgejo Secrets` item, field `zot-ci-api`, in blumeops vault) and synced to Forgejo Actions secrets via the `forgejo_actions_secrets` ansible role. The key expires every 90 days — see [[zot#API Key Rotation]] for the rotation procedure. ## Push Paths @@ -30,7 +30,7 @@ Authentication uses a zot API key generated after the service account's first OI ## Secret Flow -1Password item `zot-ci-apikey` → ansible pre_task fetches it → `forgejo_actions_secrets` role syncs to Forgejo API → both runners (k8s on indri, host on ringtail) access it as `${{ secrets.ZOT_CI_API_KEY }}`. +1Password `Forgejo Secrets` item (field `zot-ci-api`) → ansible pre_task fetches it → `forgejo_actions_secrets` role syncs to Forgejo API → both runners (k8s on indri, host on ringtail) access it as `${{ secrets.ZOT_CI_API_KEY }}`. ## Key Files diff --git a/docs/reference/services/zot.md b/docs/reference/services/zot.md index 672440d..8cfc3f5 100644 --- a/docs/reference/services/zot.md +++ b/docs/reference/services/zot.md @@ -35,9 +35,33 @@ When [[cluster|minikube]] pulls an image, containerd checks zot first. If cached ## Security Model -Network access only (no authentication). Defense is the Tailscale ACL boundary. +OIDC authentication via [[authentik]], with API key support for CI. Three-tier access control: + +| Role | Permissions | Use case | +|------|------------|----------| +| Anonymous | read | Pull images without auth | +| `artifact-workloads` group | read, create | CI push (new tags only, no overwrite/delete) | +| `admins` group | read, create, update, delete | Break-glass admin access | + +CI authenticates with a zot API key generated from the `zot-ci` service account's OIDC session. The key is stored in the `Forgejo Secrets` 1Password item (field `zot-ci-api`) and synced to Forgejo Actions secrets via ansible. + +## API Key Rotation + +The `zot-ci` API key expires every **90 days**. To rotate: + +1. In Authentik admin UI, impersonate the `zot-ci` user +2. Visit `https://registry.ops.eblu.me` — you'll land on the login page +3. Click "SIGN IN WITH OIDC" to authenticate as zot-ci +4. Navigate to `https://registry.ops.eblu.me/user/apikey` +5. Generate a new API key, copy it to clipboard +6. Update 1Password: + ```fish + pbpaste | op item edit "Forgejo Secrets" --vault blumeops "zot-ci-api[password]=-" + ``` +7. Sync to Forgejo: `mise run provision-indri -- --tags forgejo_actions_secrets` ## Related - [[forgejo]] - Container build CI - [[cluster|Cluster]] - Registry consumer +- [[authentik]] - OIDC identity provider