# ============================================================================= # TEMPLATE — NOT APPLIED, NOT ENCRYPTED (.yaml.example is ignored by kustomize and never # auto-encrypted by SOPS). Reference scaffold + OPERATOR HAND-OFF. # # WHAT THIS IS: the SECRET half of Headlamp's OIDC config for the Keycloak `homelab` realm's # `headlamp` client. The Headlamp chart's `config.oidc.externalSecret` (helmrelease.yaml) envFrom's # THIS Secret, so its KEYS ARE the container env var names — the chart renames nothing, and its # `-oidc-*` args reference those names via $(...). They MUST be spelled exactly as below. # # ── THE URLS ARE NOT HERE, AND MUST NOT BE PUT BACK (2026-09-07) ───────────────────────────── # OIDC_ISSUER_URL and OIDC_CALLBACK_URL are ENVIRONMENT-SPECIFIC and NON-SECRET. They live in # `helmrelease.yaml` under `spec.values.env`, written as # https://keycloak.${BASE_DOMAIN}/realms/homelab # https://headlamp.${BASE_DOMAIN}/oidc-callback # so Flux's postBuild `${BASE_DOMAIN}` substitution resolves them per environment. They used to be # keys in this Secret; substitution CANNOT reach inside ciphertext, so both were frozen at the apex # `home.keithjackson.dev` and PROD pointed Headlamp at DEV's Keycloak (192.168.1.53 — on the dev LAN # behind pfSense, unroutable from prod's 192.168.2.0/24). Every prod login failed with # dial tcp 192.168.1.53:443: i/o timeout on .../.well-known/openid-configuration # Dev only ever looked correct because dev IS the apex. Adding either key back here re-breaks prod. # # ONE SOURCE PER VARIABLE: never define a name in BOTH this Secret and `spec.values.env` — an # explicit `env:` entry silently WINS over the same name from `envFrom`, hiding which one is live. # # The `OIDC_CLIENT_SECRET` value MUST equal the `headlamp-client-secret` placed in # platform/keycloak/keycloak-realm-clients.sops.yaml (which the realm import injects into the # Keycloak `headlamp` client) — same cross-namespace mirror pattern as grafana/gitea (a Secret # cannot be shared across namespaces, so the one value is stored in two encrypted files). # # HAND-OFF STEPS (operator): # (a) Use the SAME headlamp-client-secret generated for keycloak-realm-clients (openssl rand -hex 32). # (b) Copy to: platform/headlamp/headlamp-oidc-credentials.sops.yaml # (c) Fill `OIDC_CLIENT_SECRET`; encrypt: # sops -e -i platform/headlamp/headlamp-oidc-credentials.sops.yaml # (d) It is already listed under `resources:` in platform/headlamp/kustomization.yaml. # ============================================================================= apiVersion: v1 kind: Secret metadata: name: headlamp-oidc namespace: headlamp type: Opaque stringData: # KEY NAMES ARE LOAD-BEARING. The chart does NOT rename these — it `envFrom`s this Secret # verbatim, so each key becomes a container env var of the SAME name, and the chart's OIDC args # reference them by exact name: `-oidc-client-id=$(OIDC_CLIENT_ID)`, # `-oidc-client-secret=$(OIDC_CLIENT_SECRET)`, `-oidc-scopes=$(OIDC_SCOPES)`. If a key is misnamed # (e.g. camelCase `clientID`), the env var is undefined, K8s leaves the LITERAL string # `$(OIDC_CLIENT_ID)` in the arg — it does NOT fail loudly — and OIDC login breaks. Use these # EXACT keys. Environment-INVARIANT, non-secret values stay here simply because the chart wants a # single envFrom source; anything that varies per environment belongs in helmrelease.yaml `env:`. OIDC_CLIENT_ID: headlamp OIDC_SCOPES: "openid email profile" # The confidential client secret — MUST match keycloak-realm-clients headlamp-client-secret. OIDC_CLIENT_SECRET: REPLACE-ME