## 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 | Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/222
55 lines
1.5 KiB
YAML
55 lines
1.5 KiB
YAML
---
|
|
apiVersion: external-secrets.io/v1
|
|
kind: ExternalSecret
|
|
metadata:
|
|
name: dex-config
|
|
namespace: dex
|
|
spec:
|
|
refreshInterval: 1h
|
|
secretStoreRef:
|
|
kind: ClusterSecretStore
|
|
name: onepassword-blumeops
|
|
target:
|
|
name: dex-config
|
|
creationPolicy: Owner
|
|
template:
|
|
data:
|
|
config.yaml: |
|
|
issuer: https://dex.ops.eblu.me
|
|
storage:
|
|
type: sqlite3
|
|
config:
|
|
file: /var/dex/dex.db
|
|
web:
|
|
http: 0.0.0.0:5556
|
|
oauth2:
|
|
skipApprovalScreen: true
|
|
connectors:
|
|
- type: gitea
|
|
id: forgejo
|
|
name: Forgejo
|
|
config:
|
|
baseURL: https://forge.ops.eblu.me
|
|
clientID: "{{ .forgejoClientID }}"
|
|
clientSecret: "{{ .forgejoClientSecret }}"
|
|
redirectURI: https://dex.ops.eblu.me/callback
|
|
staticClients:
|
|
- id: grafana
|
|
name: Grafana
|
|
secret: "{{ .grafanaClientSecret }}"
|
|
redirectURIs:
|
|
- "https://grafana.ops.eblu.me/login/generic_oauth"
|
|
- "https://grafana.tail8d86e.ts.net/login/generic_oauth"
|
|
data:
|
|
- secretKey: forgejoClientID
|
|
remoteRef:
|
|
key: "Dex (blumeops)"
|
|
property: forgejo-client-id
|
|
- secretKey: forgejoClientSecret
|
|
remoteRef:
|
|
key: "Dex (blumeops)"
|
|
property: forgejo-client-secret
|
|
- secretKey: grafanaClientSecret
|
|
remoteRef:
|
|
key: "Dex (blumeops)"
|
|
property: grafana-client-secret
|