From 030e8518c56d547a32a34f2883ed1e5305407f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Sun, 8 Feb 2026 19:58:19 +0100 Subject: [PATCH 1/2] grafana: add Grafana on monitoring02 with Kanidm OIDC Deploy Grafana test instance on monitoring02 with: - Kanidm OIDC authentication (admins -> Admin role, others -> Viewer) - PKCE enabled for secure OAuth2 flow (required by Kanidm) - Declarative datasources for Prometheus and Loki on monitoring01 - Local Caddy for TLS termination via internal ACME CA - DNS CNAME grafana-test.home.2rjus.net Terraform changes add OAuth2 client secret and AppRole policies for kanidm01 and monitoring02. Co-Authored-By: Claude Opus 4.5 --- hosts/monitoring02/configuration.nix | 3 + hosts/monitoring02/default.nix | 1 + services/grafana/default.nix | 98 ++++++++++++++++++++++++++++ services/kanidm/default.nix | 19 ++++++ terraform/vault/approle.tf | 17 +++++ terraform/vault/secrets.tf | 6 ++ 6 files changed, 144 insertions(+) create mode 100644 services/grafana/default.nix diff --git a/hosts/monitoring02/configuration.nix b/hosts/monitoring02/configuration.nix index 3065a0c..f6fc6d3 100644 --- a/hosts/monitoring02/configuration.nix +++ b/hosts/monitoring02/configuration.nix @@ -18,6 +18,9 @@ tier = "test"; # Start in test tier, move to prod after validation }; + # DNS CNAME for Grafana test instance + homelab.dns.cnames = [ "grafana-test" ]; + # Enable Vault integration vault.enable = true; diff --git a/hosts/monitoring02/default.nix b/hosts/monitoring02/default.nix index 57ed4b4..a102f2b 100644 --- a/hosts/monitoring02/default.nix +++ b/hosts/monitoring02/default.nix @@ -1,5 +1,6 @@ { ... }: { imports = [ ./configuration.nix + ../../services/grafana ]; } \ No newline at end of file diff --git a/services/grafana/default.nix b/services/grafana/default.nix new file mode 100644 index 0000000..ca22e8c --- /dev/null +++ b/services/grafana/default.nix @@ -0,0 +1,98 @@ +{ config, pkgs, ... }: +{ + services.grafana = { + enable = true; + settings = { + server = { + http_addr = "127.0.0.1"; + http_port = 3000; + domain = "grafana-test.home.2rjus.net"; + root_url = "https://grafana-test.home.2rjus.net/"; + }; + + # Disable anonymous access + "auth.anonymous".enabled = false; + + # OIDC authentication via Kanidm + "auth.generic_oauth" = { + enabled = true; + name = "Kanidm"; + client_id = "grafana"; + client_secret = "$__file{/run/secrets/grafana-oauth2}"; + auth_url = "https://auth.home.2rjus.net/ui/oauth2"; + token_url = "https://auth.home.2rjus.net/oauth2/token"; + api_url = "https://auth.home.2rjus.net/oauth2/openid/grafana/userinfo"; + scopes = "openid profile email groups"; + use_pkce = true; # Required by Kanidm, more secure + # Extract user attributes from userinfo response + email_attribute_path = "email"; + login_attribute_path = "preferred_username"; + name_attribute_path = "name"; + # Map admins group to Admin role, everyone else to Viewer + role_attribute_path = "contains(groups[*], 'admins') && 'Admin' || 'Viewer'"; + allow_sign_up = true; + }; + }; + + # Declarative datasources pointing to monitoring01 + provision.datasources.settings = { + apiVersion = 1; + datasources = [ + { + name = "Prometheus"; + type = "prometheus"; + url = "http://monitoring01.home.2rjus.net:9090"; + isDefault = true; + uid = "prometheus"; + } + { + name = "Loki"; + type = "loki"; + url = "http://monitoring01.home.2rjus.net:3100"; + uid = "loki"; + } + ]; + }; + }; + + # Vault secret for OAuth2 client secret + vault.secrets.grafana-oauth2 = { + secretPath = "services/grafana/oauth2-client-secret"; + extractKey = "password"; + services = [ "grafana" ]; + owner = "grafana"; + group = "grafana"; + }; + + # Local Caddy for TLS termination + services.caddy = { + enable = true; + package = pkgs.unstable.caddy; + configFile = pkgs.writeText "Caddyfile" '' + { + acme_ca https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory + metrics + } + + grafana-test.home.2rjus.net { + log { + output file /var/log/caddy/grafana.log { + mode 644 + } + } + + reverse_proxy http://127.0.0.1:3000 + } + + http://${config.networking.hostName}.home.2rjus.net/metrics { + metrics + } + ''; + }; + + # Expose Caddy metrics for Prometheus + homelab.monitoring.scrapeTargets = [{ + job_name = "caddy"; + port = 80; + }]; +} diff --git a/services/kanidm/default.nix b/services/kanidm/default.nix index c7baaa1..cd10c99 100644 --- a/services/kanidm/default.nix +++ b/services/kanidm/default.nix @@ -30,6 +30,16 @@ }; # Regular users (persons) are managed imperatively via kanidm CLI + + # OAuth2/OIDC clients for service authentication + systems.oauth2.grafana = { + displayName = "Grafana"; + originUrl = "https://grafana-test.home.2rjus.net/login/generic_oauth"; + originLanding = "https://grafana-test.home.2rjus.net/"; + basicSecretFile = config.vault.secrets.grafana-oauth2.outputDir; + preferShortUsername = true; + scopeMaps.users = [ "openid" "profile" "email" "groups" ]; + }; }; }; @@ -53,6 +63,15 @@ group = "kanidm"; }; + # Vault secret for Grafana OAuth2 client secret + vault.secrets.grafana-oauth2 = { + secretPath = "services/grafana/oauth2-client-secret"; + extractKey = "password"; + services = [ "kanidm" ]; + owner = "kanidm"; + group = "kanidm"; + }; + # Note: Kanidm does not expose Prometheus metrics # If metrics support is added in the future, uncomment: # homelab.monitoring.scrapeTargets = [ diff --git a/terraform/vault/approle.tf b/terraform/vault/approle.tf index acca017..c3ce6e3 100644 --- a/terraform/vault/approle.tf +++ b/terraform/vault/approle.tf @@ -89,6 +89,23 @@ locals { ] } + # kanidm01: Kanidm identity provider + "kanidm01" = { + paths = [ + "secret/data/hosts/kanidm01/*", + "secret/data/kanidm/*", + "secret/data/services/grafana/*", + ] + } + + # monitoring02: Grafana test instance + "monitoring02" = { + paths = [ + "secret/data/hosts/monitoring02/*", + "secret/data/services/grafana/*", + ] + } + } } diff --git a/terraform/vault/secrets.tf b/terraform/vault/secrets.tf index fcc4228..2d257c5 100644 --- a/terraform/vault/secrets.tf +++ b/terraform/vault/secrets.tf @@ -108,6 +108,12 @@ locals { auto_generate = true password_length = 32 } + + # Grafana OAuth2 client secret (for Kanidm OIDC) + "services/grafana/oauth2-client-secret" = { + auto_generate = true + password_length = 64 + } } } -- 2.49.1 From 02270a0e4a5b53a963b27f46bea4f4e9e11a2241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Sun, 8 Feb 2026 20:27:08 +0100 Subject: [PATCH 2/2] docs: update plans with Grafana OIDC progress - auth-system-replacement.md: Mark OAuth2 client (Grafana) as completed, document key findings (PKCE, attribute paths, user requirements) - monitoring-migration-victoriametrics.md: Note Grafana deployment on monitoring02 with Kanidm OIDC as test instance Co-Authored-By: Claude Opus 4.5 --- docs/plans/auth-system-replacement.md | 21 ++++++++- .../monitoring-migration-victoriametrics.md | 23 +++++++++- docs/user-management.md | 44 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/docs/plans/auth-system-replacement.md b/docs/plans/auth-system-replacement.md index dcd97c7..b6ef58a 100644 --- a/docs/plans/auth-system-replacement.md +++ b/docs/plans/auth-system-replacement.md @@ -151,11 +151,30 @@ Rationale: - Well above NixOS system users (typically <1000) - Avoids Podman/container issues with very high GIDs +### Completed (2026-02-08) - OAuth2/OIDC for Grafana + +**OAuth2 client deployed for Grafana on monitoring02:** +- Client ID: `grafana` +- Redirect URL: `https://grafana-test.home.2rjus.net/login/generic_oauth` +- Scope maps: `openid`, `profile`, `email`, `groups` for `users` group +- Role mapping: `admins` group → Grafana Admin, others → Viewer + +**Configuration locations:** +- Kanidm OAuth2 client: `services/kanidm/default.nix` +- Grafana OIDC config: `services/grafana/default.nix` +- Vault secret: `services/grafana/oauth2-client-secret` + +**Key findings:** +- PKCE is required by Kanidm - enable `use_pkce = true` in Grafana +- Must set `email_attribute_path`, `login_attribute_path`, `name_attribute_path` to extract from userinfo +- Users need: primary credential (password + TOTP for MFA), membership in `users` group, email address set +- Unix password is separate from primary credential (web login requires primary credential) + ### Next Steps 1. Enable PAM/NSS on production hosts (after test tier validation) 2. Configure TrueNAS LDAP client for NAS integration testing -3. Add OAuth2 clients (Grafana first) +3. Add OAuth2 clients for other services as needed ## References diff --git a/docs/plans/monitoring-migration-victoriametrics.md b/docs/plans/monitoring-migration-victoriametrics.md index 7fc926a..95b1c96 100644 --- a/docs/plans/monitoring-migration-victoriametrics.md +++ b/docs/plans/monitoring-migration-victoriametrics.md @@ -169,9 +169,30 @@ Once ready to cut over: - Destroy VM in Proxmox - Remove from terraform state +## Current Progress + +### monitoring02 Host Created (2026-02-08) + +Host deployed at 10.69.13.24 (test tier) with: +- 4 CPU cores, 8GB RAM, 60GB disk +- Vault integration enabled +- NATS-based remote deployment enabled + +### Grafana with Kanidm OIDC (2026-02-08) + +Grafana deployed on monitoring02 as a test instance (`grafana-test.home.2rjus.net`): +- Kanidm OIDC authentication (PKCE enabled) +- Role mapping: `admins` → Admin, others → Viewer +- Declarative datasources pointing to monitoring01 (Prometheus, Loki) +- Local Caddy for TLS termination via internal ACME CA + +This validates the Grafana + OIDC pattern before the full VictoriaMetrics migration. The existing +`services/monitoring/grafana.nix` on monitoring01 can be replaced with the new `services/grafana/` +module once monitoring02 becomes the primary monitoring host. + ## Open Questions -- [ ] What disk size for monitoring02? 100GB should allow 3+ months with VictoriaMetrics compression +- [ ] What disk size for monitoring02? Current 60GB may need expansion for 3+ months with VictoriaMetrics - [ ] Which dashboards to recreate declaratively? (Review monitoring01 Grafana for current set) ## VictoriaMetrics Service Configuration diff --git a/docs/user-management.md b/docs/user-management.md index a3ff69a..5a732c3 100644 --- a/docs/user-management.md +++ b/docs/user-management.md @@ -43,11 +43,21 @@ kanidm person posix set-password kanidm person posix set --shell /bin/zsh ``` +### Setting Email Address + +Email is required for OAuth2/OIDC login (e.g., Grafana): + +```bash +kanidm person update --mail +``` + ### Example: Full User Creation ```bash kanidm person create testuser "Test User" +kanidm person update testuser --mail testuser@home.2rjus.net kanidm group add-members ssh-users testuser +kanidm group add-members users testuser # Required for OAuth2 scopes kanidm person posix set testuser kanidm person posix set-password testuser kanidm person get testuser @@ -129,6 +139,40 @@ Kanidm auto-assigns UIDs/GIDs from its configured range. For manually assigned G | 65,536+ | Users (auto-assigned) | | 68,000 - 68,999 | Groups (manually assigned) | +## OAuth2/OIDC Login (Web Services) + +For OAuth2/OIDC login to web services like Grafana, users need: + +1. **Primary credential** - Password set via `credential update` (separate from unix password) +2. **MFA** - TOTP or passkey (Kanidm requires MFA for primary credentials) +3. **Group membership** - Member of `users` group (for OAuth2 scope mapping) +4. **Email address** - Set via `person update --mail` + +### Setting Up Primary Credential (Web Login) + +The primary credential is different from the unix/POSIX password: + +```bash +# Interactive credential setup +kanidm person credential update + +# In the interactive prompt: +# 1. Type 'password' to set a password +# 2. Type 'totp' to add TOTP (scan QR with authenticator app) +# 3. Type 'commit' to save +``` + +### Verifying OAuth2 Readiness + +```bash +kanidm person get +``` + +Check for: +- `mail:` - Email address set +- `memberof:` - Includes `users@home.2rjus.net` +- Primary credential status (check via `credential update` → `status`) + ## PAM/NSS Client Configuration Enable central authentication on a host: -- 2.49.1