@serfab/cadre-host 1.13.0 → 1.15.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/README.md +347 -423
- package/dist/bin/host.d.ts +6 -3
- package/dist/bin/host.js +332 -524
- package/dist/bin/host.js.map +1 -1
- package/dist/bin/join-output.d.ts +56 -0
- package/dist/bin/join-output.js +92 -0
- package/dist/bin/join-output.js.map +1 -0
- package/dist/bin/nat-output.d.ts +54 -0
- package/dist/bin/nat-output.js +138 -0
- package/dist/bin/nat-output.js.map +1 -0
- package/dist/bin/ui-access.d.ts +22 -0
- package/dist/bin/ui-access.js +44 -0
- package/dist/bin/ui-access.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +66 -0
- package/dist/hosted/claim-details.js +60 -0
- package/dist/hosted/claim-details.js.map +1 -0
- package/dist/hosted/hosted-node-service.d.ts +325 -0
- package/dist/hosted/hosted-node-service.js +678 -0
- package/dist/hosted/hosted-node-service.js.map +1 -0
- package/dist/hosted/hosted-node-store.d.ts +40 -0
- package/dist/hosted/hosted-node-store.js +126 -0
- package/dist/hosted/hosted-node-store.js.map +1 -0
- package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
- package/dist/hosted/hosted-node-supervisor.js +396 -0
- package/dist/hosted/hosted-node-supervisor.js.map +1 -0
- package/dist/hosted/hosted-node-watcher.d.ts +95 -0
- package/dist/hosted/hosted-node-watcher.js +260 -0
- package/dist/hosted/hosted-node-watcher.js.map +1 -0
- package/dist/hosted/index.d.ts +16 -0
- package/dist/hosted/index.js +13 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/hosted/node-status.d.ts +51 -0
- package/dist/hosted/node-status.js +77 -0
- package/dist/hosted/node-status.js.map +1 -0
- package/dist/hosted/types.d.ts +141 -0
- package/dist/hosted/types.js +22 -0
- package/dist/hosted/types.js.map +1 -0
- package/dist/index.d.ts +12 -24
- package/dist/index.js +9 -19
- package/dist/index.js.map +1 -1
- package/dist/installer/config.d.ts +27 -34
- package/dist/installer/config.js +46 -15
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +9 -19
- package/dist/installer/index.js +21 -85
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/paths.d.ts +0 -2
- package/dist/installer/paths.js +0 -4
- package/dist/installer/paths.js.map +1 -1
- package/dist/installer/wizard.d.ts +12 -9
- package/dist/installer/wizard.js +15 -14
- package/dist/installer/wizard.js.map +1 -1
- package/dist/nat/address-resolver.d.ts +18 -17
- package/dist/nat/address-resolver.js +79 -25
- package/dist/nat/address-resolver.js.map +1 -1
- package/dist/nat/address-watch.d.ts +37 -0
- package/dist/nat/address-watch.js +84 -0
- package/dist/nat/address-watch.js.map +1 -0
- package/dist/nat/ddns/duckdns.d.ts +2 -1
- package/dist/nat/ddns/duckdns.js +5 -1
- package/dist/nat/ddns/duckdns.js.map +1 -1
- package/dist/nat/ddns/index.d.ts +6 -0
- package/dist/nat/ddns/index.js.map +1 -1
- package/dist/nat/index.d.ts +13 -9
- package/dist/nat/index.js +7 -5
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +220 -77
- package/dist/nat/nat-service.js +653 -239
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.d.ts +12 -5
- package/dist/nat/nat-store.js +124 -29
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/network-mode.d.ts +19 -0
- package/dist/nat/network-mode.js +32 -0
- package/dist/nat/network-mode.js.map +1 -0
- package/dist/nat/port-mapper.d.ts +83 -79
- package/dist/nat/port-mapper.js +148 -189
- package/dist/nat/port-mapper.js.map +1 -1
- package/dist/nat/reachability.d.ts +32 -15
- package/dist/nat/reachability.js +110 -22
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +98 -27
- package/dist/nat/types.js +8 -5
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
- package/dist/orchestrator/host-process-orchestrator.js +84 -233
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
- package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
- package/dist/orchestrator/identity-file.js.map +1 -0
- package/dist/orchestrator/index.d.ts +2 -3
- package/dist/orchestrator/index.js +1 -1
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/node-identity.d.ts +6 -6
- package/dist/orchestrator/node-identity.js +7 -7
- package/dist/orchestrator/node-identity.js.map +1 -1
- package/dist/orchestrator/port-allocator.d.ts +5 -6
- package/dist/orchestrator/port-allocator.js +9 -11
- package/dist/orchestrator/port-allocator.js.map +1 -1
- package/dist/orchestrator/state-store.d.ts +3 -5
- package/dist/orchestrator/state-store.js +2 -10
- package/dist/orchestrator/state-store.js.map +1 -1
- package/dist/orchestrator/types.d.ts +33 -22
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +4 -0
- package/dist/package-version.js.map +1 -0
- package/dist/server/error-handler.d.ts +5 -25
- package/dist/server/error-handler.js +11 -74
- package/dist/server/error-handler.js.map +1 -1
- package/dist/server/events/types.d.ts +7 -11
- package/dist/server/events/types.js +2 -1
- package/dist/server/events/types.js.map +1 -1
- package/dist/server/index.d.ts +11 -37
- package/dist/server/index.js +30 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/hosted-nodes.d.ts +28 -0
- package/dist/server/routes/hosted-nodes.js +68 -0
- package/dist/server/routes/hosted-nodes.js.map +1 -0
- package/dist/server/routes/nat.d.ts +4 -1
- package/dist/server/routes/nat.js +13 -2
- package/dist/server/routes/nat.js.map +1 -1
- package/dist/server/routes/nodes.d.ts +6 -20
- package/dist/server/routes/nodes.js +7 -86
- package/dist/server/routes/nodes.js.map +1 -1
- package/dist/server/routes/settings.d.ts +3 -7
- package/dist/server/routes/settings.js +18 -8
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.d.ts +2 -25
- package/dist/server/routes/status.js +3 -43
- package/dist/server/routes/status.js.map +1 -1
- package/dist/server/server.js +8 -8
- package/dist/server/settings-store.d.ts +2 -2
- package/dist/server/settings-store.js +2 -2
- package/dist/server/static.js +1 -2
- package/dist/server/static.js.map +1 -1
- package/dist/ui/assets/index-BHd8CWZA.css +1 -0
- package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
- package/dist/ui/assets/index-Ccr2GjPT.js.map +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/store.d.ts +1 -1
- package/dist/update/store.js +1 -1
- package/dist/update/types.d.ts +1 -1
- package/dist/update/types.js +1 -1
- package/package.json +5 -7
- package/src/__tests__/cli-nat.smoke.test.ts +219 -212
- package/src/__tests__/cli.smoke.test.ts +2 -2
- package/src/__tests__/global-setup.ts +2 -3
- package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
- package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
- package/src/__tests__/orchestrator-handle-contract.ts +134 -134
- package/src/__tests__/orchestrator-node-identity.test.ts +177 -203
- package/src/__tests__/orchestrator-ports.test.ts +124 -143
- package/src/__tests__/orchestrator-push.test.ts +138 -146
- package/src/__tests__/orchestrator.test.ts +1017 -1063
- package/src/__tests__/ui-access.test.ts +30 -0
- package/src/bin/host.ts +397 -572
- package/src/bin/join-output.ts +120 -0
- package/src/bin/nat-output.ts +188 -0
- package/src/bin/ui-access.ts +50 -0
- package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
- package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
- package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
- package/src/hosted/__tests__/test-invitation.ts +33 -0
- package/src/hosted/claim-details.ts +103 -0
- package/src/hosted/hosted-node-service.ts +814 -0
- package/src/hosted/hosted-node-store.ts +144 -0
- package/src/hosted/hosted-node-supervisor.ts +439 -0
- package/src/hosted/hosted-node-watcher.ts +288 -0
- package/src/hosted/index.ts +53 -0
- package/src/hosted/node-status.ts +97 -0
- package/src/hosted/types.ts +160 -0
- package/src/index.ts +72 -91
- package/src/installer/__tests__/config.test.ts +15 -40
- package/src/installer/__tests__/installer.smoke.test.ts +184 -263
- package/src/installer/__tests__/wizard.test.ts +25 -13
- package/src/installer/config.ts +201 -175
- package/src/installer/index.ts +252 -327
- package/src/installer/paths.ts +104 -109
- package/src/installer/wizard.ts +24 -26
- package/src/nat/__tests__/address-resolver.test.ts +107 -82
- package/src/nat/__tests__/nat-service.test.ts +608 -515
- package/src/nat/__tests__/nat-store.test.ts +49 -12
- package/src/nat/__tests__/network-mode.test.ts +38 -0
- package/src/nat/__tests__/port-mapper.test.ts +30 -191
- package/src/nat/__tests__/reachability.test.ts +96 -13
- package/src/nat/address-resolver.ts +94 -52
- package/src/nat/address-watch.ts +92 -0
- package/src/nat/ddns/duckdns.ts +5 -1
- package/src/nat/ddns/index.ts +6 -0
- package/src/nat/index.ts +33 -13
- package/src/nat/nat-service.ts +942 -501
- package/src/nat/nat-store.ts +126 -31
- package/src/nat/network-mode.ts +36 -0
- package/src/nat/port-mapper.ts +241 -329
- package/src/nat/reachability.ts +155 -34
- package/src/nat/types.ts +110 -37
- package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
- package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
- package/src/orchestrator/host-process-orchestrator.ts +1089 -1260
- package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
- package/src/orchestrator/index.ts +9 -10
- package/src/orchestrator/node-identity.ts +41 -41
- package/src/orchestrator/port-allocator.ts +71 -73
- package/src/orchestrator/state-store.ts +76 -90
- package/src/orchestrator/types.ts +34 -23
- package/src/package-version.ts +4 -0
- package/src/server/__tests__/bus.test.ts +38 -38
- package/src/server/__tests__/error-handler.test.ts +81 -118
- package/src/server/__tests__/fakes.ts +15 -18
- package/src/server/__tests__/hosted-nodes-route.test.ts +161 -0
- package/src/server/__tests__/nodes-route.test.ts +114 -283
- package/src/server/__tests__/publishers.test.ts +193 -180
- package/src/server/__tests__/server.smoke.test.ts +134 -223
- package/src/server/__tests__/settings-route.test.ts +210 -195
- package/src/server/__tests__/sse-route.test.ts +119 -119
- package/src/server/__tests__/status-route.test.ts +82 -121
- package/src/server/error-handler.ts +112 -178
- package/src/server/events/types.ts +20 -24
- package/src/server/index.ts +223 -267
- package/src/server/routes/hosted-nodes.ts +83 -0
- package/src/server/routes/nat.ts +75 -63
- package/src/server/routes/nodes.ts +96 -188
- package/src/server/routes/settings.ts +20 -15
- package/src/server/routes/status.ts +5 -67
- package/src/server/server.ts +8 -8
- package/src/server/settings-store.ts +2 -2
- package/src/server/static.ts +1 -2
- package/src/update/index.ts +2 -2
- package/src/update/store.ts +1 -1
- package/src/update/types.ts +1 -1
- package/dist/auth/duration.d.ts +0 -1
- package/dist/auth/duration.js +0 -32
- package/dist/auth/duration.js.map +0 -1
- package/dist/auth/index.d.ts +0 -11
- package/dist/auth/index.js +0 -9
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/self-label.d.ts +0 -36
- package/dist/auth/self-label.js +0 -57
- package/dist/auth/self-label.js.map +0 -1
- package/dist/auth/trust-circle-store.d.ts +0 -26
- package/dist/auth/trust-circle-store.js +0 -129
- package/dist/auth/trust-circle-store.js.map +0 -1
- package/dist/auth/trust-circle.d.ts +0 -156
- package/dist/auth/trust-circle.js +0 -307
- package/dist/auth/trust-circle.js.map +0 -1
- package/dist/auth/types.d.ts +0 -84
- package/dist/auth/types.js +0 -24
- package/dist/auth/types.js.map +0 -1
- package/dist/donation/donation-service.d.ts +0 -389
- package/dist/donation/donation-service.js +0 -828
- package/dist/donation/donation-service.js.map +0 -1
- package/dist/donation/donation-store.d.ts +0 -51
- package/dist/donation/donation-store.js +0 -152
- package/dist/donation/donation-store.js.map +0 -1
- package/dist/donation/donation-supervisor.js +0 -364
- package/dist/donation/donation-supervisor.js.map +0 -1
- package/dist/donation/grant-service.d.ts +0 -80
- package/dist/donation/grant-service.js +0 -210
- package/dist/donation/grant-service.js.map +0 -1
- package/dist/donation/grant-store.d.ts +0 -38
- package/dist/donation/grant-store.js +0 -135
- package/dist/donation/grant-store.js.map +0 -1
- package/dist/donation/index.d.ts +0 -20
- package/dist/donation/index.js +0 -17
- package/dist/donation/index.js.map +0 -1
- package/dist/donation/types.d.ts +0 -224
- package/dist/donation/types.js +0 -40
- package/dist/donation/types.js.map +0 -1
- package/dist/installer/identity.js.map +0 -1
- package/dist/owner/index.d.ts +0 -11
- package/dist/owner/index.js +0 -11
- package/dist/owner/index.js.map +0 -1
- package/dist/owner/owner-node-client.d.ts +0 -110
- package/dist/owner/owner-node-client.js +0 -184
- package/dist/owner/owner-node-client.js.map +0 -1
- package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
- package/dist/server/routes/bootstrap-node-validation.js +0 -197
- package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
- package/dist/server/routes/grants-admin.d.ts +0 -29
- package/dist/server/routes/grants-admin.js +0 -58
- package/dist/server/routes/grants-admin.js.map +0 -1
- package/dist/server/routes/grants.d.ts +0 -28
- package/dist/server/routes/grants.js +0 -165
- package/dist/server/routes/grants.js.map +0 -1
- package/dist/server/routes/provision-request-validation.d.ts +0 -45
- package/dist/server/routes/provision-request-validation.js +0 -97
- package/dist/server/routes/provision-request-validation.js.map +0 -1
- package/dist/server/routes/strands.d.ts +0 -20
- package/dist/server/routes/strands.js +0 -38
- package/dist/server/routes/strands.js.map +0 -1
- package/dist/server/routes/trust-circle.d.ts +0 -17
- package/dist/server/routes/trust-circle.js +0 -35
- package/dist/server/routes/trust-circle.js.map +0 -1
- package/dist/strands/index.d.ts +0 -7
- package/dist/strands/index.js +0 -6
- package/dist/strands/index.js.map +0 -1
- package/dist/strands/strand-service.d.ts +0 -50
- package/dist/strands/strand-service.js +0 -103
- package/dist/strands/strand-service.js.map +0 -1
- package/dist/strands/types.d.ts +0 -93
- package/dist/strands/types.js +0 -23
- package/dist/strands/types.js.map +0 -1
- package/dist/ui/assets/index-C6nELNfC.js +0 -23
- package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
- package/dist/ui/assets/index-TtIYSeZO.css +0 -1
- package/src/__tests__/cli-invite.smoke.test.ts +0 -192
- package/src/__tests__/host-owner.smoke.test.ts +0 -123
- package/src/__tests__/orchestrator-owner.test.ts +0 -234
- package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
- package/src/auth/__tests__/duration.test.ts +0 -28
- package/src/auth/__tests__/self-label.test.ts +0 -85
- package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
- package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
- package/src/auth/__tests__/trust-circle.test.ts +0 -445
- package/src/auth/duration.ts +0 -33
- package/src/auth/index.ts +0 -21
- package/src/auth/self-label.ts +0 -74
- package/src/auth/trust-circle-store.ts +0 -155
- package/src/auth/trust-circle.ts +0 -395
- package/src/auth/types.ts +0 -99
- package/src/donation/__tests__/donation-service.test.ts +0 -1193
- package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
- package/src/donation/__tests__/grant-service.test.ts +0 -213
- package/src/donation/__tests__/grant-store.test.ts +0 -113
- package/src/donation/donation-service.ts +0 -1033
- package/src/donation/donation-store.ts +0 -176
- package/src/donation/donation-supervisor.ts +0 -411
- package/src/donation/grant-service.ts +0 -252
- package/src/donation/grant-store.ts +0 -152
- package/src/donation/index.ts +0 -65
- package/src/donation/types.ts +0 -260
- package/src/owner/__tests__/owner-node-client.test.ts +0 -194
- package/src/owner/index.ts +0 -12
- package/src/owner/owner-node-client.ts +0 -259
- package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
- package/src/server/__tests__/grants-admin-route.test.ts +0 -171
- package/src/server/__tests__/grants-route.test.ts +0 -394
- package/src/server/__tests__/strands-route.test.ts +0 -234
- package/src/server/routes/bootstrap-node-validation.ts +0 -206
- package/src/server/routes/grants-admin.ts +0 -74
- package/src/server/routes/grants.ts +0 -193
- package/src/server/routes/provision-request-validation.ts +0 -105
- package/src/server/routes/strands.ts +0 -54
- package/src/server/routes/trust-circle.ts +0 -49
- package/src/strands/__tests__/strand-service.test.ts +0 -165
- package/src/strands/index.ts +0 -14
- package/src/strands/strand-service.ts +0 -135
- package/src/strands/types.ts +0 -102
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostProcessOrchestrator claim-secret test — the one piece of a hosted node's
|
|
3
|
+
* spawn that is secret.
|
|
4
|
+
*
|
|
5
|
+
* A node started waiting to be claimed takes its secret from `CADRE_CLAIM_SECRET`
|
|
6
|
+
* (`cadre-cli start`). The orchestrator must hand it to the child through the
|
|
7
|
+
* environment and nowhere else: not as an argument (the process list shows
|
|
8
|
+
* those) and not in `state.json` (which outlives the child and is readable by
|
|
9
|
+
* anything on the machine). A fake CLI records its environment and arguments to a
|
|
10
|
+
* file so both are observable without a real cadre node.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
14
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
|
|
18
|
+
import { HostProcessOrchestrator } from '../orchestrator/host-process-orchestrator.js';
|
|
19
|
+
import { removeAllNodes } from './orchestrator-teardown.js';
|
|
20
|
+
|
|
21
|
+
// Writes CADRE_CLAIM_SECRET (as seen in the child env) and the argv next to the
|
|
22
|
+
// startup token, then behaves like a minimal long-lived node.
|
|
23
|
+
const FAKE_CLI = `
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
|
|
28
|
+
const tokenPath = get('--startup-token-file');
|
|
29
|
+
const token = process.env.CADRE_STARTUP_TOKEN ?? '';
|
|
30
|
+
if (tokenPath) {
|
|
31
|
+
const dir = path.dirname(tokenPath);
|
|
32
|
+
const record = { secret: process.env.CADRE_CLAIM_SECRET ?? null, args };
|
|
33
|
+
const seenPath = path.join(dir, 'spawn-seen.json');
|
|
34
|
+
try { fs.writeFileSync(seenPath + '.tmp', JSON.stringify(record), 'utf8'); fs.renameSync(seenPath + '.tmp', seenPath); } catch (e) { console.error(e); }
|
|
35
|
+
if (token) { try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (e) { console.error(e); } }
|
|
36
|
+
}
|
|
37
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
38
|
+
setInterval(() => {}, 1 << 30);
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const SECRET = 'claim-secret-' + 'x'.repeat(30);
|
|
42
|
+
|
|
43
|
+
let tmpRoot: string;
|
|
44
|
+
let scriptPath: string;
|
|
45
|
+
const orchestrators: HostProcessOrchestrator[] = [];
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-claim-'));
|
|
49
|
+
scriptPath = join(tmpRoot, 'fake-cli.mjs');
|
|
50
|
+
writeFileSync(scriptPath, FAKE_CLI, 'utf8');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(async () => {
|
|
54
|
+
try {
|
|
55
|
+
await removeAllNodes(orchestrators);
|
|
56
|
+
} finally {
|
|
57
|
+
await sleep(50);
|
|
58
|
+
try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function makeOrchestrator(rootDir: string): HostProcessOrchestrator {
|
|
63
|
+
mkdirSync(rootDir, { recursive: true });
|
|
64
|
+
const orch = new HostProcessOrchestrator({
|
|
65
|
+
rootDir,
|
|
66
|
+
portRange: { start: 18500, end: 18999 },
|
|
67
|
+
stopTimeoutMs: 1500,
|
|
68
|
+
spawn: { entrypoint: scriptPath },
|
|
69
|
+
});
|
|
70
|
+
orchestrators.push(orch);
|
|
71
|
+
return orch;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function waitForFile(path: string, timeoutMs = 5000): Promise<string> {
|
|
75
|
+
const deadline = Date.now() + timeoutMs;
|
|
76
|
+
while (Date.now() < deadline) {
|
|
77
|
+
if (existsSync(path)) return readFileSync(path, 'utf8');
|
|
78
|
+
await sleep(50);
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`file never appeared: ${path}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sleep(ms: number): Promise<void> {
|
|
84
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe('HostProcessOrchestrator claim secret', () => {
|
|
88
|
+
it('reaches the child as CADRE_CLAIM_SECRET only: not an argument, not state.json', async () => {
|
|
89
|
+
const rootDir = join(tmpRoot, 'a');
|
|
90
|
+
const orch = makeOrchestrator(rootDir);
|
|
91
|
+
await orch.init();
|
|
92
|
+
|
|
93
|
+
await orch.createContainer({
|
|
94
|
+
containerId: 'hn_claim',
|
|
95
|
+
partyId: 'unclaimed',
|
|
96
|
+
profile: 'storage',
|
|
97
|
+
claimSecret: SECRET,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const seen = JSON.parse(await waitForFile(join(rootDir, 'hn_claim', 'spawn-seen.json'))) as { secret: string | null; args: string[] };
|
|
101
|
+
expect(seen.secret).toBe(SECRET);
|
|
102
|
+
expect(seen.args.join(' ')).not.toContain(SECRET);
|
|
103
|
+
expect(readFileSync(join(rootDir, 'state.json'), 'utf8')).not.toContain(SECRET);
|
|
104
|
+
|
|
105
|
+
// No secret, no variable: `cadre-cli start` treats a set-but-empty value as unset,
|
|
106
|
+
// but the child must not even see the name.
|
|
107
|
+
await orch.createContainer({ containerId: 'hn_plain', partyId: 'p', profile: 'storage' });
|
|
108
|
+
const plain = JSON.parse(await waitForFile(join(rootDir, 'hn_plain', 'spawn-seen.json'))) as { secret: string | null };
|
|
109
|
+
expect(plain.secret).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -1,163 +1,160 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HostProcessOrchestrator env-scrub test — the manager's own `process.env` must
|
|
3
|
-
* not leak `CADRE_*` config-override vars into spawned children.
|
|
4
|
-
*
|
|
5
|
-
* `launchChild` used to build each child's environment as
|
|
6
|
-
* `{ ...process.env, ...extraEnv, <fixed per-child vars> }`, so ANY `CADRE_*`
|
|
7
|
-
* var set on the manager (not just the ones the orchestrator deliberately
|
|
8
|
-
* sets) reached every child. Because `@serfab/cadre-cli` treats an inherited
|
|
9
|
-
* `CADRE_*` var as a config override that beats the per-child config file, a
|
|
10
|
-
* `CADRE_PARTY_ID` set on the manager could silently pull every child into
|
|
11
|
-
* that party.
|
|
12
|
-
*
|
|
13
|
-
* A fake CLI records the vars under test from its environment to a file so
|
|
14
|
-
* the scrub is observable without a real cadre node.
|
|
15
|
-
*
|
|
16
|
-
* The same record carries `CADRE_LISTEN_ADDRS`, one of the vars the orchestrator
|
|
17
|
-
* deliberately SETS: a child must be handed a WebSocket listener beside its TCP one,
|
|
18
|
-
* on the ports its handle holds, or a phone cannot reach it.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
22
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
23
|
-
import { tmpdir } from 'node:os';
|
|
24
|
-
import { join } from 'node:path';
|
|
25
|
-
|
|
26
|
-
import { HostProcessOrchestrator } from '../orchestrator/host-process-orchestrator.js';
|
|
27
|
-
import { removeAllNodes } from './orchestrator-teardown.js';
|
|
28
|
-
|
|
29
|
-
// Writes the vars under test (as seen in the child env) next to the startup token.
|
|
30
|
-
const FAKE_CLI = `
|
|
31
|
-
import fs from 'node:fs';
|
|
32
|
-
import path from 'node:path';
|
|
33
|
-
const args = process.argv.slice(2);
|
|
34
|
-
const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
|
|
35
|
-
const tokenPath = get('--startup-token-file');
|
|
36
|
-
const token = process.env.CADRE_STARTUP_TOKEN ?? '';
|
|
37
|
-
if (tokenPath) {
|
|
38
|
-
const dir = path.dirname(tokenPath);
|
|
39
|
-
const record = {
|
|
40
|
-
CADRE_PARTY_ID: process.env.CADRE_PARTY_ID ?? null,
|
|
41
|
-
CADRE_STORAGE_PATH: process.env.CADRE_STORAGE_PATH ?? null,
|
|
42
|
-
CADRE_ADMIN_PORT: process.env.CADRE_ADMIN_PORT ?? null,
|
|
43
|
-
CADRE_LISTEN_ADDRS: process.env.CADRE_LISTEN_ADDRS ?? null,
|
|
44
|
-
DEBUG: process.env.DEBUG ?? null,
|
|
45
|
-
};
|
|
46
|
-
// Write then rename: the test polls for the file's existence, and must never read it half-written.
|
|
47
|
-
const seenPath = path.join(dir, 'env-seen.json');
|
|
48
|
-
try { fs.writeFileSync(seenPath + '.tmp', JSON.stringify(record), 'utf8'); fs.renameSync(seenPath + '.tmp', seenPath); } catch (e) { console.error(e); }
|
|
49
|
-
if (token) { try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (e) { console.error(e); } }
|
|
50
|
-
}
|
|
51
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
52
|
-
setInterval(() => {}, 1 << 30);
|
|
53
|
-
`;
|
|
54
|
-
|
|
55
|
-
let tmpRoot: string;
|
|
56
|
-
let scriptPath: string;
|
|
57
|
-
const orchestrators: HostProcessOrchestrator[] = [];
|
|
58
|
-
let savedEnv: Record<string, string | undefined>;
|
|
59
|
-
|
|
60
|
-
beforeEach(() => {
|
|
61
|
-
tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-env-scrub-'));
|
|
62
|
-
scriptPath = join(tmpRoot, 'fake-cli.mjs');
|
|
63
|
-
writeFileSync(scriptPath, FAKE_CLI, 'utf8');
|
|
64
|
-
savedEnv = {
|
|
65
|
-
CADRE_PARTY_ID: process.env.CADRE_PARTY_ID,
|
|
66
|
-
CADRE_STORAGE_PATH: process.env.CADRE_STORAGE_PATH,
|
|
67
|
-
CADRE_ADMIN_PORT: process.env.CADRE_ADMIN_PORT,
|
|
68
|
-
DEBUG: process.env.DEBUG,
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
afterEach(async () => {
|
|
73
|
-
try {
|
|
74
|
-
await removeAllNodes(orchestrators);
|
|
75
|
-
} finally {
|
|
76
|
-
await sleep(50);
|
|
77
|
-
try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
78
|
-
for (const [key, value] of Object.entries(savedEnv)) {
|
|
79
|
-
if (value === undefined) delete process.env[key];
|
|
80
|
-
else process.env[key] = value;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
function makeOrchestrator(rootDir: string): HostProcessOrchestrator {
|
|
86
|
-
mkdirSync(rootDir, { recursive: true });
|
|
87
|
-
const orch = new HostProcessOrchestrator({
|
|
88
|
-
rootDir,
|
|
89
|
-
portRange: { start: 18000, end: 18499 },
|
|
90
|
-
stopTimeoutMs: 1500,
|
|
91
|
-
spawn: { entrypoint: scriptPath },
|
|
92
|
-
});
|
|
93
|
-
orchestrators.push(orch);
|
|
94
|
-
return orch;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function waitForFile(path: string, timeoutMs = 5000): Promise<string> {
|
|
98
|
-
const deadline = Date.now() + timeoutMs;
|
|
99
|
-
while (Date.now() < deadline) {
|
|
100
|
-
if (existsSync(path)) return readFileSync(path, 'utf8');
|
|
101
|
-
await sleep(50);
|
|
102
|
-
}
|
|
103
|
-
throw new Error(`file never appeared: ${path}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function sleep(ms: number): Promise<void> {
|
|
107
|
-
return new Promise((res) => setTimeout(res, ms));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
describe('HostProcessOrchestrator child env scrub', () => {
|
|
111
|
-
it('does not leak manager CADRE_* config-override vars into children, but passes through non-CADRE vars', async () => {
|
|
112
|
-
process.env.CADRE_PARTY_ID = 'conflicting-party';
|
|
113
|
-
process.env.CADRE_STORAGE_PATH = '/tmp/should-not-leak';
|
|
114
|
-
// Not an ENV_MAPPINGS config override — read directly by the cli's `start`
|
|
115
|
-
// command — so it only stays scrubbed if the scrub is prefix-wide.
|
|
116
|
-
process.env.CADRE_ADMIN_PORT = '19999';
|
|
117
|
-
process.env.DEBUG = 'some:value';
|
|
118
|
-
|
|
119
|
-
const rootDir = join(tmpRoot, 'a');
|
|
120
|
-
const orch = makeOrchestrator(rootDir);
|
|
121
|
-
await orch.init();
|
|
122
|
-
|
|
123
|
-
await orch.createContainer({
|
|
124
|
-
containerId: 'child-1',
|
|
125
|
-
partyId: 'party-1',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
expect(seen.
|
|
141
|
-
expect(seen.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
await orch.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
expect(seen.CADRE_LISTEN_ADDRS).toBe(`/ip4/0.0.0.0/tcp/${ports.p2p},/ip4/0.0.0.0/tcp/${ports.ws}/ws`);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* HostProcessOrchestrator env-scrub test — the manager's own `process.env` must
|
|
3
|
+
* not leak `CADRE_*` config-override vars into spawned children.
|
|
4
|
+
*
|
|
5
|
+
* `launchChild` used to build each child's environment as
|
|
6
|
+
* `{ ...process.env, ...extraEnv, <fixed per-child vars> }`, so ANY `CADRE_*`
|
|
7
|
+
* var set on the manager (not just the ones the orchestrator deliberately
|
|
8
|
+
* sets) reached every child. Because `@serfab/cadre-cli` treats an inherited
|
|
9
|
+
* `CADRE_*` var as a config override that beats the per-child config file, a
|
|
10
|
+
* `CADRE_PARTY_ID` set on the manager could silently pull every child into
|
|
11
|
+
* that party.
|
|
12
|
+
*
|
|
13
|
+
* A fake CLI records the vars under test from its environment to a file so
|
|
14
|
+
* the scrub is observable without a real cadre node.
|
|
15
|
+
*
|
|
16
|
+
* The same record carries `CADRE_LISTEN_ADDRS`, one of the vars the orchestrator
|
|
17
|
+
* deliberately SETS: a child must be handed a WebSocket listener beside its TCP one,
|
|
18
|
+
* on the ports its handle holds, or a phone cannot reach it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
22
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
|
|
26
|
+
import { HostProcessOrchestrator } from '../orchestrator/host-process-orchestrator.js';
|
|
27
|
+
import { removeAllNodes } from './orchestrator-teardown.js';
|
|
28
|
+
|
|
29
|
+
// Writes the vars under test (as seen in the child env) next to the startup token.
|
|
30
|
+
const FAKE_CLI = `
|
|
31
|
+
import fs from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
|
|
35
|
+
const tokenPath = get('--startup-token-file');
|
|
36
|
+
const token = process.env.CADRE_STARTUP_TOKEN ?? '';
|
|
37
|
+
if (tokenPath) {
|
|
38
|
+
const dir = path.dirname(tokenPath);
|
|
39
|
+
const record = {
|
|
40
|
+
CADRE_PARTY_ID: process.env.CADRE_PARTY_ID ?? null,
|
|
41
|
+
CADRE_STORAGE_PATH: process.env.CADRE_STORAGE_PATH ?? null,
|
|
42
|
+
CADRE_ADMIN_PORT: process.env.CADRE_ADMIN_PORT ?? null,
|
|
43
|
+
CADRE_LISTEN_ADDRS: process.env.CADRE_LISTEN_ADDRS ?? null,
|
|
44
|
+
DEBUG: process.env.DEBUG ?? null,
|
|
45
|
+
};
|
|
46
|
+
// Write then rename: the test polls for the file's existence, and must never read it half-written.
|
|
47
|
+
const seenPath = path.join(dir, 'env-seen.json');
|
|
48
|
+
try { fs.writeFileSync(seenPath + '.tmp', JSON.stringify(record), 'utf8'); fs.renameSync(seenPath + '.tmp', seenPath); } catch (e) { console.error(e); }
|
|
49
|
+
if (token) { try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (e) { console.error(e); } }
|
|
50
|
+
}
|
|
51
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
52
|
+
setInterval(() => {}, 1 << 30);
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
let tmpRoot: string;
|
|
56
|
+
let scriptPath: string;
|
|
57
|
+
const orchestrators: HostProcessOrchestrator[] = [];
|
|
58
|
+
let savedEnv: Record<string, string | undefined>;
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-env-scrub-'));
|
|
62
|
+
scriptPath = join(tmpRoot, 'fake-cli.mjs');
|
|
63
|
+
writeFileSync(scriptPath, FAKE_CLI, 'utf8');
|
|
64
|
+
savedEnv = {
|
|
65
|
+
CADRE_PARTY_ID: process.env.CADRE_PARTY_ID,
|
|
66
|
+
CADRE_STORAGE_PATH: process.env.CADRE_STORAGE_PATH,
|
|
67
|
+
CADRE_ADMIN_PORT: process.env.CADRE_ADMIN_PORT,
|
|
68
|
+
DEBUG: process.env.DEBUG,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
afterEach(async () => {
|
|
73
|
+
try {
|
|
74
|
+
await removeAllNodes(orchestrators);
|
|
75
|
+
} finally {
|
|
76
|
+
await sleep(50);
|
|
77
|
+
try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
78
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
79
|
+
if (value === undefined) delete process.env[key];
|
|
80
|
+
else process.env[key] = value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function makeOrchestrator(rootDir: string): HostProcessOrchestrator {
|
|
86
|
+
mkdirSync(rootDir, { recursive: true });
|
|
87
|
+
const orch = new HostProcessOrchestrator({
|
|
88
|
+
rootDir,
|
|
89
|
+
portRange: { start: 18000, end: 18499 },
|
|
90
|
+
stopTimeoutMs: 1500,
|
|
91
|
+
spawn: { entrypoint: scriptPath },
|
|
92
|
+
});
|
|
93
|
+
orchestrators.push(orch);
|
|
94
|
+
return orch;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function waitForFile(path: string, timeoutMs = 5000): Promise<string> {
|
|
98
|
+
const deadline = Date.now() + timeoutMs;
|
|
99
|
+
while (Date.now() < deadline) {
|
|
100
|
+
if (existsSync(path)) return readFileSync(path, 'utf8');
|
|
101
|
+
await sleep(50);
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`file never appeared: ${path}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function sleep(ms: number): Promise<void> {
|
|
107
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
describe('HostProcessOrchestrator child env scrub', () => {
|
|
111
|
+
it('does not leak manager CADRE_* config-override vars into children, but passes through non-CADRE vars', async () => {
|
|
112
|
+
process.env.CADRE_PARTY_ID = 'conflicting-party';
|
|
113
|
+
process.env.CADRE_STORAGE_PATH = '/tmp/should-not-leak';
|
|
114
|
+
// Not an ENV_MAPPINGS config override — read directly by the cli's `start`
|
|
115
|
+
// command — so it only stays scrubbed if the scrub is prefix-wide.
|
|
116
|
+
process.env.CADRE_ADMIN_PORT = '19999';
|
|
117
|
+
process.env.DEBUG = 'some:value';
|
|
118
|
+
|
|
119
|
+
const rootDir = join(tmpRoot, 'a');
|
|
120
|
+
const orch = makeOrchestrator(rootDir);
|
|
121
|
+
await orch.init();
|
|
122
|
+
|
|
123
|
+
await orch.createContainer({
|
|
124
|
+
containerId: 'child-1',
|
|
125
|
+
partyId: 'party-1',
|
|
126
|
+
profile: 'storage',
|
|
127
|
+
});
|
|
128
|
+
await orch.createContainer({
|
|
129
|
+
containerId: 'child-2',
|
|
130
|
+
partyId: 'party-2',
|
|
131
|
+
profile: 'storage',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const seen1 = JSON.parse(await waitForFile(join(rootDir, 'child-1', 'env-seen.json')));
|
|
135
|
+
const seen2 = JSON.parse(await waitForFile(join(rootDir, 'child-2', 'env-seen.json')));
|
|
136
|
+
|
|
137
|
+
for (const seen of [seen1, seen2]) {
|
|
138
|
+
expect(seen.CADRE_PARTY_ID).toBeNull();
|
|
139
|
+
expect(seen.CADRE_STORAGE_PATH).toBeNull();
|
|
140
|
+
expect(seen.CADRE_ADMIN_PORT).toBeNull();
|
|
141
|
+
expect(seen.DEBUG).toBe('some:value');
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('hands each child a TCP and a WebSocket listen address on its own ports', async () => {
|
|
146
|
+
const rootDir = join(tmpRoot, 'b');
|
|
147
|
+
const orch = makeOrchestrator(rootDir);
|
|
148
|
+
await orch.init();
|
|
149
|
+
|
|
150
|
+
await orch.createContainer({
|
|
151
|
+
containerId: 'child-1',
|
|
152
|
+
partyId: 'party-1',
|
|
153
|
+
profile: 'storage',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const seen = JSON.parse(await waitForFile(join(rootDir, 'child-1', 'env-seen.json')));
|
|
157
|
+
const ports = orch.getNode('child-1')!.ports;
|
|
158
|
+
expect(seen.CADRE_LISTEN_ADDRS).toBe(`/ip4/0.0.0.0/tcp/${ports.p2p},/ip4/0.0.0.0/tcp/${ports.ws}/ws`);
|
|
159
|
+
});
|
|
160
|
+
});
|