feat(relay): rendezvous-relay service — 7 packages + plans (contracts/transport/agent/control-plane/e2e/auth/web)

Multi-tenant reverse-tunnel service ("ngrok for Claude Code" with E2E): a
host-agent dials OUT to an operator-run relay; external devices reach the host
THROUGH the relay, routed by per-tenant subdomain, forwarding ciphertext only
(the relay never sees plaintext). Lets a customer reach their own self-hosted
web-terminal from anywhere with zero networking setup.

Packages — all tsc-strict + vitest green (656 tests), cross-package integration verified:
- relay-contracts: frozen shared contracts (mux frame codec, data model,
  capability token, E2E envelope, pairing) — the src/types.ts analog
- term-relay:   native WS mux + stateless data plane (subdomain routing, ciphertext forward)
- agent:        host-agent (pairing, per-host Ed25519 + mTLS dial-out, forwards to 127.0.0.1:3000)
- control-plane: accounts/hosts registry, pairing-code flow, routing table, provisioning
- relay-e2e:    browser<->agent E2E (X25519 ECDH through relay, AEAD, anti-replay, recoverable replay key)
- relay-auth:   Passkey/WebAuthn, capability tokens, per-host certs, deny-by-default tenant isolation
- relay-web:    browser login + Web Crypto E2E + client-side preview rendering

Security invariants INV1-15 enforced; cross-tenant isolation CI tripwire live
(.github/workflows/relay-tripwire.yml). Design + implementation-level plans in
docs/PLAN_RELAY_*.md and docs/EXPLORE_RELAY_SERVICE.md.

NOTE: generated autonomously per the reviewed plans. The security-critical
packages (relay-e2e, relay-auth) REQUIRE expert security audit before any real
deployment — passing tests prove self-consistency, not resistance to attackers.
Base app (src/, public/) unchanged; concurrent desktop work left uncommitted.
This commit is contained in:
Yaojia Wang
2026-07-02 06:10:16 +02:00
parent e4c327e25e
commit 2af57e6686
326 changed files with 40877 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
/**
* T15 — CA-key / secret-manager bootstrap + KMS-signer factory (§3.1). The intermediate
* leaf-signing key is a non-exportable KMS/HSM key: signing is a `sign()` CALL, the raw private
* key is NEVER loaded into control-plane memory. `buildCaSigner` FAILS FAST at startup (INV9) if
* the key ref is unresolvable OR its policy does not restrict `sign` to the control-plane
* service principal. Both T8 `signHostLeaf` and T15 `renewHostLeaf` call this shared primitive —
* neither loads a raw key.
*/
import { createPublicKey } from 'node:crypto'
import type { ControlPlaneEnv } from '../env.js'
import { ed25519Sign, generateEd25519, type KeyObject } from '../util/crypto.js'
/** Wraps KMS `sign()`; no raw key ever crosses this boundary. */
export interface CaSigner {
sign(tbsCert: Uint8Array): Promise<Uint8Array>
/** Public verifying key of the CA (for tests / chain assembly). */
readonly publicKeyRaw: Uint8Array
}
/**
* KMS resolver seam. Production resolves a real KMS/HSM key handle + policy; tests inject a fake.
* A resolver returns `{ canSign, policyRestrictedToServicePrincipal }` for the given ref, or throws
* if the ref cannot be resolved at all.
*/
export interface KmsResolver {
resolve(keyRef: string): Promise<{
readonly signer: CaSigner
readonly policyRestrictedToServicePrincipal: boolean
}>
}
/**
* Build the CA signer for the configured intermediate KMS key. Fail-fast (INV9, §3.1):
* - throws if the resolver cannot resolve the ref;
* - throws if the KMS key policy is broader than the control-plane service principal.
*/
export async function buildCaSigner(env: ControlPlaneEnv, resolver: KmsResolver): Promise<CaSigner> {
let resolved
try {
resolved = await resolver.resolve(env.caIntermediateKmsKeyRef)
} catch (err: unknown) {
// Never log the ref VALUE — only that resolution failed (INV9).
throw new Error(
`CA intermediate KMS key could not be resolved: ${err instanceof Error ? err.message : 'unknown'}`,
)
}
if (!resolved.policyRestrictedToServicePrincipal) {
throw new Error(
'CA intermediate KMS key policy is broader than the control-plane service principal (§3.1) — refusing to boot',
)
}
return resolved.signer
}
/**
* In-process Ed25519 CA signer — TEST/DEV ONLY (clearly NOT a KMS; the plan mandates a real
* non-exportable KMS key in production, §3.1). Exposes the same `sign()` surface so callers are
* KMS-shaped and never touch a raw key path themselves.
*/
export function inProcessCaSigner(privateKey?: KeyObject): CaSigner {
const key = privateKey ?? generateEd25519().privateKey
const pub = derivePublicRaw(key)
return {
publicKeyRaw: pub,
async sign(tbsCert) {
return ed25519Sign(key, tbsCert)
},
}
}
function derivePublicRaw(privateKey: KeyObject): Uint8Array {
// Recover the raw 32-byte Ed25519 public key from the private KeyObject.
const spki = createPublicKey(privateKey).export({ format: 'der', type: 'spki' }) as Buffer
return new Uint8Array(spki.subarray(spki.length - 32))
}