In a multi-tenant Kubernetes cluster using the Gateway API, how does an HTTPRoute in one namespace get attached to a Gateway in another, and what controls does the cluster operator have over which namespaces are allowed to attach?
answer
- two-sided: parentRefs + allowedRoutes
- namespaces.from: Same | All | Selector (on Namespace labels)
- hostnames must intersect the listener hostname
- ReferenceGrant lives in the TARGET namespace
- conflict tie-break: oldest creationTimestamp wins
basics
~20 sAttachment is two-sided. The route names the Gateway in parentRefs (with its namespace); the Gateway's listener declares allowedRoutes.namespaces as Same, All, or Selector plus permitted route kinds. Both must agree, and hostnames must intersect, or the route's status shows it was not accepted.
solid answer
~40 sAttachment requires consent from both sides. - The **route** opts in: `parentRefs: [{name: edge, namespace: infra, sectionName: https}]`. `sectionName` (or `port`) narrows it to one listener. - The **listener** opts in: `allowedRoutes.namespaces.from` is `Same` (default), `All`, or `Selector` with a label selector on namespaces, plus `allowedRoutes.kinds` limiting which route kinds may attach. - Hostnames must **intersect**: the route's `hostnames` are matched against the listener's `hostname`; a wildcard listener like `*.example.com` accepts `shop.example.com`, but a disjoint hostname yields no attachment. Cross-namespace references to *backends* are separate and stricter: an HTTPRoute may only target a Service in another namespace if that namespace publishes a `ReferenceGrant` allowing it. The same applies to a Gateway's TLS Secret in another namespace. Diagnose via `status.parents[]` on the route: `Accepted` with reasons like `NotAllowedByListeners` or `NoMatchingListenerHostname`, and `ResolvedRefs` with `RefNotPermitted`.
code
yaml · 28 lines# listener fragment on the shared Gateway
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
allowedRoutes:
kinds:
- kind: HTTPRoute
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-shop-routes
namespace: payments
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: shop
to:
- group: ""
kind: Servicego deeper
Know that a route names its Gateway in parentRefs and that the Gateway must allow routes from that namespace.
Explain the three values of namespaces.from, that the selector applies to Namespace labels, and that hostnames must intersect.
Add ReferenceGrant and its direction, read the status conditions to localise failures, and pick a tenancy pattern (shared, per-team listener, or per-tenant Gateway) with reasons.
Treat it as a delegation model: who onboards a namespace, how hostname squatting is prevented, blast radius of one shared data plane, and how the policy is enforced and audited.
## The handshake Gateway API deliberately avoids letting either side act unilaterally. If routes could attach to any Gateway just by naming it, one tenant could hang a route for `admin.example.com` off the shared edge and hijack traffic. If Gateways had to enumerate every route, self-service would be impossible. So attachment is a two-sided handshake, and both sides must say yes. **Route side — `parentRefs`.** An HTTPRoute lists one or more parents. Each entry has `name`, optional `namespace` (defaults to the route's own), and optionally `sectionName` (a listener name) or `port` to narrow which listener it attaches to. Omitting both means "every compatible listener on that Gateway", which is convenient but blunt: a route with no `sectionName` on a Gateway with an HTTP and an HTTPS listener attaches to both. **Gateway side — `allowedRoutes`.** Each listener carries an `allowedRoutes` block with two parts: - `namespaces.from`: `Same` (default — only routes in the Gateway's own namespace), `All`, or `Selector`, which takes a `selector` matched against **Namespace labels**. The selector matches namespace objects, not routes, which is what makes it an operator-controlled gate: the tenant cannot label their own namespace unless you let them. - `kinds`: restricts which route kinds may attach, e.g. only `HTTPRoute` on an HTTPS listener, so nobody bolts a TLSRoute onto it. **Hostname intersection.** Independently of permissions, the route's `hostnames` must intersect the listener's `hostname`. A listener with `hostname: *.example.com` accepts routes for `shop.example.com`; a route claiming `other.test` intersects nothing and is not attached, with reason `NoMatchingListenerHostname`. A route with no hostnames inherits the listener's. ## ReferenceGrant — a different axis People conflate two kinds of cross-namespace reference. Attachment is route → Gateway and is governed by `allowedRoutes`. A **backend** reference is route → Service and, when it crosses namespaces, is governed by `ReferenceGrant`. The same mechanism guards a Gateway referencing a TLS Secret in another namespace. `ReferenceGrant` lives in the *target* namespace — the one being referenced — and says "resources of kind X in namespace Y may reference resources of kind Z here". This inverts the trust direction correctly: the owner of the Secret or Service grants access; the referrer cannot self-authorise. Without a grant, the reference is refused and the route's `ResolvedRefs` condition reports `RefNotPermitted`. This exists because Kubernetes RBAC governs who may *write* objects, not what those objects may *point at* — without ReferenceGrant, anyone able to create a route could exfiltrate traffic to a Service in a namespace they cannot read. ## Overlap and hostname conflicts Once several tenants attach to one listener, overlapping matches are inevitable. The spec defines deterministic precedence rather than leaving it to controller internals: exact path match beats prefix; among prefixes, the longest wins; then method match, then the number of matching header conditions, then query params. Ties are broken by the route with the oldest `creationTimestamp`, then namespace/name ordering, then rule order within the route. "Oldest wins" is worth remembering because it means a squatter who created a route first keeps a contested hostname — an argument for keeping shared hostnames on operator-controlled routes or per-team listeners. Gateways may also report `ListenerConditionConflicted` when two listeners on the same port declare incompatible protocol or hostname configurations. ## Operating patterns Three patterns cover most clusters: 1. **Shared Gateway, selector-gated.** One `edge` Gateway in an infra namespace, `namespaces.from: Selector` matching a label the platform team applies during namespace onboarding. Cheap, one load balancer, and onboarding is an explicit act. 2. **Gateway per tenant.** Each team gets its own Gateway (and often its own address) with `from: Same`. Strong isolation and independent blast radius, at the cost of more load balancers. 3. **Listener per team on a shared Gateway.** One listener per hostname with a narrow selector, and teams pin `sectionName`. Good middle ground: shared address, per-hostname authorisation, no squatting. ## Debugging checklist Run `kubectl get httproute <name> -o yaml` and read `status.parents[]`. For each parent you get `Accepted` (`NotAllowedByListeners`, `NoMatchingListenerHostname`, `NoMatchingParent`) and `ResolvedRefs` (`BackendNotFound`, `RefNotPermitted`, `InvalidKind`). Then check the Gateway: `Programmed`, per-listener `attachedRoutes` count, and `ResolvedRefs` for the certificate. The pair of statuses almost always localises the failure without touching controller logs.
- Two teams create HTTPRoutes claiming the same hostname and path on the same listener. What happens?Both attach if both are permitted; the implementation resolves the overlap with the spec's precedence rules, and for a genuinely identical match the tie-break is the older `creationTimestamp`, then namespace/name. Nothing errors loudly, which is why hostname squatting is a real risk. Prevent it by giving each team its own listener with a narrow hostname, or by validating hostname ownership in admission control.
- Why isn't ordinary RBAC enough to control cross-namespace backend references?RBAC decides who may create or edit an object, not what that object is allowed to point at. A developer with write access to HTTPRoutes in their own namespace could otherwise reference a Service in a namespace they cannot even read. ReferenceGrant restores the missing check by requiring the referenced namespace to publish consent.
saying these in an interview costs you the question
- Thinking naming a Gateway in parentRefs is sufficient, ignoring allowedRoutes
- Believing allowedRoutes' selector matches labels on the route — it matches Namespace labels
- Putting the ReferenceGrant in the referring namespace instead of the target namespace
- Assuming conflicting routes cause a hard error; they are resolved by precedence and oldest-wins
- Confusing route-to-Gateway attachment with route-to-Service backend references — different mechanisms