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 + } } }