Project

General

Profile

Sub-Task #254

Updated by Bricklou 26 days ago

Support multi-provider OIDC configuration, config-file based, not DB-backed env/config-file based (not DB-backed) — matches how Grafana (`GF_AUTH_GENERIC_OAUTH_*` / named provider sections) and not CRD-based (Kubestro is a standalone Postgres-backed server, no operator/controller infra exists, so a CRD would mean building watch/reconcile machinery just to read config at boot). Outline (`OIDC_CLIENT_ID`/`OIDC_CLIENT_SECRET`/`OIDC_AUTH_URI`) do it. 

 Pattern (used Primary mechanism: YAML file mounted via k8s ConfigMap/Secret volume, path given by Grafana, Loki, Gitea): unified structured config file (YAML) + environment variable overrides `OIDC_PROVIDERS_FILE` (GitOps-friendly, readable, diffable). Fallback: `OIDC_PROVIDERS` env var with the same structure as JSON (safer than YAML for secrets, loaded via `config-rs` or `figment`, a single-line env var). 

 Parsed at startup into in-memory provider values, following the existing `Config::from_env` pattern (apps/server/cli/src/config.rs). 

 Kubernetes/GitOps split: 
 - ConfigMap holds Client secrets stay in env/k8s Secret, not the full provider structure (issuer, client_id, scopes, icon) *except* `client_secret` DB — fully git-committed, plaintext, PR-reviewable. 
 - Secret holds each provider's `client_secret`, injected as an env var per provider (works as-is with SOPS, Sealed Secrets, External Secrets Operator, Vault, etc — no format changes needed on the secret side). 
 - One mechanism for overrides: `config-rs`'s `Environment::default().separator("__")`, e.g. `OIDC_PROVIDERS__GOOGLE__CLIENT_SECRET` overrides `oidc_providers.google.client_secret`. encryption-at-rest or admin-CRUD/drift problem to solve. No `${VAR}` interpolation syntax in the file itself — one override mechanism, not two. DB entity/migration needed. 

 Format spec: 

 <pre> ```yaml 
 # config.yaml (ConfigMap, git-committed, no secrets) oidc-providers.yaml 
 oidc_providers: - id: google 
   google: 
     name: Google 
     
   issuer_url: https://accounts.google.com     # discovery doc = {issuer_url}/.well-known/openid-configuration 
     
   client_id: xxxxx.apps.googleusercontent.com 
     
   client_secret: GOCSPX-xxxxx 
   scopes: [openid, email, profile] 
     
   icon: google                                # slug into github.com/homarr-labs/dashboard-icons 

   keycloak: 
     

 - id: keycloak 
   name: Company SSO 
     
   issuer_url: https://sso.example.com/realms/kubestro 
     
   client_id: kubestro-dashboard 
     
   client_secret: s3cr3t 
   scopes: [openid, email] 
     
   icon_url: https://sso.example.com/icon.svg # override for providers not in the known icon set 
 </pre> ``` 

 Env vars (from Secret, per provider): 
 <pre> 
 OIDC_PROVIDERS__GOOGLE__CLIENT_SECRET=GOCSPX-xxxxx 
 OIDC_PROVIDERS__KEYCLOAK__CLIENT_SECRET=s3cr3t 
 </pre> 

 Deployment mounts the ConfigMap as `/etc/kubestro/config.yaml` (readOnly volume) and injects each `client_secret` as an env var via `secretKeyRef`. A config/secret change plus a rollout (as GitOps tools like Flux/ArgoCD already do) picks up the new config on next boot — no hot-reload, no CRD, no extra RBAC. 

 - Provider key (`google`, `keycloak`, ...) is the `id`: stable slug: slug, used in redirect URL (`/auth/oidc/{id}/callback`) and in the discovery-endpoint response. Map key replaces the earlier `id:` list-item field. 
 - `issuer_url`: OIDC issuer identifier, not the discovery endpoint itself — backend fetches `{issuer_url}/.well-known/openid-configuration` at startup to resolve authorize/token/jwks endpoints (standard OIDC discovery). 
 - `icon`: slug resolved against homarr-labs/dashboard-icons (https://github.com/homarr-labs/dashboard-icons), ([https://github.com/homarr-labs/dashboard-icons](https://github.com/homarr-labs/dashboard-icons)), served via their CDN. `icon_url`: explicit override for custom/self-hosted providers not in that set. 

 Non-k8s/simple deployments: same env-var override mechanism still works standalone (set `OIDC_PROVIDERS__<ID>__*` directly), config file path still required via e.g. `OIDC_CONFIG_FILE`.

Back