Repository navigation
fix(grafana): user-project mapping proxy fix for v1.5.7-arbisoft - #44
Merged
sobankhalid101 merged 7 commits intoSep 8, 2026
Merged
Conversation
The API server runs with UseRawPath=true, so gin re-routes HandleContext on URL.RawPath whenever it is non-empty. CheckAuthorizationHeader stripped the /rest prefix from URL.Path only, leaving RawPath prefixed, so the replay matched no route and returned gin's 404. net/url sets RawPath only when the caller's escaping differs from Go's own encodePath output. That is the case for the reserved characters encodeURIComponent escapes but encodePath does not, notably "@" (%40) in an email login and "/" (%2F) inside a segment; a space (%20) escapes identically and was never affected. No existing /rest caller has hit this because none passes an encoded path parameter. The Grafana datasource proxy route added later in this branch does, on every click, since the mapping key is an email address.
The user-project-mapping routes are about to become reachable through Grafana's datasource proxy. That endpoint is authorized on datasources:query, not on org role, so any signed-in Grafana user including a Viewer can call it and would otherwise be able to grant themselves any project. Requests carrying the REST API-key identity must now also present X-Grafana-User, and that user must hold the Admin org role, checked live against Grafana's /api/org/users. Everything else fails closed: a missing header, absent configuration, or an unreachable Grafana all yield 403. Callers authenticated any other way pass through untouched, so config-ui behaviour is unchanged; gating the session path is tracked separately, alongside the /access/* routes that already do it. The lookup matches on login OR email because Grafana sends the login, which equals the email for SSO accounts but not for the migrated local ones, and it requires an exact hit before trusting the role because Grafana's ?query= is a substring search that can return an unrelated admin row. Needs GRAFANA_BASE_URL and GRAFANA_SERVICE_ACCOUNT_TOKEN. The token needs a Grafana service account with the Admin basic role, since Grafana OSS cannot scope /api/org/users below it; give it a dedicated, separately revocable account.
The Add/Remove buttons posted to a hostname-less /api/user-project-mappings path, so the request followed whichever host happened to serve Grafana. On prod that is Grafana's own hostname, where /api is Grafana's API and no such route exists: 404. On staging it did reach DevLake, but has returned 403 "csrf token missing or invalid" since the AUTH_ENABLED cutover, because the panel sends no X-CSRF-Token. Both are the same defect and both are fixed by calling Grafana's own datasource proxy instead. Grafana forwards to DevLake over internal DNS and attaches a scoped API key server-side, so the key never reaches the browser and the call stays on Grafana's own origin: no CORS, no SameSite, no new hostname or route, and DevLake stays off the public edge. It also fixes the CSRF failure, though not because of the Bearer key: there is no Bearer exemption in CSRFProtect, and the /rest rewrite runs the whole middleware chain twice. Grafana strips every cookie from the proxied request, so no devlake_session arrives and the check never engages. That makes jsonData.keepCookies actively dangerous here; the provisioning comment says so. The URL is built from appSubUrl at click time because Grafana's API root is "" on prod and "/grafana" on staging, so one file is now correct in both. The panels move to the plugin's code-only update mode, which also replaces the "Check DevLake API URL" toast with the real status and body.
…nd use limit query param
…erpolation
Add/Remove Mapping threw SyntaxError before issuing any request:
const userLogin = asghar.ali@arbisoft.com;
const project = Corey Systems;
Grafana's json formatter returns a bare string unchanged -- it only
stringifies arrays -- so `${userLogin:json}` emitted an unquoted token and
the panel failed to compile the code on every real value.
Switching to a quoting formatter would only narrow the window: :doublequote
and :singlequote escape their own quote character but not backslashes or
newlines, so a login or project name carrying either still breaks out of the
literal.
The interpolation is removed instead. replaceVariables() is applied to the
whole of this code before it is compiled, so any variable value placed in
the source is a potential syntax error; reading through templateService at
runtime hands back a plain string that never passes through the compiler.
The variable name is concatenated so the read itself does not match
Grafana's variable regex. Written as one literal it would be substituted in
place and put the raw value straight back into the source, which is the bug
this commit removes. There is a comment on the line saying so.
Verified by simulating replaceVariables over both panels and syntax-checking
the result: the source is unchanged by interpolation, and both compile for
values containing an apostrophe or a backslash, each of which fails before
this change.
The rationale for reading variables at runtime belongs in the commit and the PR, not in a code field edited through a small editor box in the panel options. Sixteen comment lines against fifteen of code made the actual request hard to find. Cut to seven: what appSubUrl is for, a pointer to the datasource file that already carries the full proxy and CSRF explanation, and the one warning that has to survive -- keep the variable name split, or the line becomes a variable reference and puts the raw value back in the source. Also adds blank lines between the three sections (URL, variables, request) and moves the two long fetch calls onto their own lines. No behaviour change. Re-checked that neither panel's source contains anything matching Grafana's variable regex, including in the comments, and that both still compile after simulated interpolation with values carrying an apostrophe, a backslash or a newline.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release PR for v1.5.7-arbisoft. Cherry-picks the Grafana user-project-mapping fix onto
release-1.5.7-arbisoft, which is branched from thev1.5.6-arbisofttag — not frommain-arbisoft.Prod runs
v1.5.6-arbisoft.main-arbisoftis 103 commits / 152 files ahead of it, carrying the Claude-Code OTel connector, the native-OIDC Access Directory, and a large config-ui refactor — none of which is ready for production. Taggingmain-arbisoftwould ship all of it. This branch carries the fix and nothing else.What it fixes
The Admin: User Project Access dashboard's Add/Remove buttons return
404on prod: they post to a hostname-less/api/user-project-mappingspath, which ondevlake-grafana.arbisoft.comresolves to Grafana's own API where no such route exists. They now go through Grafana's datasource proxy, which forwards to DevLake over internal DNS with a scoped API key attached server-side, so the key never reaches the browser.Already merged to
main-arbisoftas #40, #41 and #42 and running on staging. The cluster-side config is live in prod via arbisoft/hks-argocd#931.Commits (7)
fix(auth): authorize webhooks when auth is enabled (apache#9008)fix(api): strip /rest from RawPath so encoded path params rerouteUseRawPath=truemakes gin re-route onRawPath, which was left prefixed with/rest; every email-shaped login 404'dfeat(api): require a Grafana org Admin for user-project mapping changesfix(grafana): route user-project mapping through the datasource proxyfix(grafana): safe JSON variable interpolation … use limit query paramfix(grafana): read mapping dashboard variables at runtime${var:json}returns bare strings for scalars →SyntaxErrorstyle(grafana): trim the mapping panel commentsIt is not one of the fix commits, and it is not optional. It introduces
shared.SetRestAuthUserand its call site. Without it:gin_utils.goreferences an undefinedrestAuthKeyand the build fails; andGetRestAuthUserwould always returnfalse,RequireGrafanaAdminwould take its pass-through branch for every request, and the Admin gate would be a silent no-op — any Grafana Viewer could grant themselves any project. Exactly the hole this release exists to close.Verified present on this branch: call site at
middlewares.go:225, accessors atgin_utils.go:36,44, gate reading it atgrafanarole/middleware.go:43.Cherry-pick notes
Applied cleanly except one conflict in
env.example, resolved by keeping only theGRAFANA_BASE_URL/GRAFANA_SERVICE_ACCOUNT_TOKENblock and droppingOIDC_ALLOW_EMAILS/OIDC_ALLOW_DOMAINS, whose Go code does not exist at this tag.The merged
api.goandrouter.gocame out correct for 1.5.6:grafanarole.Initsits afterauth.Initwith noaccess.Init, the middleware chain keeps 1.5.6's unconditionalOAuth2ProxyAuthentication, and there are noaccess-package imports.Leakage check: no files under
config-ui/,telemetry/,backend/plugins/, or the access directory. 12 files, +806/−29.Why this PR exists at all
Partly to run the tests. The fork's
test.ymltriggers on PRs tomainandrelease-*— upstream Apache's branch names — so PRs tomain-arbisoftonly ever ran lint, and the Go unit tests in this fix have never executed anywhere. Targeting arelease-*base is what finally makes them run:grafanarole: Admin passes; Viewer/Editor, missingX-Grafana-User, unset config and unreachable Grafana all 403; a substring near-miss must not inherit an admin row%40,%2F,%20and a plain path all reach their handler with params correctly unescapedPlease confirm
testis green before merging.After merge
Tag
v1.5.7-arbisoftonrelease-1.5.7-arbisoft. That tag push builds all three images and deploys to production unattended — there is no gate after it, and ECR immutability means the tag name cannot be reused.release-1.5.7-arbisoftstays as the 1.5.x hotfix line. No back-merge needed; these commits are already onmain-arbisoft.🤖 Generated with Claude Code
https://claude.ai/code/session_014QrN5pWnSEk3RCAh9v9ADf