diff --git a/docs/tenant-abstraction.md b/docs/tenant-abstraction.md index bf6b9aeb2d..d5f7ce74d3 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 @@ -168,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 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: