Skip to content

feat(oauth): forward-mode OAuth broker with X-JWT-Assertion pass-through - #1

Open
BorisTyshkevich wants to merge 2 commits into
mainfrom
feature/oauth
Open

feat(oauth): forward-mode OAuth broker with X-JWT-Assertion pass-through#1
BorisTyshkevich wants to merge 2 commits into
mainfrom
feature/oauth

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

Summary

Adds an opt-in forward-mode OAuth broker (--oauth-enabled) so the MCP server can validate inbound user bearers against an upstream IdP, mount /oauth/* + RFC 9728 discovery, and forward the raw bearer to Grafana as X-JWT-Assertion. Grafana's [auth.jwt] block validates the assertion independently and maps it to a user, so per-user identity reaches Grafana while the existing service-account token continues to authorise the API call.

Also includes a per-arch local build script + Dockerfile.local for iterating on the fork without round-tripping through upstream CI.

What's in this PR

  • pkg/oauth/wiring.go + tests — the broker itself: upstream IdP discovery, JWKS-backed bearer validation, audience/required-scope/email-domain/hosted-domain checks, stateless auth-code + pending-auth artifacts (HKDF-derived JWE keys), RFC 9728 protected-resource metadata.
  • mcpgrafana.goJWTAssertion field on GrafanaConfig and a new JWTAssertionRoundTripper installed by default in BuildTransport. The round-tripper is purely additive: it sets X-JWT-Assertion if present on the request context, never touches Authorization, so the existing SA-token bearer continues to authorise the call.
  • cmd/mcp-grafana/main.gooauthFlags surface with every secret-bearing field read from files / env only (never CLI flags), so they don't leak into ps eww, shell history, or container metadata.
  • Dockerfile.local + scripts/build-mcp-image.sh — cross-compile the binary on the host, build the image with legacy docker build per arch, stitch a multi-arch manifest. Sidesteps buildx (whose privileged builder is blocked in our sandbox).

Why

Per-user identity in OAuth-secured Grafana deployments — needed for downstream features (the vertamedia ClickHouse adapter in the follow-up PR depends on this).

Test plan

  • go test -tags unit ./... — green
  • Manual: enable --oauth-enabled, sign in via the upstream IdP, hit an MCP tool, observe X-JWT-Assertion lands on Grafana and [auth.jwt] resolves the right user.
  • Manual: with broker disabled, verify zero behaviour change (round-tripper is a no-op when context has no JWTAssertion).

🤖 Generated with Claude Code

BorisTyshkevich and others added 2 commits May 20, 2026 09:49
…iterations

Cross-compiles the mcp-grafana binary on the host (CGO_ENABLED=0), assembles
the image with legacy `docker build` per arch, and stitches a multi-arch
manifest. Sidesteps buildx, whose privileged builder is blocked by the
sandbox docker proxy. Lets us push fork images to ghcr.io/altinity/mcp-grafana
without round-tripping through upstream CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds an opt-in OAuth broker (--oauth-enabled) that validates inbound bearers
against an upstream IdP, mounts /oauth/* + RFC 9728 discovery, and forwards
the raw bearer to Grafana as X-JWT-Assertion. Grafana's [auth.jwt] block
validates the assertion independently and maps it to a user, so per-user
identity reaches Grafana while the existing service-account token continues
to authorise the API call.

Secret-bearing config (client secret, signing secret) is read from files /
env only — never command-line flags — to avoid leaks into `ps eww`, shell
history, or container metadata.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant