news 2026/10/14 17:55:11

WuKongIM Chat Lifecycle 双 SSH 身份分离:部署凭证与 Codex 诊断密钥的隔离设计与源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WuKongIM Chat Lifecycle 双 SSH 身份分离:部署凭证与 Codex 诊断密钥的隔离设计与源码实现
  • 即时通讯
  • 后端

【免费下载链接】WuKongIM

More than just IM 不只是即时通讯(IM)

项目地址:https://gitcode.com/gh_mirrors/wu/WuKongIM
点击查看免费下载

导读

本文解读 WuKongIM 仓库中的架构决策记录 ADR 0044:Use separate deployment and Codex SSH identities,阐述自动化 Chat Lifecycle 云租期(Cloud Lease)为何要为"部署运维"与"Codex 实时诊断"各签发一把相互独立的临时 Ed25519 SSH 身份,并深入其落地实现:wkchatlifecycle的seal-deployment-identity/open-deployment-identity命令、materialize的公钥绑定,以及stage-orchestrate.sh中的完整调用链。读完本文,你将理解这套"一把加密凭证给 GitHub 部署、一把本地私钥给 Codex 诊断、双双随租期失效"的隔离模型,以及它如何用 NaCl 密封盒保证凭证可审计、可绑定、不可越权。

背景:自动化 Chat Lifecycle 需要临时直接访问

在 WuKongIM 的自动化 Chat Lifecycle 流程中,系统会在阿里云按量付费(PostPaid)租用四台同规格主机(三台 4 vCPU / 8 GiB 的 WuKongIM 服务节点 + 一台承载 worker、协调、监控、分析与公共代理职责的 4 vCPU / 8 GiB 主机),见 ADR 0041:Use four PostPaid hosts for automated chat lifecycle。为保证"负载节点可直达、服务节点保持私有",ADR 0042 明确:自动化运行期间负载节点仅在租期内开放 22 端口 SSH 与 80 端口 HTTP,三个服务节点保持私有离线、禁用密码与 root 密码登录,租期结束时所有规则与 authorized keys 一并清除。

在这种"临时测试服务器、租期结束即销毁"的拓扑下,谁有权 SSH 进去、以什么身份进去,直接决定了整个流程的可审计性与安全边界。ADR 0044 正是在此背景下回答"部署动作与诊断动作应该用同一把钥匙吗"的问题。

双身份设计:一把给部署,一把给诊断

ADR 0044 的结论可以浓缩为一句话:

每个 Chat Lifecycle Cloud Lease 授权两个相互独立的临时 SSH 身份——一把由 GitHub 加密持有的身份用于部署、监控、收尾与证据救援;一把本地生成的 Ed25519 身份,其私钥仅保留在 Codex 手中用于实时诊断。两者都随 Lease 过期失效,并且只有在证明库存归零(zero inventory)之后才会被删除。

拆开来看,这把"双身份"模型包含四个关键属性:

维度部署身份(Deployment Identity)诊断身份(Diagnostic / Codex Identity)
私钥持有方GitHub 侧,以加密信封形式流转,私钥主体经密封后随 Artifact 传递本地生成的 Ed25519 私钥,仅保留在 Codex 手中
用途Deployment(部署)、监控、finalization(收尾)、证据救援实时诊断(live diagnosis)
生成方式由编排脚本ssh-keygen生成,经seal-deployment-identity密封由本地脚本ssh-keygen生成,公钥注入 Run Plan
失效方式随 Lease 过期(expires_at强校验)随 Lease 过期
删除时机证明 zero inventory 之后证明 zero inventory 之后

这样分离的直接收益写在 ADR 原文中:保持可审计性(preserves auditability),并防止 GitHub 的部署凭证变成 Codex 的诊断秘密。即使 Codex 的本地环境或诊断会话被攻破,攻击者得到的也只是租期内的诊断身份,而不是那条能驱动整个部署链路的 GitHub 部署凭证;反过来,GitHub Actions 工件链路上的泄露也不会直接暴露 Codex 的诊断私钥。

部署身份的密封与打开:源码级实现

双身份机制的核心代码位于 cmd/wkchatlifecycle/deployment_identity.go,由wkchatlifecycle工具以两个 cobra 子命令暴露:seal-deployment-identity与open-deployment-identity。

密封命令:把部署私钥锁进加密信封

seal-deployment-identity的职责是"将一把 Lease 作用域内的部署 SSH 身份密封为加密信封",其完整参数如下:

wkchatlifecycle seal-deployment-identity \ --recipient <wrapping-key OpenSSH Ed25519 公钥> \ --identity <mode-0600 部署 Ed25519 私钥> \ --request-id <请求身份> \ --lease-id <Lease 身份> \ --source-sha <40 位十六进制源码提交> \ --plan-digest <64 位十六进制 Cloud Lease Plan 摘要> \ --expires-at <不可变的 Lease 过期时间 RFC3339 UTC>

对应源码中的 flag 定义见 deployment_identity.go 第 102-110 行,七个参数全部markRequired强制必填。密封过程如下:

  1. 元数据校验validDeploymentIdentityMetadata:request-id/lease-id需匹配accessIdentityPattern(^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$),source-sha需为 40 位小写十六进制(hex40Pattern),plan-digest需为 64 位十六进制(hex64Pattern),expires-at必须是UTC 时区的 RFC3339 时间(第 182-189 行)。
  2. 解析 recipient 公钥(parseEd25519PublicKey,实现在 access.go 第 194-212 行),只接受单条、无 authorized_keys 选项、无附加内容的ssh-ed25519公钥。
  3. 读取部署私钥(readPrivateIdentityBody),强制私钥必须是常规文件且权限位不含 group/other 位(info.Mode().Perm()&0o077 != 0即拒绝),大小在 0 到 64 KiB 之间,且必须是 Ed25519 类型(第 208-245 行)。
  4. 计算部署公钥的 authorized_keys 文本与 SHA256 指纹(ssh.MarshalAuthorizedKey/ssh.FingerprintSHA256)。
  5. 组装明文 payload(deploymentIdentityPayload),其中private_key_base64承载私钥主体。
  6. 用box.SealAnonymous(匿名密封,不需要发送方密钥,只需 recipient 公钥)对明文加密,算法标识为x25519-xsalsa20-poly1305-sealed-box(常量accessEncryptionAlgorithm,access.go 第 31 行)。
  7. 输出encryptedDeploymentIdentityEnvelope信封 JSON,包含 schema、algorithm、request-id、lease-id、source-sha、plan-digest、recipient-fingerprint、deployment-public-key、deployment-fingerprint、expires-at 与 ciphertext-base64。

信封 schema 常量定义在 deployment_identity.go 第 19-22 行:

  • wukongim.chat_lifecycle.deployment_identity/v1:明文 payload schema
  • wukongim.chat_lifecycle.encrypted_deployment_identity/v1:信封 schema

这意味着即使信封被放在 GitHub Actions Artifact 这种有界存储上,也无法直接读出任何私钥材料——测试TestDeploymentIdentityEnvelopeRoundTripIsLeaseBoundAndPrivate专门断言"加密后的部署身份不包含明文密钥材料"(bytes.Contains(sealed.Bytes(), privateBody)必须为 false)。

打开命令:只有持 wrapping 私钥的人能还原

open-deployment-identity是密封的逆操作,参数为:

wkchatlifecycle open-deployment-identity \ --envelope <加密信封 JSON> \ --identity <mode-0600 wrapping-key Ed25519 私钥> \ --request-id / --lease-id / --source-sha / --plan-digest <精确匹配> \ --now <可信的当前 RFC3339 UTC 时间> \ --output <新部署私钥输出路径>

它的校验链(第 114-172 行)层层收紧:

  1. 信封 JSON 严格解析(readStrict:DisallowUnknownFields、拒绝尾随数据),并通过validEncryptedDeploymentIdentityEnvelope验证 schema、算法、元数据格式、指纹前缀SHA256:、deployment_public_key必须以ssh-ed25519开头、密文长度有界。
  2. 请求参数必须与信封中的request_id、lease_id、source_sha、plan_digest逐一精确相等。
  3. --now必须解析为 UTC 时区,且必须早于信封中的expires_at——过期即拒绝,实现"随 Lease 过期失效"。
  4. wrapping 私钥的 SHA256 指纹必须与信封recipient_fingerprint一致,防止用错钥匙。
  5. box.OpenAnonymous解密失败(ok == false)即拒绝。
  6. 解密出的 payload 必须与信封完全匹配(payloadMatchesEnvelope),且私钥主体解码后重新推导出的公钥,必须同时匹配信封的deployment_fingerprint与deployment_public_key——防止信封内容被篡改后解密出另一把钥匙。
  7. 最后writePrivateAtomic以0600 权限、原子写(临时文件 + rename)落盘,且绝不覆盖已存在文件(测试TestOpenDeploymentIdentityRejectsMismatchExpirationAndExistingOutput验证失败场景下不会创建输出、已有文件内容不被改动)。

诊断身份的注入:materialize 与 access 命令

Codex 诊断身份(--codex-diagnostic-pubkey)与部署身份(--deployment-pubkey)在 Run Plan 物化阶段被同时绑定。wkchatlifecycle materialize命令(cmd/wkchatlifecycle/main.go 第 199-227 行)接收两个独立参数:

  • --codex-diagnostic-pubkey:Codex 诊断 Ed25519 公钥
  • --deployment-pubkey:派生的部署 Ed25519 公钥

两者均标记为必填,与source-sha、operator、request-id、repository、bundle-digest、now、attempt一起传入chatlifecyclerun.Materialize。这把"公钥入计划"的设计意味着:诊断公钥与部署公钥在计划层面就是两个独立字段,后续分别进入bootstrap_access(引导访问)与部署激活流程,互不混用。

与部署身份配套的是同一工具中的seal-access/open-access命令(cmd/wkchatlifecycle/access.go 第 70-156 行):它们把 UI 凭据(Manager / Demo 地址、operator 用户名、密码、租期过期时间)以同样的box.SealAnonymous密封为encryptedAccessEnvelope,recipient正是Codex 的 Ed25519 公钥。也就是说:

  • 部署身份信封的 recipient 是 GitHub 侧的 wrapping 公钥(由WK_CHAT_WRAPPING_PUBLIC_KEY提供);
  • 访问凭据信封的 recipient 是 Codex 的诊断公钥;
  • 两个信封体系都遵循"密封在部署侧、由各自私钥持有者打开"的对称结构。

值得一提的是 access.go 第 214-296 行 实现了readEd25519Identity与ed25519PublicToX25519:将 Ed25519 公钥的 Edwards y 坐标映射为 X25519 的 Montgomery u 坐标(u = (1+y)/(1-y) over 2^255-19),私钥侧通过SHA-512(seed)前 32 字节并做位掩码(bit 0 &= 248、bit 31 &= 127、bit 31 |= 64)派生 X25519 私钥,并用curve25519.X25519探测标量做合法性验证。这是"用 OpenSSH Ed25519 密钥承载 NaCl sealed box"的关键桥接,也解释了为何所有身份必须严格限定为 Ed25519 类型。

端到端调用链:stage-orchestrate.sh 里的实际编排

理解双身份如何落地的最终答案在编排脚本 scripts/chat-lifecycle/stage-orchestrate.sh 中。完整流程如下:

第一步:生成部署密钥并物化计划(第 476-490 行):

deployment_key="$WK_CHAT_WORK_DIR/deployment-${chat_stage}-${attempt}" ssh-keygen -q -t ed25519 -N '' -C "wukongim-chat-lifecycle-${WK_CHAT_REQUEST_ID}-${chat_stage}-${attempt}" -f "$deployment_key" chmod 0600 "$deployment_key" deployment_public_key="$(<"${deployment_key}.pub")"

随后materialize同时注入两把公钥:

"$WK_CHAT_TOOL" materialize --template "$WK_CHAT_TEMPLATE" --source-sha "$WK_CHAT_SOURCE_SHA" \ --operator "$WK_CHAT_OPERATOR" --codex-diagnostic-pubkey "$WK_CHAT_CODEX_DIAGNOSTIC_PUBKEY" \ --request-id "$WK_CHAT_REQUEST_ID" --repository "$GITHUB_REPOSITORY" \ --bundle-digest "$bundle_digest" --deployment-pubkey "$deployment_public_key" \ --now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --attempt "$attempt" \ --committed-micros "$committed_micros"

第二步:密封部署身份(第 518-522 行):

"$WK_CHAT_TOOL" seal-deployment-identity \ --recipient "$WK_CHAT_WRAPPING_PUBLIC_KEY" --identity "$deployment_key" \ --request-id "$WK_CHAT_REQUEST_ID" --lease-id "$planned_lease_id" \ --source-sha "$WK_CHAT_SOURCE_SHA" --plan-digest "$planned_plan_digest" \ --expires-at "$planned_expires_at" >"$attempt_dir/encrypted-deployment-identity.json"

注意--expires-at直接取自 Run Plan 的lease_plan.expires_at,把部署身份的寿命硬绑定到 Lease 过期时间;--plan-digest取自 release selector,保证信封与将要下发的 Lease Plan 一一对应。

第三步:把信封交给部署 Actions。脚本通过dispatch_and_wait cloud-deployment-activate.yml将encrypted_deployment_identity_json作为参数传给部署 workflow(第 404 行),随后从部署产物中取回encrypted-deployment-identity.json并用jq校验其 schema、request/lease/source/plan 绑定关系与公钥前缀(第 564-569 行)。部署侧再以open-deployment-identity打开信封还原部署私钥(测试 scripts/chat_lifecycle_workflows_test.go 断言部署 workflow 必须包含open-deployment-identity与WK_CHAT_LIFECYCLE_WRAPPING_PRIVATE_KEY环境变量)。

第四步:用部署身份建立 SSH 通道。脚本导出WK_CLOUD_SSH_KEY="$deployment_key"后调用 scripts/cloud-deployment/write-ssh-config.sh,生成以wkdeploy用户、IdentitiesOnly yes、BatchMode yes、StrictHostKeyChecking accept-new、独立 known_hosts 的 SSH config,三个服务节点通过ProxyJump wukong-load经负载节点跳转(write-ssh-config.sh 第 44-70 行)。后续所有ssh -F "$WK_CLOUD_SSH_CONFIG"操作——启动 stage 服务、轮询 run-start.json、读取 journal 游标——都走这把部署身份。

第五步:失败路径上的清理。release_failed_attempt在释放租期后立即rm -f部署私钥/公钥,并删除attempt_dir下所有encrypted-deployment-identity.json(第 289-293 行);成功路径则在 handoff 完成后rm -f "$deployment_key" "${deployment_key}.pub"(第 748 行)。cleanup_on_exit的 EXIT trap 保证即使脚本异常退出,本地部署密钥也会被清除(第 247-255 行)。这正对应 ADR 中"只在证明 zero inventory 后删除"的约束——租期释放动作cloud-lease-release.yml会返回带zero_inventory字段的 release.json,只有确认零库存后才算彻底收尾。

本地实验室路径 scripts/chat-lifecycle/direct-lab.sh 同样贯彻双身份:initialize_request_state一次生成两把密钥——diagnostic_ed25519(注释wukongim-chat-lab-diagnostic-$request_id)与deployment_ed25519(注释wukongim-chat-lab-deployment-$request_id)(第 356-359 行),并在start_request的 materialize 调用中分别以--codex-diagnostic-pubkey "$(<"$directory/diagnostic_ed25519.pub")"与--deployment-pubkey "$(<"$directory/deployment_ed25519.pub")"注入(第 434-437 行)。

测试与契约:隔离机制的可验证性

双身份机制被多层测试锁定:

  • cmd/wkchatlifecycle/deployment_identity_test.go:
    • TestDeploymentIdentityEnvelopeRoundTripIsLeaseBoundAndPrivate:完整密封→打开回环,验证信封无明文私钥、schema 正确、Lease 绑定正确、打开后字节一致且权限为 0600;
    • TestSealDeploymentIdentityAcceptsCommentedRecipient:允许 recipient 公钥带注释;
    • TestEd25519RecipientRejectsAuthorizedKeyOptionsAndAdditionalKeys:拒绝带 authorized_keys 选项或多条公钥的模糊输入;
    • TestOpenDeploymentIdentityRejectsMismatchExpirationAndExistingOutput:错误 wrapping 私钥、错误 request/lease、过期时间均拒绝打开,且失败时绝不创建输出文件、绝不覆盖已有文件。
  • cmd/wkchatlifecycle/access_test.go:覆盖 access 信封的同类回环与拒绝路径。
  • scripts/chat_lifecycle_workflows_test.go:从编排脚本静态断言seal-deployment-identity、open-deployment-identity、encrypted-deployment-identity.json与WK_CHAT_LIFECYCLE_WRAPPING_PRIVATE_KEY等关键元素必须存在。
  • scripts/chat_lifecycle_direct_lab_test.go 与 scripts/chat_lifecycle_direct_lab_deploy_integration_test.go:断言本地请求状态目录必须同时包含diagnostic_ed25519与deployment_ed25519两套文件,验证双身份是初始化状态的固有组成部分。

安全边界小结

从设计到实现,ADR 0044 的双 SSH 身份隔离可以归纳为四层边界:

  1. 密钥来源隔离:部署私钥由编排脚本在租期开始时生成,密封后私钥主体只以密文形式存在于 Artifact;诊断私钥由本地生成,从不进入 GitHub 工件链。
  2. 持有者隔离:部署身份的 recipient 是 GitHub 侧 wrapping 公钥,只有 GitHub 部署流程能打开;Codex 诊断身份的公钥进入 Run Plan 的 bootstrap access,私钥只留在 Codex 手中。
  3. 租期绑定:信封中的expires_at与lease_id、plan_digest、source_sha、request_id强绑定,打开时必须逐项精确匹配且时间未过期。
  4. 销毁纪律:无论成功、失败还是 operator 手动终止,部署密钥都在租期释放(zero inventory 证明)之后被立即删除,绝不跨租期保留。

这套机制与 ADR 0034(run-scoped internal capability tokens)、ADR 0037(本地运行 Codex 加密分析会话) 一脉相承:所有特权都做成"租期作用域、最小化持有、到期即毁"。双身份正是这一原则在 SSH 访问层面的一次具体落地——既满足了操作者对临时测试服务器直接访问的要求,又没有让任何单一凭证同时承担"部署"与"诊断"两种职责。

  • 即时通讯
  • 后端

【免费下载链接】WuKongIM

More than just IM 不只是即时通讯(IM)

项目地址:https://gitcode.com/gh_mirrors/wu/WuKongIM
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 17:53:48

AI经典试题拆解:从逻辑回归推导到模型上线排查

简介&#xff1a;这份百度人工智能经典考试试题合集&#xff0c;适合准备AI相关面试、期末考试或系统复习机器学习、自然语言处理等方向的读者&#xff0c;无论求职还是课业备考&#xff0c;都能借此巩固AI核心概念。文档完整收录了选择题、填空题、简答题和应用题四种题型&…

作者头像 李华
网站建设 2026/10/14 17:53:19

2025网络安全成熟度曲线解读:聚焦AI安全、业务转型与组织韧性

简介&#xff1a;围绕2025年中国网络安全成熟度曲线&#xff0c;Gartner发布的最新研究报告系统梳理了国内网络安全的关键趋势与技术演进&#xff0c;面向CISO、CIO及安全风险管理负责人&#xff0c;也适用于希望将AI安全纳入长期规划的技术团队。报告指出&#xff0c;当前重点…

作者头像 李华
网站建设 2026/10/14 17:52:51

告别 useState 噩梦:用 Recoil 和 Jotai 搞定 AI 参数面板的细粒度响应式更新

最近在折腾一个 AIGC 相关的后台管理项目,其中最让人头疼的就是"AI 生成参数控制台"。 不知道你有没有写过类似的页面:一个面板上塞满了各种滑块和输入框,比如 Temperature(温度)、Top-P、Frequency Penalty(重复惩罚)。最要命的是,这些参数不是独立的,它们…

作者头像 李华
网站建设 2026/10/14 17:52:28

TurboPower 组件库实战指南:Delphi 遗留系统的基础设施补全

简介&#xff1a;这份资源是TurboPower全套VCL组件源码及相关文档的完整归档&#xff0c;面向仍在使用Delphi进行桌面与数据库开发的程序员&#xff0c;以及需要研究经典第三方控件实现的技术人员。TurboPower作为有18年历史的Delphi组件厂商&#xff0c;于2004年停止运营并开放…

作者头像 李华
网站建设 2026/10/14 17:51:50

2026 土工合成材料行业深度评估报告|防渗膜应用专题:国产防渗、护坡与排水专用装备材料选型手册

引言2026年&#xff0c;国内固废环保、矿山尾矿治理、水利渠系、市政基建、水产养殖、生态修复工程持续扩容&#xff0c;土工膜、防渗膜、水泥毯等土工主材市场需求稳步攀升。传统项目多采用分品类零散采购、多家供应商分头供货模式&#xff0c;越来越难以匹配现阶段工程管控要…

作者头像 李华
网站建设 2026/10/14 17:51:33

Superset源码安装与调试实战:构建可断点热更新的开发环境

想折腾 Superset 源码安装的人&#xff0c;多半已经被 Docker 版折磨过一轮了。浏览器里点开各平台的教程&#xff0c;最热的那几篇基本都是同一个套路&#xff1a;拉一份 docker-compose.yml&#xff0c; docker compose up -d &#xff0c;等两分钟容器起来&#xff0c;图表…

作者头像 李华