Skip to content

feat(mesh): mutually-authenticated TLS for the peer transport#557

Merged
rickcrawford merged 1 commit into
mainfrom
rickcrawford/wor-1564-mesh-peer-mtls
Jun 27, 2026
Merged

feat(mesh): mutually-authenticated TLS for the peer transport#557
rickcrawford merged 1 commit into
mainfrom
rickcrawford/wor-1564-mesh-peer-mtls

Conversation

@rickcrawford

Copy link
Copy Markdown
Contributor

What

Real peer mTLS for the mesh cache transport. Until now the mesh TCP transport was plaintext and peer_auth was an unwired X.509 verifier; this gives the transport an actual mutually-authenticated TLS layer.

How

  • transport/tls.rs (new): build_acceptor / build_connector build a rustls TlsAcceptor / TlsConnector from PEM cert/key/ca. Both sides present a cert and verify the peer against the shared CA using rustls's built-in WebPkiClientVerifier (server) and root-store server verification (client). The crypto provider is pinned to ring to match the rest of the build.
  • Server (server.rs): start_with_security(port, cache, cipher, tls) wraps each accepted TCP stream in a TLS session inside the per-connection task before reading frames; a failed handshake drops the peer without stalling the accept loop. start_with_cipher delegates with tls: None. handle_connection is now generic over the stream type.
  • Client (client.rs): PeerClient::with_security(addr, cipher, tls) dials, then wraps in TLS via a MeshConn enum (Plain(TcpStream) / Tls(...)) that delegates AsyncRead/AsyncWrite. The read_frame/write_frame codec was already generic over the stream.

The existing optional Cipher (AES-GCM framing) threads through unchanged; mTLS is the alternative transport-security layer.

Tests

  • End-to-end: mtls_server_and_peer_client_roundtrip — a PeerClient does a put then get to a TransportServer, both configured for mTLS, and the value round-trips through the mutually-authenticated session and the server's cache.
  • Handshake: a valid mutual handshake transfers bytes; a client cert signed by a different (untrusted) CA is rejected.
  • All 416 mesh lib tests pass — no regression on the plaintext path. clippy -D warnings and rustdoc -D warnings -D missing_docs clean. New dep: tokio-rustls (ring features, matching rustls).

Scope

This is the transport mechanism, proven end to end. Reading cert/key/ca from the mesh config block and enabling mTLS at bootstrap (so an operator turns it on from sb.yml) is the immediate follow-up. Streaming/QUIC transports and a default-on policy are out of scope here.

Adds peer mTLS to the mesh cache transport. A new transport::tls module
builds a rustls TlsAcceptor/TlsConnector (pinned to the ring provider,
peer certs verified against a shared CA via WebPkiClientVerifier) from PEM
cert/key/CA. The TCP transport is wired to use it:

- TransportServer::start_with_security wraps each accepted connection in a
  TLS session before any frame is read, so a peer without a CA-signed cert
  is dropped before it can issue an RPC; the handshake runs in the
  per-connection task so it can't stall the accept loop.
- PeerClient::with_security wraps outbound connections in TLS.
- handle_connection is now generic over the stream, and PeerClient holds a
  plain-or-TLS connection enum (the frame codec was already stream-agnostic).

Verified end to end by an in-crate test: a PeerClient does a put/get
roundtrip to a TransportServer over a mutual-TLS session, plus loopback
handshake tests (a valid mutual handshake transfers bytes; an untrusted
client cert is rejected). All 416 mesh tests pass with no plaintext-path
regression.

This is the transport mechanism; reading cert/key/CA from the mesh config
and enabling it at bootstrap is the immediate follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X19S6eQzKKExZ9RUPAHuGy
@rickcrawford rickcrawford merged commit 7e55b64 into main Jun 27, 2026
4 checks passed
@rickcrawford rickcrawford deleted the rickcrawford/wor-1564-mesh-peer-mtls branch June 27, 2026 13:15
rickcrawford added a commit that referenced this pull request Jun 27, 2026
…558)

Wires the mesh peer-mTLS transport (the mechanism landed in #557) through
to operator config. A new `peer_tls` block under `key_management.cache.mesh`
takes `cert_file` / `key_file` / `ca_file`, plus an optional `server_name`
(default `sbproxy-mesh`).

Flow across the layers:
- sbproxy-config gains MeshPeerTlsConfig on MeshClusterConfig (config schema
  regenerated).
- key_plane reads the PEM files (fail-closed on a read error) into a
  PeerTlsParams on BootstrapConfig.
- bootstrap builds the rustls acceptor + connector from it and threads them
  into TransportServer::start_with_security and
  TransportClientPool::with_security (which now creates TLS PeerClients).

Unset keeps the plaintext transport. The transport mechanism and its
end-to-end handshake tests are already in place; this is the operator-facing
toggle.


Claude-Session: https://claude.ai/code/session_01X19S6eQzKKExZ9RUPAHuGy

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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