Register Zot as OIDC client in Authentik (#236)

## Summary
- Add Authentik blueprint (`zot.yaml`) with OAuth2 provider, application, `artifact-workloads` group, and `zot-ci` service account
- Wire `zot-client-secret` through ExternalSecret → worker Deployment env var → blueprint `!Env`
- Add Ansible pre_task to fetch OIDC secret from 1Password (item ID `oor7os5kapczgpbwv7obkca4y4`)
- Add `oidc-credentials.json.j2` template and deploy task in zot role (with `when` guard)

## Manual Steps Required Before Deploy
1. Generate client secret: `openssl rand -hex 32`
2. Store in 1Password: add field `zot-client-secret` to "Authentik (blumeops)" item in vault `blumeops`

## What This Does NOT Do
- Does NOT modify `config.json.j2` (that's the root goal `harden-zot-registry`)
- Does NOT wire CI auth (that's `wire-ci-registry-auth`)
- Does NOT set service account password or API keys (manual post-deploy)

## Verification
After ArgoCD sync:
- [ ] Authentik admin UI shows "Zot Registry" application
- [ ] OIDC discovery at `https://authentik.ops.eblu.me/application/o/zot/.well-known/openid-configuration` returns valid JSON
- [ ] Blueprint status is `successful`
- [ ] `artifact-workloads` group exists with `zot-ci` service account

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/236
This commit is contained in:
Erich Blume 2026-02-21 08:45:06 -08:00
commit 21b6533aea
7 changed files with 116 additions and 0 deletions

View file

@ -116,6 +116,23 @@
no_log: true no_log: true
tags: [forgejo_actions_secrets] tags: [forgejo_actions_secrets]
# Zot OIDC client secret
- name: Fetch zot OIDC client secret
ansible.builtin.command:
cmd: op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/oor7os5kapczgpbwv7obkca4y4/zot-client-secret"
delegate_to: localhost
register: _zot_oidc_secret
changed_when: false
no_log: true
check_mode: false
tags: [zot]
- name: Set zot OIDC client secret fact
ansible.builtin.set_fact:
zot_oidc_client_secret: "{{ _zot_oidc_secret.stdout }}"
no_log: true
tags: [zot]
# Caddy Gandi token for ACME DNS-01 challenges # Caddy Gandi token for ACME DNS-01 challenges
- name: Fetch Gandi PAT for Caddy - name: Fetch Gandi PAT for Caddy
ansible.builtin.command: ansible.builtin.command:

View file

@ -46,6 +46,14 @@
mode: '0644' mode: '0644'
notify: Restart zot notify: Restart zot
- name: Deploy zot OIDC credentials
ansible.builtin.template:
src: oidc-credentials.json.j2
dest: "{{ zot_config_dir }}/oidc-credentials.json"
mode: '0600'
notify: Restart zot
when: zot_oidc_client_secret is defined
- name: Deploy zot LaunchAgent plist - name: Deploy zot LaunchAgent plist
ansible.builtin.template: ansible.builtin.template:
src: zot.plist.j2 src: zot.plist.j2

View file

@ -0,0 +1,4 @@
{
"clientid": "zot",
"clientsecret": "{{ zot_oidc_client_secret }}"
}

View file

@ -154,3 +154,80 @@ data:
enabled: true enabled: true
negate: false negate: false
timeout: 30 timeout: 30
zot.yaml: |
version: 1
metadata:
name: BlumeOps Zot SSO
labels:
blueprints.goauthentik.io/description: "Zot OIDC provider, application, and CI identity"
entries:
# artifact-workloads group (CI push identity)
- model: authentik_core.group
id: artifact-workloads-group
identifiers:
name: artifact-workloads
attrs:
name: artifact-workloads
# Service account for CI push (admin sets password via UI after deploy)
- model: authentik_core.user
id: zot-ci-user
identifiers:
username: zot-ci
attrs:
username: zot-ci
name: Zot CI Service Account
type: service_account
is_active: true
groups:
- !KeyOf artifact-workloads-group
# OAuth2 provider for Zot
- model: authentik_providers_oauth2.oauth2provider
id: zot-provider
identifiers:
name: Zot
attrs:
name: Zot
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
client_type: confidential
client_id: zot
client_secret: !Env AUTHENTIK_ZOT_CLIENT_SECRET
redirect_uris:
- matching_mode: strict
url: https://registry.ops.eblu.me/zot/auth/callback/oidc
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
sub_mode: hashed_user_id
include_claims_in_id_token: true
# Zot application — linked to the OAuth2 provider
- model: authentik_core.application
id: zot-app
identifiers:
slug: zot
attrs:
name: Zot Registry
slug: zot
provider: !KeyOf zot-provider
meta_launch_url: https://registry.ops.eblu.me
policy_engine_mode: any
# Policy binding — restrict Zot to admins group
- model: authentik_policies.policybinding
identifiers:
order: 0
target: !KeyOf zot-app
group: !Find [authentik_core.group, [name, admins]]
attrs:
target: !KeyOf zot-app
group: !Find [authentik_core.group, [name, admins]]
order: 0
enabled: true
negate: false
timeout: 30

View file

@ -63,6 +63,11 @@ spec:
secretKeyRef: secretKeyRef:
name: authentik-config name: authentik-config
key: forgejo-client-secret key: forgejo-client-secret
- name: AUTHENTIK_ZOT_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: authentik-config
key: zot-client-secret
volumeMounts: volumeMounts:
- name: blueprints - name: blueprints
mountPath: /blueprints/custom mountPath: /blueprints/custom

View file

@ -45,3 +45,7 @@ spec:
remoteRef: remoteRef:
key: "Authentik (blumeops)" key: "Authentik (blumeops)"
property: forgejo-client-secret property: forgejo-client-secret
- secretKey: zot-client-secret
remoteRef:
key: "Authentik (blumeops)"
property: zot-client-secret

View file

@ -0,0 +1 @@
Register Zot as an OIDC client in Authentik via blueprint, with artifact-workloads group, zot-ci service account, and OIDC credentials template for Ansible deployment.