Files
nodeagent/tests/unit/install-zerotrust.test.mjs
T
jkooe 8b97fc6106 feat(v24): 零信任优先安装 —— 拿到控制端公钥就走 ed25519,否则明确告警
## 为什么不是直接改 auth_mode 默认值
ed25519 的**私钥必须在控制端**,被控端安装时拿不到公钥。硬改默认值会让新装的控制端
连不上(还得手工改配置回退)—— 比「有条件就走零信任 + 无条件时明确告警」更糟。
所以这里的"默认"是**优先顺序**,不是死值。

## 做法
install.ps1 新增 -ClientAclFile(默认探测脚本同目录 client-acl.json,内容即
`nodeagent keygen` 打印的那一条 acl.clients JSON):
- 有且含 client_id+pubkey → auth_mode=ed25519,写 acl{default_effect=deny, clients=[…]},
  绿色确认 + 打印 --auth-mode ed25519 连接示例
- 无 / 缺字段 → 保持 psk,但输出**醒目黄色告警**:说明 client_id 可自报的固有风险
  + 三步切换指引(keygen → 存 client-acl.json → 重跑 install.cmd)
install.cmd 无需改动(install.ps1 自行探测)。

## 验证(真机,不是推理)
- **PowerShell 5.1 语法校验**:PARSE_OK,BOM 保留(EF BB BF)
- **等效逻辑三 CASE**:
  A 有文件 → auth_mode=ed25519 + default_effect=deny + clients 完整(allow/deny/note 保留)
  B 无文件 → auth_mode=psk 且**不写 acl 字段**
  C 缺 pubkey → WARN 并回退 psk(不写半截 ACL)
- 编码守卫:ps1-encoding 24 例全过(BOM + 无 U+FFFD)
- 新增 tests/unit/install-zerotrust.test.mjs(8 例源码断言:参数、探测路径、
  useZeroTrust 条件化、deny 兜底、字段校验、告警文案、连接示例、ConvertTo-Json -Depth 6)

## 文档
docs/SECURITY.md 新增 §4.7 并更新 §5 清单 —— 把 v23 的三项(握手封禁/会话上限/
安装期密钥)与本次 ed25519 默认化一并标为已完成,只留「审计链外部锚定」待做。

## 数据
单测 250 → **258** 全绿;7 包构建通过。
2026-10-09 23:53:28 +08:00

58 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
/**
* 零信任优先安装(v24)的**源码断言**。
*
* 为什么用源码断言:install.ps1 是 5.1 脚本,Node 侧无法直接执行它;
* 而这里要守的是「关键决策逻辑不许被误删」—— 一旦有人重构掉 acl 写入或
* 告警文案,测试立刻红。运行时的正确性已在真机用等效片段验证过
* (CASE A ed25519+deny / CASE B psk 无 acl / CASE C 缺字段回退 psk)。
*/
const ROOT = fileURLToPath(new URL('../../', import.meta.url));
const ps1 = readFileSync(join(ROOT, 'scripts', 'install.ps1'), 'utf8');
test('install.ps1 有 ClientAclFile 参数(零信任入口)', () => {
assert.match(ps1, /\[string\]\$ClientAclFile/, '缺少 -ClientAclFile 参数');
});
test('install.ps1 默认探测脚本同目录的 client-acl.json', () => {
assert.match(ps1, /client-acl\.json/, '缺少默认探测路径');
assert.match(ps1, /\$scriptDir/, '应用 $scriptDir 兜底(-Command 调用时 $PSScriptRoot 为空)');
});
test('install.ps1 有 useZeroTrust 判定且 auth_mode 条件化', () => {
assert.match(ps1, /\$useZeroTrust\s*=/, '缺少 useZeroTrust 判定');
assert.match(ps1, /auth_mode\s*=\s*if \(\$useZeroTrust\)/, 'auth_mode 必须按零信任开关条件化');
});
test('零信任分支写入 acl 且 default_effect=deny(兜底拒绝)', () => {
assert.match(ps1, /default_effect\s*=\s*"deny"/, 'default_effect 必须是 deny(未登记一律拒)');
assert.match(ps1, /clients\s*=\s*@\(\$aclEntry\)/, '缺少 clients 数组写入');
});
test('缺 client_id/pubkey 时忽略并回退(不写半截 ACL)', () => {
assert.match(ps1, /-not \$aclEntry\.client_id -or -not \$aclEntry\.pubkey/, '缺少字段校验');
assert.match(ps1, /\$aclEntry = \$null/, '校验失败必须置空以回退');
});
test('psk 路径必须给出醒目安全告警(不能静默降级)', () => {
assert.match(ps1, /SECURITY WARNING/, 'psk 分支缺少醒目告警');
assert.match(ps1, /SELF-REPORTED/, '告警须说明 client_id 可自报这一固有问题');
assert.match(ps1, /nodeagent keygen/, '告警须给出切换命令');
assert.match(ps1, /Yellow/, '告警须用醒目颜色(Yellow)');
});
test('零信任路径给出正确的连接指引(--id + --auth-mode)', () => {
assert.match(ps1, /--auth-mode ed25519/, '缺少 ed25519 连接示例');
assert.match(ps1, /Zero-trust is ON/, '缺少启用确认输出');
});
test('安装写入的 JSON 用 -Depth 6(acl 嵌套不会被截断)', () => {
assert.match(ps1, /ConvertTo-Json -Depth 6/, 'acl 是嵌套结构,Depth 不足会被截成字符串');
});