From 405fc59c122188b3c540d109fcd0247f64091a93 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Thu, 5 Mar 2026 09:07:25 -0800 Subject: [PATCH] Add Authentik OIDC login for ArgoCD (#284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add Authentik OAuth2 provider + application blueprint for ArgoCD (ringtail side) - Add OIDC config to ArgoCD ConfigMap with Authentik as identity provider (indri side) - Map Authentik `admins` group to ArgoCD `role:admin` via RBAC policy - ExternalSecrets on both sides pull `argocd-client-secret` from 1Password - Local admin password remains as break-glass — both login methods coexist ## Pre-deployment manual step Add `argocd-client-secret` field to "Authentik (blumeops)" in 1Password with a random value (e.g., `openssl rand -hex 32`). ## Deployment order 1. Sync Authentik app on ringtail first (blueprint + secret + worker env var) 2. Sync ArgoCD app on indri second (cm, rbac, ExternalSecret) ## Verification - [ ] `argocd-client-secret` field added to 1Password - [ ] Authentik app synced on ringtail — blueprint applied, provider created - [ ] ArgoCD app synced on indri — OIDC config applied - [ ] SSO login works: visit `https://argocd.ops.eblu.me` → "Log in via Authentik" → admin access - [ ] Break-glass: local admin/password login still works Reviewed-on: https://forge.eblu.me/eblume/blumeops/pulls/284 --- argocd/manifests/argocd/argocd-cm-patch.yaml | 16 ++++- .../argocd/argocd-rbac-cm-patch.yaml | 8 ++- .../external-secret-oidc-authentik.yaml | 28 +++++++++ argocd/manifests/argocd/kustomization.yaml | 1 + .../authentik/configmap-blueprint.yaml | 58 +++++++++++++++++++ .../authentik/deployment-worker.yaml | 5 ++ .../manifests/authentik/external-secret.yaml | 4 ++ .../feature-argocd-authentik-oidc.feature.md | 1 + 8 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 argocd/manifests/argocd/external-secret-oidc-authentik.yaml create mode 100644 docs/changelog.d/feature-argocd-authentik-oidc.feature.md diff --git a/argocd/manifests/argocd/argocd-cm-patch.yaml b/argocd/manifests/argocd/argocd-cm-patch.yaml index 5df2f41..cb7e27f 100644 --- a/argocd/manifests/argocd/argocd-cm-patch.yaml +++ b/argocd/manifests/argocd/argocd-cm-patch.yaml @@ -1,13 +1,23 @@ -# ArgoCD ConfigMap patch for workflow-bot account +# ArgoCD ConfigMap patch # -# Creates a service account that can generate API tokens for CI/CD workflows. -# Account is used by forgejo-runner to sync apps after builds. +# - workflow-bot: service account for CI/CD automation +# - OIDC: Authentik SSO for admin login # apiVersion: v1 kind: ConfigMap metadata: name: argocd-cm data: + url: https://argocd.ops.eblu.me # workflow-bot: service account for CI/CD automation # - apiKey: allows generating API tokens via `argocd account generate-token` accounts.workflow-bot: apiKey + oidc.config: | + name: Authentik + issuer: https://authentik.ops.eblu.me/application/o/argocd/ + clientID: argocd + clientSecret: $argocd-oidc-authentik:client-secret + requestedScopes: + - openid + - profile + - email diff --git a/argocd/manifests/argocd/argocd-rbac-cm-patch.yaml b/argocd/manifests/argocd/argocd-rbac-cm-patch.yaml index ba616ff..c2ea095 100644 --- a/argocd/manifests/argocd/argocd-rbac-cm-patch.yaml +++ b/argocd/manifests/argocd/argocd-rbac-cm-patch.yaml @@ -1,14 +1,16 @@ -# ArgoCD RBAC ConfigMap patch for workflow-bot permissions +# ArgoCD RBAC ConfigMap patch # -# Grants minimal permissions for CI/CD workflows: -# - applications: sync, get (for syncing apps after builds) +# - workflow-bot: minimal CI/CD permissions (sync, get) +# - admins: Authentik admins group mapped to ArgoCD admin role # apiVersion: v1 kind: ConfigMap metadata: name: argocd-rbac-cm data: + scopes: '[groups]' policy.csv: | p, role:workflow-bot, applications, sync, *, allow p, role:workflow-bot, applications, get, *, allow g, workflow-bot, role:workflow-bot + g, admins, role:admin diff --git a/argocd/manifests/argocd/external-secret-oidc-authentik.yaml b/argocd/manifests/argocd/external-secret-oidc-authentik.yaml new file mode 100644 index 0000000..776d88f --- /dev/null +++ b/argocd/manifests/argocd/external-secret-oidc-authentik.yaml @@ -0,0 +1,28 @@ +# ExternalSecret for ArgoCD OIDC client secret (Authentik) +# +# Referenced from argocd-cm as $argocd-oidc-authentik:client-secret +# Must have app.kubernetes.io/part-of: argocd label for ArgoCD to read it +# +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: argocd-oidc-authentik + namespace: argocd +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: onepassword-blumeops + target: + name: argocd-oidc-authentik + creationPolicy: Owner + template: + metadata: + labels: + app.kubernetes.io/part-of: argocd + data: + - secretKey: client-secret + remoteRef: + key: "Authentik (blumeops)" + property: argocd-client-secret diff --git a/argocd/manifests/argocd/kustomization.yaml b/argocd/manifests/argocd/kustomization.yaml index a829c7d..89b5970 100644 --- a/argocd/manifests/argocd/kustomization.yaml +++ b/argocd/manifests/argocd/kustomization.yaml @@ -8,6 +8,7 @@ resources: - https://raw.githubusercontent.com/argoproj/argo-cd/v3.3.2/manifests/install.yaml - ingress-tailscale.yaml - external-secret-repo-forge.yaml + - external-secret-oidc-authentik.yaml patches: - path: argocd-cmd-params-cm.yaml diff --git a/argocd/manifests/authentik/configmap-blueprint.yaml b/argocd/manifests/authentik/configmap-blueprint.yaml index e867c3a..f6ea4d6 100644 --- a/argocd/manifests/authentik/configmap-blueprint.yaml +++ b/argocd/manifests/authentik/configmap-blueprint.yaml @@ -246,6 +246,64 @@ data: negate: false timeout: 30 + argocd.yaml: | + version: 1 + metadata: + name: BlumeOps ArgoCD SSO + labels: + blueprints.goauthentik.io/description: "ArgoCD OIDC provider and application" + entries: + # OAuth2 provider for ArgoCD + - model: authentik_providers_oauth2.oauth2provider + id: argocd-provider + identifiers: + name: ArgoCD + attrs: + name: ArgoCD + 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: argocd + client_secret: !Env AUTHENTIK_ARGOCD_CLIENT_SECRET + redirect_uris: + - matching_mode: strict + url: https://argocd.ops.eblu.me/auth/callback + - matching_mode: strict + url: https://argocd.tail8d86e.ts.net/auth/callback + 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 + + # ArgoCD application — linked to the OAuth2 provider + - model: authentik_core.application + id: argocd-app + identifiers: + slug: argocd + attrs: + name: ArgoCD + slug: argocd + provider: !KeyOf argocd-provider + meta_launch_url: https://argocd.ops.eblu.me + policy_engine_mode: any + + # Policy binding — restrict ArgoCD to admins group + - model: authentik_policies.policybinding + identifiers: + order: 0 + target: !KeyOf argocd-app + group: !Find [authentik_core.group, [name, admins]] + attrs: + target: !KeyOf argocd-app + group: !Find [authentik_core.group, [name, admins]] + order: 0 + enabled: true + negate: false + timeout: 30 + jellyfin.yaml: | version: 1 metadata: diff --git a/argocd/manifests/authentik/deployment-worker.yaml b/argocd/manifests/authentik/deployment-worker.yaml index 14becb6..11d9e44 100644 --- a/argocd/manifests/authentik/deployment-worker.yaml +++ b/argocd/manifests/authentik/deployment-worker.yaml @@ -73,6 +73,11 @@ spec: secretKeyRef: name: authentik-config key: jellyfin-client-secret + - name: AUTHENTIK_ARGOCD_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: authentik-config + key: argocd-client-secret volumeMounts: - name: blueprints mountPath: /blueprints/custom diff --git a/argocd/manifests/authentik/external-secret.yaml b/argocd/manifests/authentik/external-secret.yaml index f0218b9..495eda8 100644 --- a/argocd/manifests/authentik/external-secret.yaml +++ b/argocd/manifests/authentik/external-secret.yaml @@ -53,3 +53,7 @@ spec: remoteRef: key: "Authentik (blumeops)" property: jellyfin-client-secret + - secretKey: argocd-client-secret + remoteRef: + key: "Authentik (blumeops)" + property: argocd-client-secret diff --git a/docs/changelog.d/feature-argocd-authentik-oidc.feature.md b/docs/changelog.d/feature-argocd-authentik-oidc.feature.md new file mode 100644 index 0000000..0ff0677 --- /dev/null +++ b/docs/changelog.d/feature-argocd-authentik-oidc.feature.md @@ -0,0 +1 @@ +Add Authentik OIDC login for ArgoCD — `eblume` (admins group) gets admin access via SSO while local admin password remains as break-glass.