phase 6: TLS dist via inet_tls_dist

make_dist_tls_context() builds an ssl.SSLContext tuned for OTP defaults
(verify_peer, mTLS, TLSv1.2 minimum). Node accepts tls_context= and
wraps the TCP socket in TLS before the v6 handshake runs.

Critical quirk found by experimentation: inet_tls_dist uses {packet, 4}
on the SSL socket during the handshake. Plain inet_tcp_dist uses
{packet, 2} for handshake then switches to {packet, 4} post-nodeup.
handshake() now takes a frame_size= kwarg (2 or 4); Node auto-selects 4
whenever tls_context is supplied.

Cert requirements (found by experimentation against Erlang E2E):
  - CA cert with basicConstraints CA:TRUE
  - Leaf certs with SAN including the dist hostname (and localhost)
  - extendedKeyUsage covering both serverAuth and clientAuth

Tests:
  - make_dist_tls_context unit tests
  - Live: spawn erl -proto_dist inet_tls with SAN-bearing certs,
    Node.call(gen_target, {ping, 99}) round-trips through the tunnel
  - Live negative: plaintext connection to TLS-only peer must fail
  - Live negative: client cert from a different CA must fail

115 tests green across 5 consecutive runs, lint clean.
This commit is contained in:
russell@unturf.com 2026-06-16 12:06:07 -04:00
parent ac4636e00c
commit 271d712237
No known key found for this signature in database
7 changed files with 416 additions and 18 deletions

View file

@ -79,11 +79,20 @@ if we hit a peer that requires it.
responder, drop-on-the-floor against an unknown registered name,
Ref uniqueness across the Node lifetime
## Phase 6 — TLS dist
## Phase 6 — TLS dist
- Wrap the post-EPMD socket in TLS
- Match `inet_tls_dist` config on the Erlang side (cert + key + ca paths)
- Same handshake, just runs inside the TLS tunnel
- `make_dist_tls_context(cert=, key=, ca=)` helper builds an
`ssl.SSLContext` tuned for OTP defaults (verify_peer, TLSv1.2 min)
- `Node(..., tls_context=ctx)` wraps the post-EPMD socket in TLS
before the dist handshake
- Auto-selects `frame_size=4` for the handshake when TLS is in use
(the non-obvious quirk: `inet_tls_dist` uses `{packet, 4}` on the
SSL socket, where plain `inet_tcp_dist` uses `{packet, 2}` during
handshake)
- Live tests: generate CA + SAN-bearing certs with openssl, spawn
`erl -proto_dist inet_tls`, drive a full `Node.call()` through
the tunnel. Negative tests confirm: plaintext connection fails,
wrong-CA client cert fails.
## Phase 7 — unfeed integration