Skip to content

feat(iam): re-home audit-log-querier grant to a standalone activity role (replaces #677) - #679

Draft
ecv wants to merge 1 commit into
mainfrom
iam-rehome-audit-log-querier-standalone-role
Draft

ecv wants to merge 1 commit into
mainfrom
iam-rehome-audit-log-querier-standalone-role

Conversation

@ecv

@ecv ecv commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces #677. Closes #676.

Delivers the "users can query their own audit logs" capability from the activity service overlay without mutating the shared iam-user-self-manage Role, keeping the foundational milo control plane free of any activity dependency.

Why #677 doesn't work

#677 added a partial Role/iam-user-self-manage manifest (only spec.inheritedRoles) to the activity overlay and relied on server-side apply to merge that single entry into the core role.

That assumption fails under the actual GitOps setup (datum-cloud/infra):

  • The core role is applied by the milo-core-control-plane-crds Flux Kustomization (./crd/overlays/core-control-plane into ../../../roles).
  • The partial overlay would be applied by milo-activity-policies (./services/activity).
  • Flux's kustomize-controller server-side-applies every Kustomization under a single shared field manager (kustomize-controller). There is no per-Kustomization manager and no field-manager override on the Kustomization spec.

With one shared manager, each apply re-declares its full field set, so SSA prunes any field that manager owned but no longer lists. The two Kustomizations therefore prune each other's fields on every reconcile (interval: 1h each). The role flip-flops:

After reconcile of… Result
milo-core-control-plane-crds includedPermissions present, audit inheritance gone
milo-activity-policies audit inheritance present, all self-manage permissions gone

inheritedRoles being listType=map only enables clean merge across distinct field managers owning distinct keys, which Flux does not provide here. (The companion infra PR's premise that inheritedRoles is "an atomic list" is also incorrect, it is map-type, but the shared-field-manager problem stands regardless.)

What this PR does instead

  • New: config/services/activity/roles/activity-self-audit-log-querier.yaml, a standalone Role that inherits activity.miloapis.com-audit-log-querier. A distinct object with a single owner means no cross-Kustomization field contention.
  • Edit: config/services/activity/kustomization.yaml wires the role into the activity overlay.
  • Webhook (user_webhook.go): grants the role per-user, scoped to the user's own User resource, mirroring the existing user-self-manage PolicyBinding, so self-audit access stays self-scoped. Gated on a configurable role name (empty means skip), so the core control plane carries zero activity coupling when activity is not deployed.
  • Controller (user_controller.go): attaches an owner reference to the binding for GC on user deletion, IsNotFound-guarded so it is inert when the activity overlay is absent.

Why not a static group binding

A single PolicyBinding to system:authenticated (like organization-creator-policy) needs no per-user code, but only resourceKind: User is available for a Group subject, which would grant every user query access to all users' audit logs. Rejected as a scope regression.

Validation

  • go build on edited packages is clean.
  • task generate:code produces no manifest diff (no new API types or markers).
  • Full builds and tests via CI.

Coordination

  • Part of: datum-cloud/infra#2943, datum-cloud/infra#2939.
  • Pairs with: datum-cloud/infra#2953 (its "atomic list" justification should be corrected to "map-type, but shared Flux field manager").

Replaces #677. That PR added a partial Role manifest (only
spec.inheritedRoles) for the existing iam-user-self-manage role to the
activity overlay, relying on server-side apply to merge it into the core
role. That does not hold under the actual GitOps setup: both the core role
(via milo-core-control-plane-crds) and the partial overlay (via
milo-activity-policies) are applied by Flux's kustomize-controller, which
uses a single shared field manager (kustomize-controller) for every
Kustomization. With one shared manager, each apply re-declares its full
field set, so the two Kustomizations prune each other's fields on every
reconcile -- the role flip-flops between "has includedPermissions, no audit
inheritance" and "has audit inheritance, no permissions". The map-type of
inheritedRoles only helps across distinct field managers, which Flux does
not provide here.

This instead delivers the self-audit capability without mutating the shared
core role:

- New standalone Role activity-self-audit-log-querier in the activity
  service overlay, inheriting activity.miloapis.com-audit-log-querier. It is
  a distinct object with a single owner -- no cross-Kustomization field
  contention.
- The user webhook grants it per-user, scoped to the user's own User
  resource (mirroring the existing user-self-manage PolicyBinding), so
  self-audit access stays self-scoped. A static Group binding to
  system:authenticated was rejected because only resourceKind=User is
  available for a group subject, which would grant every user access to all
  users' audit logs.
- The grant is gated on a configurable role name (empty -> skip), so the
  core control plane carries zero activity coupling when activity is not
  deployed -- preserving the dependency-decoupling goal of #676.
- The user controller attaches an owner reference to the binding for GC on
  user deletion, guarded by IsNotFound so it is inert when the activity
  overlay is absent.

Refs: #676
Replaces: #677

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ecv
ecv marked this pull request as draft June 30, 2026 20:06
@ecv ecv self-assigned this Aug 6, 2026
ecv added a commit that referenced this pull request Sep 15, 2026
## Summary

Every Claude Code session on this repo rediscovers the same handful of
tooling habits and one piece of milo-specific architecture, because
nothing in the tree records them.

This adds a small version-controlled memory next to the agent config,
one file per fact plus an index, covering the GitHub CLI habits that
keep issue bodies rendering and sub-issues reachable, keeping a local
main current, running an unattended loop without permission prompts, and
where this repo's ActivityPolicy resources live and how they ship.

Everything environment-specific stayed out, so there are no incident
catalogues, cluster names, project IDs, credentials, or deploy-repo
paths.

The ActivityPolicy note overlaps the area
#679 touches, so review that entry
against it.

## Test plan

- [ ] Each entry holds for this repo on its own and is safe to publish
- [ ] A session that loads the index can act on each note without asking
for more context
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.

Move audit-log-querier role inheritance from core CRD bundle to activity service

1 participant