// #1873 T1 follow-up (org-defscan): a definition that holds a credential the bus holds refuses at // launch (the launch check asks the bus before a contract exists) and at `mosaic bus start`, and the // value reaches no refusal, record or log. import test from 'node:test'; import assert from 'node:assert/strict'; import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { createLauncher } from '../src/launcher.mjs'; import { bootConfig, loadSystem } from '../src/config.mjs'; import { readBootStatus, bootEventsFile } from '../src/boot-status.mjs'; import { hostFile } from '../src/host.mjs'; import { startBroker } from '../../bus/src/runtime.mjs'; import { initializeControl } from '../../processing-control/src/state.mjs'; import { launchLogFile } from '../../seat/src/session.mjs'; import { entryFromTemplate, readTemplate } from '../../organization/src/definitions.mjs'; import { reserveOrg, activateOrg } from '../../organization/src/org-registry.mjs'; import { REPO_ROLES, writeJson } from '../../organization/tests/helpers.mjs'; import { fixture, notifyConfig, tmp, withoutHostCredentials } from './helpers.mjs'; // The fixture bus holds SENTINEL as coder's gitea token. const SENTINEL = 'sk-test-NOT-A-SECRET-1234'; const CLI = fileURLToPath(new URL('../src/cli.mjs', import.meta.url)); function files(dir) { if (!existsSync(dir)) return []; return readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? files(join(dir, e.name)) : e.isFile() ? [join(dir, e.name)] : []); } const withBuilder = (builder) => (doc) => ({ ...doc, definitions: { builder }, roles: { ...doc.roles, coder: { ...doc.roles.coder, definition: 'org:builder' } } }); test('org-defscan: a file edited after start to hold a held credential refuses the next launch of an instance naming it; a template launch still works', async (t) => { const root = tmp(t); const f = fixture(root, 'acme', withBuilder(entryFromTemplate(readTemplate(REPO_ROLES, 'coder')))); const file = join(root, 'config/businesses/acme.json'); writeFileSync(f.doc.roles.coder.credentials.gitea.file, SENTINEL); withoutHostCredentials(f.env, { piKey: true }); initializeControl(f.dataRoot); const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: 'acme', env: f.env }); const bus = await startBroker({ dataRoot: f.dataRoot, businesses: boot.businesses, organization: boot.organization }); t.after(() => bus.close()); // The real host's op path for the scan, over a structured clone as IPC carries it; the fixture // stops at authorization, so "reached authorizeLaunch" is "would have launched". const ops = []; const host = { business: 'acme', beforeClose: () => {}, op: async (x) => { ops.push(x.op); if (x.op === 'identity') return { business: 'acme', role: 'pm' }; if (x.op === 'mayLaunch') return { allow: true }; if (x.op === 'authorizeLaunch') throw Object.assign(Error('fixture authorization stop'), { code: 'fixture-policy' }); if (x.op === 'definitionScan') return structuredClone(bus.definitionScan(structuredClone({ business: x.business, definition: x.definition, entry: x.entry }))); return {}; } }; const launcher = createLauncher({ host, system: JSON.parse(readFileSync(f.config, 'utf8')), env: f.env, bwrap: '/usr/bin/true', log: () => {} }); t.after(() => launcher.close?.()); // Clean: the scan passes and the launch reaches authorization. assert.equal((await launcher.fromSession('fixture-cap', 'coder')).error, 'fixture-policy'); assert.ok(ops.includes('definitionScan')); // Edited by hand while the bus runs. const doc = JSON.parse(readFileSync(file, 'utf8')); doc.definitions.builder.instructions += `\nThe forge token is ${SENTINEL}.\n`; writeJson(file, doc); ops.length = 0; assert.deepEqual(await launcher.fromSession('fixture-cap', 'coder'), { ok: false, error: 'definition-holds-credential' }); assert.ok(!ops.includes('authorizeLaunch'), 'nothing launches'); const event = JSON.parse(readFileSync(launchLogFile(f.dataRoot, 'acme'), 'utf8').trim().split('\n').at(-1)); assert.equal(event.code, 'definition-holds-credential'); assert.equal(event.instance, 'coder'); assert.match(event.why, /coder's definition org:builder holds a credential in instructions/); // A personality field is checked the same way. doc.definitions.builder.instructions = doc.definitions.builder.instructions.replace(SENTINEL, 'REDACTED'); doc.definitions.builder.personality = { summary: 'Plain.', voice: `Signs off with ${SENTINEL}` }; writeJson(file, doc); assert.deepEqual(await launcher.fromSession('fixture-cap', 'coder'), { ok: false, error: 'definition-holds-credential' }); assert.match(JSON.parse(readFileSync(launchLogFile(f.dataRoot, 'acme'), 'utf8').trim().split('\n').at(-1)).why, /in personality\.voice$/); // An instance on a shipped template launches as before, and asks no scan. ops.length = 0; assert.equal((await launcher.fromSession('fixture-cap', 'reviewer')).error, 'fixture-policy'); assert.ok(!ops.includes('definitionScan')); // The value reached no record the bus or the launcher wrote. for (const path of files(f.dataRoot)) assert.ok(!readFileSync(path).includes(SENTINEL), path); }); // A bootstrapped instance with an active organization whose file names org:builder. async function orgFixture(t, builder) { const root = tmp(t, 'o-'); const f = fixture(root, 'acme', withBuilder(builder)); const configDir = join(root, '.config', 'mosaic-boot'); const env = { ...f.env, HOME: root, MOSAIC_CONFIG: join(configDir, 'config.json') }; const bootstrap = spawnSync(join(process.cwd(), 'scripts/bootstrap.sh'), ['--instance', 'boot'], { env, encoding: 'utf8' }); assert.equal(bootstrap.status, 0, bootstrap.stderr); mkdirSync(join(configDir, 'businesses'), { recursive: true, mode: 0o700 }); const file = join(configDir, 'businesses', 'acme.json'); writeFileSync(file, readFileSync(join(root, 'config/businesses/acme.json')), { mode: 0o600 }); writeFileSync(f.doc.roles.coder.credentials.gitea.file, SENTINEL); const dataRoot = join(root, '.mosaic-boot'); const ctx = { configDir, dataRoot, instance: 'boot' }; const org = await reserveOrg(ctx, 'acme'); await activateOrg(ctx, 'acme'); // A notifier config, so a start that passes the load goes on to start the broker. notifyConfig(org.root, 'acme', null); return { env, file, dataRoot, orgRoot: org.root }; } test('org-defscan: mosaic bus start refuses a held credential in a definition, records why, and the value is nowhere', async (t) => { const builder = entryFromTemplate(readTemplate(REPO_ROLES, 'coder')); const o = await orgFixture(t, { ...builder, instructions: `${builder.instructions}\nThe forge token is ${SENTINEL}.\n` }); const run = (...args) => spawnSync(process.execPath, [CLI, ...args], { env: o.env, encoding: 'utf8', timeout: 60000 }); const r = run('bus', 'start', 'acme', '--detach', '--no-launch'); // Should a start get through (it must not), stop it before the fixture's directory goes. if (r.status === 0 || existsSync(hostFile(o.orgRoot))) run('bus', 'stop', 'acme'); assert.equal(r.status, 3, r.stderr); assert.ok(!existsSync(join(o.orgRoot, 'bus', 'broker.sock')), 'no socket'); const s = readBootStatus(o.orgRoot); assert.equal(s.ok, false); assert.equal(s.business, 'acme'); assert.match(s.reason, /definition-holds-credential: definitions\.builder instructions holds a credential this bus holds/); assert.equal(s.lastGood, null, 'the load that the broker then refused is not a good load'); const events = readFileSync(bootEventsFile(o.orgRoot), 'utf8').trim().split('\n').map(JSON.parse); assert.equal(events.at(-1).event, 'bus.boot.refused'); const status = run('bus', 'status', 'acme', '--json'); assert.equal(JSON.parse(status.stdout).boot.ok, false); for (const text of [r.stdout, r.stderr, status.stdout, status.stderr]) assert.ok(!text.includes(SENTINEL)); for (const path of files(o.dataRoot)) assert.ok(!readFileSync(path).includes(SENTINEL), path); });