Skip to content

feat: add Milo IAM (ProtectedResource + Roles) for inventory group (#39)#40

Merged
ecv merged 1 commit into
mainfrom
feat/inventory-iam-39
Jun 4, 2026
Merged

feat: add Milo IAM (ProtectedResource + Roles) for inventory group (#39)#40
ecv merged 1 commit into
mainfrom
feat/inventory-iam-39

Conversation

@ecv

@ecv ecv commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #39. Inventory CRDs are Established on the Milo core control plane but no principal can write them: Milo authz is ProtectedResource-driven IAM, not k8s RBAC, and config/base shipped zero IAM resources. Every apply was rejected:

providers.inventory.miloapis.com "netactuate" is forbidden:
User "evetere@datum.net" cannot get resource "providers" in API group
"inventory.miloapis.com" at the cluster scope

What's shipped — config/base/iam/

  • protected-resources/ — one ProtectedResource per kind (12). Cluster-scoped root resources (no parentResources), mirroring the quota service. Registering them is what makes inventory.miloapis.com/<plural>.<verb> addressable by IAM.
  • roles/ — group-wide Roles, namespaced to milo-system:
    • inventory.miloapis.com-viewerget/list/watch on all kinds
    • inventory.miloapis.com-editor — viewer + create/update/patch/delete
    • inventory.miloapis.com-admin — inherits editor (full)
    • inventory.miloapis.com-operator — read + update/patch for the controller
  • policybindings.example.yaml + README.md — documents the binding a loader/operator principal needs (per-kind resourceKind, platform-wide) and the self-serve gap (a caller with only policybindings.create can't bind itself without an existing Role).

Deployment model

Mirrors config/base/crd: targets Milo, not the manager pod's local cluster (the iam.miloapis.com CRDs only exist on Milo). Applied by the same Flux Kustomization that applies base/crd + the webhook component — add config/base/iam to its paths on the infra side (datum-cloud/infra#2675). Intentionally not wired into config/base, which forces inventory-system and feeds the dev overlay against a kind cluster with no IAM CRDs.

Verification

  • kubectl kustomize builds clean for config/base/iam, protected-resources/, roles/, the example, and the unchanged config/base + dev overlay.
  • Once the infra Flux Kustomization picks up base/iam and a binding exists for the loader principal:
    bin/load-inventory --dry-run all   # expect 0 Forbidden
    bin/load-inventory all
    

Also extends the CLAUDE.md "Adding a new kind" checklist with the IAM step so this gap doesn't recur.

🤖 Generated with Claude Code

Inventory CRDs were Established on the Milo core control plane but no
principal could write them: Milo authz is ProtectedResource-driven IAM,
not k8s RBAC, and config/base shipped no IAM resources. Every apply was
rejected with "cannot get resource ... in API group inventory.miloapis.com".

Ship inventory IAM under config/base/iam:

- A ProtectedResource per kind (12) registering the group's permission
  strings. Cluster-scoped root resources, no parentResources.
- Group-wide Roles in milo-system: viewer, editor, admin, operator.
- An example PolicyBinding (per-kind resourceKind, platform-wide) plus a
  README documenting who can populate inventory and the self-serve gap.

Mirrors config/base/crd: applied to Milo by the same Flux Kustomization,
intentionally not wired into config/base (which forces inventory-system
and feeds the dev overlay against a kind cluster with no IAM CRDs).

Also extends the CLAUDE.md "Adding a new kind" checklist with the IAM step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ecv ecv merged commit a54f201 into main Jun 4, 2026
3 checks passed
@ecv ecv deleted the feat/inventory-iam-39 branch June 4, 2026 17:38
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.

Inventory unwritable: no IAM (ProtectedResource/Role/PolicyBinding) for inventory.miloapis.com

2 participants