@bongos/core 1.19.916 → 1.19.917

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.
@@ -0,0 +1,135 @@
1
+ // tests/provision_account_identity.mjs — a plane that cannot create a per-instance unix
2
+ // account is refused, not handed a shared uid (task 1004018, ADR 0317). ADR 0281 makes an
3
+ // instance's identity its own account; the self-host image runs `USER node` with no sudo,
4
+ // where `useradd` cannot run at all. Before this, that plane crashed mid-provision at step
5
+ // 1c — or, once someone gives the container a front door, would have quietly co-located
6
+ // every child under one uid. Hermetic: exec is a fake, nothing touches a box.
7
+ import assert from 'node:assert/strict';
8
+ import { test } from 'node:test';
9
+ import { createRequire } from 'node:module';
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const P = require('../scripts/gds/provision.js');
13
+ const { accountPlaneGap, accountPreflight } = require('../scripts/gds/provision-repo.js');
14
+
15
+ const PROBE = `sh -c 'test "$(id -u)" = 0'`;
16
+ const isRoot = () => ({ ok: true, stdout: '' });
17
+ // What a shell that RAN reports: exit 1 from `test`, or 127 when sudo is not installed.
18
+ // execSync ALWAYS sets stderr to a Buffer, empty when the command failed silently — the
19
+ // fakes carry it because a fake that omits it hides a message with no cause in it.
20
+ const answered = (status, msg, stderr = '') => () => {
21
+ const e = new Error(msg); e.status = status; e.stderr = Buffer.from(stderr); throw e;
22
+ };
23
+ const unreachable = () => {
24
+ const e = new Error('Command failed'); e.status = 255;
25
+ e.stderr = Buffer.from('ssh: connect to host box port 22: Connection timed out'); throw e;
26
+ };
27
+
28
+ test('accountPlaneGap: a runner that reaches root passes; one that cannot is named as sharing a uid', () => {
29
+ const cmds = [];
30
+ assert.equal(accountPlaneGap((c) => { cmds.push(c); return isRoot(); }), null);
31
+ assert.deepEqual(cmds, [PROBE], 'one read-only command, no mutation');
32
+ const gap = accountPlaneGap(answered(1, 'Command failed'));
33
+ assert.match(gap, /cannot create a per-instance unix account/);
34
+ assert.match(gap, /one uid/, 'names the consequence, not just the failed command');
35
+ assert.match(gap, /ADR 0281/);
36
+ });
37
+
38
+ test('accountPlaneGap: the probe runs the SAME escalation step 1c will, so a pass is evidence', () => {
39
+ const cmds = [];
40
+ const rec = (c) => { cmds.push(c); return isRoot(); };
41
+ accountPlaneGap(rec, { privileged: true });
42
+ accountPlaneGap(rec, { privileged: false });
43
+ assert.deepEqual(cmds, [`sudo ${PROBE}`, PROBE]);
44
+ // sh -c, not a bare `test`: under sudo an outer-shell expansion would answer about the
45
+ // unprivileged user and pass on exactly the plane this refuses.
46
+ assert.ok(cmds.every((c) => /sh -c '/.test(c)), 'the command substitution runs INSIDE the escalation');
47
+ });
48
+
49
+ test('accountPlaneGap: only a shell that answered proves it — a transport failure says it could not confirm', () => {
50
+ const unsure = accountPlaneGap(unreachable);
51
+ assert.match(unsure, /could not confirm/);
52
+ assert.doesNotMatch(unsure, /Provision from a host/, 'a network blip is never diagnosed as a plane property');
53
+ assert.match(unsure, /Connection timed out/, 'the real cause leads');
54
+ // The container's two real shapes, both proof.
55
+ assert.match(accountPlaneGap(answered(127, 'Command failed', 'sh: 1: sudo: not found'), { privileged: true }), /sudo: not found/);
56
+ assert.match(accountPlaneGap(answered(1, 'Command failed')), /cannot create a per-instance unix account/);
57
+ });
58
+
59
+ test('accountPlaneGap: a SILENT failure still names a cause — an empty stderr is not a blank reason', () => {
60
+ // The commonest refusal there is: `test` exits 1 and writes nothing. execSync hands back a
61
+ // zero-length stderr Buffer, which is truthy and String()s to '' — so a naive
62
+ // `e.stderr || e.message` prints "failed ()" and tells the operator nothing.
63
+ const gap = accountPlaneGap(answered(1, 'Command failed: sh -c'));
64
+ assert.doesNotMatch(gap, /failed \(\)/, 'the parenthetical exists to name the cause');
65
+ assert.match(gap, /failed \(Command failed: sh -c\)/, 'falls through the empty Buffer to the message');
66
+ });
67
+
68
+ test('accountPreflight: a dedicated droplet skips it, and a dry run asks the box nothing', () => {
69
+ const cmds = [];
70
+ const rec = (c) => { cmds.push(c); return isRoot(); };
71
+ assert.equal(accountPreflight({ exec: rec, apply: true, log: () => {}, shape: 'dedicated' }), null);
72
+ assert.deepEqual(cmds, [], 'cloud-init creates that account as root — nothing to ask');
73
+ const lines = [];
74
+ assert.equal(accountPreflight({ exec: rec, apply: false, log: (m) => lines.push(m), shape: 'co-tenant' }), null);
75
+ assert.deepEqual(cmds, [], 'a dry run changes nothing and asks nothing');
76
+ assert.match(lines.join('\n'), /would probe/, 'the plan says the probe is coming, rather than printing step 1c as though it works');
77
+ });
78
+
79
+ // -- the runner leg ---------------------------------------------------------
80
+
81
+ const coTenant = { id: 1, slug: 'mercury', hosting_shape: 'co-tenant', status: 'requested', owner_builder_id: 7, port: 3003, db_name: 'mercury', domain: null };
82
+
83
+ // `noRoot` is the self-host image: USER node, no sudo, so the probe's shell answers 127.
84
+ function harness({ noRoot = false } = {}) {
85
+ const cmds = [], writes = [], statuses = [];
86
+ const exec = (cmd) => {
87
+ cmds.push(cmd);
88
+ if (noRoot && cmd.includes('id -u')) { const e = new Error('sh: 1: sudo: not found'); e.status = 127; throw e; }
89
+ return { ok: true, stdout: '' };
90
+ };
91
+ const deps = {
92
+ apply: true, privileged: true, log: () => {}, sleep: async () => {},
93
+ db: { query: async () => ({ rows: [] }) },
94
+ provisioning: {
95
+ setInstanceStatus: async (_db, _id, s) => { statuses.push(s); }, recordEvent: async () => {},
96
+ recordDnsNote: async () => {}, clearReleasedDomains: async () => {},
97
+ },
98
+ exec,
99
+ writeFile: (p) => { writes.push(p); return { ok: true }; },
100
+ doApi: null,
101
+ checkDnsTokenScope: async () => ({ reachable: true, zone: 'example.com' }),
102
+ checkCaddyWiring: () => null,
103
+ getGithubToken: async () => null,
104
+ };
105
+ return { deps, cmds, writes, statuses, ran: (re) => cmds.some((c) => re.test(c)) };
106
+ }
107
+
108
+ test('provision: a plane that cannot give the child its own account is refused before a single write', async () => {
109
+ const h = harness({ noRoot: true });
110
+ const r = await P.provisionInstance(coTenant, h.deps);
111
+ assert.equal(r.ok, false);
112
+ assert.match(r.error, /^Account preflight failed: /);
113
+ assert.match(r.error, /one uid/);
114
+ assert.equal(h.writes.length, 0, 'nothing written — not web.env, not the unit');
115
+ assert.ok(!h.ran(/useradd/), 'the useradd that cannot work is never attempted');
116
+ assert.ok(!h.ran(/createdb/), 'no database either — the refusal precedes every mutation');
117
+ assert.deepEqual(h.statuses, [], 'the instance never flips to provisioning');
118
+ });
119
+
120
+ test('provision: the probe runs BEFORE step 1c, so the refusal replaces the crash rather than following it', async () => {
121
+ const h = harness({ noRoot: true });
122
+ await P.provisionInstance(coTenant, h.deps);
123
+ assert.equal(h.cmds.filter((c) => c.includes('id -u')).length, 1, 'asked once');
124
+ assert.ok(h.cmds[h.cmds.length - 1].includes('id -u'), 'the probe is the last thing the box was asked');
125
+ });
126
+
127
+ test('provision: a box where the runner reaches root is unchanged — the live server still provisions', async () => {
128
+ const h = harness();
129
+ const r = await P.provisionInstance(coTenant, h.deps);
130
+ assert.equal(r.ok, true);
131
+ assert.ok(h.ran(/useradd --system/), 'step 1c still creates the per-instance account');
132
+ assert.ok(h.ran(/createdb mercury/));
133
+ const idx = (re) => h.cmds.findIndex((c) => re.test(c));
134
+ assert.ok(idx(/id -u.*= 0/) < idx(/useradd --system/), 'the probe precedes the account it guards');
135
+ });