Repository navigation
Add MCP remote access: OAuth-minted tokens with SpiceDB-native authorization - #17
Open
josephschorr wants to merge 22 commits into
Open
josephschorr wants to merge 22 commits into
josephschorr wants to merge 22 commits into
Conversation
…ons in CheckAccessTokenOp
…mid-pass expiry still requeues
TestCleanCoversEveryCRD has been red since the AccessToken CRD landed (cab8b0c) without a matching crGroupVersionResources entry. AccessToken carries FinalizerAccessToken (cleared only by the operator), so omitting it from clean's CR sweep would also wedge teardown the same way the PublicEndpoint comment describes.
…ered re-lists bounded by a freshness floor + singleflight
admind: GET /admin/v1/tokens (view_tokens) lists AccessToken CRs in the system namespace joined with each token's SpiceDB grant via a new optional AccessTokenGrantReader config seam (ReadAccessTokenGrant); a per-item read error degrades that row to role "unknown" and found=false marks it revoked — one bad token never 500s the page, and spec.tokenHash never reaches the response. POST /admin/v1/tokens/revoke (revoke_token) deletes the CR; the finalizer tears down the SpiceDB tuples. adminui: exact proxy rows /admin/api/tokens (view_tokens) and /admin/api/tokens/revoke (revoke_token), plus the TokensPanel React view registered in the shell nav and client router. operator: wires AccessTokenGrants (spiceDBClient) and AccessTokenNamespace (systemNS) into admind.Config. webassets/dist regenerated (committed build output; mage web:check guard).
…ner-denial coverage
The RBAC test-then-minimize harness (TestRBACSufficiency) flagged every accesstokens verb as OVER-BROAD because the hand-maintained required tables never learned the new resource. Fix per the harness's own convention — trim the roles to actual client calls, then declare them: - operator marker drops main-resource patch (the reconciler's only patch targets accesstokens/status); required rows get/list/watch/update/delete plus the status-patch sufficiency row; create pinned denied (minting is webd's alone). - webd clusterrole drops watch (direct client.New, no informer; the admin tokens page reads through admind in the operator binary); required rows create/get/list plus the status-patch sufficiency row; delete pinned denied (a browser-facing pod must not destroy token records). config/manager/role.yaml and install.yaml regenerated via mage gen:api + mage manifests.
…spec-narrowing notes, consent frame headers
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
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.
Adds a remote access plane so people can reach shared-cluster agents from local tools (coding agents, editors) over MCP, with OAuth-minted tokens whose authorization truth lives entirely in SpiceDB.
What's included
SpiceDB model — a new
accesstokendefinition: one role tuple binds owner + role + expiry (expiration trait),scope_classholds the agentclass filter (wildcard for unfiltered), and$samepermmirror permissions (read_transcript,read,view,interact,approve,start_session) spell out acan_read ⊂ can_interact ⊂ can_fullladder. Every/mcpoperation is authorized by oneCheckBulkPermissionswith three legs: token grants the permission, token covers the session's class, owner holds the real resource permission. Go carries no role→permission table;deniedand owner-offboarding win automatically through leg 3. Also addsplatform#view_tokens/#revoke_token.AccessToken CRD + reconciler — the CRD is the authentication record only (sha256 token hash, owner, client metadata, expiry; immutability enforced via CEL). The reconciler owns finalizer cleanup (deleting the SpiceDB tuples = revocation) and expiry GC. Fail-closed in both directions: tuples without CR → no hash match → 401; CR without tuples → 403.
OAuth authorization server in identityd — RFC 8414 metadata, RFC 7591 dynamic client registration with stateless signed client_ids (no storage, replica-safe), PKCE-only (S256) authorize → consent → token. The consent screen defaults to read-only and offers role + agent-class scoping backed by live SpiceDB lookups. Minting writes tuples first, then the CR, so a fresh token is usable the moment the endpoint responds; CR-create failure compensates by deleting the tuples.
/mcpendpoint in webd (pkg/web/mcpfront) — bearer middleware with uniform 401s (no failure-mode oracle), a 30s token cache with a re-list freshness floor + singleflight (bounds unauthenticated apiserver List pressure), per-token rate limiting that survives cache rebuilds, andstatus.lastUsedAtobservation writes debounced to once per hour.Six read-role MCP tools —
list_sessions,get_session,get_transcript,search_memory,list_artifacts,get_artifact. Denial and not-found return identical text (no existence oracle). Memory search passes only pre-authorized session scopes — the scope list is the authorization boundary.Admin console tokens page — list every token (owner, client, role read back from SpiceDB, scope, expiry, last used, revoked) and revoke; the token hash never leaves the server. Gated by the new platform permissions.
oap setup-mcp— emits local-tool MCP config (claude-code / cursor / generic). OAuth-native clients get just the URL and run their own consent;--mintruns the flow and embeds the bearer in the emitted config.Testing
mage test:unit/test:integration/test:e2e).Deliberate scope choices (follow-ups filed)
/mcpops are structured logs (outcome, failed leg, token id) in this phase; routing them into the append-onlyauthzdecisionledger needs a new write-capable memory-token class and is written up separately for review before the write-capable tools land.get_transcriptis pagination-only (tail=truestreaming deferred);list_sessionsfilters by agent/state (time range deferred). Both recorded in code comments./mcpbearer auth itself is replica-safe.oap tokenmanagement are the next phase per the design doc.