feat(gateway): onecli-gateway relay mode (Phase 3) - #4
Open
marcorivm wants to merge 1 commit into
Open
Conversation
Add a `relay` subcommand to the gateway binary: a plain HTTP-proxy →
mTLS byte-tunnel that lets local agents reach a REMOTE gateway. Agents
point HTTPS_PROXY at the relay (they can't reliably present a client
cert to an HTTPS proxy); the relay holds the client cert and carries
their traffic to the gateway's mTLS listener.
The relay is a BLIND BYTE-SPLICE: it accepts the agent's CONNECT /
absolute-form request, opens one mTLS connection to the remote gateway
presenting its client cert, and copy_bidirectionals bytes. It does NOT
parse or rewrite the agent's CONNECT line or Proxy-Authorization, so the
agent's aoc_ token reaches the gateway verbatim and the gateway
authenticates + MITMs exactly as today. Both identities arrive together:
the relay's host cert (mTLS handshake) and the agent's token (header).
- New relay.rs + relay/{enroll,renew,tunnel}.rs. No MITM, DB, crypto, or
policy in the relay — it only reuses client_ca PEM loaders + shutdown.
- Enrollment: the relay generates its own keypair + CSR (private key
never leaves it), enrolls via Phase 2 POST /v1/gateway/client-cert
with an oc_ API key, and renews before expiry (ArcSwap hot-swap,
reusing the stored hostId).
- SECURITY: always verifies the remote server cert via out-of-band
--gateway-server-ca (never an accept-any verifier); fails closed on
every error path (dial/handshake/timeout/expired-cert/unreachable
enrollment → 502 + close, never direct egress or plaintext). Private
key file written O_CREAT 0600 atomically, hard-error on failure.
- clap restructured so bare `onecli-gateway` / `--port N` still runs the
SERVER unchanged; `relay` is an added subcommand.
Stacked on Phase 2 (cert issuance). 646 tests (28 relay), clippy
--all-targets -D warnings + fmt clean. cargo build --features cloud
unchanged (27 pre-existing ee/*.rs errors, none from this change).
Claude-Session: https://claude.ai/code/session_01BgJuqEJqf7ZiUdqHWxi6bt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 3 of remote-gateway hardening —
onecli-gateway relaymodeStacked on #3 (Phase 2) — base is
phase2/client-cert-issuance. Review/merge #2 then #3 first; this diff shows only Phase 3.What this PR does
Adds a
relaysubcommand to the gateway binary so local agents can reach a remote gateway. Agent HTTP clients can't reliably present a client cert to an HTTPS proxy, so agents keep pointingHTTPS_PROXYat a plain local proxy — the relay — which holds the client cert and carries their traffic to the gateway's Phase 1 mTLS listener.The design (why it's small and safe)
The relay is a blind byte-splice. It accepts the agent's
CONNECT/ absolute-form request, opens one mTLS connection to the remote gateway presenting its client cert, andcopy_bidirectionals bytes. It does not parse or rewrite the agent's CONNECT line orProxy-Authorization— so the agent'saoc_token reaches the gateway verbatim and the gateway authenticates + MITMs exactly as today. Both identities arrive together: the relay's host cert (from the mTLS handshake) and the agent's token (in the header) — which is exactly what Phase 5 will bind. The relay holds no MITM, DB, crypto, or policy.Security properties (all with direct test evidence)
refuse()(drain →502→ close). Agent bytes leave the host only through the verified mTLS socket.--gateway-server-ca(real rustls WebPKI roots). The accept-any verifier that exists for the WebSocket leg is not reused here.Proxy-Authorization: Basic …) at a fake gateway.O_CREATmode0600atomically, hard-erroring on failure (no world-readable window).bareonecli-gateway/--port Nstill runs the SERVER**, unchanged — therelay` subcommand is additive (regression-tested).Config (relay)
--bind(default127.0.0.1:10255),--gateway-addr,--gateway-server-name,--gateway-server-ca(required, PEM or path),--api-url,--api-key(oc_),--label,--state-dir— all withRELAY_*env fallbacks.Testing
646 tests (28 in
relay);cargo clippy --all-targets -- -D warnings+cargo fmt --checkclean. Covers token-passthrough, all fail-closed paths (wrong CA → 502, expired → refuse, timeout → 502, enrollment unreachable → err), 0600 key atomicity + hard-error, renewal timing/backoff, and the clap default-arm regression.cargo build --features cloudunchanged (27 pre-existingee/*.rserrors, none from this change).Notes for reviewers
https://claude.ai/code/session_01BgJuqEJqf7ZiUdqHWxi6bt