|
| 1 | + |
| 2 | +# GEP-2643: TLS based passthrough Route / TLSRoute |
| 3 | + |
| 4 | +* Issue: [#2643](https://github.com/kubernetes-sigs/gateway-api/issues/2643) |
| 5 | +* Status: Standard |
| 6 | + |
| 7 | +## TLDR |
| 8 | + |
| 9 | +This GEP documents the implementation of a route type that uses the Server Name |
| 10 | +Indication attribute (aka SNI) of a TLS handshake to make the routing decision. |
| 11 | + |
| 12 | +This is also known as TLS passthrough, where after the server name is identified, |
| 13 | +the gateway does a full encrypted passthrough of the communication. |
| 14 | + |
| 15 | +## Goals |
| 16 | + |
| 17 | +* Provide a TLS passthrough route type, based on the SNI identification. |
| 18 | +* Provide a load balancing of a passthrough’d route, allowing a user to define |
| 19 | +a route, based on the SNI identification that should pass the traffic to N load |
| 20 | +balanced backends |
| 21 | + |
| 22 | +## Longer Term Goals |
| 23 | +* Implement capabilities for [ESNI](https://www.cloudflare.com/learning/ssl/what-is-encrypted-sni/) |
| 24 | + |
| 25 | +## Non-Goals |
| 26 | +* Provide an interface for users to define different listeners or ports for the |
| 27 | +`TLSRoute` - This will be covered by the `ListenerSet` enhancement. |
| 28 | +* Replace `TCPRoute` for cases where terminated frontend encryption is not needed. |
| 29 | +* When using `TLSRoute` passthrough, support `PROXY` protocol on the gateway listener. |
| 30 | +* When using `TLSRoute` passthrough, support `PROXY` protocol on the communication |
| 31 | +between the Gateway and the backend. |
| 32 | +* Allow a `TLSRoute` to have the encryption termination on the `Gateway` side and |
| 33 | +do the unencrypted passthrough. This case is covered by using a `TCPRoute`. |
| 34 | + * The feature of TLSRoute termination is tracked on its own [issue](https://github.com/kubernetes-sigs/gateway-api/issues/2111) |
| 35 | + and should be covered on a specific GEP. |
| 36 | +* Support TLS over UDP or UDP-based protocols such as `QUIC`. |
| 37 | +* Support attributes other than the SNI/hostname for the route selection |
| 38 | + |
| 39 | +## Introduction |
| 40 | + |
| 41 | +While many application routing cases can be implemented using HTTP/L7 matching |
| 42 | +(the tuple protocol:hostname:port:path), there are some specific cases where direct, |
| 43 | +encrypted communication to the backend may be required, without further assertion. For example: |
| 44 | +* A backend that is TLS based but not HTTP based (e.g., a Kafka service, or a |
| 45 | +Postgres service, with its listener being TLS enabled). |
| 46 | +* Some WebRTC solutions. |
| 47 | +* Backends that can require direct client-certificate authentication (e.g., OAuth). |
| 48 | + |
| 49 | +For the example cases above, it is desired that the routing is made as a passthrough |
| 50 | +mode, where the `Gateway` passes the packets to the backend without terminating TLS. |
| 51 | + |
| 52 | +While this routing is possible using other mechanisms such as a `TCPRoute` or a |
| 53 | +Kubernetes service of type `LoadBalancer`, it may be desired to have a single point |
| 54 | +of traffic convergence (like one single `LoadBalancer`) for reasons like cost, |
| 55 | +traffic control, etc. |
| 56 | + |
| 57 | +An implementation of `TLSRoute` achieves this end using the |
| 58 | +[server_name TLS attribute](https://datatracker.ietf.org/doc/html/rfc6066#section-3) |
| 59 | +to determine what backend should be used for a given request. `TLSRoute` thereby |
| 60 | +enables the use of a single gateway listener to handle traffic for multiple routes. |
| 61 | + |
| 62 | +## API |
| 63 | + |
| 64 | +```golang |
| 65 | +type TLSRouteSpec struct { |
| 66 | + // Hostnames defines a set of SNI hostnames that should match against the |
| 67 | + // SNI attribute of TLS ClientHello message in TLS handshake. This matches |
| 68 | + // the RFC 1123 definition of a hostname with 2 notable exceptions: |
| 69 | + // |
| 70 | + // 1. IPs are not allowed in SNI hostnames per RFC 6066. |
| 71 | + // 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard |
| 72 | + // label must appear by itself as the first label. |
| 73 | + // |
| 74 | + // If a hostname is specified by both the Listener and TLSRoute, there |
| 75 | + // must be at least one intersecting hostname for the TLSRoute to be |
| 76 | + // attached to the Listener. For example: |
| 77 | + // |
| 78 | + // * A Listener with `test.example.com` as the hostname matches TLSRoutes |
| 79 | + // that have specified at least one of `test.example.com` or |
| 80 | + // `*.example.com`. |
| 81 | + // * A Listener with `*.example.com` as the hostname matches TLSRoutes |
| 82 | + // that have specified at least one hostname that matches the Listener |
| 83 | + // hostname. For example, `test.example.com` and `*.example.com` would both |
| 84 | + // match. On the other hand, `example.com` and `test.example.net` would not |
| 85 | + // match. |
| 86 | + // |
| 87 | + // If both the Listener and TLSRoute have specified hostnames, any |
| 88 | + // TLSRoute hostnames that do not match the Listener hostname MUST be |
| 89 | + // ignored. For example, if a Listener specified `*.example.com`, and the |
| 90 | + // TLSRoute specified `test.example.com` and `test.example.net`, |
| 91 | + // `test.example.net` must not be considered for a match. |
| 92 | + // |
| 93 | + // If both the Listener and TLSRoute have specified hostnames, and none |
| 94 | + // match with the criteria above, then the TLSRoute is not accepted. The |
| 95 | + // implementation must raise an 'Accepted' Condition with a status of |
| 96 | + // `False` in the corresponding RouteParentStatus. |
| 97 | + // +required |
| 98 | + // +kubebuilder:validation:MinItems=1 |
| 99 | + // +kubebuilder:validation:MaxItems=16 |
| 100 | + Hostnames []Hostname `json:"hostnames,omitempty"` |
| 101 | + // Rules are a list of actions. |
| 102 | + // |
| 103 | + // +required |
| 104 | + // +kubebuilder:validation:MinItems=1 |
| 105 | + // +kubebuilder:validation:MaxItems=1 |
| 106 | + Rules []TLSRouteRule `json:"rules,omitempty"` |
| 107 | +} |
| 108 | + |
| 109 | +type TLSRouteRule struct { |
| 110 | + // Name is the name of the route rule. This name MUST be unique within a Route if it is set. |
| 111 | + // |
| 112 | + // +optional |
| 113 | + Name *SectionName `json:"name,omitempty"` |
| 114 | + // BackendRefs (same as other BackendRef here) |
| 115 | + // +required |
| 116 | + // +kubebuilder:validation:MinItems=1 |
| 117 | + // +kubebuilder:validation:MaxItems=16 |
| 118 | + BackendRefs []BackendRef `json:"backendRefs,omitempty"` |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | + |
| 123 | +## Request flow |
| 124 | + |
| 125 | +A simple `TLSRoute` implementation manifest would look like: |
| 126 | + |
| 127 | +```yaml |
| 128 | +apiVersion: gateway.networking.k8s.io/v1 |
| 129 | +kind: Gateway |
| 130 | +metadata: |
| 131 | + name: gateway-tlsroute |
| 132 | +spec: |
| 133 | + gatewayClassName: "my-class" |
| 134 | + listeners: |
| 135 | + - name: somelistener |
| 136 | + port: 443 |
| 137 | + protocol: TLS |
| 138 | + hostname: "*.example.com" |
| 139 | + allowedRoutes: |
| 140 | + namespaces: |
| 141 | + from: Same |
| 142 | + kinds: |
| 143 | + - kind: TLSRoute |
| 144 | + tls: |
| 145 | + mode: Passthrough |
| 146 | +--- |
| 147 | +apiVersion: gateway.networking.k8s.io/v1alpha2 |
| 148 | +kind: TLSRoute |
| 149 | +metadata: |
| 150 | + name: my-tls-route |
| 151 | +spec: |
| 152 | + parentRefs: |
| 153 | + - name: gateway-tlsroute |
| 154 | + hostnames: |
| 155 | + - abc.example.com |
| 156 | + rules: |
| 157 | + - backendRefs: |
| 158 | + - name: tls-backend |
| 159 | + port: 443 |
| 160 | +``` |
| 161 | +
|
| 162 | +A typical [north/south](https://gateway-api.sigs.k8s.io/concepts/glossary/#northsouth-traffic) |
| 163 | +API request flow for a gateway implemented using a `TLSRoute` is: |
| 164 | +* A client makes a request to https://foo.example.com. |
| 165 | +* DNS resolves the name to a `Gateway` address. |
| 166 | +* The reverse proxy receives the request on a `Listener` and uses the |
| 167 | +[Server Name Indication](https://datatracker.ietf.org/doc/html/rfc6066#section-3) |
| 168 | +attribute to match an `TLSRoute`. |
| 169 | +* The reverse proxy passes through the request directly to one or more objects, |
| 170 | +i.e. `Service`, in the cluster based on `backendRefs` rules of the `TLSRoute`. |
| 171 | + |
| 172 | +## Conflict management and precedences |
| 173 | + |
| 174 | +The following conflict situations are covered by TLSRoute and TLS passthrough cases: |
| 175 | + |
| 176 | +* When a Gateway contains a listener with `protocol=TLS`, the Gateway MUST NOT |
| 177 | +allow any other kind of listener on the same port and the Gateway SHOULD be |
| 178 | +marked as `Accepted=False` in case there are different kinds of listeners on |
| 179 | +the same port. |
| 180 | +* When a Gateway contains a listener with `protocol=TLS` and `tls.mode=Passthrough`, |
| 181 | +the `Gateway` MUST NOT allow another listener on the same port with a different |
| 182 | +`tls.mode` and the `Gateway` SHOULD be marked as `Accepted=False`. |
| 183 | +* Any violating Listener should have a Condition `Conflicted=True`. |
| 184 | +* If a hostname is specified by both the `Listener` and `TLSRoute`, there must |
| 185 | +be at least one intersecting hostname for the `TLSRoute` to be attached to the |
| 186 | +`Listener`. |
| 187 | + * A `Gateway listener` with `test.example.com` as the hostname matches a `TLSRoute` that |
| 188 | + specifies `test.example.com` but does not match a `TLSRoute` that specifies `*.example.com` |
| 189 | + * A `Gateway listener` with `*.example.com` as the hostname matches a `TLSRoute` that |
| 190 | + specifies at least one hostname that matches the `Gateway listener` hostname. |
| 191 | + For example, `test.example.com` and `*.example.com` would both match. On the |
| 192 | + other hand, `example.com` and `test.example.net` would not match. |
| 193 | + * If both the `Gateway listener` and `TLSRoute` specify hostnames, any `TLSRoute` |
| 194 | + hostnames that do not match the `Gateway listener` hostname MUST be ignored |
| 195 | + for that Listener. For example, if a `Gateway listener` specified `*.example.com`, |
| 196 | + and the `TLSRoute` specified `test.example.com` and `test.example.net`, |
| 197 | + the later must not be considered for a match. |
| 198 | + * In any of the cases above, the `TLSRoute` should have a `Condition` of `Accepted=True`. |
| 199 | + |
| 200 | +## Conformance Details |
| 201 | + |
| 202 | +### Feature Names |
| 203 | + |
| 204 | +* TLSRoute |
| 205 | + |
| 206 | +### Conformance tests |
| 207 | + |
| 208 | +| Description | Outcome | Notes | |
| 209 | +| :---- | :---- | :---- | |
| 210 | +| A single TLSRoute in the gateway-conformance-infra namespace attaches to a Gateway in the same namespace | A request to a hostname served by the TLSRoute should be passthrough directly to the backend. Check if the termination happened, if no additional Gateway header was added | Already [implemented](https://github.com/kubernetes-sigs/gateway-api/blob/main/conformance/tests/tlsroute-simple-same-namespace.go) needs review | |
| 211 | +| A single TLSRoute in the gateway-conformance-infra namespace, with a backendRef in another namespace without valid ReferenceGrant, should have the ResolvedRefs condition set to False | TLSRoute conditions must have a Not Permitted status | Already [implemented](https://github.com/kubernetes-sigs/gateway-api/blob/main/conformance/tests/tlsroute-invalid-reference-grant.go) needs review | |
| 212 | +| A TLSRoute trying to attach to a gateway without a “tls/passthrough” listener should be rejected | Condition on the TLSRoute that it was rejected (discuss with community the right condition to be used here) | [https://github.com/kubernetes-sigs/gateway-api/issues/1579](https://github.com/kubernetes-sigs/gateway-api/issues/1579) | |
| 213 | +| A TLSRoute with a hostname that does not match the Gateway hostname should be rejected (eg.: route with hostname [www.example.com](http://www.example.com), gateway with hostname www1.example.com) | Condition on the TLSRoute that it was rejected | | |
| 214 | +| A TLSRoute with an IP on its hostname should be rejected | Condition on the TLSRoute that it was rejected | | |
| 215 | +| (impl specific) A Gateway containing a Listener of type TLS/Passthrough and a Listener of type HTTPS/Terminate should be accepted, and should multiplex the requests to TLSRoute and HTTPRoute correctly | Being able to do a request to a HTTP route being terminated on gateway (eg.: terminated.example.tld/xpto) and to a TLS Passthrough route on the same gateway, but different host (passthrough.example.tld) | | |
| 216 | +| A Gateway with \*.example.tld on a TLS listener should allow a TLSRoute with hostname some.example.tld to be attached to it (and the same, but with a non wildcard hostname) | TLSRoute should be able to attach to the Gateway using the matching hostname, a request should succeed | [https://github.com/kubernetes-sigs/gateway-api/issues/1579](https://github.com/kubernetes-sigs/gateway-api/issues/1579) | |
| 217 | +| A Gateway with something.example.tld on a TLS listener hostname should not allow a TLSRoute of \*.example.tld to be attached | TLSRoute should be rejected with invalid hostname (we should NOT support wildcard hostnames on a TLSRoute spec) | [https://github.com/kubernetes-sigs/gateway-api/issues/1579](https://github.com/kubernetes-sigs/gateway-api/issues/1579) | |
| 218 | +| Invalid TLSRoute with invalid BackendObjectReference performs no default forwarding | | [https://github.com/kubernetes-sigs/gateway-api/issues/1579](https://github.com/kubernetes-sigs/gateway-api/issues/1579) | |
| 219 | +| For a [Listener](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) setting mode: "terminate", TLSRoute should not be present in [ListenerStatus.SupportedKinds](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) | | [https://github.com/kubernetes-sigs/gateway-api/issues/1579](https://github.com/kubernetes-sigs/gateway-api/issues/1579) | |
| 220 | +| Attempting to attach a TLSRoute to a [Listener](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) setting mode: "terminate" should yield [RouteConditionType](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) Accepted with status: false and [RouteConditionReason](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) NotAllowedByListeners on the TLSRoute | | [https://github.com/kubernetes-sigs/gateway-api/issues/1579](https://github.com/kubernetes-sigs/gateway-api/issues/1579) | |
| 221 | +| For a [Listener](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) setting mode: "terminate", attempting to set TLSRoute in [AllowedRoutes.kinds](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.AllowedRoutes) would yield a [ListenerConditionType](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) Accepted with status: false and [ListenerConditionReason](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.ListenerStatus) InvalidRouteKinds | | [https://github.com/kubernetes-sigs/gateway-api/issues/1579](https://github.com/kubernetes-sigs/gateway-api/issues/1579) | |
| 222 | + |
| 223 | +Pending conformance verifications: |
| 224 | + |
| 225 | +* [https://github.com/kubernetes-sigs/gateway-api/issues/3466](https://github.com/kubernetes-sigs/gateway-api/issues/3466) |
| 226 | +* [https://github.com/kubernetes-sigs/gateway-api/issues/2153](https://github.com/kubernetes-sigs/gateway-api/issues/2153) |
| 227 | + |
| 228 | +### References |
| 229 | +* [Existing API](https://github.com/kubernetes-sigs/gateway-api/blob/main/apis/v1alpha3/tlsroute_types.go) |
| 230 | +* [TLS Terminology - Needs update](https://github.com/kubernetes-sigs/gateway-api/blob/d28cd59d37887be07b879f098cff7b14a87c0080/geps/gep-2907/index.md?plain=1#L29) |
| 231 | +* [TLSRoute promotion issue](https://github.com/kubernetes-sigs/gateway-api/issues/3165) |
| 232 | +* [TLS Multiplexing issue discussion](https://github.com/kubernetes-sigs/gateway-api/issues/623) |
| 233 | +* [TLSRoute intersecting hostnames issue](https://github.com/kubernetes-sigs/gateway-api/issues/3541) |
| 234 | +* [TLSRoute termination feature request](https://github.com/kubernetes-sigs/gateway-api/issues/2111) |
| 235 | +* [GatewayAPI TLS Use Cases](https://docs.google.com/document/d/17sctu2uMJtHmJTGtBi_awGB0YzoCLodtR6rUNmKMCs8) |
0 commit comments