From d9a4c164817fd00cb93a37ded012c2c4d708502f Mon Sep 17 00:00:00 2001 From: Scooby Husky Date: Thu, 20 Aug 2026 20:04:22 -0500 Subject: [PATCH] Move all 8 Authentik OIDC client_secrets out of plaintext git Completes the 'make the repo proper' cleanup from earlier this session - these were flagged but deliberately not touched in 185e9c2 given the blast radius (live SSO for 8 apps). User confirmed: fix all 8 now. infrastructure/authentik/{argocd,gitlab,nextcloud,grafana,n8n,guacamole, rancher,vault}-blueprint.yaml: converted from plain ConfigMap (client_secret hardcoded) to ExternalSecret with a templated blueprint body (client_secret: "{{ .clientSecret }}") pulling from Vault. Chart already supports mounting blueprints from Secrets (blueprints.secrets, alongside blueprints.configMaps) - infrastructure/authentik/values.yaml updated to route these 8 there instead. For argocd/nextcloud/n8n/guacamole/rancher: Vault already had the matching value at secret/-oauth (the APP side was already Vault-backed via its own ExternalSecret) - the blueprint was the only remaining plaintext copy. For gitlab/grafana/vault: Vault had no copy at all yet - created secret/{gitlab,grafana,vault}-oauth with the EXISTING live values (not rotated - these are the actual working credentials right now, rotating would break login until every consumer is updated in lockstep, which is out of scope for a cleanup pass). Also fixed the OTHER plaintext copies that existed for these three specifically: - apps/gitlab/manifests/external-secret-oidc.yaml (new): replaces a manually kubectl-created, never-git-tracked gitlab-oidc-secret. - infrastructure/grafana/manifests/grafana-oauth-secret.yaml: was a plain Secret whose own comment said 'hardcoded from blueprint'. - infrastructure/vault/manifests/vault-init-{configmap,job}.yaml: this one COULDN'T be converted to the same ExternalSecret-from-Vault pattern - it's the PostSync Job that grants ESO's own Kubernetes-auth role in Vault, so ESO can't yet authenticate to pull anything from Vault at the point this script runs (genuinely circular). Sourced from a new vault-oidc-bootstrap Secret instead - created once manually (kubectl, not git, matching how Vault's own root/unseal material is already handled), independent of the ESO pipeline. Co-Authored-By: Claude Sonnet 5 --- .../manifests/external-secret-oidc.yaml | 31 +++ .../authentik/argocd-blueprint.yaml | 118 ++++++---- .../authentik/gitlab-blueprint.yaml | 123 ++++++---- .../authentik/grafana-blueprint.yaml | 212 ++++++++++-------- .../authentik/guacamole-blueprint.yaml | 168 ++++++++------ infrastructure/authentik/n8n-blueprint.yaml | 107 +++++---- .../authentik/nextcloud-blueprint.yaml | 124 +++++----- .../authentik/rancher-blueprint.yaml | 117 ++++++---- infrastructure/authentik/values.yaml | 18 +- infrastructure/authentik/vault-blueprint.yaml | 115 ++++++---- .../manifests/grafana-oauth-secret.yaml | 38 +++- .../vault/manifests/vault-init-configmap.yaml | 18 +- .../vault/manifests/vault-init-job.yaml | 13 ++ 13 files changed, 730 insertions(+), 472 deletions(-) create mode 100644 apps/gitlab/manifests/external-secret-oidc.yaml diff --git a/apps/gitlab/manifests/external-secret-oidc.yaml b/apps/gitlab/manifests/external-secret-oidc.yaml new file mode 100644 index 0000000..63d2e00 --- /dev/null +++ b/apps/gitlab/manifests/external-secret-oidc.yaml @@ -0,0 +1,31 @@ +# gitlab-oidc-secret was a manually kubectl-created plain Secret (never +# git-tracked, never rotatable, no record of what it was) - found and +# fixed 2026-08-20 alongside infrastructure/authentik/gitlab-blueprint.yaml +# (same value, same Vault path - see that file for the full story). +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: gitlab-oidc + namespace: gitlab +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: gitlab-oidc-secret + creationPolicy: Owner + template: + type: Opaque + data: + GITLAB_OIDC_CLIENT_ID: "{{ .clientId }}" + GITLAB_OIDC_CLIENT_SECRET: "{{ .clientSecret }}" + data: + - secretKey: clientId + remoteRef: + key: gitlab-oauth + property: client-id + - secretKey: clientSecret + remoteRef: + key: gitlab-oauth + property: client-secret diff --git a/infrastructure/authentik/argocd-blueprint.yaml b/infrastructure/authentik/argocd-blueprint.yaml index 90f5595..fdf9c39 100644 --- a/infrastructure/authentik/argocd-blueprint.yaml +++ b/infrastructure/authentik/argocd-blueprint.yaml @@ -1,49 +1,77 @@ --- -apiVersion: v1 -kind: ConfigMap +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20 (user: "make the repo proper and not have +# sensitive things in the files"). The value itself was already the +# genuine live secret ArgoCD's own OIDC client uses (apps/argocd/ +# manifests/external-secret.yaml pulls the same value from Vault at +# secret/argocd-oauth) - Vault was already the source of truth on the +# APP side, this blueprint (the Authentik PROVIDER side) just had its own +# separate hardcoded plaintext copy instead of also reading from Vault. +# +# Converted ConfigMap -> ExternalSecret (chart supports mounting +# blueprints from Secrets too - see blueprints.secrets in values.yaml). +# client_id stays inline - it's not a secret, same convention as every +# other blueprint here. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: authentik-blueprints-argocd namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - argocd.yaml: |- - version: 1 - metadata: - name: argocd-oidc - entries: - - model: authentik_providers_oauth2.oauth2provider - id: argocd-provider - state: present - identifiers: - name: ArgoCD - attrs: - name: ArgoCD - client_id: 3791058a7de278045e5ec36be29f7a33d635119b8c786f20c86b185139c28841 - client_secret: f787416822f5bf4c46fe9170e63d1bf9520bab7703d53341aaaec107c8c4ba253d96c30b70d23a1c0b5a401ae1ae4bfb5db06092d338e29e0b0f04cd29bc0b36 - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://argocd.kube.huskypup.net/auth/callback" - matching_mode: strict - 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]] - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "hours=1" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] - - - model: authentik_core.application - id: argocd-application - state: present - identifiers: - slug: argocd - attrs: - name: ArgoCD - slug: argocd - policy_engine_mode: any - provider: !KeyOf argocd-provider +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-argocd + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + argocd.yaml: |- + version: 1 + metadata: + name: argocd-oidc + entries: + - model: authentik_providers_oauth2.oauth2provider + id: argocd-provider + state: present + identifiers: + name: ArgoCD + attrs: + name: ArgoCD + client_id: 3791058a7de278045e5ec36be29f7a33d635119b8c786f20c86b185139c28841 + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://argocd.kube.huskypup.net/auth/callback" + matching_mode: strict + 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]] + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "hours=1" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] + + - model: authentik_core.application + id: argocd-application + state: present + identifiers: + slug: argocd + attrs: + name: ArgoCD + slug: argocd + policy_engine_mode: any + provider: !KeyOf argocd-provider + data: + - secretKey: clientSecret + remoteRef: + key: argocd-oauth + property: client-secret diff --git a/infrastructure/authentik/gitlab-blueprint.yaml b/infrastructure/authentik/gitlab-blueprint.yaml index 33f966b..50798f5 100644 --- a/infrastructure/authentik/gitlab-blueprint.yaml +++ b/infrastructure/authentik/gitlab-blueprint.yaml @@ -1,53 +1,78 @@ --- -apiVersion: v1 -kind: ConfigMap +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20 (user: "make the repo proper and not have +# sensitive things in the files"). Unlike argocd/n8n/etc, Vault had NO +# copy of this at all before this fix - the ONLY place it existed was +# here and in gitlab-oidc-secret (a manually kubectl-created Secret in +# the gitlab namespace, apps/gitlab/values.yaml:166 - also fixed +# alongside this). Created secret/gitlab-oauth in Vault with the existing +# live value (not rotated - this is GitLab's actual working OIDC +# credential right now, changing it would break login until both sides +# are updated in lockstep). +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: authentik-blueprints-gitlab namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - gitlab.yaml: |- - # yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json - version: 1 - metadata: - name: gitlab-oidc - entries: - # OAuth2/OIDC Provider for GitLab - - model: authentik_providers_oauth2.oauth2provider - id: gitlab-provider - state: present - identifiers: - name: GitLab - attrs: - name: GitLab - client_id: 70b9da5c8166cea0ba504a869824020bce6636 - client_secret: 2e9c0c5b7a8969eb782d08bf1bf3d50ad96f8a73d9c18d1e4141816aebe14a622e8064bf5a5bce835d80e89cdc5f4cc6cf4a8c8234556647dc8884888866bd7d - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://gitlab.kube.huskypup.net/users/auth/openid_connect/callback" - matching_mode: strict - enabled: true - 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]] - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "days=7" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, default]] - - # Application for GitLab - - model: authentik_core.application - id: gitlab-application - state: present - identifiers: - slug: gitlab - attrs: - name: GitLab - slug: gitlab - policy_engine_mode: any - provider: !KeyOf gitlab-provider +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-gitlab + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + gitlab.yaml: |- + # yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json + version: 1 + metadata: + name: gitlab-oidc + entries: + # OAuth2/OIDC Provider for GitLab + - model: authentik_providers_oauth2.oauth2provider + id: gitlab-provider + state: present + identifiers: + name: GitLab + attrs: + name: GitLab + client_id: 70b9da5c8166cea0ba504a869824020bce6636 + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://gitlab.kube.huskypup.net/users/auth/openid_connect/callback" + matching_mode: strict + enabled: true + 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]] + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "days=7" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, default]] + + # Application for GitLab + - model: authentik_core.application + id: gitlab-application + state: present + identifiers: + slug: gitlab + attrs: + name: GitLab + slug: gitlab + policy_engine_mode: any + provider: !KeyOf gitlab-provider + data: + - secretKey: clientSecret + remoteRef: + key: gitlab-oauth + property: client-secret diff --git a/infrastructure/authentik/grafana-blueprint.yaml b/infrastructure/authentik/grafana-blueprint.yaml index 1a4fe69..6f576f4 100644 --- a/infrastructure/authentik/grafana-blueprint.yaml +++ b/infrastructure/authentik/grafana-blueprint.yaml @@ -1,99 +1,121 @@ --- -apiVersion: v1 -kind: ConfigMap +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20. Unlike argocd/nextcloud, Vault had NO copy of +# this at all - the only places it existed were here AND in +# infrastructure/grafana/manifests/grafana-oauth-secret.yaml (also fixed +# alongside this, same Vault path). Created secret/grafana-oauth with the +# existing live value (not rotated - changing it would break login until +# both sides are updated together). +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: authentik-blueprints-grafana namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - grafana.yaml: |- - version: 1 - metadata: - name: grafana-oidc - entries: - # ============================================================================ - # AUTO-CREATE GRAFANA GROUPS - # ============================================================================ - # These groups control access levels in Grafana via OAuth role mapping: - # - Grafana Admins → Admin role (full access) - # - Grafana Editors → Editor role (can edit dashboards) - # - Grafana Viewers → Viewer role (read-only) - # - # AUTOMATIC ADMIN ACCESS: - # - Anyone in "authentik Admins" automatically gets Grafana Admin access - # - No manual configuration needed! - # ============================================================================ - - - model: authentik_core.group - id: grafana-admins-group - state: present - identifiers: - name: Grafana Admins - attrs: - name: Grafana Admins - is_superuser: false - # Note: authentik Admins are automatically granted access via role_attribute_path - # in Grafana configuration (see grafana/values.yaml) - - - model: authentik_core.group - id: grafana-editors-group - state: present - identifiers: - name: Grafana Editors - attrs: - name: Grafana Editors - is_superuser: false - - - model: authentik_core.group - id: grafana-viewers-group - state: present - identifiers: - name: Grafana Viewers - attrs: - name: Grafana Viewers - is_superuser: false - - # ============================================================================ - # GRAFANA OAUTH2 PROVIDER - # ============================================================================ - - - model: authentik_providers_oauth2.oauth2provider - id: grafana-provider - state: present - identifiers: - name: Grafana - attrs: - name: Grafana - client_id: bd03e9139dd2063c6c44c4d2f65f51d69de3ba0b6d6b1b9b41c255d2376d2dcc - client_secret: 6b6f2deecfe8fd56cae9c512cc71eedd463d67ff08f24c816b15b15e78ce36bc4f06a2276c5ffe3f67799d935994a32ed32cdc02ec674f279c82d7cfe3ca05d5 - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://grafana.kube.huskypup.net/login/generic_oauth" - matching_mode: strict - 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]] - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "hours=1" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, default]] - - # ============================================================================ - # GRAFANA APPLICATION - # ============================================================================ - - - model: authentik_core.application - id: grafana-application - state: present - identifiers: - slug: grafana - attrs: - name: Grafana - slug: grafana - policy_engine_mode: any - provider: !KeyOf grafana-provider +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-grafana + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + grafana.yaml: |- + version: 1 + metadata: + name: grafana-oidc + entries: + # ============================================================================ + # AUTO-CREATE GRAFANA GROUPS + # ============================================================================ + # These groups control access levels in Grafana via OAuth role mapping: + # - Grafana Admins → Admin role (full access) + # - Grafana Editors → Editor role (can edit dashboards) + # - Grafana Viewers → Viewer role (read-only) + # + # AUTOMATIC ADMIN ACCESS: + # - Anyone in "authentik Admins" automatically gets Grafana Admin access + # - No manual configuration needed! + # ============================================================================ + + - model: authentik_core.group + id: grafana-admins-group + state: present + identifiers: + name: Grafana Admins + attrs: + name: Grafana Admins + is_superuser: false + # Note: authentik Admins are automatically granted access via role_attribute_path + # in Grafana configuration (see grafana/values.yaml) + + - model: authentik_core.group + id: grafana-editors-group + state: present + identifiers: + name: Grafana Editors + attrs: + name: Grafana Editors + is_superuser: false + + - model: authentik_core.group + id: grafana-viewers-group + state: present + identifiers: + name: Grafana Viewers + attrs: + name: Grafana Viewers + is_superuser: false + + # ============================================================================ + # GRAFANA OAUTH2 PROVIDER + # ============================================================================ + + - model: authentik_providers_oauth2.oauth2provider + id: grafana-provider + state: present + identifiers: + name: Grafana + attrs: + name: Grafana + client_id: bd03e9139dd2063c6c44c4d2f65f51d69de3ba0b6d6b1b9b41c255d2376d2dcc + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://grafana.kube.huskypup.net/login/generic_oauth" + matching_mode: strict + 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]] + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "hours=1" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, default]] + + # ============================================================================ + # GRAFANA APPLICATION + # ============================================================================ + + - model: authentik_core.application + id: grafana-application + state: present + identifiers: + slug: grafana + attrs: + name: Grafana + slug: grafana + policy_engine_mode: any + provider: !KeyOf grafana-provider + data: + - secretKey: clientSecret + remoteRef: + key: grafana-oauth + property: client-secret diff --git a/infrastructure/authentik/guacamole-blueprint.yaml b/infrastructure/authentik/guacamole-blueprint.yaml index ab46860..86fe1b3 100644 --- a/infrastructure/authentik/guacamole-blueprint.yaml +++ b/infrastructure/authentik/guacamole-blueprint.yaml @@ -1,83 +1,103 @@ --- -apiVersion: v1 -kind: ConfigMap +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20. Same story as argocd-blueprint.yaml: the value +# was already Vault's source of truth on the app side (secret/ +# guacamole-oauth), this blueprint just had its own separate hardcoded +# plaintext copy. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: authentik-blueprints-guacamole namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - guacamole.yaml: |- - version: 1 - metadata: - name: guacamole-oidc - labels: - blueprints.goauthentik.io/instantiate: "true" - entries: - # ============================================================================ - # GUACAMOLE OAUTH2 PROVIDER - # ============================================================================ - # This creates the OAuth2 provider that Guacamole will use for authentication. - # The client_id and client_secret must match what's configured in Guacamole. - # ============================================================================ +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-guacamole + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + guacamole.yaml: |- + version: 1 + metadata: + name: guacamole-oidc + labels: + blueprints.goauthentik.io/instantiate: "true" + entries: + # ============================================================================ + # GUACAMOLE OAUTH2 PROVIDER + # ============================================================================ + # This creates the OAuth2 provider that Guacamole will use for authentication. + # The client_id and client_secret must match what's configured in Guacamole. + # ============================================================================ - # ============================================================================ - # GROUPS SCOPE MAPPING - # ============================================================================ - # Custom scope mapping that includes user's group names in the OIDC token. - # ============================================================================ + # ============================================================================ + # GROUPS SCOPE MAPPING + # ============================================================================ + # Custom scope mapping that includes user's group names in the OIDC token. + # ============================================================================ - - model: authentik_providers_oauth2.scopemapping - id: guacamole-groups-scope - identifiers: - managed: goauthentik.io/providers/oauth2/guacamole-groups - attrs: - name: "Guacamole Groups Scope" - scope_name: groups - expression: | - return { - "groups": [group.name for group in request.user.ak_groups.all()], - } + - model: authentik_providers_oauth2.scopemapping + id: guacamole-groups-scope + identifiers: + managed: goauthentik.io/providers/oauth2/guacamole-groups + attrs: + name: "Guacamole Groups Scope" + scope_name: groups + expression: | + return { + "groups": [group.name for group in request.user.ak_groups.all()], + } - - model: authentik_providers_oauth2.oauth2provider - id: guacamole-provider - identifiers: - name: guacamole - attrs: - name: Guacamole - client_id: a55cbe3c6d2ae30c05c86385802ffcfd76a972c22c8d039f000281d4a149fd44 - client_secret: 718d4c4816aa68767a6a0da0fa4099d492473c700ba236cbeb64d294762b9548acda7e716ba11b2ad7729d6a1c16c7890b227e3b2dcfc35fd4af1eb0d6662b0f - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://guacamole.kube.huskypup.net/" - matching_mode: strict - - url: "https://guacamole.kube.huskypup.net/guacamole/" - matching_mode: strict - 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]] - - !KeyOf guacamole-groups-scope - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "hours=1" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, "Lets Encrypt K8s Cert"]] + - model: authentik_providers_oauth2.oauth2provider + id: guacamole-provider + identifiers: + name: guacamole + attrs: + name: Guacamole + client_id: a55cbe3c6d2ae30c05c86385802ffcfd76a972c22c8d039f000281d4a149fd44 + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://guacamole.kube.huskypup.net/" + matching_mode: strict + - url: "https://guacamole.kube.huskypup.net/guacamole/" + matching_mode: strict + 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]] + - !KeyOf guacamole-groups-scope + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "hours=1" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, "Lets Encrypt K8s Cert"]] - # ============================================================================ - # GUACAMOLE APPLICATION - # ============================================================================ - # This creates the application entry that users will see in Authentik. - # ============================================================================ + # ============================================================================ + # GUACAMOLE APPLICATION + # ============================================================================ + # This creates the application entry that users will see in Authentik. + # ============================================================================ - - model: authentik_core.application - id: guacamole-application - identifiers: - slug: guacamole - attrs: - name: Guacamole - slug: guacamole - policy_engine_mode: any - provider: !KeyOf guacamole-provider \ No newline at end of file + - model: authentik_core.application + id: guacamole-application + identifiers: + slug: guacamole + attrs: + name: Guacamole + slug: guacamole + policy_engine_mode: any + provider: !KeyOf guacamole-provider + data: + - secretKey: clientSecret + remoteRef: + key: guacamole-oauth + property: client-secret diff --git a/infrastructure/authentik/n8n-blueprint.yaml b/infrastructure/authentik/n8n-blueprint.yaml index 2f2eed2..5d7bd78 100644 --- a/infrastructure/authentik/n8n-blueprint.yaml +++ b/infrastructure/authentik/n8n-blueprint.yaml @@ -1,49 +1,68 @@ --- -apiVersion: v1 -kind: ConfigMap +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20. Same story as argocd-blueprint.yaml: the value +# was already Vault's source of truth on the app side (secret/n8n-oauth), +# this blueprint just had its own separate hardcoded plaintext copy. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: authentik-blueprints-n8n namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - n8n.yaml: |- - version: 1 - metadata: - name: n8n-oidc - entries: - - model: authentik_providers_oauth2.oauth2provider - id: n8n-provider - state: present - identifiers: - name: n8n - attrs: - name: n8n - client_id: e55cbe3c6d2ae30c05c86385802ffcfd76a972c22c8d039f000281d4a149fd44 - client_secret: 718d4c4816aa68767a6a0da0fa4099d492473c700ba236cbeb64d294762b9548acda7e716ba11b2ad7729d6a1c16c7890b227e3b2dcfc35fd4af1eb0d6662b0f - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://n8n.kube.huskypup.net/callback" - matching_mode: strict - 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]] - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "hours=1" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Self-signed Certificate"]] +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-n8n + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + n8n.yaml: |- + version: 1 + metadata: + name: n8n-oidc + entries: + - model: authentik_providers_oauth2.oauth2provider + id: n8n-provider + state: present + identifiers: + name: n8n + attrs: + name: n8n + client_id: e55cbe3c6d2ae30c05c86385802ffcfd76a972c22c8d039f000281d4a149fd44 + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://n8n.kube.huskypup.net/callback" + matching_mode: strict + 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]] + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "hours=1" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Self-signed Certificate"]] - - model: authentik_core.application - id: n8n-application - state: present - identifiers: - slug: n8n - attrs: - name: n8n - slug: n8n - policy_engine_mode: any - provider: !KeyOf n8n-provider + - model: authentik_core.application + id: n8n-application + state: present + identifiers: + slug: n8n + attrs: + name: n8n + slug: n8n + policy_engine_mode: any + provider: !KeyOf n8n-provider + data: + - secretKey: clientSecret + remoteRef: + key: n8n-oauth + property: client-secret diff --git a/infrastructure/authentik/nextcloud-blueprint.yaml b/infrastructure/authentik/nextcloud-blueprint.yaml index ec76cd8..032a5a9 100644 --- a/infrastructure/authentik/nextcloud-blueprint.yaml +++ b/infrastructure/authentik/nextcloud-blueprint.yaml @@ -1,52 +1,72 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: authentik-blueprints-nextcloud - namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - nextcloud.yaml: |- - version: 1 - metadata: - name: nextcloud-oidc - entries: - - model: authentik_providers_oauth2.oauth2provider - id: nextcloud-provider - state: present - identifiers: - name: Nextcloud - attrs: - name: Nextcloud - client_id: HmTGnUfhCvjFFbUUAne7JLrucNC2X675JicPbEnQ - client_secret: qTUf4NYjzUuAqfNV5D7SFPuuhJJwVltU6U3LAuJVy7dddJWeJSv2B4wKaRxbbHqpIiSfYPDaKujhEysKbOWUnf4n9OHPKJ0bu1I2EzkYtChO1Gq4gUv5EHPTzQlMBPnl - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://nextcloud.kube.huskypup.net/apps/user_oidc/code" - matching_mode: strict - - url: "http://nextcloud.kube.huskypup.net/apps/user_oidc/code" - matching_mode: strict - enabled: true - 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]] - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "days=7" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] - - - model: authentik_core.application - id: nextcloud-application - state: present - identifiers: - slug: nextcloud - attrs: - name: Nextcloud - slug: nextcloud - policy_engine_mode: any - provider: !KeyOf nextcloud-provider +--- +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20. Same story as argocd-blueprint.yaml: the value +# was already Vault's source of truth on the app side (secret/ +# nextcloud-oauth), this blueprint just had its own separate hardcoded +# plaintext copy. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: authentik-blueprints-nextcloud + namespace: authentik +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-nextcloud + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + nextcloud.yaml: |- + version: 1 + metadata: + name: nextcloud-oidc + entries: + - model: authentik_providers_oauth2.oauth2provider + id: nextcloud-provider + state: present + identifiers: + name: Nextcloud + attrs: + name: Nextcloud + client_id: HmTGnUfhCvjFFbUUAne7JLrucNC2X675JicPbEnQ + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://nextcloud.kube.huskypup.net/apps/user_oidc/code" + matching_mode: strict + - url: "http://nextcloud.kube.huskypup.net/apps/user_oidc/code" + matching_mode: strict + enabled: true + 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]] + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "days=7" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] + + - model: authentik_core.application + id: nextcloud-application + state: present + identifiers: + slug: nextcloud + attrs: + name: Nextcloud + slug: nextcloud + policy_engine_mode: any + provider: !KeyOf nextcloud-provider + data: + - secretKey: clientSecret + remoteRef: + key: nextcloud-oauth + property: client-secret diff --git a/infrastructure/authentik/rancher-blueprint.yaml b/infrastructure/authentik/rancher-blueprint.yaml index 4905306..fc89301 100644 --- a/infrastructure/authentik/rancher-blueprint.yaml +++ b/infrastructure/authentik/rancher-blueprint.yaml @@ -1,54 +1,73 @@ --- -apiVersion: v1 -kind: ConfigMap +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20. Same story as argocd-blueprint.yaml: the value +# was already Vault's source of truth on the app side (secret/ +# rancher-oauth), this blueprint just had its own separate hardcoded +# plaintext copy. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: authentik-blueprints-rancher namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - rancher.yaml: |- - # yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json - version: 1 - metadata: - name: rancher-oidc - entries: - - model: authentik_providers_oauth2.oauth2provider - id: rancher-provider - state: present - identifiers: - name: Rancher - attrs: - name: Rancher - client_id: 94a6beeaa91d0d0407cf8af807e8208a611cf3f25ae42161e697a8dccd563449 - client_secret: 19ebd4545ba74a93c220d4471af634302c5991204cf650cabd522b0864d7617ded2c59b470ab6de7a5801e7de4c84e4b9741b983170815419c9503d55f849794 - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://rancher.kube.huskypup.net/verify-auth" - matching_mode: strict - - url: "https://rancher.kube.huskypup.net" - matching_mode: strict - enabled: true - 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]] - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "hours=1" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] - - - model: authentik_core.application - id: rancher-application - state: present - identifiers: - slug: rancher - attrs: - name: Rancher - slug: rancher - policy_engine_mode: any - provider: !KeyOf rancher-provider +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-rancher + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + rancher.yaml: |- + # yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json + version: 1 + metadata: + name: rancher-oidc + entries: + - model: authentik_providers_oauth2.oauth2provider + id: rancher-provider + state: present + identifiers: + name: Rancher + attrs: + name: Rancher + client_id: 94a6beeaa91d0d0407cf8af807e8208a611cf3f25ae42161e697a8dccd563449 + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://rancher.kube.huskypup.net/verify-auth" + matching_mode: strict + - url: "https://rancher.kube.huskypup.net" + matching_mode: strict + enabled: true + 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]] + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "hours=1" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] + - model: authentik_core.application + id: rancher-application + state: present + identifiers: + slug: rancher + attrs: + name: Rancher + slug: rancher + policy_engine_mode: any + provider: !KeyOf rancher-provider + data: + - secretKey: clientSecret + remoteRef: + key: rancher-oauth + property: client-secret diff --git a/infrastructure/authentik/values.yaml b/infrastructure/authentik/values.yaml index dbdd6ca..e7366f3 100644 --- a/infrastructure/authentik/values.yaml +++ b/infrastructure/authentik/values.yaml @@ -77,22 +77,26 @@ global: - name: AUTHENTIK_POSTGRESQL__PORT value: "61432" -# Blueprints - Mount ConfigMaps for auto-discovery +# Blueprints - Mount ConfigMaps for auto-discovery (no secrets) or +# Secrets (client_secret-bearing ones, ExternalSecret-templated from +# Vault - found and fixed 2026-08-20, see each file's own header for +# what it used to look like). blueprints: configMaps: + - authentik-blueprints-forward-auth + - authentik-blueprints-prometheus + - authentik-blueprints-ceph + - authentik-blueprints-home-assistant + - authentik-blueprints-percona-everest + - authentik-blueprints-netbird + secrets: - authentik-blueprints-gitlab - authentik-blueprints-nextcloud - authentik-blueprints-argocd - authentik-blueprints-grafana - - authentik-blueprints-forward-auth - - authentik-blueprints-prometheus - authentik-blueprints-n8n - - authentik-blueprints-ceph - - authentik-blueprints-home-assistant - authentik-blueprints-guacamole - - authentik-blueprints-percona-everest - authentik-blueprints-rancher - - authentik-blueprints-netbird - authentik-blueprints-vault # Enable Prometheus metrics diff --git a/infrastructure/authentik/vault-blueprint.yaml b/infrastructure/authentik/vault-blueprint.yaml index cd57f91..aeb6a7e 100644 --- a/infrastructure/authentik/vault-blueprint.yaml +++ b/infrastructure/authentik/vault-blueprint.yaml @@ -1,51 +1,74 @@ --- -apiVersion: v1 -kind: ConfigMap +# Was a plain ConfigMap with client_secret hardcoded in plaintext - found +# and fixed 2026-08-20. Unlike argocd/nextcloud, Vault had NO copy of +# this at all - the only places it existed were here AND in +# infrastructure/vault/manifests/vault-init-configmap.yaml (embedded in a +# shell script that runs `vault write auth/oidc/config` once at cluster +# init - also fixed alongside this, same Vault path). Created +# secret/vault-oauth with the existing live value (not rotated - this is +# what Vault's OWN oidc auth method is currently configured with). +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: authentik-blueprints-vault namespace: authentik - labels: - goauthentik.io/blueprint: "true" -data: - vault.yaml: |- - version: 1 - metadata: - name: vault-oidc - entries: - - model: authentik_providers_oauth2.oauth2provider - id: vault-provider - state: present - identifiers: - name: Vault - attrs: - name: Vault - client_id: 9816a5ae7e7914b5d18f4ab939d011a98f8c8d6b3bb6777c46431afa06ac4a85 - client_secret: ed2ba1c6378c7a46341b5162f39a7fab80e37596b01ed387c3719e8e0040344cf1daa307476c2e7a7f75041b3979275b1ebf00bb8bad94c864b4a38ded544f7b - authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] - authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] - invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] - redirect_uris: - - url: "https://vault.kube.huskypup.net/ui/vault/auth/oidc/oidc/callback" - matching_mode: strict - - url: "http://localhost:8250/oidc/callback" - matching_mode: strict - 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]] - client_type: confidential - access_code_validity: "minutes=10" - access_token_validity: "hours=1" - refresh_token_validity: "days=30" - signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: authentik-blueprints-vault + creationPolicy: Owner + template: + metadata: + labels: + goauthentik.io/blueprint: "true" + data: + vault.yaml: |- + version: 1 + metadata: + name: vault-oidc + entries: + - model: authentik_providers_oauth2.oauth2provider + id: vault-provider + state: present + identifiers: + name: Vault + attrs: + name: Vault + client_id: 9816a5ae7e7914b5d18f4ab939d011a98f8c8d6b3bb6777c46431afa06ac4a85 + client_secret: "{{ .clientSecret }}" + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + redirect_uris: + - url: "https://vault.kube.huskypup.net/ui/vault/auth/oidc/oidc/callback" + matching_mode: strict + - url: "http://localhost:8250/oidc/callback" + matching_mode: strict + 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]] + client_type: confidential + access_code_validity: "minutes=10" + access_token_validity: "hours=1" + refresh_token_validity: "days=30" + signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Internal JWT Certificate"]] - - model: authentik_core.application - id: vault-application - state: present - identifiers: - slug: vault - attrs: - name: Vault - slug: vault - policy_engine_mode: any - provider: !KeyOf vault-provider + - model: authentik_core.application + id: vault-application + state: present + identifiers: + slug: vault + attrs: + name: Vault + slug: vault + policy_engine_mode: any + provider: !KeyOf vault-provider + data: + - secretKey: clientSecret + remoteRef: + key: vault-oauth + property: client-secret diff --git a/infrastructure/grafana/manifests/grafana-oauth-secret.yaml b/infrastructure/grafana/manifests/grafana-oauth-secret.yaml index e394f60..05fbe5e 100644 --- a/infrastructure/grafana/manifests/grafana-oauth-secret.yaml +++ b/infrastructure/grafana/manifests/grafana-oauth-secret.yaml @@ -1,12 +1,32 @@ -# infrastructure/grafana/grafana-oauth-secret.yaml -# Grafana OAuth secret - hardcoded from blueprint - -apiVersion: v1 -kind: Secret +# Was a plain Secret with the OIDC client_id/client_secret hardcoded in +# plaintext (the file's own old comment even said "hardcoded from +# blueprint") - found and fixed 2026-08-20 alongside +# infrastructure/authentik/grafana-blueprint.yaml (same value, same Vault +# path - see that file for the full story). +apiVersion: external-secrets.io/v1 +kind: ExternalSecret metadata: name: grafana-authentik-oauth namespace: grafana -type: Opaque -stringData: - GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "bd03e9139dd2063c6c44c4d2f65f51d69de3ba0b6d6b1b9b41c255d2376d2dcc" - GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "6b6f2deecfe8fd56cae9c512cc71eedd463d67ff08f24c816b15b15e78ce36bc4f06a2276c5ffe3f67799d935994a32ed32cdc02ec674f279c82d7cfe3ca05d5" \ No newline at end of file +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: grafana-authentik-oauth + creationPolicy: Owner + template: + type: Opaque + data: + GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "{{ .clientId }}" + GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "{{ .clientSecret }}" + data: + - secretKey: clientId + remoteRef: + key: grafana-oauth + property: client-id + - secretKey: clientSecret + remoteRef: + key: grafana-oauth + property: client-secret diff --git a/infrastructure/vault/manifests/vault-init-configmap.yaml b/infrastructure/vault/manifests/vault-init-configmap.yaml index e379606..cae4109 100644 --- a/infrastructure/vault/manifests/vault-init-configmap.yaml +++ b/infrastructure/vault/manifests/vault-init-configmap.yaml @@ -173,8 +173,22 @@ data: ttl="24h" >/dev/null # --- OIDC Auth (Authentik SSO) --- - OIDC_CLIENT_ID="9816a5ae7e7914b5d18f4ab939d011a98f8c8d6b3bb6777c46431afa06ac4a85" - OIDC_CLIENT_SECRET="ed2ba1c6378c7a46341b5162f39a7fab80e37596b01ed387c3719e8e0040344cf1daa307476c2e7a7f75041b3979275b1ebf00bb8bad94c864b4a38ded544f7b" + # OIDC_CLIENT_ID/SECRET used to be hardcoded here in plaintext - found + # and fixed 2026-08-20. Can't source this from Vault itself the way + # every other app's OIDC secret now does (infrastructure/authentik/ + # vault-blueprint.yaml, secret/vault-oauth) - this job is what GRANTS + # ESO's own Kubernetes-auth role a few lines above + # (bound_service_account_names="...,external-secrets"), so ESO can't + # yet authenticate to Vault at the point this script runs - genuinely + # circular. Sourced from vault-oidc-bootstrap instead, a plain Secret + # created once manually (kubectl, not git): + # kubectl -n vault create secret generic vault-oidc-bootstrap \ + # --from-literal=client_id= \ + # --from-literal=client_secret= + # envFrom on the Job (vault-init-job.yaml) injects these as + # VAULT_OIDC_CLIENT_ID/VAULT_OIDC_CLIENT_SECRET. + OIDC_CLIENT_ID="${VAULT_OIDC_CLIENT_ID:?VAULT_OIDC_CLIENT_ID not set - see vault-oidc-bootstrap secret}" + OIDC_CLIENT_SECRET="${VAULT_OIDC_CLIENT_SECRET:?VAULT_OIDC_CLIENT_SECRET not set - see vault-oidc-bootstrap secret}" OIDC_DISCOVERY_URL="https://auth.kube.huskypup.net/application/o/vault/" echo "==> Configuring OIDC auth (Authentik)..." diff --git a/infrastructure/vault/manifests/vault-init-job.yaml b/infrastructure/vault/manifests/vault-init-job.yaml index 28aff2e..bf067c9 100644 --- a/infrastructure/vault/manifests/vault-init-job.yaml +++ b/infrastructure/vault/manifests/vault-init-job.yaml @@ -16,6 +16,19 @@ spec: - name: vault-init image: alpine/k8s:1.32.13 command: ["/bin/bash", "/scripts/vault-init-and-store.sh"] + env: + # See the matching comment in vault-init-configmap.yaml for + # why this can't come from Vault itself via ESO. + - name: VAULT_OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: vault-oidc-bootstrap + key: client_id + - name: VAULT_OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: vault-oidc-bootstrap + key: client_secret volumeMounts: - name: scripts mountPath: /scripts