@namzu/sandbox 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +474 -0
- package/LICENSE.md +110 -0
- package/README.md +148 -0
- package/dist/backends/aci-standby-pool/index.d.ts +104 -0
- package/dist/backends/aci-standby-pool/index.d.ts.map +1 -0
- package/dist/backends/aci-standby-pool/index.js +425 -0
- package/dist/backends/aci-standby-pool/index.js.map +1 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts +40 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts.map +1 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js +157 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js.map +1 -0
- package/dist/backends/docker/index.d.ts +118 -0
- package/dist/backends/docker/index.d.ts.map +1 -0
- package/dist/backends/docker/index.js +645 -0
- package/dist/backends/docker/index.js.map +1 -0
- package/dist/backends/firecracker/__tests__/backend.test.d.ts +13 -0
- package/dist/backends/firecracker/__tests__/backend.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/backend.test.js +353 -0
- package/dist/backends/firecracker/__tests__/backend.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts +19 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js +201 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts +39 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js +149 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js.map +1 -0
- package/dist/backends/firecracker/__tests__/protocol.test.d.ts +6 -0
- package/dist/backends/firecracker/__tests__/protocol.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/protocol.test.js +77 -0
- package/dist/backends/firecracker/__tests__/protocol.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/transport.test.d.ts +20 -0
- package/dist/backends/firecracker/__tests__/transport.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/transport.test.js +449 -0
- package/dist/backends/firecracker/__tests__/transport.test.js.map +1 -0
- package/dist/backends/firecracker/index.d.ts +124 -0
- package/dist/backends/firecracker/index.d.ts.map +1 -0
- package/dist/backends/firecracker/index.js +334 -0
- package/dist/backends/firecracker/index.js.map +1 -0
- package/dist/backends/firecracker/protocol.d.ts +132 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -0
- package/dist/backends/firecracker/protocol.js +112 -0
- package/dist/backends/firecracker/protocol.js.map +1 -0
- package/dist/backends/firecracker/transport.d.ts +251 -0
- package/dist/backends/firecracker/transport.d.ts.map +1 -0
- package/dist/backends/firecracker/transport.js +524 -0
- package/dist/backends/firecracker/transport.js.map +1 -0
- package/dist/index.d.ts +611 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +376 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +28 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +670 -0
- package/dist/index.test.js.map +1 -0
- package/package.json +54 -0
- package/src/backends/aci-standby-pool/index.ts +602 -0
- package/src/backends/docker/__tests__/leaf-permissions.smoke.test.ts +169 -0
- package/src/backends/docker/index.ts +826 -0
- package/src/backends/firecracker/__tests__/backend.test.ts +418 -0
- package/src/backends/firecracker/__tests__/control-plane-mtls.test.ts +253 -0
- package/src/backends/firecracker/__tests__/fixtures/mtls-pki.ts +166 -0
- package/src/backends/firecracker/__tests__/protocol.test.ts +90 -0
- package/src/backends/firecracker/__tests__/transport.test.ts +526 -0
- package/src/backends/firecracker/index.ts +528 -0
- package/src/backends/firecracker/protocol.ts +191 -0
- package/src/backends/firecracker/transport.ts +667 -0
- package/src/index.test.ts +731 -0
- package/src/index.ts +930 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback transport round-trip tests for the Firecracker vsock path.
|
|
3
|
+
*
|
|
4
|
+
* These prove the NEW transport (host dialer ↔ in-VM agent) over a
|
|
5
|
+
* **unix-socket stand-in** — the faithful local peer for AF_VSOCK,
|
|
6
|
+
* because the production vsock path also terminates on a host
|
|
7
|
+
* unix socket (see `transport.ts` docblock). The peer is the REAL
|
|
8
|
+
* `agent/agent.cjs` connection handler (its spawn/jail + NDJSON shapes
|
|
9
|
+
* verbatim), driven in-process over a temp unix socket; the client is
|
|
10
|
+
* the REAL {@link VsockAgentTransport}. So one test exercises:
|
|
11
|
+
* - exec → streamed stdout/stderr/result NDJSON, accumulated;
|
|
12
|
+
* - writeFile / readFile → base64 round-trip through the jail;
|
|
13
|
+
* - a simulated resume (server torn down + re-listened) survived by
|
|
14
|
+
* the dialer's connect-retry budget — the FC #4713 invariant.
|
|
15
|
+
*
|
|
16
|
+
* No docker, no Azure, no AF_VSOCK kernel support required: pure
|
|
17
|
+
* loopback, safe in CI.
|
|
18
|
+
*/
|
|
19
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
20
|
+
import { createServer, connect as netConnect } from 'node:net';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { createServer as createTlsServer } from 'node:tls';
|
|
24
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
25
|
+
import { VsockAgentTransport } from '../transport.js';
|
|
26
|
+
// The agent is a CommonJS module that reads NAMZU_SANDBOX_WORKSPACE at
|
|
27
|
+
// require-time. Set the env, then require it through createRequire so
|
|
28
|
+
// each test file gets a fresh module bound to its own workspace dir.
|
|
29
|
+
import { createRequire } from 'node:module';
|
|
30
|
+
const require_ = createRequire(import.meta.url);
|
|
31
|
+
let workDir;
|
|
32
|
+
let sockPath;
|
|
33
|
+
let server;
|
|
34
|
+
let agent;
|
|
35
|
+
function startAgentServer(connHandler) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const s = createServer(connHandler);
|
|
38
|
+
s.on('error', reject);
|
|
39
|
+
s.listen(sockPath, () => resolve(s));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
workDir = mkdtempSync(join(tmpdir(), 'fc-agent-test-'));
|
|
44
|
+
sockPath = join(workDir, 'agent.sock');
|
|
45
|
+
// Bind the agent's workspace jail to the temp dir BEFORE requiring it.
|
|
46
|
+
process.env.NAMZU_SANDBOX_WORKSPACE = workDir;
|
|
47
|
+
// The agent's root-normalization filters env on PRESENCE; `= undefined`
|
|
48
|
+
// sets the literal string "undefined" and would widen READ/WRITE_ROOTS, so
|
|
49
|
+
// these must be true deletes.
|
|
50
|
+
// biome-ignore lint/performance/noDelete: must remove the var, not set "undefined".
|
|
51
|
+
delete process.env.NAMZU_SANDBOX_READ_ROOTS;
|
|
52
|
+
// biome-ignore lint/performance/noDelete: must remove the var, not set "undefined".
|
|
53
|
+
delete process.env.NAMZU_SANDBOX_WRITE_ROOTS;
|
|
54
|
+
// Fresh module each test so WORKSPACE_ROOT is rebound.
|
|
55
|
+
delete require_.cache[require_.resolve('../../../../agent/agent.cjs')];
|
|
56
|
+
agent = require_('../../../../agent/agent.cjs');
|
|
57
|
+
});
|
|
58
|
+
afterEach(async () => {
|
|
59
|
+
if (server) {
|
|
60
|
+
await new Promise((r) => server?.close(() => r()));
|
|
61
|
+
server = undefined;
|
|
62
|
+
}
|
|
63
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
64
|
+
});
|
|
65
|
+
describe('VsockAgentTransport over a unix-socket loopback agent', () => {
|
|
66
|
+
it('streams stdout/stderr/result NDJSON from an exec', async () => {
|
|
67
|
+
server = await startAgentServer(agent.handleConnection);
|
|
68
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath });
|
|
69
|
+
const r = await transport.execute({
|
|
70
|
+
command: '/bin/sh',
|
|
71
|
+
args: ['-c', 'echo out-line; echo err-line 1>&2; exit 3'],
|
|
72
|
+
});
|
|
73
|
+
expect(r.stdout).toContain('out-line');
|
|
74
|
+
expect(r.stderr).toContain('err-line');
|
|
75
|
+
expect(r.exitCode).toBe(3);
|
|
76
|
+
expect(r.timedOut).toBe(false);
|
|
77
|
+
expect(r.durationMs).toBeGreaterThanOrEqual(0);
|
|
78
|
+
});
|
|
79
|
+
it('streams a large multi-chunk stdout intact (delta accumulation)', async () => {
|
|
80
|
+
server = await startAgentServer(agent.handleConnection);
|
|
81
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath });
|
|
82
|
+
// 200 lines so the agent emits multiple stdout_delta frames the
|
|
83
|
+
// FrameReader must reassemble across socket chunks.
|
|
84
|
+
const r = await transport.execute({
|
|
85
|
+
command: '/bin/sh',
|
|
86
|
+
args: ['-c', 'for i in $(seq 1 200); do echo "line-$i"; done'],
|
|
87
|
+
});
|
|
88
|
+
expect(r.exitCode).toBe(0);
|
|
89
|
+
const lines = r.stdout.trim().split('\n');
|
|
90
|
+
expect(lines.length).toBe(200);
|
|
91
|
+
expect(lines[0]).toBe('line-1');
|
|
92
|
+
expect(lines[199]).toBe('line-200');
|
|
93
|
+
});
|
|
94
|
+
it('round-trips writeFile/readFile as base64 through the workspace jail', async () => {
|
|
95
|
+
server = await startAgentServer(agent.handleConnection);
|
|
96
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath });
|
|
97
|
+
// Binary content (non-UTF8 bytes) proves base64 fidelity, not
|
|
98
|
+
// just text.
|
|
99
|
+
const payload = Buffer.from([0x00, 0xff, 0x10, 0x42, 0x00, 0x99, 0xfe]);
|
|
100
|
+
await transport.writeFile('sub/dir/blob.bin', payload);
|
|
101
|
+
const read = await transport.readFile('sub/dir/blob.bin');
|
|
102
|
+
expect(read.equals(payload)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
it('rejects a path that escapes the workspace jail', async () => {
|
|
105
|
+
server = await startAgentServer(agent.handleConnection);
|
|
106
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath });
|
|
107
|
+
await expect(transport.readFile('../../../../etc/passwd')).rejects.toThrow(/escapes/);
|
|
108
|
+
});
|
|
109
|
+
it('healthz returns true against a live agent', async () => {
|
|
110
|
+
server = await startAgentServer(agent.handleConnection);
|
|
111
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath });
|
|
112
|
+
expect(await transport.healthz()).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
it('surfaces an exec error event as a thrown error', async () => {
|
|
115
|
+
server = await startAgentServer(agent.handleConnection);
|
|
116
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath });
|
|
117
|
+
// Empty command name → agent emits { type: 'error' }.
|
|
118
|
+
await expect(transport.execute({ command: '' })).rejects.toThrow();
|
|
119
|
+
});
|
|
120
|
+
it('survives a simulated resume: server torn down then re-listened, dialer reconnects', async () => {
|
|
121
|
+
// 1. Agent is up; one exec succeeds.
|
|
122
|
+
server = await startAgentServer(agent.handleConnection);
|
|
123
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath },
|
|
124
|
+
// Tight retry budget so the test is fast but still proves retry.
|
|
125
|
+
{ connectRetryBudgetMs: 5_000, connectRetryIntervalMs: 50, connectTimeoutMs: 1_000 });
|
|
126
|
+
const first = await transport.execute({ command: '/bin/sh', args: ['-c', 'echo before'] });
|
|
127
|
+
expect(first.stdout).toContain('before');
|
|
128
|
+
// 2. Simulate a resume: the FC vsock driver closes all connections
|
|
129
|
+
// and the listener is gone until the agent re-LISTENs. Tear the
|
|
130
|
+
// server down entirely.
|
|
131
|
+
await new Promise((r) => server?.close(() => r()));
|
|
132
|
+
server = undefined;
|
|
133
|
+
// 3. Kick off an exec WHILE the agent is still down. The dialer's
|
|
134
|
+
// connect-retry budget must hold (ECONNREFUSED) until the agent
|
|
135
|
+
// re-listens — this is the FC #4713 / TRANSPORT_RESET-not-
|
|
136
|
+
// delivered mitigation: the host re-dials rather than hanging.
|
|
137
|
+
const pending = transport.execute({ command: '/bin/sh', args: ['-c', 'echo after'] });
|
|
138
|
+
// 4. After a beat, the agent re-establishes its listen on the SAME
|
|
139
|
+
// address (the resume re-listen invariant).
|
|
140
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
141
|
+
server = await startAgentServer(agent.handleConnection);
|
|
142
|
+
const after = await pending;
|
|
143
|
+
expect(after.stdout).toContain('after');
|
|
144
|
+
expect(after.exitCode).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
it('connect-retry budget gives up with a clear error when the agent never returns', async () => {
|
|
147
|
+
// No server started at all. The dialer should exhaust its budget
|
|
148
|
+
// and throw a descriptive error rather than hang forever.
|
|
149
|
+
const transport = new VsockAgentTransport({ kind: 'unix', path: sockPath }, { connectRetryBudgetMs: 400, connectRetryIntervalMs: 50, connectTimeoutMs: 200 });
|
|
150
|
+
await expect(transport.healthz()).resolves.toBe(false);
|
|
151
|
+
await expect(transport.readFile('x')).rejects.toThrow(/could not connect to agent/);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Ring-0 mTLS arm — pure-local TLS loopback "relay" (no Azure, no FC host)
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
//
|
|
158
|
+
// Proves the ADDITIVE `kind:'mtls'` transport arm without a real fleet:
|
|
159
|
+
// - a `tls.createServer` plays the per-FC-host RELAY (requestCert +
|
|
160
|
+
// rejectUnauthorized), reads the `SANDBOX <id>` routing preamble,
|
|
161
|
+
// then bridges verbatim to the SAME `agent.handleConnection` peer
|
|
162
|
+
// the vsock/unix arms use — so the IDENTICAL framing round-trips.
|
|
163
|
+
// - the dialer presents the fleet CLIENT cert and verifies the relay
|
|
164
|
+
// server cert; a WRONG-CA client is rejected at the TLS layer.
|
|
165
|
+
// - the `SANDBOX <id>` preamble is the FIRST bytes after the
|
|
166
|
+
// handshake (the relay's routing key), and the caller never writes
|
|
167
|
+
// a guest `CONNECT 1024` line (the relay issues that host-side).
|
|
168
|
+
//
|
|
169
|
+
// The cert material is a throwaway P-256 test PKI (CA + server leaf +
|
|
170
|
+
// client leaf + an unrelated rogue client) baked in as PEM constants —
|
|
171
|
+
// no openssl/forge at test time, keeping the package dependency-light.
|
|
172
|
+
// Test PKI. Minted offline with: openssl ec/x509 self-signed CA + leaves
|
|
173
|
+
// (server SAN = DNS:sandbox.fc.internal, IP:127.0.0.1). Far-future expiry.
|
|
174
|
+
// `ROGUE_*` chains to a DIFFERENT CA to prove rejectUnauthorized blocks it.
|
|
175
|
+
const CA_CRT = `-----BEGIN CERTIFICATE-----
|
|
176
|
+
MIIBjjCCATWgAwIBAgIUGXUtG14KQr4spNGW7rpfjZGoBIIwCgYIKoZIzj0EAwIw
|
|
177
|
+
HDEaMBgGA1UEAwwRdmFuZGFsLWZjLXRlc3QtY2EwIBcNMjYwNjE2MDgzMjUyWhgP
|
|
178
|
+
MjEyNjA1MjMwODMyNTJaMBwxGjAYBgNVBAMMEXZhbmRhbC1mYy10ZXN0LWNhMFkw
|
|
179
|
+
EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5l+vjhjm/PX02D55n56EwelgvM1jrqQM
|
|
180
|
+
Z+ezzvMmrZEBGWInvBND3z3kc6nv7snu6fHT2HHOyhwUNoLqmrIg/qNTMFEwHQYD
|
|
181
|
+
VR0OBBYEFP0M8/KZY1ovxyGDci5iuoYXcmZgMB8GA1UdIwQYMBaAFP0M8/KZY1ov
|
|
182
|
+
xyGDci5iuoYXcmZgMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIg
|
|
183
|
+
C0xtFDASnwsOYT0GiZwzYWTOBfgmjDWK7ANXQQFNTh4CICVLQwq5eLy3424drEHk
|
|
184
|
+
5Ol/FNKVZcGRjE96rB0mRUOp
|
|
185
|
+
-----END CERTIFICATE-----
|
|
186
|
+
`;
|
|
187
|
+
const SERVER_CRT = `-----BEGIN CERTIFICATE-----
|
|
188
|
+
MIIBsDCCAVWgAwIBAgIUYkXroqlz9blXW8llis6e2OxOc1MwCgYIKoZIzj0EAwIw
|
|
189
|
+
HDEaMBgGA1UEAwwRdmFuZGFsLWZjLXRlc3QtY2EwIBcNMjYwNjE2MDgzMjUyWhgP
|
|
190
|
+
MjEyNjA1MjMwODMyNTJaMBIxEDAOBgNVBAMMB2ZjLWhvc3QwWTATBgcqhkjOPQIB
|
|
191
|
+
BggqhkjOPQMBBwNCAAQodCSHY8Sz5Lh/4K6+SzekkrRCeKMBfmonVb7dUhM0ZWpo
|
|
192
|
+
ePCcRrMcJp5aNxLxEgkl2EMMMMwE3LlLSOX5LZz6o30wezAkBgNVHREEHTAbghNz
|
|
193
|
+
YW5kYm94LmZjLmludGVybmFshwR/AAABMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0G
|
|
194
|
+
A1UdDgQWBBRXHU1vlX7WvqzcbaNxRxV4f3gfDDAfBgNVHSMEGDAWgBT9DPPymWNa
|
|
195
|
+
L8chg3IuYrqGF3JmYDAKBggqhkjOPQQDAgNJADBGAiEAlqzKqgBf5hxJqd26QwcY
|
|
196
|
+
n3cvEKi4f2BSLe1Rfzr5oSoCIQD0izUOJXLNvmac9cMgV0HvkftBerDKFOcGeGTN
|
|
197
|
+
g3Eunw==
|
|
198
|
+
-----END CERTIFICATE-----
|
|
199
|
+
`;
|
|
200
|
+
const SERVER_KEY = `-----BEGIN PRIVATE KEY-----
|
|
201
|
+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg/ROnYXIkMKnipbLt
|
|
202
|
+
JnFEwXAifgeOv9TGLcK2RojE7jmhRANCAAQodCSHY8Sz5Lh/4K6+SzekkrRCeKMB
|
|
203
|
+
fmonVb7dUhM0ZWpoePCcRrMcJp5aNxLxEgkl2EMMMMwE3LlLSOX5LZz6
|
|
204
|
+
-----END PRIVATE KEY-----
|
|
205
|
+
`;
|
|
206
|
+
const CLIENT_CRT = `-----BEGIN CERTIFICATE-----
|
|
207
|
+
MIIBjjCCATWgAwIBAgIUYkXroqlz9blXW8llis6e2OxOc1QwCgYIKoZIzj0EAwIw
|
|
208
|
+
HDEaMBgGA1UEAwwRdmFuZGFsLWZjLXRlc3QtY2EwIBcNMjYwNjE2MDgzMjUyWhgP
|
|
209
|
+
MjEyNjA1MjMwODMyNTJaMBgxFjAUBgNVBAMMDWNhLXZhbmRhbC1hcHAwWTATBgcq
|
|
210
|
+
hkjOPQIBBggqhkjOPQMBBwNCAASlCMCwjtrQUicWcWsO29R5S7fzjMbbxXvDh8/K
|
|
211
|
+
w57x/PN/uQwLHWCz1Tsyk0FnbrwGP+nPtwrUPIxrLN//euXko1cwVTATBgNVHSUE
|
|
212
|
+
DDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQU2cxDCNrlVkk4ODs6ZPrkC6GzHjkwHwYD
|
|
213
|
+
VR0jBBgwFoAU/Qzz8pljWi/HIYNyLmK6hhdyZmAwCgYIKoZIzj0EAwIDRwAwRAIg
|
|
214
|
+
RghqArNm3bXnWnRi+jkEEJa8mRb2z0Wj4G7SIhtMurUCIBQSllVtICgZB9hvcM0D
|
|
215
|
+
d24fcW4nlfvaZJMJ6dRC1Wle
|
|
216
|
+
-----END CERTIFICATE-----
|
|
217
|
+
`;
|
|
218
|
+
const CLIENT_KEY = `-----BEGIN PRIVATE KEY-----
|
|
219
|
+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgaamj/ycrEuPqpiC2
|
|
220
|
+
IhAYx/M5iN4t4P+B17V4GO54Nb2hRANCAASlCMCwjtrQUicWcWsO29R5S7fzjMbb
|
|
221
|
+
xXvDh8/Kw57x/PN/uQwLHWCz1Tsyk0FnbrwGP+nPtwrUPIxrLN//euXk
|
|
222
|
+
-----END PRIVATE KEY-----
|
|
223
|
+
`;
|
|
224
|
+
const ROGUE_CLIENT_CRT = `-----BEGIN CERTIFICATE-----
|
|
225
|
+
MIIBhjCCASugAwIBAgIUbAYOlW4PQGyXjjRsjCPy62EHUmIwCgYIKoZIzj0EAwIw
|
|
226
|
+
EzERMA8GA1UEAwwIcm9ndWUtY2EwIBcNMjYwNjE2MDgzMjUyWhgPMjEyNjA1MjMw
|
|
227
|
+
ODMyNTJaMBcxFTATBgNVBAMMDHJvZ3VlLWNsaWVudDBZMBMGByqGSM49AgEGCCqG
|
|
228
|
+
SM49AwEHA0IABOPSzdzQTQz3M5CDHLVPguvHd10ncaDV9t4zKUy/PCE+U7GTJwN2
|
|
229
|
+
TtypGnmYEahbcl45j94hwu495P5VbR5ohb6jVzBVMBMGA1UdJQQMMAoGCCsGAQUF
|
|
230
|
+
BwMCMB0GA1UdDgQWBBTf0woauniche3/ps77+Kd0dsbbSDAfBgNVHSMEGDAWgBSX
|
|
231
|
+
Yu0IJlPxi79Gyi5hR1IfS/sqyzAKBggqhkjOPQQDAgNJADBGAiEAxD4N4XWtaHPJ
|
|
232
|
+
yaCOzQP0e5RRyNst3QrhH0NSMyPw89wCIQC+bezLX97TuV1sId53Bo1l9j1TSOMa
|
|
233
|
+
EZotN/Wsq6L+qw==
|
|
234
|
+
-----END CERTIFICATE-----
|
|
235
|
+
`;
|
|
236
|
+
const ROGUE_CLIENT_KEY = `-----BEGIN PRIVATE KEY-----
|
|
237
|
+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR6fGq27O9eT/hi0Q
|
|
238
|
+
c+L5+lSm5fbDEghVJCo6k7HrG62hRANCAATj0s3c0E0M9zOQgxy1T4Lrx3ddJ3Gg
|
|
239
|
+
1fbeMylMvzwhPlOxkycDdk7cqRp5mBGoW3JeOY/eIcLuPeT+VW0eaIW+
|
|
240
|
+
-----END PRIVATE KEY-----
|
|
241
|
+
`;
|
|
242
|
+
/**
|
|
243
|
+
* A loopback stand-in for the per-FC-host mTLS relay.
|
|
244
|
+
*
|
|
245
|
+
* Terminates mTLS (requestCert + rejectUnauthorized + CA pin), reads the
|
|
246
|
+
* `SANDBOX <id>\n` routing preamble line, then bridges the rest of the
|
|
247
|
+
* TLS stream verbatim to a FRESH `net.connect` to `agentSockPath` (the
|
|
248
|
+
* unix-socket-backed real agent) — exactly the dumb byte-pump the
|
|
249
|
+
* production relay is. It does NOT speak framing itself and does NOT
|
|
250
|
+
* send an ack line (matching the transport's no-ack INTEGRATE contract).
|
|
251
|
+
*/
|
|
252
|
+
function startMtlsRelay(agentSockPath, listenPort = 0) {
|
|
253
|
+
let resolvePreamble;
|
|
254
|
+
const preamble = new Promise((r) => {
|
|
255
|
+
resolvePreamble = r;
|
|
256
|
+
});
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const server = createTlsServer({
|
|
259
|
+
requestCert: true,
|
|
260
|
+
rejectUnauthorized: true,
|
|
261
|
+
ca: CA_CRT,
|
|
262
|
+
cert: SERVER_CRT,
|
|
263
|
+
key: SERVER_KEY,
|
|
264
|
+
minVersion: 'TLSv1.3',
|
|
265
|
+
}, (tlsSock) => {
|
|
266
|
+
// Read exactly the `SANDBOX <id>\n` preamble line, then bridge.
|
|
267
|
+
let pre = Buffer.alloc(0);
|
|
268
|
+
const onPreambleData = (chunk) => {
|
|
269
|
+
pre = Buffer.concat([pre, chunk]);
|
|
270
|
+
const nl = pre.indexOf(0x0a);
|
|
271
|
+
if (nl < 0)
|
|
272
|
+
return;
|
|
273
|
+
tlsSock.removeListener('data', onPreambleData);
|
|
274
|
+
// Pause + unshift any post-preamble bytes so flowing-mode
|
|
275
|
+
// data is not dropped between removing this listener and
|
|
276
|
+
// attaching the pipe (the framed request can arrive in the
|
|
277
|
+
// same OR a later chunk). `pipe` resumes the stream.
|
|
278
|
+
tlsSock.pause();
|
|
279
|
+
const line = pre.subarray(0, nl).toString('utf8');
|
|
280
|
+
const rest = pre.subarray(nl + 1);
|
|
281
|
+
if (rest.length > 0)
|
|
282
|
+
tlsSock.unshift(rest);
|
|
283
|
+
const m = /^SANDBOX (.+)$/.exec(line);
|
|
284
|
+
const id = m?.[1];
|
|
285
|
+
if (id === undefined) {
|
|
286
|
+
tlsSock.destroy();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
resolvePreamble(id);
|
|
290
|
+
// 1 inbound mTLS conn -> 1 FRESH local agent connect (the
|
|
291
|
+
// resume-survival invariant the relay must preserve).
|
|
292
|
+
const upstream = netConnect({ path: agentSockPath });
|
|
293
|
+
upstream.on('error', () => tlsSock.destroy());
|
|
294
|
+
tlsSock.on('error', () => upstream.destroy());
|
|
295
|
+
upstream.once('connect', () => {
|
|
296
|
+
tlsSock.pipe(upstream);
|
|
297
|
+
upstream.pipe(tlsSock);
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
tlsSock.on('data', onPreambleData);
|
|
301
|
+
});
|
|
302
|
+
server.on('error', reject);
|
|
303
|
+
server.listen(listenPort, '127.0.0.1', () => {
|
|
304
|
+
const addr = server.address();
|
|
305
|
+
if (addr === null || typeof addr === 'string') {
|
|
306
|
+
reject(new Error('relay: no TCP port'));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
resolve({ server, port: addr.port, preamble: () => preamble });
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/** Re-listen the relay on a FIXED port (the host-bridge-restart case). */
|
|
314
|
+
function startMtlsRelayOnPort(agentSockPath, port) {
|
|
315
|
+
return startMtlsRelay(agentSockPath, port);
|
|
316
|
+
}
|
|
317
|
+
describe('VsockAgentTransport mtls arm over a TLS loopback relay', () => {
|
|
318
|
+
let agentServer;
|
|
319
|
+
let relay;
|
|
320
|
+
afterEach(async () => {
|
|
321
|
+
if (relay) {
|
|
322
|
+
await new Promise((r) => relay?.server.close(() => r()));
|
|
323
|
+
relay = undefined;
|
|
324
|
+
}
|
|
325
|
+
if (agentServer) {
|
|
326
|
+
await new Promise((r) => agentServer?.close(() => r()));
|
|
327
|
+
agentServer = undefined;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
function mtlsTransport(opts) {
|
|
331
|
+
if (!relay)
|
|
332
|
+
throw new Error('relay not started');
|
|
333
|
+
return new VsockAgentTransport({
|
|
334
|
+
kind: 'mtls',
|
|
335
|
+
host: '127.0.0.1',
|
|
336
|
+
port: relay.port,
|
|
337
|
+
sandboxId: 'sbx-ring0-abc123',
|
|
338
|
+
tls: {
|
|
339
|
+
ca: CA_CRT,
|
|
340
|
+
cert: CLIENT_CRT,
|
|
341
|
+
key: CLIENT_KEY,
|
|
342
|
+
servername: 'sandbox.fc.internal',
|
|
343
|
+
},
|
|
344
|
+
}, opts);
|
|
345
|
+
}
|
|
346
|
+
it('round-trips exec + write/read + healthz through the mTLS relay', async () => {
|
|
347
|
+
agentServer = await startAgentServer(agent.handleConnection);
|
|
348
|
+
relay = await startMtlsRelay(sockPath);
|
|
349
|
+
const transport = mtlsTransport();
|
|
350
|
+
const r = await transport.execute({
|
|
351
|
+
command: '/bin/sh',
|
|
352
|
+
args: ['-c', 'echo mtls-out; echo mtls-err 1>&2; exit 7'],
|
|
353
|
+
});
|
|
354
|
+
expect(r.stdout).toContain('mtls-out');
|
|
355
|
+
expect(r.stderr).toContain('mtls-err');
|
|
356
|
+
expect(r.exitCode).toBe(7);
|
|
357
|
+
const payload = Buffer.from([0x00, 0xff, 0x10, 0x42, 0x00, 0x99, 0xfe]);
|
|
358
|
+
await transport.writeFile('m/blob.bin', payload);
|
|
359
|
+
const read = await transport.readFile('m/blob.bin');
|
|
360
|
+
expect(read.equals(payload)).toBe(true);
|
|
361
|
+
expect(await transport.healthz()).toBe(true);
|
|
362
|
+
});
|
|
363
|
+
it('sends `SANDBOX <id>` as the first bytes after the handshake (no CONNECT line)', async () => {
|
|
364
|
+
agentServer = await startAgentServer(agent.handleConnection);
|
|
365
|
+
relay = await startMtlsRelay(sockPath);
|
|
366
|
+
const transport = mtlsTransport();
|
|
367
|
+
// Any op drives a dial; the relay records the preamble line.
|
|
368
|
+
expect(await transport.healthz()).toBe(true);
|
|
369
|
+
// The caller's first line is the routing preamble, NOT `CONNECT 1024`
|
|
370
|
+
// — the relay issues the guest CONNECT host-side.
|
|
371
|
+
await expect(relay.preamble()).resolves.toBe('sbx-ring0-abc123');
|
|
372
|
+
});
|
|
373
|
+
it('rejects a client presenting a wrong-CA cert (rejectUnauthorized blocks it)', async () => {
|
|
374
|
+
agentServer = await startAgentServer(agent.handleConnection);
|
|
375
|
+
relay = await startMtlsRelay(sockPath);
|
|
376
|
+
// Rogue client cert chains to a DIFFERENT CA than the relay trusts.
|
|
377
|
+
const rogue = new VsockAgentTransport({
|
|
378
|
+
kind: 'mtls',
|
|
379
|
+
host: '127.0.0.1',
|
|
380
|
+
port: relay.port,
|
|
381
|
+
sandboxId: 'sbx-ring0-abc123',
|
|
382
|
+
tls: {
|
|
383
|
+
ca: CA_CRT,
|
|
384
|
+
cert: ROGUE_CLIENT_CRT,
|
|
385
|
+
key: ROGUE_CLIENT_KEY,
|
|
386
|
+
servername: 'sandbox.fc.internal',
|
|
387
|
+
},
|
|
388
|
+
}, { connectRetryBudgetMs: 600, connectRetryIntervalMs: 50, connectTimeoutMs: 300 });
|
|
389
|
+
// The relay's `rejectUnauthorized` drops the connection AFTER the
|
|
390
|
+
// client handshake completes (the client trusts the server CA), so
|
|
391
|
+
// the failure surfaces as a torn-down round-trip, not a TLS verify
|
|
392
|
+
// error on the client side. Either way no agent traffic completes:
|
|
393
|
+
// healthz swallows it into `false`; readFile rejects.
|
|
394
|
+
expect(await rogue.healthz()).toBe(false);
|
|
395
|
+
await expect(rogue.readFile('x')).rejects.toThrow();
|
|
396
|
+
});
|
|
397
|
+
it('rejects a server whose cert does not chain to the pinned CA (caller-side verify)', async () => {
|
|
398
|
+
agentServer = await startAgentServer(agent.handleConnection);
|
|
399
|
+
relay = await startMtlsRelay(sockPath);
|
|
400
|
+
// The CALLER pins the ROGUE CA, so the relay's good-CA server cert
|
|
401
|
+
// fails verification — proves the dialer's rejectUnauthorized works.
|
|
402
|
+
const wrongCaCaller = new VsockAgentTransport({
|
|
403
|
+
kind: 'mtls',
|
|
404
|
+
host: '127.0.0.1',
|
|
405
|
+
port: relay.port,
|
|
406
|
+
sandboxId: 'sbx-ring0-abc123',
|
|
407
|
+
tls: {
|
|
408
|
+
ca: ROGUE_CLIENT_CRT, // not the issuing CA → server cert unverifiable
|
|
409
|
+
cert: CLIENT_CRT,
|
|
410
|
+
key: CLIENT_KEY,
|
|
411
|
+
servername: 'sandbox.fc.internal',
|
|
412
|
+
},
|
|
413
|
+
}, { connectRetryBudgetMs: 600, connectRetryIntervalMs: 50, connectTimeoutMs: 300 });
|
|
414
|
+
await expect(wrongCaCaller.readFile('x')).rejects.toThrow(/could not connect to agent/);
|
|
415
|
+
});
|
|
416
|
+
it('survives a simulated resume through the relay (relay torn down then re-listened on the same port)', async () => {
|
|
417
|
+
agentServer = await startAgentServer(agent.handleConnection);
|
|
418
|
+
relay = await startMtlsRelay(sockPath);
|
|
419
|
+
const fixedPort = relay.port;
|
|
420
|
+
const transport = new VsockAgentTransport({
|
|
421
|
+
kind: 'mtls',
|
|
422
|
+
host: '127.0.0.1',
|
|
423
|
+
port: fixedPort,
|
|
424
|
+
sandboxId: 'sbx-ring0-abc123',
|
|
425
|
+
tls: {
|
|
426
|
+
ca: CA_CRT,
|
|
427
|
+
cert: CLIENT_CRT,
|
|
428
|
+
key: CLIENT_KEY,
|
|
429
|
+
servername: 'sandbox.fc.internal',
|
|
430
|
+
},
|
|
431
|
+
}, { connectRetryBudgetMs: 5_000, connectRetryIntervalMs: 50, connectTimeoutMs: 1_000 });
|
|
432
|
+
const first = await transport.execute({ command: '/bin/sh', args: ['-c', 'echo before'] });
|
|
433
|
+
expect(first.stdout).toContain('before');
|
|
434
|
+
// Simulate the FC host bridge going away then coming back (the
|
|
435
|
+
// network analogue of a vsock re-LISTEN): tear the relay down so the
|
|
436
|
+
// caller's TLS connect gets ECONNREFUSED, and the per-request fresh
|
|
437
|
+
// dial must ride the connect-retry budget until the relay is back.
|
|
438
|
+
await new Promise((r) => relay?.server.close(() => r()));
|
|
439
|
+
relay = undefined;
|
|
440
|
+
const pending = transport.execute({ command: '/bin/sh', args: ['-c', 'echo after'] });
|
|
441
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
442
|
+
// Re-listen the relay on the SAME port (the host bridge restarts).
|
|
443
|
+
relay = await startMtlsRelayOnPort(sockPath, fixedPort);
|
|
444
|
+
const after = await pending;
|
|
445
|
+
expect(after.stdout).toContain('after');
|
|
446
|
+
expect(after.exitCode).toBe(0);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
//# sourceMappingURL=transport.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.test.js","sourceRoot":"","sources":["../../../../src/backends/firecracker/__tests__/transport.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `microvm:self-hosted` (Firecracker) backend.
|
|
3
|
+
*
|
|
4
|
+
* Sibling of `docker/` and `aci-standby-pool/`: same
|
|
5
|
+
* {@link SandboxBackend} surface, same NDJSON exec-stream + base64
|
|
6
|
+
* file-IO **wire contract**, different transport and shipping
|
|
7
|
+
* mechanism.
|
|
8
|
+
*
|
|
9
|
+
* - docker `docker run`s a container and reaches an HTTP worker on a
|
|
10
|
+
* host loopback port.
|
|
11
|
+
* - aci PUTs an ACI container group and reaches the same HTTP worker
|
|
12
|
+
* over the group's IP.
|
|
13
|
+
* - firecracker POSTs the owned Azure **orchestrator** to CoW-resume a
|
|
14
|
+
* microVM off the golden snapshot, then reaches a **custom vsock
|
|
15
|
+
* agent** baked into the golden rootfs over the **vsock transport**
|
|
16
|
+
* (`transport.ts`). The wire FORMAT is identical (see `protocol.ts`);
|
|
17
|
+
* only the transport differs — HTTP for docker/aci, framed-over-vsock
|
|
18
|
+
* for FC, because across an FC snapshot resume a TCP control channel
|
|
19
|
+
* is dead-on-arrival while the vsock LISTEN socket survives
|
|
20
|
+
* (FC `snapshot-support.md`).
|
|
21
|
+
*
|
|
22
|
+
* ## Why this is a remote-copy backend (no host bind-mounts)
|
|
23
|
+
* A sibling process on the FC host cannot see the microVM's
|
|
24
|
+
* filesystem, so — exactly like ACI — the workspace is seeded by
|
|
25
|
+
* archive-sync over the control channel (tar.gz `writeFile` +
|
|
26
|
+
* in-sandbox `tar -xzf` via `exec`), driven by the Vandal-side
|
|
27
|
+
* lifecycle (`workspace-sync.ts`). This backend therefore exposes no
|
|
28
|
+
* `layout` mount rendering; it only needs the `outputs` container path
|
|
29
|
+
* as the workspace root, which the orchestrator returns as `rootDir`.
|
|
30
|
+
*
|
|
31
|
+
* ## Trust model
|
|
32
|
+
* The microVM is a hardware-virtualization (KVM) trust boundary —
|
|
33
|
+
* stronger than docker namespaces or ACI's managed isolation. The
|
|
34
|
+
* vsock control channel never traverses the guest's egress netns; the
|
|
35
|
+
* agent listens on a fixed AF_VSOCK port captured warm in the golden
|
|
36
|
+
* snapshot. Auth to the orchestrator rides a caller-supplied
|
|
37
|
+
* `getToken()` closure (the ACI `getArmToken` pattern) so
|
|
38
|
+
* `@namzu/sandbox` keeps zero Azure-SDK dependencies.
|
|
39
|
+
*
|
|
40
|
+
* ## Auth / SDK-dependency boundary
|
|
41
|
+
* `getToken()` mirrors ACI's `getArmToken`: the consumer's runtime
|
|
42
|
+
* owns Managed-Identity / federated-credential picking; this package
|
|
43
|
+
* only calls the closure on every orchestrator HTTP call so a
|
|
44
|
+
* long-running sandbox survives token rotation.
|
|
45
|
+
*/
|
|
46
|
+
import type { AgentSnapshotRef, SandboxBackend } from '../../index.js';
|
|
47
|
+
import type { MtlsClientMaterial, SandboxAgentHandle, VsockTransportOptions, WireSandboxAgentHandle } from './transport.js';
|
|
48
|
+
/**
|
|
49
|
+
* Async callback returning a fresh bearer token for
|
|
50
|
+
* {@link FirecrackerBackendInternalConfig.orchestratorEndpoint}.
|
|
51
|
+
* Invoked on every orchestrator HTTP call (mirrors ACI's
|
|
52
|
+
* `ArmTokenProvider`).
|
|
53
|
+
*/
|
|
54
|
+
export type OrchestratorTokenProvider = () => Promise<string>;
|
|
55
|
+
export interface FirecrackerBackendInternalConfig {
|
|
56
|
+
/** Base URL of the owned Azure control plane / orchestrator. */
|
|
57
|
+
readonly orchestratorEndpoint: string;
|
|
58
|
+
/** Bearer token provider for `orchestratorEndpoint`. */
|
|
59
|
+
readonly getToken: OrchestratorTokenProvider;
|
|
60
|
+
/** Golden template / snapshot revision id to resume from. */
|
|
61
|
+
readonly template?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Per-agent captured snapshot to resume INSTEAD of a fresh golden boot
|
|
64
|
+
* (layered on its base golden). Optional + additive: absent ⇒ the
|
|
65
|
+
* orchestrator create body is byte-identical and the generic golden-resume
|
|
66
|
+
* path is unchanged. The orchestrator honors it (routing the claim to a
|
|
67
|
+
* per-agent resume) only when its own per-agent flag is on; it is the WIRE
|
|
68
|
+
* carrier the host's per-agent trigger path sets. See {@link AgentSnapshotRef}.
|
|
69
|
+
*/
|
|
70
|
+
readonly agentSnapshot?: AgentSnapshotRef;
|
|
71
|
+
/** Fixed guest AF_VSOCK port the agent listens on (contract port). */
|
|
72
|
+
readonly agentVsockPort?: number;
|
|
73
|
+
readonly readyTimeoutMs?: number;
|
|
74
|
+
readonly readyPollIntervalMs?: number;
|
|
75
|
+
/** Transport tuning forwarded to {@link VsockAgentTransport}. */
|
|
76
|
+
readonly transport?: VsockTransportOptions;
|
|
77
|
+
/**
|
|
78
|
+
* NETWORK-mode mTLS client material (ses_051 P4). When present AND the
|
|
79
|
+
* orchestrator returns an `mtls` agent handle, this CA/cert/key is MERGED
|
|
80
|
+
* onto the handle's `tls` block before the transport dials the per-host
|
|
81
|
+
* relay. The consumer's runtime injects it (mirrors `getToken`); this
|
|
82
|
+
* package never reads it from disk or fetches it, so it stays Azure-SDK
|
|
83
|
+
* free. Absent for the single-host VSOCK default (the live proofs).
|
|
84
|
+
*/
|
|
85
|
+
readonly mtls?: MtlsClientMaterial;
|
|
86
|
+
/**
|
|
87
|
+
* CONTROL-plane mTLS client material. When present, the orchestrator
|
|
88
|
+
* control-plane calls (`POST /sandboxes`, `DELETE /sandboxes/{id}:delete`,
|
|
89
|
+
* `GET /capacity`) dial over mTLS — presenting this client cert and
|
|
90
|
+
* verifying the orchestrator's server cert against this CA — INSTEAD of the
|
|
91
|
+
* plain `fetch`. This secures the control plane when `orchestratorEndpoint`
|
|
92
|
+
* is reached over the PUBLIC internet (the `ca-vandal-app` → FC-host hop is
|
|
93
|
+
* not VNet-integrated), where the shared-secret bearer alone would be
|
|
94
|
+
* exposed. The bearer is STILL sent (defense in depth on top of mTLS).
|
|
95
|
+
*
|
|
96
|
+
* Reuses the SAME `{ca,cert,key,servername}` shape as the relay's `mtls`
|
|
97
|
+
* material — one fleet CA secures both planes. The consumer's runtime
|
|
98
|
+
* injects the bytes (mirrors `getToken` / `mtls`); the package never reads
|
|
99
|
+
* them from disk. Absent → the control plane stays plain `fetch` (the
|
|
100
|
+
* single-host DEFAULT, byte-for-byte unchanged).
|
|
101
|
+
*/
|
|
102
|
+
readonly controlPlaneMtls?: MtlsClientMaterial;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Build a {@link SandboxBackend} backed by the owned Firecracker
|
|
106
|
+
* platform. Construction is synchronous; the orchestrator POST happens
|
|
107
|
+
* on the first `create()`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function buildFirecrackerBackend(config: FirecrackerBackendInternalConfig): SandboxBackend;
|
|
110
|
+
/**
|
|
111
|
+
* Turn the orchestrator's WIRE handle into the transport handle the dialer
|
|
112
|
+
* uses. Two transforms:
|
|
113
|
+
*
|
|
114
|
+
* - `vsock` with a missing/<=0 port → fill the contract port.
|
|
115
|
+
* - `mtls` → MERGE the consumer-injected `mtls` cert material onto the
|
|
116
|
+
* wire handle (which carries only host/port/sandboxId). The orchestrator
|
|
117
|
+
* never ships cert material; without an injected `mtls` block a network
|
|
118
|
+
* handle cannot dial, so this throws loud rather than constructing a
|
|
119
|
+
* transport that would fail at connect time.
|
|
120
|
+
*
|
|
121
|
+
* `unix` + already-correct `vsock` handles pass through untouched.
|
|
122
|
+
*/
|
|
123
|
+
export declare function normalizeHandle(handle: WireSandboxAgentHandle, contractPort: number, mtls: MtlsClientMaterial | undefined): SandboxAgentHandle;
|
|
124
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAcH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAyB,MAAM,gBAAgB,CAAA;AAC7F,OAAO,KAAK,EACX,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,MAAM,gBAAgB,CAAA;AAGvB;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;AAE7D,MAAM,WAAW,gCAAgC;IAChD,gEAAgE;IAChE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAA;IAC5C,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,CAAA;IACzC,sEAAsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;IACrC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,CAAA;IAC1C;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAA;IAClC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAA;CAC9C;AAMD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gCAAgC,GAAG,cAAc,CAQhG;AAgVD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE,sBAAsB,EAC9B,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,kBAAkB,GAAG,SAAS,GAClC,kBAAkB,CAyBpB"}
|