Skip to content

feat: Let users query their own audit logs from the activity service - #679

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

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

Users should be able to query their own audit logs, but granting that through the shared self-manage role ties the core control plane to the activity service, which deploys after it.

An earlier attempt, #677, added the grant to that shared role from the activity deployment, and because Flux applies both deployments as one owner, each strips the other's fields on every reconcile, so the role swings between having audit access and losing every other permission.

This change gives the activity service its own role for the access and binds each user to it for their own account only.

When the activity service is not deployed, the binding is skipped and the core control plane carries nothing of it.

Test plan

  • A user can query their own audit logs and no one else's
  • Repeated reconciles of both deployments leave the shared self-manage role unchanged
  • Without the activity service, users are created normally and get no audit binding
  • Deleting a user removes their audit binding

Fixes #676

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
@ecv

ecv commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

Detail moved out of the description.

Why #677 fails. It added a partial Role/iam-user-self-manage (only spec.inheritedRoles) to the activity overlay and relied on server-side apply to merge that entry into the core role. In datum-cloud/infra the core role is applied by the milo-core-control-plane-crds Flux Kustomization and the partial by milo-activity-policies. Flux's kustomize-controller applies every Kustomization under one shared field manager, so each apply prunes the fields the other owns, on every reconcile (interval: 1h each):

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 is listType=map, which merges cleanly only across distinct field managers, and Flux does not provide them here.

What this change does:

  • config/services/activity/roles/activity-self-audit-log-querier.yaml adds a standalone Role inheriting activity.miloapis.com-audit-log-querier. One object with one owner, so no contention.
  • config/services/activity/kustomization.yaml wires it into the activity overlay.
  • user_webhook.go binds each user to that Role, scoped to their own User resource, mirroring the user-self-manage PolicyBinding. The role name is configurable, and empty skips the binding, so the core control plane carries no activity coupling.
  • user_controller.go sets an owner reference on the binding for garbage collection on user deletion, guarded by IsNotFound so it is inert without the activity overlay.

Rejected alternative. One PolicyBinding to system:authenticated, like organization-creator-policy, needs no per-user code, but a Group subject only allows resourceKind: User, which would let every user query every user's audit logs.

Validation so far: go build on the edited packages is clean, and task generate:code produces no manifest diff.

Supersedes #677. Part of datum-cloud/infra#2943 and datum-cloud/infra#2939.

@ecv ecv changed the title feat(iam): re-home audit-log-querier grant to a standalone activity role (replaces #677) feat: Let users query their own audit logs from the activity service Sep 23, 2026
@ecv
ecv requested review from JoseSzycho and scotwells and removed request for scotwells September 23, 2026 17:24

@scotwells scotwells left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to create a separate policy binding for this. It should be granted through role inheritance on the user self-manage role. It should be patched on the infra layer so the system's are de-coupled.

@ecv

ecv commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

better yes

@ecv ecv closed this Sep 23, 2026
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

2 participants