New relay-run/ package boots the REAL term-relay relay-node between a browser WSS
listener and an agent mTLS listener, delegating every authz verdict to the real
relay-auth onUpgrade (Origin/CSWSH + capability verify + DPoP PoP + single-use jti),
with the real P4 E2E crypto. No audited package modified.
- P1 (done): in-process integration test round-trips a sealed payload both ways
through the real createRelayNode splice + real createMuxSession; INV2 asserted
(relay sees only ciphertext); negative controls (foreign Origin / missing DPoP -> 401).
- P2 (boots): main.ts brings up self-signed TLS + 3 listeners + in-memory control-plane.
- P3 (not landed): no agent dial / node-pty / relay-web serve yet.
- 4 tests green, tsc clean. node_modules via symlinks (gitignored).
Two real seam drifts surfaced (documented in PLAN_RELAY_RUN_PHASE0, not hacked):
(1) term-relay authz-port DpopContext shape vs relay-auth — reconciled (server-derived htu/htm);
(2) control-plane CapabilityVerifier.verify is sync but relay-auth verify is async — blocks
HTTP account/pairing seeding until a 1-line async widen.
42 lines
1.8 KiB
TypeScript
42 lines
1.8 KiB
TypeScript
/**
|
|
* Best-effort control-plane (P3) boot for `main.ts`: in-memory stores + the dev in-process CA
|
|
* signer, listening for admin/enroll HTTP. Wrapped so a failure degrades gracefully (the data-plane
|
|
* is the security-critical surface and boots independently).
|
|
*
|
|
* KNOWN SEAM (reported, not hacked): P3's injected `CapabilityVerifier.verify` is SYNCHRONOUS, but
|
|
* relay-auth's real `verifyCapabilityToken` is ASYNC (WebCrypto). The real P5 verifier therefore
|
|
* cannot be injected without an additive change to control-plane, so the admin routes run on the
|
|
* default fail-closed verifier here (deny-by-default, never bypassed). Programmatic account/pairing
|
|
* seeding through those routes is consequently BLOCKED in Phase 0 — see the report.
|
|
*/
|
|
import { buildControlPlane } from 'control-plane'
|
|
import { loadEnv } from 'control-plane/src/env.js'
|
|
import { createMemoryStores } from 'control-plane/src/store/memory.js'
|
|
|
|
export interface ControlPlaneBootOptions {
|
|
readonly publicRaw: Uint8Array
|
|
readonly baseDomain: string
|
|
readonly caCertPath: string
|
|
readonly bindHost: string
|
|
readonly bindPort: number
|
|
}
|
|
|
|
export interface ControlPlaneHandle {
|
|
close(): Promise<void>
|
|
}
|
|
|
|
export async function bootControlPlane(opts: ControlPlaneBootOptions): Promise<ControlPlaneHandle> {
|
|
const env = loadEnv({
|
|
PG_URL: 'postgres://localhost:5432/relay_run_dev',
|
|
REDIS_URL: 'redis://localhost:6379',
|
|
CAPABILITY_SIGN_PUBKEY_B64: Buffer.from(opts.publicRaw).toString('base64'),
|
|
CA_INTERMEDIATE_KMS_KEY_REF: 'dev-inprocess-signer',
|
|
CA_INTERMEDIATE_CERT_PATH: opts.caCertPath,
|
|
NODE_MTLS_TRUST_BUNDLE_PATH: opts.caCertPath,
|
|
BASE_DOMAIN: opts.baseDomain,
|
|
})
|
|
const { app } = await buildControlPlane(env, { stores: createMemoryStores() })
|
|
await app.listen({ port: opts.bindPort, host: opts.bindHost })
|
|
return { close: () => app.close() }
|
|
}
|