From 957f60a584f7dbde66d90e9b3c5974a44b1b433c Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Tue, 3 Nov 2026 23:43:46 +0100 Subject: [PATCH 1/2] docs(kyverno): restate the WebApp child policies for KRO 0.9.4 Both WebApp child admission policies explained themselves in terms of KRO 0.9.2. Re-checked against the 0.9.4 source the platform runs: the apply path still adopts an existing unlabelled object, and deletion now removes every object carrying the parent's ApplySet membership label, with a UID precondition that only guards against a recreate. Both policies are still needed; their headers and the tenant abstraction guide now give today's reasons. Comments only: the rendered policies are unchanged. Fixes #5207 Co-Authored-By: Claude Opus 5.5 --- docs/tenant-abstraction.md | 11 +++++++---- .../restrict-webapp-child-deletion.yaml | 11 +++++++++-- .../restrict-webapp-child-ownership.yaml | 10 +++++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/tenant-abstraction.md b/docs/tenant-abstraction.md index ed3889f1d5..67b0d0dc16 100644 --- a/docs/tenant-abstraction.md +++ b/docs/tenant-abstraction.md @@ -100,16 +100,19 @@ and annotations; they cannot force orphaning by removing that finalizer. The existing platform-owned `flux-system/wedding-app` pilot is an exact exception, not a wildcard namespace exemption. -KRO 0.9.2 identifies children through ApplySet and `kro.run/instance-*` labels, -not WebApp owner references. Child admission therefore checks their complete -placement/identity and refuses updates that would adopt an unlabelled or +KRO 0.9.4 identifies children through ApplySet and `kro.run/instance-*` labels, +not WebApp owner references, and its apply path adopts an existing unlabelled +object. Child admission therefore checks their complete placement/identity and +refuses updates that would adopt an unlabelled or foreign-owned Deployment, Service or HTTPRoute. Non-controller writers must preserve reserved ownership labels. Controller updates may change provenance such as `kro.run/kro-version`, while instance/node/ApplySet identity remains fixed. Existing hostname, secret-store and network admission remain in force; KRO has no hostname bypass. -KRO's pinned deletion path does not itself verify child ownership. Deletion +KRO's pinned deletion path does not itself verify child ownership: it deletes every +object carrying the parent's ApplySet membership label, with a UID precondition that +only guards against a recreate between listing and deleting. Deletion admission additionally requires complete old child ownership, a read of the labelled parent's matching UID, and that parent's deletion timestamp. This prevents failed collision/rollback from deleting an unowned workload or another diff --git a/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-deletion.yaml b/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-deletion.yaml index 5a69252ac5..2262c93519 100644 --- a/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-deletion.yaml +++ b/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-deletion.yaml @@ -1,5 +1,12 @@ -# KRO 0.9.2 deletes graph targets by name without an ownership precondition. -# A failed collision followed by parent deletion must not delete another app. +# KRO 0.9.4 deletes a WebApp's children by ApplySet membership: it lists every object +# that carries the parent's applyset.kubernetes.io/part-of label in the recorded scope +# and deletes each with a UID precondition taken from that same list +# (pkg/controller/instance/deletion.go, applyset/applyset.go). The precondition only +# guards against a recreate between list and delete; a label is not proof of ownership. +# A failed collision followed by parent deletion must not delete another app, so KRO may +# delete only a child whose labels bind it to the parent being deleted. Kept as defence +# in depth: restrict-webapp-child-ownership already refuses adoption and hand edits of +# the reserved labels, the two ways a foreign object could gain that membership. apiVersion: policies.kyverno.io/v1 kind: ValidatingPolicy metadata: diff --git a/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-ownership.yaml b/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-ownership.yaml index c0d11713a4..4a430cedf4 100644 --- a/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-ownership.yaml +++ b/k8s/bases/infrastructure/cluster-policies/best-practices/restrict-webapp-child-ownership.yaml @@ -1,6 +1,10 @@ -# KRO 0.9.2 uses ApplySet labels, not a WebApp ownerReference. Its apply path can -# adopt an existing unlabelled object. Check admission oldObject, not a prior GET, -# to refuse that collision and preserve ownership even across a stale read. +# KRO tracks WebApp children with ApplySet labels, not a WebApp ownerReference. Its +# apply path in 0.9.4 refuses only an object already labelled for another ApplySet; an +# existing unlabelled object is force-applied and gains the membership label, which +# adopts it (pkg/controller/instance/applyset/applyset.go). Check admission oldObject, +# not a prior GET, to refuse that collision and preserve ownership even across a stale +# read. Only KRO may set or change the reserved labels on these kinds, so no other +# writer can add or remove an object's membership. apiVersion: policies.kyverno.io/v1 kind: ValidatingPolicy metadata: From c61e03fb989b1504fed1a56d37a7a472268b4a44 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Wed, 4 Nov 2026 16:31:48 +0100 Subject: [PATCH 2/2] docs(tenant-abstraction): name the KRO 0.9.4 compiler the WebApp tests use The compiler module pins github.com/kubernetes-sigs/kro v0.9.4, so the paragraph about its generated API tests no longer names 0.9.2. Co-Authored-By: Claude Opus 5.5 --- docs/tenant-abstraction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tenant-abstraction.md b/docs/tenant-abstraction.md index 8fb4973857..d5f7ce74d3 100644 --- a/docs/tenant-abstraction.md +++ b/docs/tenant-abstraction.md @@ -171,7 +171,7 @@ rejects duplicate members before jq can keep a last value; the maintained YAML decoder also checks the original source before conversion. Complete inputs are bounded to 1 MiB, with at most 1024 raw YAML documents. This is the platform's committed source gate, not proof of a future tenant publisher's input validation. The actual -KRO 0.9.2 compiler/generated API tests admission, defaults, literal arguments and +KRO 0.9.4 compiler/generated API tests admission, defaults, literal arguments and resolved children. Resolved deployments are separately scanned with Trivy 0.74.0; the writable-root and low-ID compatibility findings remain explicit rather than disappearing behind expressions. Both resolved states pass the actual container