From 89678c794904ff25c9327a0c659ecafe69abc8d4 Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Tue, 7 Jul 2026 04:49:38 +0200 Subject: [PATCH] fix(relay-run): agent-TLS ca must be full chain (intermediate+root), not intermediate-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node/OpenSSL rejects an agent client leaf whose only trust anchor is a non-self-signed intermediate (no PARTIAL_CHAIN flag) → every agent was reset at the TLS layer before attach(), surfacing as a silent 1006. Pass the full agent-ca bundle and stop swallowing tlsClientError. --- relay-run/src/main-phase1.ts | 6 +++++- relay-run/src/servers/agent-tls.ts | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/relay-run/src/main-phase1.ts b/relay-run/src/main-phase1.ts index f5ca366..ad02edc 100644 --- a/relay-run/src/main-phase1.ts +++ b/relay-run/src/main-phase1.ts @@ -205,7 +205,11 @@ async function main(): Promise { resolver, mtls: mtlsBridge.sync, now, - caBundle: [readFileSync(agentCaCertPath)], + // Agent-TLS trust store for validating the agent's CLIENT leaf (requestCert+rejectUnauthorized). + // MUST be the FULL chain (intermediate + self-signed root): Node/OpenSSL rejects a leaf whose only + // anchor is a non-self-signed intermediate (no PARTIAL_CHAIN flag). Intermediate-only ⇒ every agent + // is reset at the TLS layer before attach() runs. + caBundle: [readFileSync(agentCaChainPath)], onError: (e) => console.error('[data-plane]', errText(e)), tlsServerFactory: mtlsBridge.wrap(agentTlsFactory), }) diff --git a/relay-run/src/servers/agent-tls.ts b/relay-run/src/servers/agent-tls.ts index e8ba6da..3d0b833 100644 --- a/relay-run/src/servers/agent-tls.ts +++ b/relay-run/src/servers/agent-tls.ts @@ -37,6 +37,11 @@ export function makeAgentTlsServerFactory(opts: AgentTlsOptions): TlsServerFacto const der = peer && peer.raw ? new Uint8Array(peer.raw) : new Uint8Array() onPeer(wsToWebSocketLike(ws), der) }) + wss.on('error', (e) => opts.onError?.(e)) + // Surface TLS-layer client failures (e.g. a client leaf that doesn't chain to `ca`). Node + // otherwise SWALLOWS 'tlsClientError' — the peer just sees an abrupt reset with no server signal, + // which is exactly what masked the intermediate-only-CA bug. + server.on('tlsClientError', (e) => opts.onError?.(e)) server.on('error', (e) => opts.onError?.(e)) server.listen(opts.bindPort, opts.bindHost, () => opts.onListening?.()) return {