Skip to content

feat(operators): per-session attribution — session role + impersonate RBAC#45

Merged
stxkxs merged 1 commit into
mainfrom
feat/attribution-session-role
Jun 20, 2026
Merged

feat(operators): per-session attribution — session role + impersonate RBAC#45
stxkxs merged 1 commit into
mainfrom
feat/attribution-session-role

Conversation

@stxkxs

@stxkxs stxkxs commented Jun 20, 2026

Copy link
Copy Markdown
Member

Draft — the platform side of fab's per-session human attribution. Pairs with nanohype/fab#30 (also draft), which is blocked until this exists.

What

A Platform opts in with spec.attribution; the operator then reconciles, per tenant, the two resources fab's role-session entrypoint needs:

  1. Session role (<env>-<platform>-session) — assumable only by the tenant IRSA role, only while setting one of the Platform's operators as STS SourceIdentity (sts:AssumeRole + sts:SetSourceIdentity, with a StringEquals condition on sts:SourceIdentity). Scoped to the tenant baseline policy (Bedrock invoke) + the same permissions boundary — never broad sts:AssumeRole.
  2. Impersonate RBAC — a ClusterRole granting impersonate on exactly the named operator users (never impersonate *), bound to the tenant-runtime ServiceAccount. fab's session kubeconfig authenticates with that SA token while impersonating the operator, so apiserver audit records impersonatedUser=<operator>.

Each operator string is reused verbatim as both the allowed SourceIdentity and the impersonate resourceName, so the same human binds the AWS and Kubernetes audit records.

Behavior

  • Idempotent — refreshes the session-role trust on every reconcile (the operator list can change) and converges the baseline attachment.
  • Kill-switch parity — when the Platform is suspended, the session role's baseline is detached, so a suspended tenant can't keep invoking Bedrock through it.
  • Lifecycle — provisioned when spec.attribution is set, torn down when it's removed, and cleaned up in the finalizer (no-ops when never enabled).

Verification

  • 10 new unit tests (session-role trust/baseline/duration/idempotency/suspend/delete via fakeIAM; impersonate RBAC create/update/delete via the controller-runtime fake client).
  • go build, go vet, golangci-lint, and the internal/... + api/... unit suites are green.
  • make generate + make manifests regenerated deepcopy, CRD (config + Helm chart), config/rbac/role.yaml, and the CRD reference doc.
  • Not run: the conformance/envtest suite (needs setup-envtest); the new logic is unit-covered.

🤖 Generated with Claude Code

…mpersonate RBAC

The platform side of fab's per-session human attribution (nanohype/fab#30).
A Platform opts in with spec.attribution; the operator then provisions the two
resources fab's role-session entrypoint needs, per tenant, and tears them down
on removal or deletion.

─── API (api/platform/v1alpha1) ──────────────────────────────────────────
- PlatformSpec.Attribution (*AttributionSpec, optional): Operators []string
  (required, min 1) and SessionRoleMaxDurationSeconds (*int32, 900–43200,
  default 3600). nil = unattributed, the default.
- PlatformStatus.SessionRoleArn carries the provisioned session role ARN.
- Each operator string is reused verbatim as BOTH an allowed STS SourceIdentity
  and an impersonate resourceName, so the same identity binds the AWS and
  Kubernetes audit records (the contract fab documents in docs/attribution.md).

─── Session role (platform_session_iam.go) ───────────────────────────────
- ensureSessionRole mints <env>-<platform>-session (same 64-char + FNV-1a
  hash scheme as the tenant role). Trust: only the tenant IRSA role may assume,
  and only while setting one of the Platform's operators as SourceIdentity —
  Action [sts:AssumeRole, sts:SetSourceIdentity] with a StringEquals condition
  on sts:SourceIdentity. Permissions: the tenant baseline policy (Bedrock
  invoke) and the same permissions boundary — never broad sts:AssumeRole.
- Idempotent: GetRole→Create on miss; on hit it refreshes the trust policy
  (the operator list can change) via UpdateAssumeRolePolicy and converges the
  baseline attachment.
- Kill-switch parity: when the Platform is suspended the session role's
  baseline is DETACHED, not attached — otherwise a suspended tenant could keep
  invoking Bedrock through the session role after its tenant role's baseline
  was pulled.
- deleteIamRole/deleteSessionRole now share a detachAndDeleteRole helper.

─── Impersonate RBAC (platform_rbac.go) ──────────────────────────────────
- ensureOperatorImpersonateRBAC creates a ClusterRole granting `impersonate`
  on exactly the named operator users (never `impersonate *`) bound to the
  tenant-runtime ServiceAccount, named <tenant-ns>-impersonate. fab's session
  kubeconfig authenticates with that SA token while impersonating the operator,
  so apiserver audit records impersonatedUser=<operator>.
- Cluster-scoped, so (like the tenant namespace) it's reaped through the
  finalizer, not OwnerReferences.

─── Wiring (platform_controller.go) ──────────────────────────────────────
- Reconcile provisions the pair when spec.attribution is set (after the tenant
  IRSA role + SA exist), records status.SessionRoleArn, and tears the pair down
  when attribution is removed. Finalizer cleans up both (no-ops when never
  enabled).
- RBAC markers added for clusterroles/clusterrolebindings and (for escalation
  prevention) impersonate on users — regenerated into config/rbac/role.yaml.

─── Tests / codegen ──────────────────────────────────────────────────────
- 10 unit tests: session-role trust/baseline/duration/idempotency/suspend/
  delete (fakeIAM) and the impersonate RBAC create/update/delete (the
  controller-runtime fake client). go build, vet, golangci-lint, and the
  internal+api unit suites are green.
- make generate + make manifests regenerated deepcopy, the CRD (config + Helm
  chart copy), the RBAC role, and the CRD reference doc.

Draft: pairs with nanohype/fab#30 (also draft). The conformance/envtest suite
was not run here (needs setup-envtest); the new logic is unit-covered.

Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
@stxkxs stxkxs marked this pull request as ready for review June 20, 2026 03:49
@stxkxs stxkxs merged commit 632db91 into main Jun 20, 2026
19 checks passed
@stxkxs stxkxs deleted the feat/attribution-session-role branch June 20, 2026 03:55
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