Files
aia-website/www/tools/deploy-portal-resources.test.mjs

93 lines
6.9 KiB
JavaScript

import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
import test from 'node:test';
import { prepareResourcePlan, serializeResourcePlan } from './deploy-portal-resources.mjs';
const digest = (value) => createHash('sha256').update(value).digest('hex');
const sha = 'a'.repeat(40);
const scriptSource = path.join(path.dirname(fileURLToPath(import.meta.url)), 'portal-resource-install.sh');
async function fixture(prefix = 'aia-resource-plan-') {
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
const privateRoot = path.join(root, 'private');
const hubfs = path.join(privateRoot, 'hubfs');
const stage = path.join(root, 'staging', `portal-resources-${sha}`);
await fs.mkdir(hubfs, { recursive: true });
await fs.mkdir(path.join(root, 'secure-resources', 'staff'), { recursive: true });
await fs.mkdir(path.join(root, 'secure-resources', 'management'), { recursive: true });
return { root, privateRoot, hubfs, stage };
}
async function writeApprovedResource(privateRoot, hubfs, bytes = Buffer.from('synthetic non-phi document')) {
const localFile = 'a'.repeat(64);
await fs.writeFile(path.join(hubfs, localFile), bytes, { mode: 0o600 });
await fs.writeFile(path.join(privateRoot, 'manifest.json'), JSON.stringify({ resources: [{
classification: 'STAFF-INTERNAL', deliveryPath: '/staff-resources/guide.pdf', mappingApproved: true, accessPolicyApproved: true, rewriteApproved: true, destinationInstalled: false, destinationHashVerified: false, destinationPermissionsVerified: false, destinationAccessVerified: false, localFile, sha256: digest(bytes), size: bytes.length,
}] }));
return { localFile, bytes };
}
test('resource plan copies only hash-verified approved protected resources', async () => {
const { root, privateRoot, hubfs, stage } = await fixture();
try {
const { localFile, bytes } = await writeApprovedResource(privateRoot, hubfs);
const plan = await prepareResourcePlan(privateRoot, stage, sha);
assert.equal(plan.files.length, 1);
assert.equal((await fs.readFile(path.join(stage, 'staff', localFile))).toString(), bytes.toString());
assert.match(serializeResourcePlan(plan), /^AIA_PORTAL_RESOURCE_PLAN_V1\nsourceCommit=a{40}\nfile\tstaff\tguide\.pdf\t/);
await fs.writeFile(path.join(privateRoot, 'manifest.json'), JSON.stringify({ resources: [{ classification: 'STAFF-INTERNAL', deliveryPath: '/staff-resources/../bad', mappingApproved: true, accessPolicyApproved: true, rewriteApproved: true, localFile, sha256: digest(bytes), size: bytes.length }] }));
await assert.rejects(prepareResourcePlan(privateRoot, path.join(root, 'second'), sha), /approved for installation/);
} finally { await fs.rm(root, { recursive: true, force: true }); }
});
test('one verified source may be installed into both protected role namespaces', async () => {
const { root, privateRoot, hubfs, stage } = await fixture();
try {
const localFile = 'b'.repeat(64), bytes = Buffer.from('synthetic shared non-phi document');
await fs.writeFile(path.join(hubfs, localFile), bytes, { mode: 0o600 });
const mapping = (role) => ({ role, classification: role === 'staff' ? 'STAFF-INTERNAL' : 'MANAGEMENT-INTERNAL', deliveryPath: `/${role}-resources/shared.pdf`, mappingApproved: true, accessPolicyApproved: true, rewriteApproved: true, destinationInstalled: false, destinationHashVerified: false, destinationPermissionsVerified: false, destinationAccessVerified: false });
await fs.writeFile(path.join(privateRoot, 'manifest.json'), JSON.stringify({ resources: [{ classification: 'UNRESOLVED', installMappings: [mapping('staff'), mapping('management')], localFile, sha256: digest(bytes), size: bytes.length }] }));
const plan = await prepareResourcePlan(privateRoot, stage, sha);
assert.deepEqual(plan.files.map(({ role }) => role), ['management', 'staff']);
assert.equal((await fs.readFile(path.join(stage, 'staff', localFile))).toString(), bytes.toString());
assert.equal((await fs.readFile(path.join(stage, 'management', localFile))).toString(), bytes.toString());
} finally { await fs.rm(root, { recursive: true, force: true }); }
});
test('portable shell installer executes the reviewed staged-plan interface with synthetic files', async () => {
const { root, privateRoot, hubfs, stage } = await fixture('aia-portal-install-');
try {
const { bytes } = await writeApprovedResource(privateRoot, hubfs);
const plan = await prepareResourcePlan(privateRoot, stage, sha);
await fs.mkdir(path.join(stage, 'management'), { recursive: true });
const planFile = path.join(root, 'approved-plan.tsv');
await fs.writeFile(planFile, serializeResourcePlan(plan), { mode: 0o600 });
const fakeBin = path.join(root, 'fake-bin');
const log = path.join(root, 'ownership.log');
await fs.mkdir(fakeBin);
await fs.writeFile(path.join(fakeBin, 'id'), '#!/bin/sh\n[ "$1" = -u ] && { echo 0; exit 0; }\nexec /usr/bin/id "$@"\n', { mode: 0o755 });
await fs.writeFile(path.join(fakeBin, 'getent'), '#!/bin/sh\n[ "$1" = group ] && [ "$2" = nginx ] && { echo "nginx:x:999:"; exit 0; }\nexec /run/current-system/sw/bin/getent "$@"\n', { mode: 0o755 });
await fs.writeFile(path.join(fakeBin, 'install'), `#!/bin/sh\nprintf '%s\\n' "$*" >> ${JSON.stringify(log)}\nwhile [ "$#" -gt 0 ]; do\n case "$1" in -o|-g|-m) shift 2 ;; *) break ;; esac\ndone\nexec /usr/bin/install -m 0640 "$@"\n`, { mode: 0o755 });
const script = path.join(root, 'portal-resource-install.sh');
const source = await fs.readFile(scriptSource, 'utf8');
assert.ok(!source.includes('node '));
await fs.writeFile(script, source.replace('ROOT=/srv/www/azinstitute4autism.com', `ROOT=${root}`), { mode: 0o755 });
const result = spawnSync('/bin/sh', [script, stage, planFile], { encoding: 'utf8', env: { ...process.env, PATH: process.env.PATH, AIA_PORTAL_INSTALL_TEST_PATH: fakeBin } });
assert.equal(result.status, 0, result.stderr);
assert.equal((await fs.readFile(path.join(root, 'secure-resources/staff/guide.pdf'))).toString(), bytes.toString());
assert.match(await fs.readFile(log, 'utf8'), /-o root -g nginx -m 0640/);
const nonRoot = spawnSync('/bin/sh', [script, stage, planFile], { encoding: 'utf8', env: { ...process.env, PATH: process.env.PATH } });
assert.notEqual(nonRoot.status, 0);
assert.match(nonRoot.stderr, /requires root/);
await fs.writeFile(path.join(stage, 'staff', 'unexpected.pdf'), 'unexpected');
const rejected = spawnSync('/bin/sh', [script, stage, planFile], { encoding: 'utf8', env: { ...process.env, PATH: process.env.PATH, AIA_PORTAL_INSTALL_TEST_PATH: fakeBin } });
assert.notEqual(rejected.status, 0);
assert.match(rejected.stderr, /Unexpected staged resource/);
} finally { await fs.rm(root, { recursive: true, force: true }); }
});