git mv packages/business -> packages/organization (@mosaic/organization). The old package stays as one-line re-export shims of each moved module, with src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs forwarding to the moved executable. BusinessError is OrganizationConfigError (one constructor, name kept BusinessError); OrganizationError stays distinct. The bus adapter gains organization-context.mjs (busOrganization), with business.mjs (busBusiness) as the same function. Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure walker (organization-imports.mjs, both roots required) move to the new path. v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage A contract tests stay where they were and pass as before.
84 lines
5.1 KiB
JavaScript
84 lines
5.1 KiB
JavaScript
// Row 210 (#1707, design D3 and D6): the address checks and the argv allowlist. Row 234 (#1740, r6 S1)
|
|
// moved the registry and the grants to bus store records, so the business file refuses them.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { BusinessError, validateBusinessDocument } from "../src/index.mjs";
|
|
import { HOST_KEY as HOST_KEY_PATTERN, REFUSED_COMMANDS, USER, checkAddress, checkCommands, commandAllowed, ownMachine, validArgv } from "../src/hosts.mjs";
|
|
import { businessDoc, rolesCopy, tmp, tokenFile, FAR_DATE } from "./helpers.mjs";
|
|
|
|
const HOST_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOv6Rq3Mvo9w2j3eYvYbqX0J1pJ0x9m8Zr0w1kJ8p0Qn";
|
|
|
|
function refuses(fn, pattern) {
|
|
assert.throws(fn, (error) => {
|
|
assert.ok(error instanceof BusinessError, `expected BusinessError, got ${error}`);
|
|
assert.match(error.message, pattern);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// The fixture with coder's definition declaring the host credential and no hosts.
|
|
function check(mutate, { declare = true } = {}) {
|
|
const root = tmp();
|
|
const rolesDir = rolesCopy(root);
|
|
if (declare) {
|
|
const file = join(rolesDir, "coder.json");
|
|
const coder = JSON.parse(readFileSync(file, "utf8"));
|
|
coder.credentials.push({ service: "host" });
|
|
writeFileSync(file, JSON.stringify(coder));
|
|
}
|
|
const doc = businessDoc(root);
|
|
mutate?.(doc, root);
|
|
return validateBusinessDocument(doc, join(root, "businesses", "acme.json"), { rolesDir });
|
|
}
|
|
|
|
// Row 234 (#1740, r6 S1): hosts and grants are bus store records, so the business file refuses both keys
|
|
// and any host.<id> key reference. The control loads.
|
|
test("S1: the business file refuses hosts at the top level and in a role, and host.<id> references", () => {
|
|
assert.equal("hosts" in check(), false);
|
|
assert.equal("hosts" in check().roles.coder, false);
|
|
const entry = { address: "10.1.1.53", port: 22, user: "localadmin", hostKeys: [HOST_KEY] };
|
|
refuses(() => check((d) => { d.hosts = { web1: entry }; }), /acme\.json hosts lives in the bus store/);
|
|
refuses(() => check((d) => { d.hosts = {}; }), /hosts lives in the bus store/);
|
|
refuses(() => check((d) => { d.roles.coder.hosts = { web1: { commands: [["systemctl", "status"]] } }; }), /roles\.coder\.hosts lives in the bus store/);
|
|
refuses(() => check((d) => { d.roles.coder.hosts = {}; }, { declare: false }), /roles\.coder\.hosts lives in the bus store/);
|
|
const key = (d, root) => { d.roles.coder.credentials["host.web1"] = { file: tokenFile(join(root, "secrets"), "coder-host.key"), rotateBy: FAR_DATE }; };
|
|
refuses(() => check(key), /host\.web1/);
|
|
});
|
|
|
|
test("T1: address, user and host key checks, which the enrollment raise uses", () => {
|
|
const own = ["127.0.0.2", "0.0.0.0", "::1", "::", "0:0:0:0:0:0:0:1", "::ffff:127.0.0.1", "::ffff:0.0.0.0", "localhost", "a.localhost",
|
|
"127.1", "2130706433", "0177.0.0.1", "0x7f.1", "bad_name", "a..b", "-a.b", "fe80::1%eth0", 7];
|
|
for (const address of own) refuses(() => checkAddress(address, "grant.address"), /grant\.address/);
|
|
for (const address of ["10.1.1.53", "host.example", "2001:db8::1"]) assert.equal(checkAddress(address, "grant.address"), address);
|
|
assert.equal(ownMachine("::ffff:7f00:1"), true);
|
|
assert.equal(ownMachine("::ffff:10.0.0.1"), false);
|
|
for (const user of ["Root", "a b", ""]) assert.equal(USER.test(user), false);
|
|
assert.equal(USER.test("localadmin"), true);
|
|
for (const key of [`${HOST_KEY} root@web1`, "ssh-dss AAAA"]) assert.equal(HOST_KEY_PATTERN.test(key), false);
|
|
assert.equal(HOST_KEY_PATTERN.test(HOST_KEY), true);
|
|
});
|
|
|
|
test("T26: no wildcard and no shell, runner or interpreter as a prefix", () => {
|
|
const at = /grant\.commands/;
|
|
for (const commands of ["any", "*", {}, [], [[]], [[7]], [["ls", ""]], [["ls"], ["ls"]], [["ls", "*"]], [["ls", "a?"]], [["ls", "[a]"]], [["bin/ls"]], [["ls\0"]]])
|
|
refuses(() => checkCommands(commands, "grant.commands"), at);
|
|
const names = [...REFUSED_COMMANDS, "python", "python3", "python3.12", "awk", "gawk", "mawk", "nawk", "php", "php8.2", "/bin/bash", "/usr/bin/env"];
|
|
for (const name of names) refuses(() => checkCommands([[name, "x"]], "grant.commands"), at);
|
|
for (const name of ["systemctl", "/usr/bin/journalctl", "tee"]) assert.deepEqual(checkCommands([[name]], "grant.commands"), [[name]]);
|
|
});
|
|
|
|
test("D6: argv shape and the literal prefix match", () => {
|
|
assert.equal(validArgv(["systemctl", "status", "nginx"]), true);
|
|
for (const argv of [[], "ls", [""], ["a\0"], [7], ["bin/ls"], Array(257).fill("a"), ["a".repeat(4097)], Array(9).fill("a".repeat(4000))])
|
|
assert.equal(validArgv(argv), false, JSON.stringify(argv).slice(0, 40));
|
|
assert.equal(validArgv(["/usr/bin/ls"]), true);
|
|
const commands = [["systemctl", "status"]];
|
|
assert.equal(commandAllowed(["systemctl", "status"], commands), true);
|
|
assert.equal(commandAllowed(["systemctl", "status", "nginx"], commands), true);
|
|
assert.equal(commandAllowed(["systemctl"], commands), false);
|
|
assert.equal(commandAllowed(["systemctl", "restart", "nginx"], commands), false);
|
|
assert.equal(commandAllowed(["systemctl", "status*"], commands), false);
|
|
});
|