Deploy Dex OIDC identity provider with Grafana SSO #222

Merged
eblume merged 4 commits from feature/dex-oidc into main 2026-02-19 20:24:24 -08:00
Owner

Summary

  • Deploys Dex OIDC identity provider on ringtail k3s cluster as central authentication service
  • Integrates Grafana as first SSO client via auth.generic_oauth
  • Uses Kubernetes CRD storage backend (no PVC needed)
  • All secrets (bcrypt hash, client secrets) injected via ExternalSecrets from 1Password item "Dex (blumeops)"
  • NixOS-built container image via containers/dex/default.nix

Pre-requisites (manual, before deployment)

  1. Create 1Password item "Dex (blumeops)" in blumeops vault with fields:
    • password: strong generated password for Dex login
    • static-password-hash: bcrypt hash of above (htpasswd -BnC 10 eblume, copy hash after eblume:)
    • grafana-client-secret: random 32-char hex (openssl rand -hex 16)
  2. Build container: mise run container-tag-and-release dex v1.0.0

Deployment sequence

  1. Build container: mise run container-tag-and-release dex v1.0.0
  2. Deploy Caddy: mise run provision-indri -- --tags caddy
  3. Sync ArgoCD: argocd app sync appsargocd app sync dex
  4. Verify Dex: curl https://dex.ops.eblu.me/.well-known/openid-configuration
  5. Sync Grafana: argocd app sync grafana-configargocd app sync grafana
  6. Test SSO: Visit https://grafana.ops.eblu.me/login, click "Sign in with Dex"

Verification

  • Container image exists: mise run container-list shows dex:v1.0.0-nix
  • curl https://dex.ops.eblu.me/.well-known/openid-configuration returns valid OIDC discovery
  • curl https://dex.ops.eblu.me/healthz returns healthy
  • Grafana login shows "Sign in with Dex" button alongside local login
  • OIDC flow: click Dex → enter credentials → redirect back → logged in as Admin
  • Break-glass: local admin login still works
  • mise run services-check passes

Files changed

File Action Purpose
containers/dex/default.nix Create NixOS container build
argocd/apps/dex.yaml Create ArgoCD app targeting ringtail
argocd/manifests/dex/* (8 files) Create K8s manifests (RBAC, ExternalSecret, Deployment, Service, Ingress)
argocd/manifests/grafana-config/external-secret-dex-oauth.yaml Create Grafana OIDC client secret
argocd/manifests/grafana-config/kustomization.yaml Modify Add new ExternalSecret resource
argocd/manifests/grafana/values.yaml Modify Add auth.generic_oauth config + envFromSecrets
ansible/roles/caddy/defaults/main.yml Modify Add dex.ops.eblu.me reverse proxy entry
docs/changelog.d/feature-dex-oidc.feature.md Create Changelog fragment
## Summary - Deploys Dex OIDC identity provider on ringtail k3s cluster as central authentication service - Integrates Grafana as first SSO client via `auth.generic_oauth` - Uses Kubernetes CRD storage backend (no PVC needed) - All secrets (bcrypt hash, client secrets) injected via ExternalSecrets from 1Password item "Dex (blumeops)" - NixOS-built container image via `containers/dex/default.nix` ## Pre-requisites (manual, before deployment) 1. Create 1Password item "Dex (blumeops)" in `blumeops` vault with fields: - `password`: strong generated password for Dex login - `static-password-hash`: bcrypt hash of above (`htpasswd -BnC 10 eblume`, copy hash after `eblume:`) - `grafana-client-secret`: random 32-char hex (`openssl rand -hex 16`) 2. Build container: `mise run container-tag-and-release dex v1.0.0` ## Deployment sequence 1. Build container: `mise run container-tag-and-release dex v1.0.0` 2. Deploy Caddy: `mise run provision-indri -- --tags caddy` 3. Sync ArgoCD: `argocd app sync apps` → `argocd app sync dex` 4. Verify Dex: `curl https://dex.ops.eblu.me/.well-known/openid-configuration` 5. Sync Grafana: `argocd app sync grafana-config` → `argocd app sync grafana` 6. Test SSO: Visit `https://grafana.ops.eblu.me/login`, click "Sign in with Dex" ## Verification - [ ] Container image exists: `mise run container-list` shows `dex:v1.0.0-nix` - [ ] `curl https://dex.ops.eblu.me/.well-known/openid-configuration` returns valid OIDC discovery - [ ] `curl https://dex.ops.eblu.me/healthz` returns healthy - [ ] Grafana login shows "Sign in with Dex" button alongside local login - [ ] OIDC flow: click Dex → enter credentials → redirect back → logged in as Admin - [ ] Break-glass: local admin login still works - [ ] `mise run services-check` passes ## Files changed | File | Action | Purpose | |------|--------|---------| | `containers/dex/default.nix` | Create | NixOS container build | | `argocd/apps/dex.yaml` | Create | ArgoCD app targeting ringtail | | `argocd/manifests/dex/*` (8 files) | Create | K8s manifests (RBAC, ExternalSecret, Deployment, Service, Ingress) | | `argocd/manifests/grafana-config/external-secret-dex-oauth.yaml` | Create | Grafana OIDC client secret | | `argocd/manifests/grafana-config/kustomization.yaml` | Modify | Add new ExternalSecret resource | | `argocd/manifests/grafana/values.yaml` | Modify | Add `auth.generic_oauth` config + envFromSecrets | | `ansible/roles/caddy/defaults/main.yml` | Modify | Add `dex.ops.eblu.me` reverse proxy entry | | `docs/changelog.d/feature-dex-oidc.feature.md` | Create | Changelog fragment |
Adds Dex as a central OIDC identity provider running on ringtail's k3s
cluster. Grafana is integrated as the first SSO client via generic_oauth.
Dex uses Kubernetes CRD storage and ExternalSecrets for all sensitive
config (bcrypt hash, client secrets from 1Password).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch Dex from static passwords to Forgejo OAuth2 connector
All checks were successful
Build Container / build (push) Successful in 3s
Build Container (Nix) / build (push) Successful in 11s
fe1c92f702
Users authenticate via Forgejo at forge.ops.eblu.me instead of a
hardcoded password list. This makes user management scale through
Forgejo's existing account system and enables future collaborator
onboarding via Forgejo accounts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Kubernetes CRD storage backend crashes on k3s due to a Go URL
parsing bug with the in-cluster API address. sqlite3 with emptyDir
avoids the k8s API entirely and is sufficient for single-replica Dex.
Also removes now-unnecessary RBAC resources (ServiceAccount, ClusterRole,
ClusterRoleBinding).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OAuth state cookie is set on the domain users visit (grafana.ops.eblu.me)
but Grafana was constructing callbacks from root_url (grafana.tail8d86e.ts.net),
causing "Missing saved oauth state" errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
eblume merged commit 0cdc143227 into main 2026-02-19 20:24:24 -08:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
eblume/blumeops!222
No description provided.