From 3d64f2ca99b7b2ab2e95bf494a33119d37d6b9c2 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 31 Aug 2026 03:32:53 -0700 Subject: [PATCH] grafana: switch auth_proxy -> OAuth2 relying-party against authentik MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The embedded outpost carries no provider token (token=None), so the auth_proxy identity header was never set and no user could log in. RP mode is topology-independent: challenge gate stays in front, login runs inside Grafana via the OAuth2 code flow against auth.franksdank.cloud. Provider 'grafana' (pk 9, confidential) created via ak shell; secret written straight into the live .env (never in git). Auth URL uses the global /application/o/authorize/ path — this fork resolves the provider by client_id query param; per-slug authorize does not exist. Caddyfile comment corrected: no identity header is forwarded to Grafana. --- dankclaw/.env.example | 10 +++++----- dankclaw/Caddyfile | 15 ++++++++------- dankclaw/compose.yaml | 31 +++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 20 deletions(-) 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: