fix(agent): present client cert on relay dial (relay is mTLS-only) (VA-77) #82

Merged
David merged 1 commit from fix/VA-77-relay-mtls-client-cert into main 2026-06-29 00:47:36 +02:00
Owner

What

Follow-up defect to VA-76 (#81). After that merged, the control channel connects but the relay dial fails with tunnel WS connect failed: IO error: received fatal alert: CertificateRequired, blocking every relay usage (files, terminal, desktop, vnc, rdp, mcp).

Root cause: VA-76's net::relay_tls_config used with_no_client_auth(), assuming the relay authenticated the agent via the rendezvous rauth token alone. The relay port is the same mTLS port as the control channel (api.vervain.a8n.systems:15444 serves both /api/v1/agent and /api/v1/relay), so the server requires the client certificate at the TLS layer and aborts with CertificateRequired when none is presented.

Changes

  • net.rs: relay_tls_config(resolver, server_ca_pem) now builds an mTLS config (delegates to build_steady_state_config with a throwaway leaf capture), presenting the same enrolled client cert as the control channel. The relay config is built once per session in run_connection from the control channel's resolver and pinned server CA.
  • Threading changed from CA bytes to a prebuilt Arc<ClientConfig>: Session::new -> Dispatcher::set_relay_tls_config -> handle_msg_tunnel -> tunnel::spawn/open/dial_relay.
  • host/tunnel.rs: dial_relay uses the provided mTLS config directly (TlsConnector::from) for wss://; ws:// (dev) unchanged. Stale VA-76 doc comments corrected.

Testing

just pre-commit green (fmt, clippy --deny warnings, build, windows target, lib + tpm tests). The live wss:// mTLS handshake against :15444 is not exercised by tests; verify on the desktop-02 agent by opening the Files tab.

Fixes VA-77. Relates to VA-76.

🤖 Generated with Claude Code

## What Follow-up defect to VA-76 (#81). After that merged, the control channel connects but the relay dial fails with `tunnel WS connect failed: IO error: received fatal alert: CertificateRequired`, blocking every relay usage (files, terminal, desktop, vnc, rdp, mcp). Root cause: VA-76's `net::relay_tls_config` used `with_no_client_auth()`, assuming the relay authenticated the agent via the rendezvous `rauth` token alone. The relay port is the same mTLS port as the control channel (`api.vervain.a8n.systems:15444` serves both `/api/v1/agent` and `/api/v1/relay`), so the server requires the client certificate at the TLS layer and aborts with `CertificateRequired` when none is presented. ## Changes - `net.rs`: `relay_tls_config(resolver, server_ca_pem)` now builds an mTLS config (delegates to `build_steady_state_config` with a throwaway leaf capture), presenting the same enrolled client cert as the control channel. The relay config is built once per session in `run_connection` from the control channel's resolver and pinned server CA. - Threading changed from CA bytes to a prebuilt `Arc<ClientConfig>`: `Session::new` -> `Dispatcher::set_relay_tls_config` -> `handle_msg_tunnel` -> `tunnel::spawn`/`open`/`dial_relay`. - `host/tunnel.rs`: `dial_relay` uses the provided mTLS config directly (`TlsConnector::from`) for `wss://`; `ws://` (dev) unchanged. Stale VA-76 doc comments corrected. ## Testing `just pre-commit` green (fmt, clippy `--deny warnings`, build, windows target, lib + tpm tests). The live `wss://` mTLS handshake against `:15444` is not exercised by tests; verify on the desktop-02 agent by opening the Files tab. Fixes VA-77. Relates to VA-76. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(agent): present client cert on relay dial (relay is mTLS-only)
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 12m18s
Create release / Create release from merged PR (pull_request) Has been skipped
89432f1602
VA-76's relay dial built its rustls config with `with_no_client_auth()`, on the assumption the relay authenticated the agent via the rendezvous `rauth` token alone. The relay port is the same mTLS port as the control channel (`:15444` serves both `/api/v1/agent` and `/api/v1/relay`), so the server aborts the handshake with `received fatal alert: CertificateRequired` when no client certificate is presented. Every relay usage (files, terminal, desktop, vnc, rdp, mcp) failed.

Make the relay dial use the same mTLS config as the control channel: pinned server CA plus the enrolled client-cert resolver. `net::relay_tls_config` now takes the resolver and delegates to `build_steady_state_config` (throwaway leaf capture). The config is built once per session in `run_connection` and threaded `Session::new` -> `Dispatcher::set_relay_tls_config` -> `handle_msg_tunnel` -> `tunnel::spawn`/`open`/`dial_relay`, replacing the CA-bytes path. The dev `ws://` loop is unchanged. Stale VA-76 doc comments corrected.

#VA-77
David scheduled this pull request to auto merge when all checks succeed 2026-06-29 00:41:10 +02:00
David merged commit eadcd78b22 into main 2026-06-29 00:47:36 +02:00
David deleted branch fix/VA-77-relay-mtls-client-cert 2026-06-29 00:47:36 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
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
psa-systems/vervain-agent!82
No description provided.