Skip to content

Supported Resources

This document details the feature support matrix for each Gateway API resource type in the Cloudflare Tunnel Gateway Controller.

Resource Overview

Resource API Version Status
GatewayClass gateway.networking.k8s.io/v1 Supported
Gateway gateway.networking.k8s.io/v1 Supported
HTTPRoute gateway.networking.k8s.io/v1 Supported
GRPCRoute gateway.networking.k8s.io/v1 Supported
ReferenceGrant gateway.networking.k8s.io/v1beta1 Supported
ListenerSet gateway.networking.k8s.io/v1 Supported — see ListenerSet
TCPRoute gateway.networking.k8s.io/v1alpha2 Not supported
TLSRoute gateway.networking.k8s.io/v1alpha2 Not supported
UDPRoute gateway.networking.k8s.io/v1alpha2 Not supported

GatewayClass

Field Supported Notes
spec.controllerName Yes Must match --controller-name flag
spec.parametersRef Yes Via GatewayClassConfig CRD
spec.description Yes Informational only

Gateway

The Gateway resource is fully processed. Listeners are used for route binding, status reporting, and validation. TLS termination is handled by the Cloudflare edge: HTTPS listeners are served, and their certificate references are validated (including ReferenceGrant checks), but clients get the edge certificate.

Field Supported Notes
spec.gatewayClassName Yes Required; the referenced GatewayClass must have a matching spec.controllerName
spec.listeners Yes Fully processed for route binding and status
spec.listeners[].name Yes Used for route binding, status reporting, attached route counting
spec.listeners[].port Yes Used for route binding when route specifies a port, and as the redirect port of a scheme-less RequestRedirect (redirect port)
spec.listeners[].protocol Yes Used for route kind filtering (HTTP/HTTPS allow HTTPRoute/GRPCRoute)
spec.listeners[].hostname Yes Routes must have intersecting hostnames; a host is served only by routes on the most specific listener that matches it (listener isolation)
spec.listeners[].tls Yes certificateRefs validated with ReferenceGrant support, never served: clients get the Cloudflare edge certificate
spec.listeners[].allowedRoutes Yes Namespace (Same/All/Selector) and kind filtering. A kind's group defaults to gateway.networking.k8s.io only when omitted; an explicit "" is the core group, which serves no route kinds, so that entry is reported as InvalidRouteKinds and admits nothing
spec.tls.backend.clientCertificateRef Yes kubernetes.io/tls Secret only; same-namespace or via ReferenceGrant; presented during backend TLS handshake only when the target Service has a BackendTLSPolicy (no client cert is sent over plaintext)
spec.tls.frontend No Refused: a Gateway that sets it is Accepted=False, Reason=Invalid and none of its routes is served, because clients complete TLS with the Cloudflare edge (client certificate validation)
spec.addresses Partial Only the tunnel CNAME; other hostnames report AddressNotUsable, other types UnsupportedAddress
spec.infrastructure.parametersRef Yes Opts the Gateway into a dedicated data plane (GatewayConfig, group cf.k8s.lex.la): its own proxy and tunnel
spec.infrastructure.labels / .annotations Yes Propagated to the rendered per-Gateway resources, generated Secrets and pod template

TLS Termination

Cloudflare Tunnel terminates TLS at Cloudflare's edge network. TLS certificate references on listeners are validated (a valid TLS Secret, ReferenceGrant for cross-namespace refs), but the actual TLS termination is handled by Cloudflare, not by the controller, and clients get the edge certificate rather than the listener's. The listener port and protocol fields are used for Gateway API route binding semantics, not for configuring network listeners.

HTTPRoute

All HTTPRoute matching and filter behavior is performed by the in-process L7 proxy that the chart deploys alongside the controller.

Field Supported Notes
spec.parentRefs Yes References to Gateway
spec.parentRefs[].group Yes Omit it or set gateway.networking.k8s.io. Any other value, including an explicit "" (the core group), names some other resource: the route does not bind to this controller's Gateway or ListenerSet, is not counted in attachedRoutes and gets no new status entry. An entry this controller wrote earlier for that ref is removed from status.parents
spec.parentRefs[].name Yes Gateway name
spec.parentRefs[].namespace Yes Gateway namespace
spec.parentRefs[].sectionName Yes Listener name (optional)
spec.parentRefs[].port Yes Listener port (optional)
spec.hostnames Yes Wildcard * supported
spec.rules Yes Routing rules
spec.rules[].name Yes Metadata only; preserved on the spec but not consulted during matching
spec.rules[].matches Yes Full matching
spec.rules[].matches[].path Yes PathPrefix, Exact, RegularExpression
spec.rules[].matches[].headers Yes Exact and RegularExpression matchers
spec.rules[].matches[].queryParams Yes Exact and RegularExpression matchers
spec.rules[].matches[].method Yes All HTTP methods
spec.rules[].filters Yes See Filters
spec.rules[].backendRefs Yes Service, ServiceImport, or ExternalBackend — see Supported Backend Kinds
spec.rules[].backendRefs[].name Yes Name of the Service / ServiceImport / ExternalBackend
spec.rules[].backendRefs[].namespace Yes Cross-namespace refs require ReferenceGrant (keyed on the backend group/kind)
spec.rules[].backendRefs[].port Yes Service / ServiceImport port; ignored for ExternalBackend (its spec.port wins)
spec.rules[].backendRefs[].weight Yes True weighted traffic splitting across backends
spec.rules[].backendRefs[].filters Yes Per-backend filters applied after rule-level filters
spec.rules[].timeouts Yes Per-rule request and backend timeouts
spec.rules[].retry Yes Experimental channel; see Retries

Backend Protocol (appProtocol)

When an HTTPRoute references a Service whose target port sets appProtocol, the L7 proxy selects the upstream transport accordingly:

Service.spec.ports[].appProtocol Supported Notes
kubernetes.io/h2c Yes Proxy speaks HTTP/2 cleartext (prior knowledge) to the backend
(unset) Yes Proxy speaks HTTP/1.1 to the backend (default)
kubernetes.io/ws Yes WebSocket over cleartext: the proxy detects upgrade headers and routes through a dedicated upgrade path that dials the backend, forwards the handshake with the same Host the plain HTTP path would send, writes the 101 status, then bidirectionally pipes bytes after hijack
kubernetes.io/wss Yes WebSocket over TLS: requires a matching BackendTLSPolicy (same precondition as appProtocol: https); see Backend Protocol notes
any other value No Logged with a warning; proxy falls back to default HTTP/1.1

A GRPCRoute backend is HTTP/2 by definition, so it is dialed h2c by default and only the TLS-vs-cleartext decision applies: a TLS appProtocol (https / HTTPS / kubernetes.io/wss) with no BackendTLSPolicy fails the backend closed (gRPC status UNAVAILABLE, ResolvedRefs=False, Reason=UnsupportedProtocol), as the HTTPRoute path does with HTTP 500. See gRPC backends and appProtocol.

Filters

The following HTTPRoute filters are supported:

Filter Type Supported Notes
RequestHeaderModifier Yes Add, set, or remove request headers
ResponseHeaderModifier Yes Add, set, or remove response headers
RequestRedirect Yes Redirect with scheme, hostname, port, path, status code
URLRewrite Yes Rewrite hostname and/or path
RequestMirror Yes Mirror traffic to one or more secondary backends. Per Gateway API only one of percent or fraction may be set on a filter; percent takes precedence if both appear.
ExtensionRef No Not implemented

Supported Service Types

Service Type Supported Notes
ClusterIP Yes Routes via cluster-local DNS
NodePort Yes Routes via cluster-local DNS
LoadBalancer Yes Routes via cluster-local DNS
ExternalName Yes Routes directly to external hostname

Supported Backend Kinds

A backendRef may target one of the following kinds (applies to both HTTPRoute and GRPCRoute):

Group Kind Supported Notes
(core) Service Yes Default when group/kind are unset; all Service types above
multicluster.x-k8s.io ServiceImport Yes Multi-Cluster Services API; resolved via clusterset.local DNS, port validated against spec.ports. Dialed plaintext: a BackendTLSPolicy and a port appProtocol apply to core Services only, so a same-named local Service lends it neither — see Backend mTLS
cf.k8s.lex.la ExternalBackend Yes Out-of-cluster HTTP(S) URL; see ExternalBackend
(any other) (any other) No ResolvedRefs=False, InvalidKind

Cross-namespace ServiceImport/ExternalBackend references require a ReferenceGrant whose to entry names the matching group/kind, the same way a cross-namespace Service ref does.

GRPCRoute

GRPCRoute is served by the in-process L7 proxy. gRPC requests are HTTP/2 POSTs to /{service}/{method}, so the converter maps each method match onto the proxy's path matcher. The upstream hop is cleartext h2c by default; attaching a BackendTLSPolicy upgrades the hop to TLS with HTTP/2 negotiated via ALPN, and the Gateway's clientCertificateRef is presented on the handshake for mTLS.

Field Supported Notes
spec.parentRefs Yes Gateway or ListenerSet, same as HTTPRoute
spec.hostnames Yes Wildcard * supported
spec.rules[].matches[].method Yes Exact (service+method, service-only, method-only) and RegularExpression
spec.rules[].matches[].headers Yes Exact and RegularExpression matchers (shared with HTTP)
spec.rules[].backendRefs Yes Service backends; cleartext h2c by default, TLS + ALPN HTTP/2 when a BackendTLSPolicy targets the Service
spec.rules[].filters Partial Core RequestHeaderModifier and extended ResponseHeaderModifier are served through the shared header-modifier pipeline (rule- and backend-scoped). RequestMirror and ExtensionRef are not served yet and fail closed (matching requests receive gRPC status UNAVAILABLE).
BackendTLSPolicy / Gateway clientCertificateRef Yes When a BackendTLSPolicy targets the backend Service, the proxy dials TLS with HTTP/2 negotiated via ALPN. The Gateway's clientCertificateRef is presented for mTLS only when a policy is also attached (Gateway API spec — a client cert over plaintext is meaningless). See GRPCRoute docs.

gRPC method matching maps to paths as follows:

Method match Proxy path rule
Exact service + method exact /{service}/{method}
Exact service only prefix /{service}/ (all methods)
Exact method only regex /[^/]+/{method} (any service)
RegularExpression regex /{service}/{method} (as written)

Conformance runs through an edge-routing gRPC client

The upstream Gateway API gRPC conformance tests dial the Gateway address (*.cfargotunnel.com) directly, and that hostname resolves to Cloudflare's ULA range, which is not externally routable. The suite allows injecting a custom gRPC client, so the conformance run supplies one that dials the Cloudflare edge instead — the same approach the HTTP suite already uses via a custom RoundTripper. GRPCExactMethodMatching, GRPCRouteHeaderMatching, GRPCRouteNamedRule, GRPCRouteListenerHostnameMatching, and GRPCRouteWeight all run this way (the run pins the http2 tunnel transport, since gRPC trailers do not survive QUIC). gRPC routing is additionally validated end-to-end by the in-house e2e suite against a real tunnel.

ReferenceGrant

ReferenceGrant enables cross-namespace backend references in HTTPRoute and GRPCRoute resources.

Field Supported Notes
spec.from Yes Source routes (HTTPRoute, GRPCRoute)
spec.from[].group Yes Must be gateway.networking.k8s.io
spec.from[].kind Yes Must be HTTPRoute or GRPCRoute
spec.from[].namespace Yes Namespace where routes are located
spec.to Yes Target resources (Services)
spec.to[].group Yes Must be "" (core) for Services
spec.to[].kind Yes Must be Service
spec.to[].name Yes Optional; if omitted, all Services allowed

See ReferenceGrant for detailed examples.

Path Matching

All matching is performed by the in-process L7 proxy:

Type Behavior
PathPrefix Full prefix matching
Exact True exact matching
RegularExpression Full regex matching (RE2)

gRPC Method Matching

gRPC methods are mapped to HTTP/2 paths using the standard format /package.Service/Method. All matching happens inside the in-process L7 proxy (see the GRPCRoute table above for the same mapping in the route context).

Match Type Example Proxy path rule
Service only service: mypackage.MyService prefix /mypackage.MyService/
Service + Method service: mypackage.MyService, method: GetUser exact /mypackage.MyService/GetUser
Method only method: GetUser regex /[^/]+/GetUser (any service)
No match (empty) Matches all gRPC traffic

Weight and Traffic Splitting

True weighted traffic splitting across multiple backends is performed by the in-process L7 proxy.

  • Default weight: 1 (per Gateway API specification)
  • Zero weight: Backends with weight: 0 are disabled

An invalid backend ref fails its own traffic fraction

If a backend ref in a rule fails validation — cross-namespace denial (RefNotPermitted), a missing Service (BackendNotFound), an unsupported kind/port, or a BackendTLSPolicy the proxy cannot enforce (InvalidBackendTLSPolicy) — it stays in the weighted pool and the proxy returns HTTP 500 (gRPC: UNAVAILABLE) for its traffic fraction only; the other backends in the same rule keep serving their proportional share. The route status shows ResolvedRefs=False. Weighting is not failover: an invalid ref fails closed for its fraction rather than silently shifting that traffic to the healthy backends. Only when every backend in a rule is unavailable (or all carry weight: 0) does the rule return 500 (gRPC: UNAVAILABLE) for all of its traffic. A weight: 0 invalid ref carries no traffic, so it is dropped rather than marked. See Unavailable backends return a status, not a dial error.

Status Conditions

Gateway Conditions

Type Status Reason Description
Accepted True Accepted Gateway accepted by controller
Accepted True or False ListenersNotValid One or more own listeners are invalid (they conflict and carry Conflicted=True, use an unsupported protocol, or have an allowedRoutes.namespaces.selector that is missing or does not parse); False only when no listener is valid. The message names the conflicted listeners and the accepted ones
Accepted False InvalidParameters The configuration the Gateway depends on cannot be used: its GatewayClass parametersRef or GatewayClassConfig is missing or invalid, the managed GatewayClasses disagree on parametersRef, its spec.infrastructure.parametersRef cannot be resolved, a dedicated data plane has no proxy image, or its connector token claims a tunnel another Gateway holds or Cloudflare does not confirm. The message names the cause
Accepted False DataPlaneQuotaExceeded The namespace is at its cap of dedicated data planes (Programmed=False, NoResources)
Accepted False UnsupportedAddress spec.addresses holds an address type other than Hostname
Accepted False Invalid spec.tls.frontend is set (client certificate validation)
Programmed True Programmed Gateway configured. On the shared plane it is set whenever the Gateway is accepted, or has no valid listener of its own but an attached ListenerSet, and spec.addresses asks for no other hostname; a dedicated plane also needs a ready proxy replica (details)
Programmed False Pending The dedicated data plane has no ready proxy replica yet
Programmed False AddressNotUsable spec.addresses requests a hostname other than the tunnel CNAME; the Gateway stays Accepted and is served at the CNAME
Programmed False Invalid / NoResources The Gateway is Accepted=False for any of the reasons above (NoResources for the data-plane cap); with ListenersNotValid only while no ListenerSet is attached
ResolvedRefs False InvalidClientCertificateRef / RefNotPermitted spec.tls.backend.clientCertificateRef cannot be used, or crosses namespaces without a ReferenceGrant

Gateway Listener Conditions

Type Status Reason Description
Accepted True Accepted Listener accepted
Accepted False HostnameConflict / ProtocolConflict Listener conflicts with another listener of the Gateway on the same port. Every listener of a conflicting set is refused; none wins. A listener whose protocol is not served (TCP, TLS, UDP) is refused as UnsupportedProtocol instead and conflicts with no other listener
Accepted False UnsupportedValue allowedRoutes.namespaces.from is Selector and the selector is missing or does not parse, so the listener admits no route. The message says the selector is invalid without quoting it
Programmed True Programmed Listener programmed
Programmed False Invalid Listener has unresolved references, or is not Accepted
Programmed False HostnameConflict / ProtocolConflict Listener conflicts with another listener of the Gateway
Conflicted True HostnameConflict / ProtocolConflict Listener clashes with another listener on hostname (same port + hostname) or protocol (different protocol on the same port)
ResolvedRefs True ResolvedRefs References resolved
ResolvedRefs False InvalidCertificateRef TLS certificate reference invalid
ResolvedRefs False RefNotPermitted Cross-namespace TLS ref denied by ReferenceGrant
ResolvedRefs False InvalidRouteKinds Invalid route kind in allowedRoutes

InvalidCertificateRef and RefNotPermitted are reported only for a reference the controller could read. When a certificate Secret or the ReferenceGrants of its namespace cannot be read, or the ListenerSets attached to the Gateway cannot be listed, the controller leaves the Gateway's status as it was and retries.

A listener's attachedRoutes counts each Route attached to it that is Accepted for the Gateway, once per listener. A conflicted listener counts a Route when the same parentRef also matches a usable listener. A Route whose only matching listeners are conflicted is rejected, so it counts nowhere. A Route that is Accepted: False with reason Pending is not counted either, for example one on a dedicated Gateway whose tunnel claim Cloudflare has not confirmed, so the count can drop while the Cloudflare API is unavailable. When the controller cannot finish a count, for example because a Route's namespace cannot be read for a from: Selector listener, every listener keeps the count it had and the controller counts again.

HTTPRoute/GRPCRoute Conditions

Type Status Reason Description
Accepted True Accepted Route accepted and synced
Accepted False NoMatchingParent No listener or ListenerSet entry matches the parentRef's sectionName or port; every listener or entry it matches is conflicted; or the parent Gateway's allowedListeners refuses the parent ListenerSet, including when that selector is missing or does not parse
Accepted False NoMatchingListenerHostname Route hostnames don't intersect with the selected listeners, pinned or not
Accepted False NotAllowedByListeners Route namespace or kind not allowed by the selected listeners, pinned or not. A listener whose allowedRoutes.namespaces.selector does not parse is treated as not allowing the route and the other listeners are still evaluated; when none of them admits the route, the rejection message, whatever its reason, names that listener, and the parse error goes to the controller log
Accepted False Conflicted Route lost a cross-route-type conflict (HTTPRoute vs GRPCRoute on a shared Gateway with intersecting hostnames); the oldest Route by creationTimestamp is accepted
ResolvedRefs True ResolvedRefs Backend references resolved
ResolvedRefs False RefNotPermitted Cross-namespace reference denied
ResolvedRefs False BackendNotFound Backend Service not found

When none of a route's parentRefs leads to a Gateway this controller manages any more, for example because the route now points at another controller's Gateway, its Gateway moved to another GatewayClass, its GatewayClass was deleted, or its ListenerSet now points at another Gateway, the controller removes the status.parents entries it wrote for that route and leaves other controllers' entries alone.