3.4 KiB
3.4 KiB
Reality 密钥主控托管(Key Hosting)
Reality 密钥对由控制面生成、保存和下发,节点只是落地执行。这样节点重装
系统后重新部署的仍是同一把密钥,客户端订阅里的 pbk(publicKey)永不变化,
客户无需重新复制配置。
数据模型
- 控制面数据库
nexusgate.json新增顶层对象keyStore:{ "reality-<nodeId>": { "privateKey": "...", "publicKey": "...", "updatedAt": "..." } } - 密钥格式与此前一致:Node JWK 导出的 x25519 密钥对,
privateKey/publicKey为 base64url 无填充(43 字符),与sing-box generate reality-keypair输出一致。 - 旧数据库没有
keyStore时自动补为空对象,不影响启动。
协议变更
下发(控制面 → Agent)
apply_resource 任务的 payload 新增可选字段:
{
"resource": { "...": "inbound 里仍是 ${REALITY_PRIVATE:reality-<nodeId>} 占位符" },
"kind": "node",
"keyMaterial": { "reality-<nodeId>": { "privateKey": "...", "publicKey": "..." } }
}
- 控制面在构建部署任务时(
rebuildNode/ 对账reconcileServer):节点是 Reality 协议且keyStore无此 keyId → 面板现场生成并入库;已有则复用。 - 出口(node-exit)任务不带
keyMaterial(出口资源没有 Reality 占位符)。 /api/jobs管理接口不返回 payload,私钥不会经管理 API 泄漏。
Agent 行为(Go agent)
- 收到
keyMaterial时,先写入本地/etc/nexusgate/keys.json(0600), 再做占位符 materialize —— 部署的必然是主控托管的那把钥匙。 - 若某个被使用的 keyId 不是本次 payload 提供的(本地已有或本地新生成),
在任务结果里回传:
result.artifacts.keyMaterial = { "<keyId>": { privateKey, publicKey } }。 - payload 无
keyMaterial(旧控制面)时行为与托管前完全一致。
Node 版 agent 不读 keyMaterial 字段,照旧本地生成/使用密钥,协议兼容;
其任务结果不带 keyMaterial 回传,控制面也不会被它改写(见下)。
回填(Agent → 控制面)
任务成功完成(completeJob)时,控制面合并 artifacts.keyMaterial:
keyStore无此 keyId → 入库(存量节点密钥由此自动迁入主控)。- 已有且相同 → 无操作。
- 已有且不同 → 保留主控的,写一条
reality_key_conflict审计日志; 下一次下发会把主控密钥重新发给 agent,agent 本地副本随之被纠正。 - 合并后节点的
artifacts.realityPublicKey强制以keyStore为准, 保证订阅输出稳定。keyMaterial本身不会存进node.artifacts。
优先级
主控 keyStore > 本次下发 payload > 节点本地 keys.json
payload 的内容就是 keyStore 的副本,所以前两者永远一致;本地仅在主控 从未托管过该密钥的迁移期短暂生效,回填完成后即以主控为准。
删除清理
节点记录被真正删除时(草稿直接删除、删除任务确认后、服务器强制遗忘),
对应的 keyStore["reality-<nodeId>"] 一并移除。
恢复语义
节点重装系统 → 重新注册 agent → 主控重新下发节点资源,payload 携带原 keyMaterial → agent 落地同一私钥 → sing-box 配置与重装前一致,客户端 订阅(pbk + shortId)逐字不变,客户无感。