The CEL in packages/system/cozystack-basics/templates/route-hostname-policy.yaml short-circuits on absence:
!has(object.spec.hostnames) || object.spec.hostnames.all(h, h == ... || h.endsWith("." + ...))
A route that does not set spec.hostnames satisfies the first operand, so it is admitted without a single hostname being checked. The policy exists to keep a tenant's routes inside its own apex, and the one shape it never inspects is the shape that names no hostname at all.
That shape is not inert under Gateway API. A TLSRoute with no spec.hostnames is not a route matching nothing; it inherits the hostnames of the listener it attaches to. The route then covers whatever that listener covers, which is exactly the boundary this policy is written to hold.
The fix I would land is to require the field to be present and non-empty before validating it:
has(object.spec.hostnames) && size(object.spec.hostnames) > 0 && object.spec.hostnames.all(h, ...)
Both policies rendered from that template share the same $celValidator string, so HTTPRoute and TLSRoute pick the change up together.
On radius: the policy only matches namespaces whose name starts with tenant-. The three first-party TLSRoutes the platform ships, kubernetes-api in default plus cdi-uploadproxy and vm-exportproxy, all declare hostnames explicitly and none of them sits in a tenant-* namespace, so they are not matched today and nothing about them changes.
One rollout note. The fix turns a currently-admitted shape into a rejected one, so before it lands it is worth checking that no chart a tenant can install emits a TLSRoute with an absent or empty hostname list.
Related but not duplicates: #3205 hardens the same family of hostname policies with case-insensitive comparison, and #3252 covers whether this group of policies should be always-on.
The CEL in
packages/system/cozystack-basics/templates/route-hostname-policy.yamlshort-circuits on absence:A route that does not set
spec.hostnamessatisfies the first operand, so it is admitted without a single hostname being checked. The policy exists to keep a tenant's routes inside its own apex, and the one shape it never inspects is the shape that names no hostname at all.That shape is not inert under Gateway API. A TLSRoute with no
spec.hostnamesis not a route matching nothing; it inherits the hostnames of the listener it attaches to. The route then covers whatever that listener covers, which is exactly the boundary this policy is written to hold.The fix I would land is to require the field to be present and non-empty before validating it:
Both policies rendered from that template share the same
$celValidatorstring, so HTTPRoute and TLSRoute pick the change up together.On radius: the policy only matches namespaces whose name starts with
tenant-. The three first-party TLSRoutes the platform ships,kubernetes-apiindefaultpluscdi-uploadproxyandvm-exportproxy, all declare hostnames explicitly and none of them sits in atenant-*namespace, so they are not matched today and nothing about them changes.One rollout note. The fix turns a currently-admitted shape into a rejected one, so before it lands it is worth checking that no chart a tenant can install emits a TLSRoute with an absent or empty hostname list.
Related but not duplicates: #3205 hardens the same family of hostname policies with case-insensitive comparison, and #3252 covers whether this group of policies should be always-on.