SNI passthrough: route TLS by domain without terminating (L4 phase A) #2

Closed
opened 2026-07-06 14:31:21 +00:00 by james.oates · 1 comment
Owner

Execution tracker for Phase A of the L4 passthrough design.

Spec (source of truth): Obsidian — Personal/Projects/edge-router/specs/2026-07-06-l4-passthrough-and-grpc-design.md

Scope

Peek the ClientHello on the existing :443 listener, match SNI against a new l4_routes table, and tunnel matching connections raw to their backend (copy_bidirectional) — TLS never terminated, so backend-owned certs and TLS-layer mTLS survive end-to-end. Non-matching connections fall through to the existing terminating acceptor unchanged.

Independent value: front services that hold their own certificates without storing their keys; genuine end-to-end TLS for sensitive backends. First consumer: Playground's agent orchestration endpoint.

Key design points (from the spec)

  • Buffer the ClientHello via rustls' sans-io server::Acceptor so bytes stay replayable (LazyConfigAcceptor consumes the stream — wrong tool)
  • New l4_routes table + POST/GET/PATCH/DELETE /api/l4 endpoints, [[l4_routes]] TOML parity, dashboard listing
  • Connection-metadata-only logging; optional TCP-connect health probe
  • Documented limits: no path routing / X-Forwarded-* / filtering on passthrough; ECH-using clients cannot be SNI-routed

Must-pass test

rustls client presenting a client certificate → through passthrough → rustls backend requiring client auth: handshake succeeds, cert visible to backend. Plus: non-matching SNI regression, fragmented ClientHello, existing-behaviour regression suite.

Out of scope (later phases in the same spec)

  • Phase B: raw TCP port forwarding (mode='tcp')
  • Phase C: h2c upstream + trailer forwarding (terminated gRPC, OTLP)
Execution tracker for Phase A of the L4 passthrough design. **Spec (source of truth):** Obsidian — `Personal/Projects/edge-router/specs/2026-07-06-l4-passthrough-and-grpc-design.md` ## Scope Peek the ClientHello on the existing :443 listener, match SNI against a new `l4_routes` table, and tunnel matching connections raw to their backend (`copy_bidirectional`) — TLS never terminated, so backend-owned certs and TLS-layer mTLS survive end-to-end. Non-matching connections fall through to the existing terminating acceptor unchanged. Independent value: front services that hold their own certificates without storing their keys; genuine end-to-end TLS for sensitive backends. First consumer: Playground's agent orchestration endpoint. ## Key design points (from the spec) - Buffer the ClientHello via rustls' sans-io `server::Acceptor` so bytes stay replayable (`LazyConfigAcceptor` consumes the stream — wrong tool) - New `l4_routes` table + `POST/GET/PATCH/DELETE /api/l4` endpoints, `[[l4_routes]]` TOML parity, dashboard listing - Connection-metadata-only logging; optional TCP-connect health probe - Documented limits: no path routing / X-Forwarded-* / filtering on passthrough; ECH-using clients cannot be SNI-routed ## Must-pass test rustls client presenting a client certificate → through passthrough → rustls backend requiring client auth: handshake succeeds, cert visible to backend. Plus: non-matching SNI regression, fragmented ClientHello, existing-behaviour regression suite. ## Out of scope (later phases in the same spec) - Phase B: raw TCP port forwarding (`mode='tcp'`) - Phase C: h2c upstream + trailer forwarding (terminated gRPC, OTLP)
Author
Owner

Phase A (SNI passthrough) complete — implemented across 13 tasks, each individually reviewed, plus a final whole-branch review. Pushed to main at fca0b42 (19 commits from baseline 4004265).

Delivered: ClientHello SNI peek via rustls' sans-io server::Acceptor, raw-byte tunnel (copy_bidirectional) preserving end-to-end TLS/mTLS, full l4_routes REST API + TOML config parity, per-route max_connections limiting, connection/byte metrics, a TLS-handshake health checker, and a dashboard section — plus the fragmented-ClientHello and mTLS-survival tests called out in the original design spec's "must-pass" cases.

Two deliberate deviations from the spec, both confirmed before merging:

  • Malformed ClientHellos now fall through to normal TLS termination (matching Envoy/nginx/HAProxy's fail-open pattern) rather than being silently dropped.
  • L4 route registration uses the same per-service-key auth model as /api/services, not master-key-only.

Non-blocking follow-ups filed as separate issues: #4 (route list re-queries the DB on every HTTPS connection — perf), #5–#9 (various minor polish/observability items).

Phase B (raw TCP forwarding) and Phase C (h2c/gRPC upstream) remain as documented next phases in the design spec, not started.

Phase A (SNI passthrough) complete — implemented across 13 tasks, each individually reviewed, plus a final whole-branch review. Pushed to `main` at `fca0b42` (19 commits from baseline `4004265`). Delivered: ClientHello SNI peek via rustls' sans-io `server::Acceptor`, raw-byte tunnel (`copy_bidirectional`) preserving end-to-end TLS/mTLS, full `l4_routes` REST API + TOML config parity, per-route `max_connections` limiting, connection/byte metrics, a TLS-handshake health checker, and a dashboard section — plus the fragmented-ClientHello and mTLS-survival tests called out in the original design spec's "must-pass" cases. Two deliberate deviations from the spec, both confirmed before merging: - Malformed ClientHellos now fall through to normal TLS termination (matching Envoy/nginx/HAProxy's fail-open pattern) rather than being silently dropped. - L4 route registration uses the same per-service-key auth model as `/api/services`, not master-key-only. Non-blocking follow-ups filed as separate issues: #4 (route list re-queries the DB on every HTTPS connection — perf), #5–#9 (various minor polish/observability items). Phase B (raw TCP forwarding) and Phase C (h2c/gRPC upstream) remain as documented next phases in the design spec, not started.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
IsoHex/edge-router#2
No description provided.