diff --git a/dankclaw/.env.example b/dankclaw/.env.example index da213b4..566e49c 100644 --- a/dankclaw/.env.example +++ b/dankclaw/.env.example @@ -20,11 +20,11 @@ WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=25s # Gitea database GITEA_DB_PASSWORD=changeme -# Grafana auth-proxy trust token. The outpost proxy provider 'grafana-proxy' -# forwards authenticated traffic to Grafana with Authorization: Bearer -# _; the live .env must set this to that exact -# concatenation (provider created via ak shell on authentik-server). -GRAFANA_PROXY_TOKEN=changeme +# Grafana OAuth2 client secret (authentik OAuth2Provider "grafana", pk 9). +# Created via ak shell on authentik-server; the live .env must hold the +# provider's generated client_secret verbatim. Rotation = rotate the provider +# secret in authentik, restart grafana after updating the live .env value. +GRAFANA_OAUTH_CLIENT_SECRET=changeme # Caddy (the homelab's single reverse proxy, on this host) # bcrypt hash (caddy hash-password). deploy.sh renders it into diff --git a/dankclaw/Caddyfile b/dankclaw/Caddyfile index c019d5d..5e913bf 100644 --- a/dankclaw/Caddyfile +++ b/dankclaw/Caddyfile @@ -26,9 +26,9 @@ # Authentik IdP itself (auth.franksdank.cloud — its login flow must never sit # behind a challenge) are exempt by construction: their vhosts never route # through Anubis. ComfyUI left this exempt set on 2026-08-31 and Grafana left -# it on 2026-09-01 (auth_proxy mode; the outpost proxies authenticated traffic -# through to it): both now sit behind the guard chain with their proxy -# providers registered on the embedded outpost. +# it on 2026-09-01 (OAuth2 relying-party mode against authentik): both now sit +# behind the guard chain; Grafana authenticates users itself via the OAuth2 +# code flow, so no identity header is forwarded to it. # The pihole vhost keeps its basicauth edge; the hash placeholder below is # rendered by deploy.sh from CADDY_BASICAUTH_HASH (host .env, git-ignored) into # /home/dank/Containers/caddy/Caddyfile, which the container mounts. @@ -133,10 +133,11 @@ gitea.franksdank.cloud { reverse_proxy 192.168.0.143:3000 } -# --- Grafana (guarded: Anubis → Authentik; grafana-proxy provider, auth_proxy mode) --- -# Authenticated traffic returns at the internal :8017 listener and is proxied -# through by the outpost with Authorization: Bearer _; -# Grafana's auth.proxy trusts that token and auto-upserts a user per login. +# --- Grafana (guarded: Anubis → Authentik outpost gate; login inside Grafana) --- +# Auth model = OAuth2 relying party against authentik (OAuth2Provider "grafana", +# confidential client). The guard chain gates unauthenticated traffic; the +# outpost carries no provider token in this deployment, so identity headers +# never reach Grafana — RP mode needs none and works regardless of topology. # The basicauth edge this replaces (CADDY_BASICAUTH_HASH) is retired. grafana.franksdank.cloud { reverse_proxy anubis:8923 { diff --git a/dankclaw/compose.yaml b/dankclaw/compose.yaml index 9bcfffa..586d337 100644 --- a/dankclaw/compose.yaml +++ b/dankclaw/compose.yaml @@ -177,14 +177,29 @@ services: user: "0:0" environment: GF_SERVER_ROOT_URL: "https://grafana.franksdank.cloud" - # Auth proxy mode: the authentik embedded outpost proxies authenticated - # traffic through with Authorization: Bearer _ - # (the grafana-proxy provider on the outpost). The token lives in the - # host .env (never committed); auto-upsert maps each authenticated - # authentik user to a Grafana user by email, default role Viewer. - GF_AUTH_PROXY_ENABLED: "true" - GF_AUTH_PROXY_HEADERS: "name Auth;logo https://grafana.franksdank.cloud/public/img/login-app-logo.png;auto-upsert" - GF_AUTH_PROXY_TOKEN: "${GRAFANA_PROXY_TOKEN}" + # Auth model = OAuth2 relying party against authentik (OAuth2Provider + # name "grafana", pk 9, confidential client_type, created via ak shell). + # Chosen over auth_proxy because the embedded outpost carries no provider + # token (token=None), so no identity header ever reaches Grafana — RP mode + # works regardless of outpost topology. The public vhost stays behind the + # Anubis challenge + outpost gate; login happens inside Grafana via the + # Authentik button. Users auto-upsert as Viewer (no role mapping yet); + # the local admin account remains the only Admin. + GF_AUTH_GENERIC_OAUTH_ENABLED: "true" + GF_AUTH_GENERIC_OAUTH_NAME: "Authentik" + GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "grafana-21ca190cde75d60f" + GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "${GRAFANA_OAUTH_CLIENT_SECRET}" + # Global authorize path: this fork resolves the provider by client_id + # query param — /application/o//authorize/ does not exist here. + GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://auth.franksdank.cloud/application/o/authorize/" + GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://auth.franksdank.cloud/application/o/token/" + # ?api=1 = introspection-style userinfo: returns all custom claims + # (entitlements/groups) for access tokens, not just the standard set. + GF_AUTH_GENERIC_OAUTH_API_URL: "https://auth.franksdank.cloud/application/o/userinfo/?api=1" + GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: "false" + GF_AUTH_GENERIC_OAUTH_AUTO_UPSERT: "true" + GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_ORG: "true" + GF_AUTH_GENERIC_OAUTH_ORG_ID: "1" ports: - "3002:3000" volumes: