From dba4d270ffeca6290b7854e24f1266411fb4113b Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Mon, 8 Dec 2025 23:23:21 -0300 Subject: [PATCH] sso: fix vault OIDC bootstrap and render zot oidc config --- services/vault/kustomization.yaml | 1 + services/vault/oauth2-proxy-vault.yaml | 4 +- services/vault/oidc-bootstrap-job.yaml | 79 ++++++++++++++++++++++++++ services/zot/configmap.yaml | 20 ++++++- services/zot/deployment.yaml | 37 +++++++++--- 5 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 services/vault/oidc-bootstrap-job.yaml diff --git a/services/vault/kustomization.yaml b/services/vault/kustomization.yaml index 4c0f07e..41d9ef4 100644 --- a/services/vault/kustomization.yaml +++ b/services/vault/kustomization.yaml @@ -10,3 +10,4 @@ resources: - middleware.yaml - serverstransport.yaml - oauth2-proxy-vault.yaml + - oidc-bootstrap-job.yaml diff --git a/services/vault/oauth2-proxy-vault.yaml b/services/vault/oauth2-proxy-vault.yaml index e79a142..75e87bc 100644 --- a/services/vault/oauth2-proxy-vault.yaml +++ b/services/vault/oauth2-proxy-vault.yaml @@ -61,8 +61,8 @@ spec: - --cookie-refresh=20m - --cookie-expire=168h - --insecure-oidc-allow-unverified-email=true - - --upstream=https://vault-ui.vault.svc.cluster.local:8200 - - --ssl-insecure-skip-verify=true + # Vault UI is served over HTTP on the service endpoint. + - --upstream=http://vault.vault.svc.cluster.local:8200 - --http-address=0.0.0.0:4180 - --skip-provider-button=true - --skip-jwt-bearer-tokens=true diff --git a/services/vault/oidc-bootstrap-job.yaml b/services/vault/oidc-bootstrap-job.yaml new file mode 100644 index 0000000..059434a --- /dev/null +++ b/services/vault/oidc-bootstrap-job.yaml @@ -0,0 +1,79 @@ +# services/vault/oidc-bootstrap-job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: vault-oidc-bootstrap + namespace: vault + labels: + app: vault-oidc-bootstrap +spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 86400 + template: + metadata: + labels: + app: vault-oidc-bootstrap + spec: + restartPolicy: Never + containers: + - name: configure-oidc + image: hashicorp/vault:1.20.4 + imagePullPolicy: IfNotPresent + env: + - name: VAULT_ADDR + value: http://vault.vault.svc.cluster.local:8200 + - name: VAULT_TOKEN + valueFrom: + secretKeyRef: + name: vault-oidc-admin-token + key: token + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: oauth2-proxy-vault-oidc + key: client_secret + - name: VAULT_CLIENT_TIMEOUT + value: "30s" + command: + - /bin/sh + - -c + - | + set -euo pipefail + vault status + # Enable OIDC auth (idempotent) + vault auth enable oidc >/dev/null 2>&1 || vault auth tune -description="Keycloak OIDC" oidc + + # Configure Keycloak OIDC + vault write auth/oidc/config \ + oidc_discovery_url="https://sso.bstein.dev/realms/atlas" \ + oidc_client_id="oauth2-proxy" \ + oidc_client_secret="$OIDC_CLIENT_SECRET" \ + default_role="admin" \ + bound_issuer="https://sso.bstein.dev/realms/atlas" \ + allowed_redirect_uris="https://secret.bstein.dev/ui/vault/auth/oidc/oidc/callback" + + # Admin policy (wide permissions) + vault policy write vault-admin - <<'EOF' + path "*" { + capabilities = ["create", "read", "update", "delete", "list", "sudo"] + } + EOF + + # Role mapping admin group -> vault-admin policy + cat >/tmp/role.json <<'EOF' + { + "user_claim": "sub", + "groups_claim": "groups", + "bound_audiences": "oauth2-proxy", + "allowed_redirect_uris": "https://secret.bstein.dev/ui/vault/auth/oidc/oidc/callback", + "claim_mappings": { + "email": "email", + "name": "name" + }, + "token_policies": ["vault-admin"], + "oidc_scopes": ["profile", "email", "groups"], + "bound_claims": { "groups": ["admin"] } + } + EOF + vault write auth/oidc/role/admin @/tmp/role.json + echo "vault OIDC bootstrap complete" diff --git a/services/zot/configmap.yaml b/services/zot/configmap.yaml index 0261fc1..83a4b33 100644 --- a/services/zot/configmap.yaml +++ b/services/zot/configmap.yaml @@ -20,20 +20,34 @@ data: "realm": "zot-registry", "compat": ["docker2s2"], "auth": { - "htpasswd": { "path": "/etc/zot/htpasswd" } + "openid": { + "providers": [ + { + "name": "keycloak", + "issuer": "https://sso.bstein.dev/realms/atlas", + "clientID": "zot", + "clientSecret": "__CLIENT_SECRET__", + "scopes": ["openid", "profile", "email", "groups"], + "redirectURL": "https://registry.bstein.dev/oidc/callback", + "adminGroup": "admin", + "groupClaim": "groups" + } + ] + } }, + "externalUrl": "https://registry.bstein.dev", "accessControl": { "repositories": { "**": { "policies": [ - { "users": ["bstein"], "actions": ["read", "create", "update", "delete"] } + { "groups": ["admin", "image-pusher"], "actions": ["read", "create", "update", "delete"] } ], "defaultPolicy": [], "anonymousPolicy": [] } }, "adminPolicy": { - "users": ["bstein"], + "groups": ["admin"], "actions": ["read", "create", "update", "delete"] } } diff --git a/services/zot/deployment.yaml b/services/zot/deployment.yaml index e4fdc1f..bcf7c8d 100644 --- a/services/zot/deployment.yaml +++ b/services/zot/deployment.yaml @@ -38,14 +38,10 @@ spec: ports: - { name: http, containerPort: 5000 } volumeMounts: - - name: cfg + - name: cfg-rendered mountPath: /etc/zot/config.json subPath: config.json readOnly: true - - name: htpasswd - mountPath: /etc/zot/htpasswd - subPath: htpasswd - readOnly: true - name: zot-data mountPath: /var/lib/registry readinessProbe: @@ -60,13 +56,36 @@ spec: periodSeconds: 10 resources: requests: { cpu: "50m", memory: "64Mi" } + initContainers: + - name: render-config + image: busybox:1.36 + command: + - /bin/sh + - -c + - | + set -eu + if [ -z "${ZOT_CLIENT_SECRET:-}" ]; then + echo "ZOT_CLIENT_SECRET is empty; ensure zot-oidc-client secret exists" >&2 + exit 1 + fi + sed "s|__CLIENT_SECRET__|${ZOT_CLIENT_SECRET}|g" /config-src/config.json > /config/config.json + env: + - name: ZOT_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: zot-oidc-client + key: client_secret + volumeMounts: + - name: cfg-src + mountPath: /config-src + - name: cfg-rendered + mountPath: /config volumes: - - name: cfg + - name: cfg-src configMap: name: zot-config - - name: htpasswd - secret: - secretName: zot-htpasswd + - name: cfg-rendered + emptyDir: {} - name: zot-data persistentVolumeClaim: claimName: zot-data