Skip to content

feat(iam): resolve PolicyBinding subject uid from name#653

Open
ecv wants to merge 2 commits into
mainfrom
iam/resolve-policybinding-subject-uid
Open

feat(iam): resolve PolicyBinding subject uid from name#653
ecv wants to merge 2 commits into
mainfrom
iam/resolve-policybinding-subject-uid

Conversation

@ecv

@ecv ecv commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #652.

Adds a mutating/defaulting webhook on PolicyBinding that resolves a subject's metadata.uid from its name when the uid is omitted. For each subject with no uid, the webhook looks up the named User, Group, or ServiceAccount and stamps its current uid into the subject — before the validating CEL rule runs, so the existing Subject validation passes unchanged.

This implements Option 1 from the issue (least disruptive): the uid stays immutable in the stored spec; admission just fills it from the name.

Why

A Group's metadata.uid is cluster-assigned at create time and unknown at commit time, which made Group/User bindings non-declarative — every caller had to read the uid back imperatively (internal controllers do; external GitOps consumers shipped render-after-reconcile scripts). Now a Group + its PolicyBinding can be committed and applied together.

Behavior

  • Resolves User (cluster-scoped, by name), ServiceAccount (cluster-scoped, by name), and Group (namespaced, by name + namespace).
  • Skips subjects that already carry a uid — uid remains immutable; internal controllers that set it explicitly are unaffected.
  • Skips system groups (name starting with system:) — no backing object, matching the existing CEL exemption.
  • Rejects with a clear admission error when the named subject does not exist, or when a Group subject omits its namespace.

Instance-pinning preserved

The webhook stamps the current uid, so a delete+recreate of a same-named subject yields a different uid and the binding no longer matches until re-applied — same guarantee the uid-on-spec requirement provided, without pushing the read-back onto every caller.

Changes

  • internal/webhooks/iam/v1alpha1/policybinding_webhook.goPolicyBindingMutator + SetupPolicyBindingWebhooksWithManager.
  • cmd/milo/controller-manager/controllermanager.go — wire the webhook into the controller manager.
  • config/webhook/manifests.yaml — generated mutating webhook config (mpolicybinding.iam.miloapis.com, CREATE/UPDATE).
  • config/controller-manager/overlays/core-control-plane/rbac/role.yaml — generated; grants get;list;watch on serviceaccounts (users/groups already present).
  • Taskfile.yaml — include ./internal/webhooks/... in the RBAC generation paths so webhook +kubebuilder:rbac markers are honored.

Testing

  • Unit: internal/webhooks/iam/v1alpha1/policybinding_webhook_test.go — 8 subtests (User/Group/ServiceAccount resolution, multi-subject, skip-when-set, skip system group, not-found error, missing-namespace error). go test ./internal/webhooks/iam/... passes.
  • E2E (Chainsaw): test/iam/policybinding-subject-uid-resolution/ — name-only Group subject gets its uid resolved; non-existent Group is rejected at admission; system group admitted without a uid. Test document validates with chainsaw lint.
  • go build ./... clean.

🤖 Generated with Claude Code

Add a mutating/defaulting webhook on PolicyBinding that resolves a
subject's metadata.uid from its name when the uid is omitted. The
webhook looks up the named User, Group, or ServiceAccount and stamps its
current uid into the subject before the validating CEL rule runs.

This makes Group/User/ServiceAccount bindings declaratively committable:
a Group and its PolicyBinding can be applied together by GitOps without
an out-of-band render-after-reconcile step to read the uid back. It also
centralizes the imperative uid read-back that internal controllers
already perform.

The instance-pinning guarantee is preserved. The webhook stamps the
current uid, so a delete+recreate of a same-named subject yields a
different uid and the binding no longer matches until re-applied. System
groups (name starting with "system:") have no backing object and are
left without a uid, matching the existing CEL exemption. Subjects that
already carry a uid are untouched.

Closes #652

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ecv ecv requested a review from scotwells June 15, 2026 21:18
@ecv

ecv commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Adding chainsaw now…

@ecv ecv marked this pull request as draft June 15, 2026 21:18
Add a Chainsaw test exercising the PolicyBinding mutating webhook against
a running Milo API server:

- A name-only Group subject (no uid) gets the Group's metadata.uid
  stamped into the stored binding.
- A subject naming a non-existent Group is rejected at admission.
- A system group subject is admitted without a uid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ecv ecv marked this pull request as ready for review June 15, 2026 21:22
@ecv

ecv commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

This lets us greatly simplify https://github.com/datum-cloud/infra/pull/2706

@ecv ecv enabled auto-merge June 18, 2026 21:43
@ecv

ecv commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

bump

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.

IAM: resolve PolicyBinding Group/User subject uid by name (enable declarative GitOps bindings)

1 participant