Tenant fence
spec.tenant stays optional. Set kairon.zyvor.dev/tenant-fence: "true" on a Machine when that tenant must not reach guest addresses observed on other tenants in the same namespace.
What the controller does:
- Projects
kairon.zyvor.dev/tenantfromspec.tenant. Admission rejects a rename or a clear. - Owns
NetworkSecurityGroup/tenant-fence-<tenant>in that namespace.spec.policy.denyCidrsis the other tenants'status.guestIPandstatus.guestIPsvalues, as/32or/128. - Deletes that group when no non-deleting Machine in the namespace still opts in for that tenant.
What kairon-node does:
- Upserts the group to FluxVM, as it already does for every
NetworkSecurityGroup. - Merges the group's deny list into the
MachineNetworkPolicyit posts. A user policy is not replaced. - If no policy selects the Machine, posts
defaultAllow: trueplus those denies. Node-wide default-deny still wins ondefaultAllowwhen it is enabled.
This is not a VRF. An address Kubernetes has not observed, or an address outside this namespace, is not denied. An address that also appears on a same-tenant Machine is not denied.
A MachineNetworkPolicy whose selector sets kairon.zyvor.dev/tenant must carry the same label. That stops a tenant-scoped object from selecting another tenant.
metadata:
annotations:
kairon.zyvor.dev/tenant-fence: "true"
spec:
tenant: acme
See examples/tenant-fence.yaml.
Requirements
- kairon-controller and kairon-node from this release. The controller builds the group; kairon-node merges it.
- Controller RBAC with
createanddeleteonnetworksecuritygroups. On an upgrade, re-applydeploy/rbac.yaml(or upgrade the Helm chart) before the new controller starts, or group creation fails with 403. - The admission webhook (
webhook.enabled) for the tenant-name, rename, clear and selector checks. Without it the fence still works, but nothing stops a tenant from being renamed.
Inspect
kubectl -n NS get machines -L kairon.zyvor.dev/tenant
kubectl -n NS get networksecuritygroups -l kairon.zyvor.dev/tenant-fence=managed \
-o custom-columns=NAME:.metadata.name,DENY:.spec.policy.denyCidrs,PHASE:.status.phase
PHASE: Applied with status.appliedOn set means kairon-node pushed the group to FluxVM. An empty denyCidrs is normal until the other tenants' Machines report a guest IP.
Verified on a live k3s host on 2026-10-06: two fenced tenants each got a group that denied only the other tenant's addresses (/32 and /128), and removing the annotation from the last opted-in Machine deleted that tenant's group.