Part of the agent action attribution roadmap — the K8s half. Companion to
#39 (the AWS-half session-broker role) and #38 (direct runtime).
Blocked on Phase 0 — settling the standing hub cluster. Don't start until the
hub is validated + stable. Tracking now so the gap is visible.
What
Ship the impersonate RBAC the runtime needs so an agent's K8s action records a
human/session impersonatedUser in the audit log (instead of a bare
system:serviceaccount:…).
The runtime (the attributable path — #38, AgentSandbox/direct) calls the API with
kubectl --as=<id> (Impersonate-User header). The same scoped session id used for
the AWS-side SourceIdentity (#39) is used here, so one operator string binds both
surfaces (CloudTrail sourceIdentity + K8s audit impersonatedUser).
Current state (the gap)
- The operator creates no tenant RBAC and never sets an Impersonate header —
actions trace only to the SA system:serviceaccount:tenants-<platform>:tenant-runtime
(operators/internal/controller/agentfleet_reconcile.go:44-47;
platform_iam.go:99,163). This is the authentic UNATTRIBUTED finding today.
- The impersonate ClusterRole is documented in fab but not shipped on the
substrate (fab/docs/attribution.md:57-60,92-108); FAB_OPERATOR sessions get
kubectl Forbidden until it's applied.
Shape of the change
- A ClusterRole with
verbs: [impersonate] on users (and groups if needed),
scoped by resourceNames to the session id — never impersonate *.
- A binding to the session ServiceAccount, keyed off a new spec field (rides with
the per-session spec.operator work).
- Ship it in
charts/operator (or as an eks-gitops addon so spokes
self-reconcile it). Off-by-default — only rendered when the tenant opts into the
attributable path.
Scope / guardrails
- Blast radius = the impersonated identity's own RBAC. The synthetic
agent:<tenant>:<session> identity is bound to a least-privilege Role, so
impersonating it grants nothing beyond that.
resourceNames-scoped, never wildcard impersonation.
- One-tenant pilot first.
Note on the kagent path
kagent's MCP tool-server makes the real K8s call under its own SA
(agentfleet_reconcile.go:216-225), so it's unattributable at the agent layer
without the tool-server itself impersonating. The attributable workload therefore
runs on the direct/AgentSandbox runtime (#38); tool-server impersonation is a
later, harder option. Keep kagent as the governed/unattributed exhibit for now.
Independence
crossbearing consumes the audit log only; nothing crossbearing-specific lands here.
Part of the agent action attribution roadmap — the K8s half. Companion to
#39 (the AWS-half session-broker role) and #38 (direct runtime).
What
Ship the
impersonateRBAC the runtime needs so an agent's K8s action records ahuman/session
impersonatedUserin the audit log (instead of a baresystem:serviceaccount:…).The runtime (the attributable path — #38, AgentSandbox/direct) calls the API with
kubectl --as=<id>(Impersonate-User header). The same scoped session id used forthe AWS-side SourceIdentity (#39) is used here, so one operator string binds both
surfaces (CloudTrail
sourceIdentity+ K8s auditimpersonatedUser).Current state (the gap)
actions trace only to the SA
system:serviceaccount:tenants-<platform>:tenant-runtime(
operators/internal/controller/agentfleet_reconcile.go:44-47;platform_iam.go:99,163). This is the authentic UNATTRIBUTED finding today.substrate (
fab/docs/attribution.md:57-60,92-108);FAB_OPERATORsessions getkubectl
Forbiddenuntil it's applied.Shape of the change
verbs: [impersonate]onusers(andgroupsif needed),scoped by
resourceNamesto the session id — neverimpersonate *.the per-session
spec.operatorwork).charts/operator(or as aneks-gitopsaddon so spokesself-reconcile it). Off-by-default — only rendered when the tenant opts into the
attributable path.
Scope / guardrails
agent:<tenant>:<session>identity is bound to a least-privilege Role, soimpersonating it grants nothing beyond that.
resourceNames-scoped, never wildcard impersonation.Note on the kagent path
kagent's MCP tool-server makes the real K8s call under its own SA
(
agentfleet_reconcile.go:216-225), so it's unattributable at the agent layerwithout the tool-server itself impersonating. The attributable workload therefore
runs on the direct/AgentSandbox runtime (#38); tool-server impersonation is a
later, harder option. Keep kagent as the governed/unattributed exhibit for now.
Independence
crossbearing consumes the audit log only; nothing crossbearing-specific lands here.