Skip to content

feat: control-plane egress relay (decision 19)#6

Merged
bodaay merged 2 commits into
mainfrom
feat/control-plane-egress-relay
Jun 1, 2026
Merged

feat: control-plane egress relay (decision 19)#6
bodaay merged 2 commits into
mainfrom
feat/control-plane-egress-relay

Conversation

@bodaay

@bodaay bodaay commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

The relay side of control-plane egress relaying (DESIGN decision 19): coxswain reaches a node through beacon so the node never sees coxswain's IP.

  • egress package — protocol-blind: coxswain opens a substream and writes a one-line CONNECT target; beacon dials it over raw TCP and pumps bytes both ways. The gRPC-mTLS / SSH payload is end-to-end and opaque to the relay. Inverted-direction yamux vs the ingress tunnel (coxswain opens, relay accepts). Codec + transport unit-tested, incl. multiplexing.
  • beacon egress CLI — runnable relay; reuses run's relay.crt/key + fleet-ca.crt (no device-ca on this leg).

Consumed by coxswain via PharosVPN/coxswain#32 (which currently uses a dev replace to this branch — bump the dep + drop the replace once this lands).

🤖 Generated with Claude Code

bodaay and others added 2 commits June 2, 2026 01:02
Decision 19 (DESIGN §3): coxswain reaches a node through a relay so the node
never sees coxswain's IP. coxswain opens a substream on its outbound tunnel,
writes a one-line CONNECT target, and the relay dials it over raw TCP and
pumps bytes both ways. The relay is protocol-blind — the gRPC-mTLS or SSH
payload is end-to-end secured and opaque to it — so one generic relay serves
both control channels.

This is the core mechanism (CONNECT codec + relay Serve + controller Open),
transport-abstracted so it is unit-testable over an in-memory substream. The
egress tunnel (inverted-direction yamux) and the coxswain dialer wiring (gRPC
WithContextDialer + SSH net.Dialer) build on it next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`beacon egress` runs the relay side of decision 19: coxswain dials in on
--tunnel-addr (mutual TLS, relay cert + Fleet-CA-verified coxswain), and each
substream coxswain opens carries a CONNECT target that beacon dials over raw
TCP and pipes via egress.RunRelay. Protocol-blind — no device-ca needed on
this leg (no caravel clients), reuses run's relay.crt/key + fleet-ca.crt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bodaay bodaay merged commit a509bef into main Jun 1, 2026
0 of 2 checks passed
@bodaay bodaay deleted the feat/control-plane-egress-relay branch June 1, 2026 22:32
bodaay added a commit that referenced this pull request Jun 1, 2026
The egress PR (#6) committed only egress.go — the transport file (ClientSession,
AcceptAndServe, RunRelay) was left untracked, so `beacon egress`, which calls
egress.RunRelay, failed to compile on main. Add tunnel.go + tunnel_test.go
(inverted-direction yamux: coxswain opens substreams, the relay accepts and
dials the node), with the multiplexing round-trip test. main builds again.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bodaay added a commit to PharosVPN/coxswain that referenced this pull request Jun 1, 2026
beacon's egress package is on main now (PharosVPN/relay#6, #7), so bump the
beacon dependency to it and remove the development `replace => ../beacon`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bodaay added a commit to PharosVPN/coxswain that referenced this pull request Jun 1, 2026
…32)

* feat(egress): coxswain egress tunnel + relay-routed gRPC/SSH dialers

Decision 19: coxswain reaches nodes through a relay so a node never sees
coxswain's IP. internal/egress.Tunnel TLS-dials the relay, holds a yamux
client session (lazy reconnect on a dead session), and exposes DialContext —
the single chokepoint both control channels route through:

- gRPC NodeControl: control.NewDialer gains WithContextDialer.
- SSH: ssh.DialConfig gains an optional Dialer.

Both opt-in and backward-compatible — direct dial stays the default. The mTLS
and SSH handshakes run end-to-end over the relayed conn, so the relay is
protocol-blind. Full coxswain->relay->node path (and lazy reconnect) proven in
internal/egress tests against beacon's egress relay.

Dev: go.mod replaces github.com/PharosVPN/beacon => ../beacon for the new
egress package; drop once beacon's egress branch lands and the dep is bumped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(egress): use merged beacon egress; drop dev replace

beacon's egress package is on main now (PharosVPN/relay#6, #7), so bump the
beacon dependency to it and remove the development `replace => ../beacon`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

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