diff --git a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md index a3e1f4e..028d4e6 100644 --- a/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md +++ b/serverAppConfigs-docs/Infrastructure/Authentik Outpost Reference.md @@ -81,7 +81,7 @@ Two provisioning rules, each verified live by an outage: appears. Create the Application with `provider_id` set (the UI does this automatically; raw API/ORM drives must do it explicitly). -## OAuth2 relying-party quirks (this fork, verified live 2026-08-31) +## OAuth2 relying-party quirks (this fork; corrected live 2026-10-20) The Grafana RP provider (`grafana`, pk 9, confidential client) was created and verified end-to-end via `ak shell` ORM drives. This fork's OAuth2 provider @@ -100,23 +100,32 @@ implementation diverges from upstream in ways that bit us; all verified live: - **Provider resolution is by `client_id` query param** on the global `/application/o/authorize/` route; per-slug authorize paths do not exist in this build. Token/userinfo live at `/application/o/token/` and - `/application/o/userinfo/?api=1`. -- **Property-mapping evaluator is broken → userinfo returns `sub` only.** - `PropertyMappingEvaluator.evaluate(*args, **kwargs)` forwards kwargs to - `BaseEvaluator.evaluate(expression_source)`, so the first context kwarg - becomes the *expression source* instead of the context; every call raises a - swallowed `PropertyMappingExpressionException` and the claim is skipped. - Scope-grant filtering has the same class of bug: `ScopeMapping` has no - provider FK (linkage lives in the `ProviderPropertyMapping` through model) - so `filter(provider=...)` always yields an empty queryset and tokens asked - to carry scopes get zero. Net effect: userinfo/introspection answers with - `{sub}` only — email/groups/entitlements claims cannot be delivered by this - build until the evaluator is patched or upgraded. Clients consuming this - provider must key identity off `sub`. -- Scope mappings for this provider exist — `grafana-email-scope` and - `grafana-profile-scope` are both rows in `authentik_core_propertymapping` - (read-only `SELECT`, 2026-10-18) — and will start emitting claims the moment - the evaluator bug is fixed; no config change is needed then. + `/application/o/userinfo/` — do **not** append `?api=1` to a Grafana API URL. +- **NULL `authorization_flow` → authorize 500, `AttributeError: 'NoneType' + object has no attribute 'slug'`.** A provider created via raw `ak shell` + `create()` leaves `authorization_flow` unset; the authorize view then crashes + building the flow planner (`flows/planner.py`) and returns "Server Error — + Powered by authentik" before any claim is built. This was previously + mis-attributed to a broken evaluator; the crash is the missing flow. Fix: set + `authorization_flow = default-provider-authorization-implicit-consent` (the + flow every other provider in this instance already uses). +- **A scope mapping that returns a non-dict emits no claim.** authentik merges a + granted scope mapping's evaluated result into the token **as a mapping**: a + dict contributes its keys as claims; a bare string is keyed under the + mapping's NAME, so nothing usable appears. `grafana-email-scope` returned the + bare string `user.email`, so no `email` claim landed — the evaluator was + never broken (the dict-valued `grafana-profile-scope` mapping has always + delivered, and the stock OpenID mappings return dicts). Fix: + `return {"email": user.email, "email_verified": True}`. With no email, Grafana + v13 falls back to GET `/emails`; authentik 404s it → "Login failed: + InternalError". +- **Claims ride in the id_token.** `include_claims_in_id_token` is on for this + provider and Grafana reads identity from it (`USE_ID_TOKEN=true`). +- **Net (2026-10-20):** the evaluator is fine; email/profile claims reach RP + clients. Both IdP fixes are idempotent in `scripts/authentik-grafana-sso-fix.sh` + (re-run after an ak-shell re-creation of the provider or a blueprint import, + which revert them). Grafana's half + the verified outcome (Viewer, + `login = `, email present) are in [[Grafana]]. ## LDAP provider objects (provisioned via `ak shell` ORM) diff --git a/serverAppConfigs-docs/Infrastructure/Grafana.md b/serverAppConfigs-docs/Infrastructure/Grafana.md index a10bb1a..6325202 100644 --- a/serverAppConfigs-docs/Infrastructure/Grafana.md +++ b/serverAppConfigs-docs/Infrastructure/Grafana.md @@ -4,7 +4,7 @@ description: "Grafana visualizes the metrics that Prometheus collects from every tags: - infrastructure - grafana -timestamp: 2026-09-11 +timestamp: 2026-10-20 --- # Grafana @@ -64,19 +64,28 @@ Grafana responds on `/api/health` with 200 and `"database":"ok"`; measured 2026- - Change the default admin credentials at first boot (env or UI). - Dashboards provisioned as code (dashboard JSON plus a YAML provider, `shared/grafana/provisioning/dashboards/`) survive container recreation; UI-created ones live in the data volume — back it up. - **Auth = OAuth2 relying-party, not auth_proxy.** The embedded outpost carries - no provider token (`token=None`), so no identity header ever reaches Grafana; - RP mode is topology-independent. The full verified contract (provider pk 9, - client_id `grafana-21ca190cde75d60f`, global authorize/token/userinfo paths, - Basic client auth, form-encoded token body, and the fork's userinfo-claims - bug that leaves identity at the `sub` hash only) is in - [[Authentik Outpost Reference]] → "OAuth2 relying-party quirks". Consequences for Grafana: - The unit sets `ALLOW_SIGN_UP=false`, `AUTO_UPSERT=true`, `ALLOW_ASSIGN_ORG=true` - `ORG_ID=1` and **no** role-attribute mapping (`grafana.container:52-55`), so - users land as Viewer with `login = `; no email/groups/role claims - reach Grafana until the evaluator bug is fixed — do not write role mappings - that depend on those claims. The local admin account stays the only Admin. - (The Viewer-default outcome and the `` login are not readable from - the host without the database or an admin password. *(unverified 2026-10-18)*) + no provider token (`token=None`), so no identity header reaches Grafana; the + login is Grafana's own "Sign in with Authentik" button against the `grafana` + confidential-client provider. **Verified end-to-end 2026-10-20** after four + independent breakages had made it fail. The two Authentik-side causes (a NULL + `authorization_flow` → authorize 500; an email scope mapping that returned a + bare string so no `email` claim was emitted) and the full wire contract live in + [[Authentik Outpost Reference]] → "OAuth2 relying-party quirks"; the IdP half is + re-applied by `scripts/authentik-grafana-sso-fix.sh` (idempotent — re-run after + an ak-shell re-creation of the provider or a blueprint import, both of which + revert it silently). The Grafana half is the unit's own env (`grafana.container`): + `SCOPES="openid email profile"` (a GitLab-style `user:email` scope crashes the + authorize view), `USE_ID_TOKEN=true` + no `?api=1` (Grafana v13 otherwise GETs + `/emails`, which authentik 404s and the login dies with "Login failed: + InternalError"), attribute paths `email`/`sub`/`preferred_username`, and + `ALLOW_SIGN_UP=true` — safe because the guard fronts every request, so the + callback is unreachable to unauthenticated traffic. +- **What a new login becomes:** a Viewer in org 1 (`ALLOW_ASSIGN_ORG=true`, + `ORG_ID=1`, no role-attribute mapping), with `login = ` but the real + `email`. The local admin account stays the only Admin. The landing that the + edge-only 13-sso spec never checked is now asserted by + `tests/e2e/specs/20-grafana-sso.spec.ts` (RP round-trip → `/api/user` 200 with + an email). - **Never point this unit's `EnvironmentFile` at the repo-root `.env`.** That file is a gluetun env file and it carries `HOME=/root`; systemd imports it for the service, podman then looks for its configuration under `/root`