feat(abac): user-scoped private Tools/Agents/Skills via allowedPrincipals (#149) - #193
feat(abac): user-scoped private Tools/Agents/Skills via allowedPrincipals (#149)#193k5s-bot[bot] wants to merge 2 commits into
Conversation
…pals Resolves #149. Adds an optional `allowedPrincipals` to the Tool, LocalTool, Agent, and Skill CRDs — an ABAC private-scoping layer enforced by the orchestrator ON TOP of the existing RBAC `allowedRoles` filter (ADR 0036). Empty/unset = public (unchanged). Non-empty = private: a caller is a retrieval candidate only if RBAC passes AND their resolved principal (ADR 0030 §6, `identity.principal ?? subject`) is listed. Skills derive their effective private set by intersecting their own list with any private tool/agent they reference, so a skill can never widen a private tool's audience. - core-controller: `AllowedPrincipals` on Tool/LocalTool/Agent/Skill specs; regenerated deepcopy + CRD manifests. - agent-orchestrator: `allowedPrincipals` on the descriptors, read from the CRDs by every registry; `callerPrincipal` added to the tool/agent/skill query filters; enforced in all three Qdrant stores (query + getByIds) via a shared qdrant-abac helper; skill audience derivation extended; graph threads the principal through a single `callerFilter(identity)` at every store call. - Fail-closed and rolling-upgrade safe (explicit `private` flag on upsert). - ADR 0036 + README/docs; unit tests across stores, registries, derivation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PGNNomuXvbzwgcaF6gxDb
|
🤖 Starting work on this now. Watch live or take over the session here: https://claude.ai/code/session_01WvxjeetdHb5EnvWqTNjb48 |
…-149 # Conflicts: # docs/adr/README.md
ai-triage: brought back in sync with
|
|
Done. Here's a summary of what I did on PR #193. What the feedback was
What I changed
✅ Opened pull request: imaustink/agent-controller#193 |
Resolves #149 ("User Scoped Skills, Agents and Tools").
What & why
The issue asks for a way to mark a Skill, Agent, or Tool as privately scoped
to specific users, in addition to the existing RBAC controls. RBAC today
(
allowedRoles, ADR 0004) gates by role — a class of caller — which can'texpress "only this person (or this handful) may see and use this resource"
without minting a throwaway one-person role and attaching it everywhere.
This adds an optional
allowedPrincipalsto theTool,LocalTool,Agent, andSkillCRDs — an ABAC (attribute-based access control) layer theorchestrator enforces on top of RBAC. The design is written up in
ADR 0037.
Semantics
keeps working unchanged.
when both hold: RBAC passes (
allowedRoles ∩ callerRoles) and thecaller's resolved principal (ADR 0030 §6 —
identity.principal, falling backto
subject; e.g.github:<login>) is one ofallowedPrincipals.allowedPrincipals, and their effective private set is the intersection ofthat with the
allowedPrincipalsof every referenced tool/agent that isitself private — so a skill can never widen a private tool's audience.
Changes
core-controller (Go):
AllowedPrincipals []string(optional) onToolSpec,LocalToolSpec,AgentSpec,SkillSpec, with doc comments.zz_generated.deepcopy.goand the CRD manifests underconfig/crd/basesviamake generate manifests(chartcrds/copies aregitignored build artifacts, synced by the same target).
agent-orchestrator (TS):
allowedPrincipalsonToolDescriptor/AgentDescriptor/SkillDescriptor,read from the CRDs by every registry.
callerPrincipaladded to the tool/agent/skill query filters and enforced inall three Qdrant stores (
query+getByIds) via a sharedvector-store/qdrant-abac.tshelper (payload gainsallowedPrincipals+ adenormalized
privateflag; the filter ANDs apublic OR names-meclause).derive-access.tsextended to derive the skill's effective principal set.graph.tsthreads the principal through onecallerFilter(identity)used atevery store
query/getByIdssite — so ABAC is enforced identically on RAGretrieval, the active-skill/active-agent re-checks, IntegrationRoute forced
targets, and a skill's tool resolution. A private resource never enters the
candidate/skill-tool set, so it can never be planned or launched.
Docs/tests: ADR 0037 (+ index), orchestrator README, and unit tests across
the stores, registries, and skill-access derivation (incl. inheritance,
intersection, disjoint/unreachable, and private-respond-only cases).
Verification
apps/agent-orchestrator:tsc --noEmitclean; full vitest suite 592passed.
controllers/core-controller:go build ./...andgo vet ./api/...clean;make generate manifestsregenerates cleanly.Notes / out of scope
allowedPrincipalsgates discovery & selection; it's orthogonal toidentityProviders(which governs whose credential a launch runs with).creating user as an owner, and any management UI — listing principals on the
CR is the v1 scope; groups can layer on later without changing the filter.
Maintainer: apply the ai-review label to this PR to request an automated
code review, or the ai-triage label to have review feedback addressed and
the branch brought back in sync with its base. (The automation can't label its
own PR, so this needs a human.)