Skip to content

Add Gateway API TLSRoute support for TLS passthrough - #126

Merged
Shine-neko merged 1 commit into
mainfrom
feat/gateway-tlsroute
Jul 20, 2026
Merged

Add Gateway API TLSRoute support for TLS passthrough#126
Shine-neko merged 1 commit into
mainfrom
feat/gateway-tlsroute

Conversation

@Shine-neko

Copy link
Copy Markdown
Contributor

Adds Gateway API TLSRoute support, routing TLS connections to backends by the SNI in the ClientHello without decrypting. Builds on the sni field and Sōzu wiring from #125. Closes the Gateway API: TLSRoute board item.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
spec:
  gatewayClassName: sozune
  listeners:
    - name: passthrough
      protocol: TLS
      port: 8443
      tls:
        mode: Passthrough
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
spec:
  parentRefs:
    - name: tlsgw
  hostnames:
    - app.example.com
  rules:
    - backendRefs:
        - name: my-service
          port: 8443

Design

The listener port must be pre-declared. A Gateway TLS listener on port 8443 is served only if a proxy.tcp entry already binds it. Sōzune binds TCP ports at startup and never opens one from a Gateway alone — the same rule the Docker path follows, kept so startup state stays predictable. A route on an undeclared port is tracked but unserved, and reactivates on the next reload once the listener is declared.

Passthrough only. tls.mode: Terminate means the Gateway decrypts, which is what HTTPS entrypoints are for, so such a listener is ignored here.

No new dependency, no new watcher scaffolding. The gateway-api crate we already use ships the TLSRoute types (experimental module). The parentRef acceptance rules — shared with HTTPRoute — were generalised over a small ParentRef trait rather than duplicated. TLSRoute is v1alpha2/experimental, so the watcher probes for the CRD and exits quietly when absent.

Verified on a real cluster

This was built into an image and run on kind end to end, not just unit-tested. That is how the two infra bugs surfaced: the ClusterRole was missing tlsroutes (watcher silently stopped), and the CRD probe reported a 403 as a missing CRD (an operator with incomplete RBAC would chase the wrong thing). Both fixed; the probe now distinguishes 404 / 403 / other. The k8s suite (tests/e2e/k8s/03-tlsroute.sh) drives a self-signed backend through passthrough and asserts the cert the client sees is the backend's.

Bugs caught in review, all fixed

A Codex review of the diff found four correctness bugs:

  • Multi-port parentRefs misrouted SNI names. Hostnames were unioned across listeners but only one port was kept, so a name admitted by the :8443 listener could be installed on :443 and stranded off :8443. Now grouped per port, each listener serving only the names its own hostname admits — which also lifts the single-port limitation.
  • Entrypoint IDs were not injective. k8s-gateway-tls-{ns}-{name} let a-b/c and a/b-c collide, one route evicting the other from shared storage. Separator changed to /, which cannot occur in a Kubernetes name.
  • TLSRoute hostnames bypassed SNI validation. A pattern Sōzu rejects would sit in the snapshot looking applied while never matching. validate_sni is now extracted into the model and shared with the Docker-label path; an invalid name is dropped with a warning, the route's other names still serve.
  • Scope changes woke only one route watcher. notify_one with two subscribers left the other waiting out its 2s tick — a regression from adding the second watcher. Now posts one permit per watcher.

The TLSRoute CRD

Vendored on its own (tlsroute-crd-v1.2.0.yaml, 45 KB) rather than swapping in the full 834 KB experimental bundle, which would also pull TCPRoute/UDPRoute/BackendTLSPolicy that Sōzune does not serve.

@Shine-neko
Shine-neko merged commit c84f07f into main Jul 20, 2026
3 checks passed
@Shine-neko
Shine-neko deleted the feat/gateway-tlsroute branch July 20, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant