docs: grafana OAuth2-RP contract + authentik fork quirks (live-verified 2026-08-31)
Token exchange chain verified end-to-end in-process on the live provider (pk 9): authorize -> ORM-minted code -> form-encoded token exchange with Basic client auth -> userinfo. Key findings documented: - Application row + grant_types must exist before authorize can complete - token endpoint consumes form-encoded bodies only (JSON body => no grant_type parsed -> unsupported_grant_type); client auth is Basic-only - PropertyMappingEvaluator.evaluate(*args,**kwargs) forwards kwargs to BaseEvaluator.evaluate(expression_source), so context kwargs become the expression source and every evaluation raises a swallowed exception; userinfo therefore answers sub-only claims for every provider in this fork. Scope granting filter (provider= M2M descriptor) is always empty. - Grafana identity = sub hash until the evaluator bug is fixed upstream.
This commit is contained in:
1 parent
3d64f2ca99
commit
9d678122d7
2 files changed
+48
-1
No files matched your search
@@ -119,6 +119,42 @@ three flows (pk1's values: `default-authentication-flow`,
|
|||||||
3-second cycle.
|
3-second cycle.
|
||||||
|
|
||||||
|
|
||||||
|
## OAuth2 relying-party quirks (this fork, verified live 2026-08-31)
|
||||||
|
|
||||||
|
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
|
||||||
|
implementation diverges from upstream in ways that bit us; all verified live:
|
||||||
|
|
||||||
|
- **Application row required.** `OAuth2Provider.objects.create()` does not
|
||||||
|
create the single-table-inheritance `Application` row; without it the flow
|
||||||
|
executor can never complete an authorize request. Create both rows.
|
||||||
|
- **`grant_types` defaults to empty.** Until the provider's `grant_types`
|
||||||
|
contains `"authorization_code"` (enum value with underscore), the token
|
||||||
|
endpoint rejects every exchange with `invalid_request`.
|
||||||
|
- **Token endpoint is form-encoded + Basic-auth only.** A JSON body makes
|
||||||
|
`parse_token_request` see no `grant_type` → `unsupported_grant_type`;
|
||||||
|
POST-body client secrets are not accepted — authenticate the confidential
|
||||||
|
client with `Authorization: Basic base64(client_id:client_secret)`.
|
||||||
|
- **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`,
|
||||||
|
`grafana-profile-scope`, Python-syntax expressions) and will start emitting
|
||||||
|
claims the moment the evaluator bug is fixed — no config change needed then.
|
||||||
|
|
||||||
## Configuration (secrets)
|
## Configuration (secrets)
|
||||||
|
|
||||||
Two secrets live in the host `.env` (git-ignored; placeholders in
|
Two secrets live in the host `.env` (git-ignored; placeholders in
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Grafana
|
# Grafana
|
||||||
|
|
||||||
Grafana visualizes the metrics that Prometheus collects from every service. It runs on Dankclaw and is reachable at `grafana.franksdank.cloud` (via Caddy, with basicauth).
|
Grafana visualizes the metrics that Prometheus collects from every service. It runs on Dankclaw and is reachable at `grafana.franksdank.cloud` (via Caddy, behind the Anubis challenge + embedded-outpost gate; login happens inside Grafana via the Authentik OAuth2 button).
|
||||||
|
|
||||||
**Host:** Dankclaw — **Hostname:** `grafana.franksdank.cloud` — **Port:** 3002 (internal) — **Docs:** https://grafana.com/docs
|
**Host:** Dankclaw — **Hostname:** `grafana.franksdank.cloud` — **Port:** 3002 (internal) — **Docs:** https://grafana.com/docs
|
||||||
|
|
||||||
@@ -38,6 +38,17 @@ Grafana responds on `/api/health` with 200 and `"database":"ok"`.
|
|||||||
|
|
||||||
- Change the default admin credentials at first boot (env or UI).
|
- Change the default admin credentials at first boot (env or UI).
|
||||||
- Dashboards provisioned as YAML survive container recreation; UI-created ones live in the data volume — back it up.
|
- Dashboards provisioned as YAML 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]] → "OAuth2 relying-party quirks". Consequences for Grafana:
|
||||||
|
users auto-upsert as Viewer with `login = <sub hex>`; 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.
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user