eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1// πŸ€– Hermetic run of deploy/hardening-posture.sh β€” the box-side hardening
2// collector β€” through its documented fixture seams. This exists because the
3// collector shipped fixture-checked but never run, and its first FOUR real-box
4// encounters failed (lost merge #1155, ReadWritePaths namespacing #1159,
5// AF_NETLINK #1171, IAM prefix scope #1176): the parsing was always right and
6// the plumbing always wrong. This test at least pins the whole script path β€”
7// enumerate β†’ probe/merge β†’ sort β†’ output JSON β€” end to end in one process.
8// Requires bash + jq (same toolchain the status-contract fixtures assume).
9// node test/hardening-posture.test.mjs
10import assert from "node:assert/strict";
11import { execFileSync } from "node:child_process";
12import { mkdtempSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
13import { tmpdir } from "node:os";
14import { join } from "node:path";
15
16let failures = 0;
17function test(name, fn) {
18 try {
19 fn();
20 console.log("PASS " + name);
21 } catch (e) {
22 console.log("FAIL " + name + " β€” " + (e && e.message));
23 failures++;
24 }
25}
26
27const script = new URL("../deploy/hardening-posture.sh", import.meta.url).pathname;
28
29// Run the collector against fixtures; return the parsed hardening.json.
30// members: TSV rows; posture: {label: rowObject} self-reports peers published.
31// The default ufw fixture proves "wireguard-only": active, a 22-on-wg0 allow,
32// and NO bare public 22 β€” the shape firewall_ssh_scope requires for a hardened
33// (false) self verdict rather than the loud unknown.
34const UFW_HARDENED = [
35 "Status: active",
36 "",
37 "To Action From",
38 "-- ------ ----",
39 "80,443/tcp ALLOW Anywhere",
40 "22/tcp on wg0 ALLOW Anywhere",
41 "51820/udp ALLOW Anywhere",
42].join("\n");
43
44function collect({ members, posture = {}, ufw = UFW_HARDENED }) {
45 const dir = mkdtempSync(join(tmpdir(), "hardening-test-"));
46 const state = join(dir, "state"), pages = join(dir, "pages"), postures = join(dir, "posture");
47 mkdirSync(state); mkdirSync(pages); mkdirSync(postures);
48 writeFileSync(join(dir, "members.tsv"), members);
49 writeFileSync(join(dir, "router.env"),
50 "LCHAT_MESH_SELF=https://self.example\nLCHAT_BOX_REGION=EU Β· Finland\n");
51 writeFileSync(join(dir, "ufw.txt"), ufw + "\n");
52 writeFileSync(join(dir, "sshd.txt"), "permitrootlogin no\npasswordauthentication no\n");
53 writeFileSync(join(dir, "au.txt"), "active\n");
54 writeFileSync(join(dir, "wg0.txt"), "42: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP>\n");
55 for (const [label, row] of Object.entries(posture)) {
56 writeFileSync(join(postures, label + ".json"), JSON.stringify(row));
57 }
58 execFileSync("bash", [script], {
59 env: {
60 ...process.env,
61 HARDENING_UFW_CMD: `cat ${join(dir, "ufw.txt")}`,
62 HARDENING_SSHD_CMD: `cat ${join(dir, "sshd.txt")}`,
63 HARDENING_SYSTEMCTL_CMD: `cat ${join(dir, "au.txt")}`,
64 HARDENING_IP_CMD: `cat ${join(dir, "wg0.txt")}`,
65 HARDENING_ROUTER_ENV: join(dir, "router.env"),
66 HARDENING_MEMBERS_FILE: join(dir, "members.tsv"),
67 HARDENING_POSTURE_DIR: postures,
68 HARDENING_STATE_DIR: state,
69 HARDENING_PAGES_DIR: pages,
70 HARDENING_NO_UPLOAD: "1",
71 },
72 });
73 return JSON.parse(readFileSync(join(pages, "hardening.json"), "utf8"));
74}
75
76const SELF_ROW = "https://self.example\t\t\t\n";
77const kepler = (probe) => `https://198.51.100.7\tkepler-conducting\tUK Β· London\t${probe}\n`;
78
79test("probe-only peer renders unknowns, sorted by exposure", () => {
80 const out = collect({ members: SELF_ROW + kepler("serving-ssh") });
81 assert.equal(out.boxes.length, 2);
82 const [first, second] = out.boxes;
83 assert.equal(first.label, "kepler-conducting"); // exposed sorts first
84 assert.equal(first.ssh_internet_facing, true);
85 assert.equal(first.source, "probe");
86 assert.equal(first.root_login, "unknown");
87 assert.equal(second.is_self, true);
88 assert.equal(second.ssh_internet_facing, false); // wg up + no public 22 in ufw
89 assert.equal(second.firewall_active, true);
90});
91
92test("fresh peer self-report replaces the probe row's unknowns", () => {
93 const out = collect({
94 members: SELF_ROW + kepler("not-reachable"),
95 posture: {
96 "kepler-conducting": {
97 label: "kepler-conducting", is_self: true, source: "local",
98 ssh_internet_facing: false, detail: "wireguard-only",
99 root_login: "no", password_auth: "no", auto_updates: "active",
100 wireguard: "up", firewall_active: true, region: "UK Β· London",
101 generated: new Date().toISOString().replace(/\.\d+Z$/, "Z"),
102 },
103 },
104 });
105 const peer = out.boxes.find((b) => b.label === "kepler-conducting");
106 assert.equal(peer.source, "self-reported");
107 assert.equal(peer.is_self, false); // reader's perspective wins
108 assert.equal(peer.root_login, "no");
109 assert.equal(peer.wireguard, "up");
110 assert.equal(peer.firewall_active, true);
111 assert.equal(peer.ssh_internet_facing, false);
112 assert.equal(peer.generated, undefined); // stamp stays in posture/, not the page
113});
114
115test("an outside SSH banner vetoes a self-report claiming closed", () => {
116 const out = collect({
117 members: SELF_ROW + kepler("serving-ssh"),
118 posture: {
119 "kepler-conducting": {
120 label: "kepler-conducting", is_self: true, source: "local",
121 ssh_internet_facing: false, detail: "wireguard-only",
122 root_login: "no", password_auth: "no", auto_updates: "active",
123 wireguard: "up", firewall_active: true,
124 generated: new Date().toISOString().replace(/\.\d+Z$/, "Z"),
125 },
126 },
127 });
128 const peer = out.boxes.find((b) => b.label === "kepler-conducting");
129 assert.equal(peer.source, "self-reported");
130 assert.equal(peer.ssh_internet_facing, true); // the banner we saw wins
131 assert.match(peer.detail, /serving SSH/);
132 assert.equal(peer.root_login, "no"); // the rest stays self-reported
133 assert.equal(out.boxes[0].label, "kepler-conducting"); // and it still sorts as exposed
134});
135
136process.exit(failures ? 1 : 0);
137