grafana: switch auth_proxy -> OAuth2 relying-party against authentik

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.
This commit is contained in:
Frank
2026-08-31 03:32:53 -07:00
parent ec03929d95
commit 3d64f2ca99
3 changed files with 36 additions and 20 deletions
+5 -5
View File
@@ -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
# <client_id>_<client_secret>; 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
+8 -7
View File
@@ -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 <client_id>_<client_secret>;
# 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 {
+23 -8
View File
@@ -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 <client_id>_<client_secret>
# (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/<slug>/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: