Mã nguồn đã công bố
Tải mã nguồn dạng ZIP
CH-J Server Manager
Duyệt thư mục và tệp của một bản phát hành ứng dụng cụ thể.
CH-J / Server Manager
368 tệp
core-20261017T201717Z-057348c4a70f
Ngày thêm: Chưa ghi nhận ngày · Ngày dựng:
1
"use strict";3
// System Monitor + chj.users on ONE isolated Linux test VM over real SSH (key login, sudo with password).4
// Never point this at a production server: it creates/deletes a swap file, a test account and changes sshd policy5
// (always reverted). Environment: CHJ_VM_HOST, CHJ_VM_FINGERPRINT (SHA256:...), CHJ_VM_KEY (private key path),6
// CHJ_VM_USER (default ai) and CHJ_VM_SUDO_PASSWORD or CHJ_VM_SUDO_PASSWORD_FILE + CHJ_VM_SUDO_PASSWORD_LINE7
// ("name = value" line read at runtime, never printed).8
const test = require("node:test");9
const assert = require("node:assert/strict");10
const fs = require("node:fs");11
const os = require("node:os");12
const path = require("node:path");13
const { execFileSync, spawnSync } = require("node:child_process");14
const { SessionManager } = require("../src/main/sessions/sessionManager");15
const { PrivilegedExecutor } = require("../src/main/sessions/privilegedExec");16
const { VirtualMemoryService } = require("../src/main/system/virtualMemoryService");17
const { UserAdminService, createUserAdminApi } = require("../src/main/users/userAdminService");18
const { detectPlatform } = require("../src/main/sessions/platformDetect");20
const env = process.env;21
const enabled = Boolean(env.CHJ_VM_HOST && env.CHJ_VM_FINGERPRINT && env.CHJ_VM_KEY && (env.CHJ_VM_SUDO_PASSWORD || env.CHJ_VM_SUDO_PASSWORD_FILE));22
const skip = enabled ? false : "Set CHJ_VM_* to run against an isolated Linux test VM.";23
const sudoPassword = !enabled ? "" : env.CHJ_VM_SUDO_PASSWORD24
|| fs.readFileSync(env.CHJ_VM_SUDO_PASSWORD_FILE, "utf8").split("\n")[Number(env.CHJ_VM_SUDO_PASSWORD_LINE || 1) - 1].split("=").slice(1).join("=").trim();25
const vmUser = env.CHJ_VM_USER || "ai";26
const host = env.CHJ_VM_HOST;27
const SWAP = "/swapfile-chjvmtest";28
const USER = "chjvmtest";29
let keyDir; let knownHosts;31
function keygen(name) {32
const file = path.join(keyDir, name);33
execFileSync("ssh-keygen", ["-q", "-t", "ed25519", "-N", "", "-C", `chj-vm-${name}`, "-f", file]);34
return { privatePath: file, publicKey: fs.readFileSync(`${file}.pub`, "utf8").trim() };35
}36
function remote(command) {37
return execFileSync("ssh", ["-i", env.CHJ_VM_KEY, "-o", "BatchMode=yes", "-o", `UserKnownHostsFile=${knownHosts}`, "-o", "StrictHostKeyChecking=yes", `${vmUser}@${host}`, command], { encoding: "utf8" }).trim();38
}39
function canLogin(user, key, address = host) {40
return spawnSync("ssh", ["-i", key, "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", "PreferredAuthentications=publickey", "-o", `UserKnownHostsFile=${knownHosts}`, "-o", "StrictHostKeyChecking=yes", "-o", "HostKeyAlias=" + host, "-o", "ConnectTimeout=8", `${user}@${address}`, "exit 0"], { encoding: "utf8" }).status === 0;41
}43
test("System Monitor and chj.users on an isolated Linux test VM", { skip, timeout: 1200000 }, async (t) => {44
keyDir = fs.mkdtempSync(path.join(os.tmpdir(), "chj-vm-"));45
knownHosts = path.join(keyDir, "known_hosts");46
execFileSync("sh", ["-c", `ssh-keyscan -t ed25519 ${host} > '${knownHosts}' 2>/dev/null`]);47
assert.equal(execFileSync("ssh-keygen", ["-lf", knownHosts], { encoding: "utf8" }).split(" ")[1], env.CHJ_VM_FINGERPRINT, "documented host key");48
const hostKeys = new Map();49
const profileService = {50
get: () => ({ id: "vm", label: "vm", host, port: 22, username: vmUser, authMethod: "privateKey", privateKeyPath: env.CHJ_VM_KEY }),51
getHostKey: (h, p) => hostKeys.get(`${h}:${p}`) || null, trustHostKey: (h, p, fp) => hostKeys.set(`${h}:${p}`, fp), getStoredPassword: () => "", markUsed() {}52
};53
const manager = new SessionManager({ profileService });54
const latency = new Promise((resolve) => manager.on("state", (state) => { if (state.latency?.measuredAt) resolve(state.latency); }));55
try { await manager.connect({ sessionId: "vm", profileId: "vm" }); }56
catch (error) {57
if (error.code !== "HOST_KEY_UNKNOWN") throw error;58
assert.equal(`SHA256:${Buffer.from(error.fingerprint, "hex").toString("base64").replace(/=+$/, "")}`, env.CHJ_VM_FINGERPRINT, "app sees the documented host key");59
manager.trustPending({ sessionId: "vm", profileId: "vm", fingerprint: error.fingerprint });60
await manager.connect({ sessionId: "vm", profileId: "vm" });61
}62
const executor = new PrivilegedExecutor({ sessionManager: manager });63
const vm = new VirtualMemoryService({ sessionManager: manager, privilegedExecutor: executor });64
const api = createUserAdminApi(new UserAdminService({ sessionManager: manager, privilegedExecutor: executor, detectPlatform }));65
const auth = { sudoPassword };66
// systemd keeps the user manager alive for a few seconds after logout; CH-J correctly refuses USERS_USER_HAS_PROCESSES.67
async function deleteUser() {68
for (let attempt = 0; ; attempt += 1) {69
try { return await api.manage("vm", { action: "delete", username: USER, removeHome: true, ...auth }); }70
catch (error) { if (error.code !== "USERS_USER_HAS_PROCESSES" || attempt >= 30) throw error; await new Promise((resolve) => setTimeout(resolve, 1000)); }71
}72
}73
const aiKey = env.CHJ_VM_KEY;74
let fstabBefore = null;75
t.after(async () => {76
// Best-effort cleanup of everything this test may have created.77
for (const step of [78
() => vm.manage("vm", { action: "delete", path: SWAP, ...auth }),79
() => api.sshPolicy("vm", { username: "root", policy: {}, ...auth }),80
() => api.sshPolicy("vm", { username: USER, policy: {}, ...auth }),81
() => deleteUser()82
]) { try { await step(); } catch {} }83
try {84
const detail = await api.detail("vm", { username: "root", ...auth });85
for (const file of detail.keys?.files || []) for (const entry of [...file.entries].reverse()) {86
if (entry.comment === "chj-vm-root") await api.keys("vm", { action: "remove", username: "root", path: file.path, line: entry.line, fingerprint: entry.fingerprint, ...auth });87
}88
} catch {}89
await manager.disconnectAll("test");90
fs.rmSync(keyDir, { recursive: true, force: true });91
assert.ok(canLogin(vmUser, aiKey) || true);92
});94
await t.test("platform detection and metrics", async () => {95
const metrics = await manager.readSystemMetrics("vm");96
t.diagnostic(`${metrics.platform} | kernel ${metrics.kernel} | RAM ${Math.round(metrics.memory.total / 1048576)} MiB`);97
assert.equal(metrics.os, "linux");98
assert.equal(metrics.loadAverageSupported, true);99
assert.ok(metrics.cpu.logicalCores >= 1 && metrics.uptimeSeconds > 0);100
const d = metrics.memoryDetail;101
assert.ok(Math.abs(d.used + d.free + d.buffers + d.cached + d.slabReclaimable - d.total) < 1048576, "RAM slices add up without double counting");102
assert.ok(metrics.disk.total > 0 && metrics.disk.mount === "/");103
assert.ok(metrics.network.interfaces.some((entry) => entry.state === "up"));104
});106
await t.test("ICMP ping and SSH RTT", async () => {107
const value = await Promise.race([latency, new Promise((resolve) => setTimeout(() => resolve(null), 40000))]);108
t.diagnostic(`ping ${value?.pingMs} ms (${value?.pingStatus}), ssh ${value?.sshRttMs} ms (${value?.sshRttMethod})`);109
assert.ok(value?.sshRttMs > 0);110
assert.ok(value.pingMs > 0 || ["timeout", "unreachable", "denied"].includes(value.pingStatus));111
});113
await t.test("swap file lifecycle on the real root file system", async () => {114
const before = await vm.read("vm", { privileged: true, ...auth });115
if (before.swapAreas.some((entry) => entry.path === SWAP)) { await vm.manage("vm", { action: "delete", path: SWAP, ...auth }); t.diagnostic("removed leftover test swap file from an earlier run"); }116
fstabBefore = remote("cksum /etc/fstab");117
t.diagnostic(`root fs: ${JSON.stringify(before.locations || before.fileSystems || null)}`);118
assert.equal(before.platform, "linux"); assert.equal(before.canManage, true);119
await assert.rejects(() => vm.manage("vm", { action: "create", path: "/etc/passwd", sizeMiB: 64, persist: false, ...auth }), { code: "VM_SWAP_PATH_INVALID" });120
await assert.rejects(() => vm.manage("vm", { action: "create", path: SWAP, sizeMiB: 128, persist: false, sudoPassword: "wrong-pass" }), { code: /SUDO_PASSWORD_INCORRECT/ });121
let result = await vm.manage("vm", { action: "create", path: SWAP, sizeMiB: 128, priority: 5, persist: true, ...auth });122
assert.ok(result.active.some((area) => area.path === SWAP && area.priority === 5), "created + active");123
let state = await vm.read("vm", { privileged: true, ...auth });124
const area = state.swapAreas.find((entry) => entry.path === SWAP);125
assert.equal(area.persistent, true); assert.equal(area.management, "managed");126
t.diagnostic(`fs ${area.fsType}`);127
result = await vm.manage("vm", { action: "resize", path: SWAP, sizeMiB: 192, priority: 5, ...auth });128
assert.ok(result.active.some((entry) => entry.path === SWAP && Math.abs(entry.size - 192 * 1048576) <= 65536));129
result = await vm.manage("vm", { action: "priority", path: SWAP, priority: 7, ...auth });130
assert.ok(result.active.some((entry) => entry.path === SWAP && entry.priority === 7));131
result = await vm.manage("vm", { action: "deactivate", path: SWAP, ...auth });132
assert.equal(result.active.some((entry) => entry.path === SWAP), false);133
result = await vm.manage("vm", { action: "activate", path: SWAP, priority: 3, ...auth });134
assert.ok(result.active.some((entry) => entry.path === SWAP && entry.priority === 3));135
const concurrent = vm.manage("vm", { action: "persist", path: SWAP, persist: false, ...auth });136
await assert.rejects(() => vm.manage("vm", { action: "delete", path: SWAP, ...auth }), { code: "VM_BUSY" });137
await concurrent;138
await vm.manage("vm", { action: "delete", path: SWAP, ...auth });139
state = await vm.read("vm", { privileged: true, ...auth });140
assert.equal(state.swapAreas.some((entry) => entry.path === SWAP), false, "swap file removed");141
assert.equal(remote("cksum /etc/fstab"), fstabBefore, "fstab back to original content");142
});144
await t.test("chj.users: account, Ed25519 key and per-user SSH policy", async () => {145
const privileges = await api.privileges("vm");146
t.diagnostic(`privileges ${privileges.mode}`);147
assert.equal(privileges.platform, "linux");148
const inventory = await api.inventory("vm", auth);149
assert.ok(inventory.users.some((account) => account.username === "root"));150
if ((await api.inventory("vm", auth)).users.some((account) => account.username === USER)) await deleteUser();151
await api.manage("vm", { action: "create", username: USER, password: "ChjVm-Test-Pass-9", comment: "CH-J VM test", ...auth });152
const key = keygen("user");153
await api.keys("vm", { action: "add", username: USER, publicKey: key.publicKey, ...auth });154
assert.ok(canLogin(USER, key.privatePath), "new user logs in with the added Ed25519 key");155
let result = await api.sshPolicy("vm", { username: USER, policy: { access: "deny" }, ...auth });156
assert.equal(canLogin(USER, key.privatePath), false, "access denied by CH-J policy");157
assert.ok(canLogin(vmUser, aiKey), "session account unaffected");158
result = await api.sshPolicy("vm", { username: USER, policy: {}, ...auth });159
assert.ok(canLogin(USER, key.privatePath), "inherit restores access");160
await deleteUser();161
assert.equal((await api.inventory("vm", auth)).users.some((account) => account.username === USER), false);162
});164
await t.test("chj.users: root keys and PermitRootLogin via sudo; root stays protected", async () => {165
await assert.rejects(() => api.manage("vm", { action: "delete", username: "root", ...auth }), { code: "USERS_PROTECTED_USER" });166
await assert.rejects(() => api.manage("vm", { action: "setShell", username: "root", shell: "/bin/sh", ...auth }), { code: "USERS_PROTECTED_USER" });167
const rootKey = keygen("root");168
try { await api.keys("vm", { action: "add", username: "root", publicKey: rootKey.publicKey, ...auth }); }169
catch (error) {170
// Proxmox: /root/.ssh/authorized_keys is a symlink into /etc/pve; CH-J must refuse rather than follow it.171
if (error.code !== "USERS_KEYS_FILE_UNMANAGEABLE") throw error;172
t.diagnostic(`root keys refused: ${error.message}`);173
await api.sshPolicy("vm", { username: "root", policy: { permitRootLogin: "no" }, ...auth });174
const effective = (await api.detail("vm", { username: "root", ...auth }));175
t.diagnostic(`policy after no: ${JSON.stringify(effective.sshPolicy?.managed || effective.ssh?.policy || null)}`);176
assert.ok(canLogin(vmUser, aiKey), "session account unaffected");177
await api.sshPolicy("vm", { username: "root", policy: {}, ...auth });178
return;179
}180
await assert.rejects(() => api.sshPolicy("vm", { username: "root", policy: { password: "yes" }, ...auth }), { code: "USERS_ROOT_PASSWORD_CONFIRMATION_REQUIRED" });181
await api.sshPolicy("vm", { username: "root", policy: { permitRootLogin: "prohibit-password" }, ...auth });182
const allowed = canLogin("root", rootKey.privatePath);183
t.diagnostic(`root key login with prohibit-password: ${allowed}`);184
if (!allowed) {185
// OpenSSH without PAM (Alpine) refuses every login, keys included, for an account whose password field starts with "!".186
const rootAccount = (await api.inventory("vm", auth)).users.find((account) => account.username === "root");187
t.diagnostic(`root passwordState=${rootAccount.passwordState} locked=${rootAccount.locked}`);188
assert.ok(rootAccount.locked || rootAccount.passwordState === "none", "root key login allowed by PermitRootLogin prohibit-password (unless the root account itself is locked)");189
}190
await api.sshPolicy("vm", { username: "root", policy: { permitRootLogin: "no" }, ...auth });191
assert.equal(canLogin("root", rootKey.privatePath), false, "PermitRootLogin no blocks root");192
assert.ok(canLogin(vmUser, aiKey), "session account unaffected");193
await api.sshPolicy("vm", { username: "root", policy: {}, ...auth });194
const detail = await api.detail("vm", { username: "root", ...auth });195
for (const file of detail.keys.files) for (const entry of [...file.entries].reverse()) {196
if (entry.comment === "chj-vm-root") await api.keys("vm", { action: "remove", username: "root", path: file.path, line: entry.line, fingerprint: entry.fingerprint, ...auth });197
}198
assert.equal(canLogin("root", rootKey.privatePath), false, "removed root key no longer works");199
});200
});SHA-256
d4bdf6efac3fefdae0f0602a48edf894660563898a622c690d45ea3d035836bfCH-J Proprietary Software License 1.14 · Mã nguồn được cung cấp theo CH-J Proprietary Software License 1.14. Việc công khai mã nguồn không thay đổi các điều khoản giấy phép và không cấp thêm quyền nào.
SHA-256 của tệp nén
cf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3