Commit Graph
5 Commits
Author SHA1 Message Date
chick af738a5bf9 fix(relay): 限额改为从共享策略推导,修掉信封上限过小导致的合法请求被拒
中继此前自带一份限额常量,并在注释里声称"与共享策略一致",而没有任何东西
校验这句话。其中两条已经漂移成真实缺陷:

1. 信封上限 64 KiB 是**信封**的上限,而信封约为其承载 body 的 1.8 倍(body
   先 base64、再 AEAD、再 base64)。于是 body 仍在 agent 的 256 KiB
   maxRequestBytes 之内的请求,在中继处被拒为 relay_payload_too_large——一个
   在局域网上能成功、走中继就失败的操作。响应方向同理:节点返回合法的大
   body,应答在中继被拒,手机白等到超时。实测 120 KiB 的 body 就被拒。
2. mailboxDepth 只靠注释与策略"相等",改任一侧都不会有人发现,而这个深度
   是 agent 重放缓存容量的依据。

现在 relay/src/limits.js 从 contracts/agent-policy.json 推导全部限额,并在
文件不可读、缺 limits、数值不可用时**拒绝启动**:中继无法区分"诚实的大请求"
与"内存耗尽尝试",上限是它唯一的工具,凭空发明一个等于在强制一个没人选过的
数字。推导结果:信封上限 513037 字节、深度 8、请求期限 30s、轮询窗口 24s。

策略摘要在健康检查里公开并逐节点对比:/healthz 返回中继自己那份的摘要,以及
每个在线节点上报的摘要与 policyMatches。它只上报不强制——中继读不到节点上的
文件,声称不一致就拒服务会把"节点跑着旧二进制"变成一次故障;而策略本来就是
agent 在强制。没有上报的节点显示 null 而不是 false,"没说"与"说了不一样的"
是两个不同的发现。

顺带修掉一个只在 sudo 配对时才暴露的缺陷:config.Save 用临时文件加重命名,
在 root 下会把配置文件变成 root 所有,而 systemd 单元以 msa-agent 用户运行,
于是配对看起来成功、节点重启后读不到自己的配置。现在重命名前保留原文件的
uid/gid,且无法保留时报错而非警告。

测试新增 test/limits.test.js(11 项),分三层:推导与策略文件一致、推导出的
上限确实容得下用真实 wire 代码构造的最坏信封(这是旧上限失败的那次测量)、
以及经真实 HTTP 面把合法大请求送过去。另有健康检查的摘要上报与 null 语义。
四处断言都做了变异验证:把上限改回 64 KiB,四项测试失败。

Node 93 项、Go 全包、Swift 99 项、端到端全部通过。
2026-09-16 23:24:28 +08:00
chick f8277791a9 feat: 端到端测试(真 agent + 真中继 + TLS),并修三处只有它能发现的缺陷
新增 test/e2e.test.js:编译并运行 Go agent,经 TLS 接到真中继,由模拟手机发起
签名请求,再断言节点实际收到的内容(含 audit 日志不含 body/密码、危险写 body
逐字节一致)。缺 go/openssl 则跳过并说明原因。

它一次抓出三个单元测试各自都通过、合起来却坏了的问题:

1. transport.Client 把会话 token 写进 c.token,而 c.token 是 bearer——Poll/Result
   因此用会话 token 当 bearer 发出,中继一律 401。现象是 agent 连上后无限重连,
   日志说'凭据被拒'。现在两个凭据分成两个字段,并加测试断言握手带 bearer 不带会话、
   轮询带会话。
2. main.loop 每个周期都重新握手。中继每个节点只保留一个会话,新握手会顶掉旧的并
   丢弃其挂起的轮询,于是两次握手之间到达的请求被投进已不存在的会话、以'agent 离线'
   超时——而 agent 全程在线。改为只在会话失效/传输失败时重新握手。
3. auth.registerDevices 把'已存在'一律跳过,随后又被'本轮未声明'的回收循环删掉,
   所以节点的第二次握手会把自己注册的设备全部注销。改为本节点已持有即确认(幂等)。

另:transport 支持 relayCAFile(PEM 钉 CA,替代系统信任库,不是关闭校验),
测试用自签证书 + 钉 CA 走真实 TLS 校验路径。
2026-09-16 20:17:01 +08:00
chick 78e0d303ab feat: 配对管理端点与中继侧设备注册上报
- agent --pair:临时监听 :3001 仅开放 /provision 与 /destroy,以节点密码为共享秘密;
  窗口由三个独立条件关闭(首次成功配对 / 失败尝试 10 次 / 10 分钟超时),配对成功后
  管理监听即关(设计 §7 第 4 步)
- agent 为每台配对手机生成中继 bearer(config.Save 0600 原子写;启动时为手写条目补发)
- transport.PushDevices:握手成功后全量上报本节点受信设备集;空集是吊销声明而非无操作
- relay /v1/agent/devices:agent 会话后鉴权的设备注册;全量替换本节点条目;
  操作员 MSA_DEVICES 为 static 不可被覆盖;跨节点 id 不可抢占
- 中继设备注册替代了 §11 第 6 项的一部分:节点 destroy + 重握手即可在路由层吊销
- 配置:provisionSecret 字段(无密码保护节点需显式设置才可配对);Validate(pairMode)
- 测试:pair 流程 8 项、设备注册 6 项、transport 1 项;文档 §7 实现修订注记与 README 状态
2026-09-16 11:43:52 +08:00
chick 443af20e0a feat: agent 本机 SimAdmin 登录与会话维持;中继设备侧限流与 agent Retry-After
- runtime.NodeAuth:agent 自查 /api/auth/status,需要时自行 POST /api/auth/login 并持
  simadmin_session Cookie 转发;错误密码进入 60s 冷却(对齐控制台 ensureAuthenticated),
  节点回 401/403 即失效重探,SimAdmin 重启无需重启 agent
- 节点密码进 agent 配置(nodePassword),永不经中继、永不下发到手机;登录路径仍被策略
  挡在电话可达面之外(TestAuthPathsAreUnreachable)
- 新拒绝分类 node_auth_failed:与 upstream_error 区分,手机可提示'该节点需重新配置'
- 中继:/v1/request 每设备固定窗口限流(默认 30/分钟),未鉴权调用不消耗预算
- transport:解析 Retry-After 并入退避;main loop 对 429 直接遵从中继指示
- contracts/envelope.schema.json 补 InnerResponse.outcome 字段
- 设计文档与 README 同步:WSS 改为长轮询的实现修订、响应方向已临时×临时、P2/P3 状态
2026-09-16 09:15:25 +08:00
chick 54d9696a4d baseline: P1 契约、agent 核心与中继投递(既有工作首次入库) 2026-09-16 08:41:13 +08:00