Files
codex-app-server/docs/chv-production-readiness.md
T

28 KiB
Raw Blame History

Cloud Hypervisor 执行器生产就绪清单

状态图例:✅ 代码已完成 · 🧪 mock 测试已覆盖(无真 VM) · ⏳ 需 KVM 宿主实测 · 🏗️ 需镜像构建 · 📈 需压测 · 🔒 需安全审计

本清单是 PR 的一部分,不是事后补的口号。凡是标 ⏳/🏗️/📈/🔒 的项,在本轨内一律不声称已验证、已建成、已通过。

0. 交付点卡在哪一关(必读,先于逐项清单)

  • CI 门禁(未过,交付点 = PR OPEN):CI 必须通过才能合并。 此前全仓 CI 因 GitHub 账单问题 0 步骤失败;若账单未恢复、 CI 仍失败,本 PR 只到 OPEN,不合并,更不为变绿改 CI 配置 或绕过检查。
  • 真 VM 门禁(未过,执行器默认关闭):生产启用(把真实任务 路由到 cloud-hypervisor、部署到生产主机)必须先有带 /dev/kvm 的宿主并完成真实 VM 启动、快照恢复、任务端到端验证。 当前事实:生产主机 124.222.165.170 本身就是 CVM (systemd-detect-virt=kvm,DMI product=CVM),/dev/kvm 不存在、 无 vmx/svm 标志、无 kvm 模块、未装 cloud-hypervisor,无嵌套 虚拟化透传;本 VM 容器内同样无 /dev/kvm。 故真 VM、快照恢复、压测在本轨内一律不声称已验证;执行器 默认关闭(server 默认仍是 docker,worker 默认仍是 local, CHV 只在任务创建时显式选 executor=cloud-hypervisor 且有 CHV worker 认领时才跑),不进入生产任务路由。
  • 还差什么才能真正上生产(按顺序):① 用户侧开通带 /dev/kvm 的裸金属或开启嵌套虚拟化的实例;② 在该宿主上构建并发布 chv-guest 镜像(见 §4);③ 在该宿主上跑通真实 VM 启动、 快照恢复、任务端到端、预热池与压测;④ 安全审计通过(§6); ⑤ CI 账单恢复且本 PR CI 变绿后,按当时 main 头复核 diff 再合并、 再部署。缺任何一项,都不把 CHV 推上生产。

1. 单任务单 VM 隔离

  • ✅🧪 一个任务一个 VM:dispatch/run/goal 均走 wake → 跑 → finally shutdown,同一任务重复 wake 先关旧 VM;VM 名、api-socket、 每任务磁盘路径均按任务派生,互不相同(测试断言)。
  • ✅🧪 每任务磁盘独立:copy 模式复制基础镜像为每任务磁盘; reuse 模式仅单租户实验,preflight 拦截(须显式实验二次确认并审计放行,feat-chv-fix 起代码真正拦截;此前“代码与文档双写”的说法是错的,代码当时并不拦截)。
  • ✅🧪 租户无共享路径:执行器不挂载宿主根、宿主代码目录或含 密钥目录;客机代码在镜像内 /app,不在宿主共享挂载上。
  • ✅🧪 密钥不进镜像/代码/prompt:SSH argv 只有 key 文件路径; 模型 key 由客机 cloud-init 预置的环境变量名注入;测试断言 命令中不含哨兵密钥值。feat-chv-secfix 起网关密钥不再走 SSH env 前缀(旧路径值进 argv 已废除),改走 provisioned seed 盘客内文件(/etc/chv/guest-env,0600),keyscan 模式 下密钥投递 fail-closed;审计 detail 里绝无密钥值。
  • ✅🧪 SSH host key 按 VM 钉死(优化轨,#216 的 no + /dev/null 已废除):每 VM 在 state_dir//known_hosts 有独立文件, 首次启动经 ssh-keyscan 在解析出的 SSH 目标上捕获 key 写入该文件, 此后 SSH 一律 StrictHostKeyChecking=yes + 该文件校验; 非 root 客机用户(CHV_SSH_USER,显式空串直接拒绝)与端口 (CHV_SSH_PORT,1-65535)为显式配置。mock 只验证捕获→ 写文件→钉死选项的管线,不验证首次密钥真实性。
  • ✅🧪 Host key 预置方案(补缺口轨,替代 TOFU 为生产路径): 宿主在启动前为每 VM 生成一次性 ed25519 host key 对, 公钥立即写进该 VM 的 known_hosts 钉死,私钥只进 per-VM cloud-init seed(随 VM 关闭/回收而删),客机镜像硬性 要求见 chv-guest/README.md §5(cloud-init NoCloud、 首启先应用 seed 再起 sshd、不得无条件重生成 host key)。 ssh-keyscan 首次捕获只保留为弱回落(须显式设 CHV_HOST_KEY_MODE=keyscan),生产默认值已是 provisioned (feat-chv-secfix,H4,此前默认值 keyscan 是反的);TOFU 首次 真实性仍待预置 key 方案在真机上验证,安全审计时须单独立项确认。
  • 🔒 需安全审计:VM 逃逸面(virtio 设备、tap 桥接、SSH 配置)、 客机 root 提权面、租户间东西向隔离的宿主网络配置,须由 安全审计逐项确认,代码自查不能代替审计。

2. 快速启动:预热快照池 + 镜像缓存

  • ✅🧪 快照/恢复接口:snapshot_vm / restore_vm(restore 后显式 resume,见 §3),WarmPool 接口与状态机(cold → prewarming → warm → assigned → running → recycled/destroyed)。
  • ⚠️ 定案(feat-chv-arch95,ADR-5):WarmPool 是实验性、非生产 路径。dispatch 永远冷启动,_run_with_vm 绝不查池;池空回落 冷启动的说法不再作为生产特性宣称,acquire/resume 只保留为 实验接口。
  • ✅🧪 预热条目一次性:acquire 消耗、release 一律销毁,绝不跨 任务复用脏 VM。
  • ✅🧪 预热池 registry 与孤儿清理(优化轨):池状态落 state_dir/warm-pool.json(原子写),worker 重启后载入的 条目一律当孤儿、只能 reconcile_orphans() 清理,绝不重新 发给任务;registry 损坏时 quarantine 成 .corrupt 并从空池 开始;registry 之外的残留 socket 仍由 cleanup_stale_vms() 扫描处理。
  • ⏳ 需 KVM 宿主实测:预热池的真实冷启动耗时、resume 耗时、 池大小与命中率、快照来源一致性,均需真机数据,mock 数字 不能当性能结论。
  • 📈 需压测:多 VM 并行预热/分配/销毁的宿主 CPU/内存/磁盘 压力、快照存储增长,均未测。

3. 完整生命周期

  • ✅🧪 create / boot:API 模式 wake_vm(vmm.ping → vm.create → vm.boot → SSH 就绪)。
  • ✅🧪 pause-resume(sleep-wake):pause_vm / resume_vm(ch-remote)。
  • ✅🧪 snapshot-restore:restore_vm(restore + 显式 resume + SSH 就绪),恢复后默认 paused 的坑已在代码与测试里钉住。
  • ✅🧪 hibernate:CHV 无原生 hibernate 端点,本轨用 pause + snapshot + shutdown 等价实现,文档与代码双写“等价实现”, 不声称接了原生端点;唤醒走 restore_vm。
  • ✅🧪 shutdown-destroy / recycle:shutdown_vm(ch-remote shutdown + 杀进程 + 删 socket)、recycle_vm(保留 task_checkpoints/task_attempts,丢失 guest /tmp 与未拷出 的 worktree 临时物,绝不声称恢复临时文件)。 feat-chv-secfix 起 stop 即删任务 disk.raw(保留期 0, 需取证须在 shutdown 前经 SSH 拷出),残留盘由 gc_stale_disks 扫描清理,删失败记审计 + 脏标记。
  • ✅🧪 execute 只读已运行 VM(优化轨):execute() 先查 VM 是否在运行(登记活进程或 api-socket 存在),未运行报 ChvFailure(kind=vm_not_running),不为一次 status 起 VM, 也不向不存在的端口发 SSH 再拿连接失败冒充任务状态。
  • ✅🧪 长任务中途死亡判 interrupted(优化轨):客内命令抛 传输异常、或非 goal 结果已 failed 且登记 VMM 进程已死时, _run_with_vm 改判 interrupted(断点可续跑)而非笼统 failed;VM 还活着时的异常/失败原样处理。中途心跳未做: host.py 单次 SSH 长回合没有客内进度信号可采,另起一路 SSH 轮询只能证明 VM 活着、不能证明任务在进展,评估后 认为风险大于收益,宁可只做死亡分类。
  • ⏳ 需 KVM 宿主实测:上述全部生命周期在真机上的状态转换、 快照恢复后 guest 状态一致性、休眠后文件/worktree 保留, 均未在真机验证。

4. 完整开发环境(客机镜像)

  • ✅ 客机镜像内容清单:chv-guest/README.md 按 cloud/docker/Dockerfile 能力对齐(Python 3.12、git、 pandoc/poppler/latex、python-docx/openpyxl/pptx/PyMuPDF/ Pillow、codex 0.164.0-alpha.9 成套、Playwright + Chromium、 openssh-server、cloud-init)。
  • ✅ 构建脚本实做(补缺口轨):chv-guest/build.sh(参数校验、 KVM 宿主检查、下载 Ubuntu 云镜像、qemu-img 转 raw、 virt-customize 装清单、拷入 /app 代码、virt-get-kernel、 BUILDINFO.json 生成与 tag 校验;--dry-run 只打印命令)。
  • 🏗️ 需镜像构建:镜像本身未构建、未发布;BUILDINFO 版本核对、 浏览器版本钉死、中文 LaTeX 单独一版,均待构建时做。
  • ⏳ 需 KVM 宿主实测:镜像在真 VM 里启动、codex/Playwright/ git/文档工具链逐项跑通,均未验证。

5. 资源与成本

  • ✅🧪 每 VM 配额:CPU/内存进 VmConfig(CHV_CPU/CHV_MEM), 磁盘配额门(CHV_DISK_GB,默认 50 GB 总量预算、超限拒绝, feat-chv-secfix 起默认开启,此前未设即不查),快照上限 (每 VM 数量默认 10、总量默认 20 GB、保留期默认 30 天, 超量最旧优先剪枝),宿主容量门 (CHV_MAX_VMS,超限明确拒绝排队,不降级;残留 socket 须真连通才计数,死 socket 不再占满容量)。
  • ✅ 超时与最大轮次:SSH 单次超时(CHV_TASK_TIMEOUT)+ worker task_timeout 外层护栏;goal 最大轮次 100(与 Local 同款),执行器内部不重试(重试是 worker max_retries 语义)。
  • ✅ 成本复用:不另起一套计费;worker 现有 quota/成本语义 (per-user USD 配额、quota 看门狗、成本写库)仍是唯一口径, 执行器只把 host.py 用量原样返回。
  • ✅ 空闲休眠省成本:hibernate_vm(pause + snapshot + shutdown)+ WarmPool 释放即销毁,空闲 VM 不占 vCPU/内存 (快照只占磁盘,见 §2 压测项)。
  • ✅ autoscaler:按 executor 过滤队列深度(已有)+ 本轨透传 --executor 给拉起的 worker(否则 CHV 池永远饿死)。
  • 📈 需压测:配额下的真实资源占用、超限拒绝的排队行为、 成本与配额在长任务下的实际表现,均未测。

6. 网络与治理

  • ✅🧪 受控 egress:默认 fail-closed(CHV_EGRESS_PROXY 未设且 未显式允许直连时 preflight 直接失败);客机命令加 env 代理前缀,出口只走白名单代理(Tinyproxy 思路复用)。 feat-chv-secfix 措辞纠正:env 前缀只是代码门,不是网络层 隔离——客内 root 可 unset/裸 socket 绕过;真正的隔离是 “代码门 + 宿主防火墙缺一不可”(见 §11 部署清单), 代码不再把 fail-closed 写成已隔离。
  • ⏳🔒 需宿主网络配置 + 安全审计:tap 桥接是否真的只路由到 白名单代理、VM 能否绕过代理直连外网,是宿主网络配置问题, 执行器代码不能自证,必须在 KVM 宿主上实测并审计。 代码已做:每 VM 独立 tap/mac/ip 校验(共享即拒绝并审计)、 net 条目字段白名单、egress 代理地址校验。
  • ✅ 审批门:任务 approval 经 _approval_cli_args 进客内 host.py 顶层参数,VM 内 agent 不能自改;delete/deploy/spend 类动作 仍走 server 的 approval/merge_gate,不由 VM 内 agent 自批 (本轨不改 approval/merge_gate 语义,只复用)。
  • ✅ 工具与系统调用范围收敛:VM 边界本身就是系统调用边界 (客机独立内核);plan 任务走 read-only 沙箱参数;最大步数 (goal 100 轮)、时间(task_timeout)、重试(worker 1 次) 超限即停并审计(vm_failed 带 kind)。

7. 可观测与审计

  • ✅🧪 VM 事件审计:create/boot/ready/pause/resume/snapshot/ restore/shutdown/destroy/failed/recycled/warm_pool_acquire/ release 均经 audit_callback 发出;worker 接线后转 AuthStore 审计(_audit 本身 best-effort)。feat-chv-secfix 起补齐: host_key_generated/host_key_captured/host_key_mismatch、 preflight_failed、wake 前置失败(容量/配额/net 冲突)、 vm_disk_deleted/vm_disk_delete_failed/vm_disk_gc、 host_secret_residue_cleaned、net_config_dual、快照剪枝; 审计回调失败记宿主 error 日志,不再双重静默。
  • ✅🧪 失败分类:ChvFailure 带 kind(kvm_missing/binary_missing/ kernel_missing/image_missing/ssh_key_missing/cpu_flag_missing/ egress_not_configured/api_timeout/guest_not_ready/vm_create_failed/ vm_boot_failed/host_capacity_exceeded/disk_quota_exceeded), 不笼统 failed。
  • ✅ 断点对齐(补缺口轨已接线):VM 快照/休眠与现有 task_checkpoints/task_attempts 语义对齐(recycle 保留两者, 丢失临时文件);worker 通用分支对任何提供 goal_resume_breakpoint_task 的执行器调用它(Local 仍走 原路径,网关任务仍被 loud fail 守卫拦住);WarmPool reconcile_orphans 已由 worker 启动与关闭时显式调用, 失败不炸主流程但进审计;VM 中途死亡改判 interrupted 后走同一断点路径,不再落 failed 死信(见 §3 与新增测试)。
  • ⏳ 需 KVM 宿主实测:审计事件在真机任务 timeline 里的完整 呈现、失败分类与真实故障的对应关系,均未验证。

8. 多 VM 并发与容量

  • ✅🧪 worker 注册:_executor_name() 返回 cloud-hypervisor, worker register/heartbeat 已上报 executor + capacity(现有 机制复用,无需新表)。
  • ✅🧪 多 VM 并行:_vms 登记 + 锁保护,wake 按任务名登记, 不同任务互不覆盖;容量门在 wake 前检查。
  • ✅🧪 残留 VM 清理:cleanup_stale_vms 处理已死/已关/socket 残留三种情况,只清 chv- 前缀 socket;feat-chv-secfix 起 清理前先跑残留盘 GC 与残留密钥扫描(不依赖 socket 是否 存在,覆盖 worker 崩溃后 socket 已删、文件仍在的情形)。
  • ✅🧪 容量不足明确拒绝:kind=host_capacity_exceeded,调用方 排队/拒绝,绝不降级 Docker/Local(测试断言 launcher 未调用)。
  • 📈 需压测:多 VM 并行下的宿主容量、VM 启动并发上限、残留 清理在高压下的表现,均未测。

9. 默认关闭与生产启用条件(重申)

  • ✅ 默认关闭:server 默认执行器仍是 docker,worker 默认 local; CHV 只在显式选 executor 且有 CHV worker 时才跑;无 CHV_* 配置时 preflight 直接失败。
  • ⏳ 生产启用前置(全部未完成):带 /dev/kvm 的宿主开通 → chv-guest 镜像构建发布 → 真实 VM 启动/快照恢复/任务端到端 验证 → 压测 → 安全审计 → CI 变绿后按当时 main 头复核 diff 再合并部署。缺一不可。

10. 审核修复轨 feat-chv-fix(堆叠于 #230 之上)

  • ✅🧪 P0 已修:seed 磁盘(CIDATA ISO,第二块只读盘)挂进 VmConfig;restore 不回落 keyscan(provisioned 下复用已钉 死 known_hosts、缺失 fail-fast,快照内 host key 不重预置); reuse 由 preflight 拦截(显式实验二次确认 + 审计放行); SSH 目标选客机 IP 方案(显式 host > net 静态 ip > 显式 转发宣称下的回环,三者皆无 fail-fast;vsock 与纯 NAT 方案 未选,理由见 config.py)。
  • ✅🧪 P1 已修或有结论:网关 extra_env 经 SSH env 前缀注入, worker 守卫收窄到 Local/CHV;取消/清理/看门狗接 CHV 通用 路径;WarmPool acquire 内 resume(本轨定案为实验性,见 ADR-5, 不再是生产特性);容量门改持久化 计数(socket 联合计数);磁盘配额改总量+余量、已存在磁盘 校验重拷;API 客户端按 Content-Length 验完整性;快照 file:// 目标验存在且非空再 shutdown;execute 死端口归类 vm_not_running。
  • ⚠️ 仍未验证(不得声称已交付):CHV + 网关真实任务;真机 wake/boot/SSH(含 seed 注入与 NAT/客机 IP 实测);快照恢复端到端;压测;安全审计; CI 账单恢复。另:P0-2 审计原说法“restore 须把 seed 接入 VmConfig”已纠正——restore 不走 vm.create,seed 无从挂盘, 正确不变量是复用钉死 known_hosts。
  • ✅🧪 P2 已做(范围已恢复为全做,上一条收窄令作废): test_chv_gaps.py 的 grep 文本断言改成真实 Worker._run_attempt 行为断言;stop_vm 删除失败记 vm_key_cleanup_failed 审计 (同名复用旧 key 的彻底根治在本轨之前列为遗留—— feat-chv-secfix 起已根治:清理失败写脏标记,同名 VM 拒绝启动,见 §11 M2);配置校验时机统一到 preflight(CPU/内存非法 进 preflight,kind=config_invalid);build.sh 与 README 口径 对齐(章节顺序 §5 host key → §6 对接、BUILDINFO contents 纠正为名字列表无逐项版本、Node.js 不装写实);第二启动路径 清理(build_vm_command 降级为私有 _build_vm_command,手动 等价写法保留;Docker goal 内联解析顶部加指向正典 parse_goal_result 的分叉说明 + 同步检查清单);长函数只做 小提取(preflight 的 reuse/CPU/内存校验块抽成 _preflight_config_checks,语义不变;config.py 字段解析与 其余长函数深耦合,本轨如实停、不硬拆)。

11. 安全审计修复轨 feat-chv-secfix(堆叠于 #239 之上)

审计报告:~/workspace/chv-security-audit/REPORT.md(只读静态 审计,verdict No-Go,Critical 0 / High 7 / Medium 8 / Low 5)。 本节逐项给结论:代码已做 = 本轨代码 + mock 测试已覆盖; 真机未验证 = 代码改了,但动态实效仍须 KVM 宿主验证, 绝不把静态通过写成动态已验。审计 verdict 本身仍是 No-Go, 直到 §11 末的真机清单全部通过——本轨只修代码层,不改 verdict。

11.1 High(全部代码已做、真机未验证)

  • H1 egress 网络层强制:代码能做的全部做了——egress 代理 地址校验(http/https + 主机名)、直连仍须显式允许 + 审计、 每 VM 独立 tap/mac/ip 校验(共享即拒绝并审计)、net 条目 字段白名单。代码做不到的宿主防火墙规则见 §11.4 部署清单, 文档明写“代码门 + 宿主防火墙缺一不可”;env 前缀不再被 写成已隔离。
  • H2 网关 key 移出 argv:密钥子集(SUB2API_API_KEY)在 provisioned 模式下随 seed 写进客内 /etc/chv/guest-env (0600 root),远程命令 source 该文件;传输层对密钥名 进 argv 直接 ValueError(fail-closed)。比 argv 安全的理由: 值既不在宿主进程表(ps /proc cmdline)里,也不在宿主子 进程环境(/proc environ)里,只活在 seed.iso(0600,随 VM 销毁而删)与客机内——客内 agent 本来就有该 key,修 的是“宿主暴露面”,不是“客内可见性”。与 Docker 线的差异: Docker 是“-e 只传名、值由 CLI 子进程环境提供”(值仍在 宿主进程环境里),CHV 是“值只进 seed 客内文件”(宿主 进程环境里也没有),比 Docker 线更严。keyscan 模式无 seed 可投递,有密钥即 fail-closed(secret_env_requires_ provisioned),不再将就跑。残留清理:seed 目录与 seed.iso 随 stop 而删,崩溃残留由 cleanup_stale_secrets 扫描。
  • H3 任务盘删除:stop/recycle/cleanup 路径删 disk.raw (保留期 0,需取证须在 shutdown 前经 SSH 拷出),删失败 记 vm_disk_delete_failed 审计 + 脏标记;gc_stale_disks 扫描非 live VM 的残留盘并审计。
  • H4 默认值改 provisioned:CHV_HOST_KEY_MODE 默认 provisioned,keyscan 只作显式 opt-in;旧测试的 CHV_SSH_FORWARD=1 等实验室宣称已明确标注为实验室路径, 不再写成生产默认。
  • H5 seed.iso 权限与私钥清理:seed.iso 生成即 0600;宿主 私钥在 seed 盘建好后即删(只留公钥 + known_hosts),删 失败 fail-fast(VM 尚未启动);私钥 chmod 失败不再静默; 启动/清理时 cleanup_stale_secrets 扫描残留私钥与 seed。
  • H6 state_dir 出 /tmp:默认 ~/.local/share/codex-app-server/chv-vms,preflight 建目录 并保证 0700(失败即 state_dir_insecure);api-socket 组/ 其它可写即 fail-fast(socket_permissions_insecure;仅禁 写位,VMM 默认 0755 类权限不误杀——VMM 默认值代码控制 不了,真机权限仍须按清单核对)。
  • H7 tap 隔离校验:NET_JSON 透传前校验 tap/mac/ip 跨 VM 不重复(wake 成功后登记 net.json,stop 时释放预约), 共用即拒绝并审计;东西向隔离的宿主防火墙部分见 §11.4, 不写进代码假装已防。

11.2 Medium(每项有修复或有理由的结论)

  • M1 SSH 远程命令转义:远程 token 全部 shlex.quote 后仍以 列表返回(ssh 拼接后远程解析结果与本地 list 语义一致), prompt 里的 ;/$()/引号不再改写执行语义。边界写清:客内 已是 root,本项防的是语义篡改与审计失真,不是客内提权。
  • M2 清理失败同名复用旧 key:彻底根治,选“清理失败即拒起” (脏标记 + provision/wake 双重拒绝),不再写“已审计即可”。
  • M3 磁盘配额默认与快照上限:CHV_DISK_GB 默认 50 GB (未设不再不查,0/负数拒绝);快照每 VM 数量默认 10、 总量默认 20 GB(超帽最旧优先剪枝)、保留期默认 30 天。
  • M4 供应链版本钉死:已钉死——云镜像 SHA256 必填(干跑验 “已设”、全量验哈希)、Ubuntu 版本与 codex 版本均为必填 环境变量;BUILDINFO 新增 versions(构建时从客机读回的 dpkg + pip freeze 实际版本,可审计、可复现比对)。未钉死 的清单:apt/pip 事前精确版本 lockfile(须在构建宿主上 生成一次、再固化进脚本,本轨无构建宿主、未编造版本号); Playwright Chromium 二进制版本同样只随 playwright pip 版本记录,未单独钉死。
  • M5 审计补齐:host_key_generated/host_key_captured/ host_key_mismatch(stderr 含钉死拒绝标记时立即 fail-fast, 不轮询成普通超时)、preflight_failed、wake 前置失败 (容量/配额/net 冲突)均独立审计;审计回调失败记宿主 error 日志,不再双重静默。worker 层 _audit 本身仍 best-effort(未改,见 §7 既定语义)。
  • M6 reuse 实验标志单租户校验:代码能校验的已校验—— 实验放行须同时声明 CHV_SINGLE_TENANT_PROJECT,否则 preflight 拒绝(reuse_tenant_undeclared);不能校验的 部分明写:代码无法验证数据库里是否真的只有一个租户, 声明 + 审计就是本项的机制,不假装已校验。
  • M7 容量计数残留 socket 误伤:只有真能连通的 socket 才 计数,空文件/死 socket 不计入 CHV_MAX_VMS;竞态窗口 写清(刚启动未 listen 的 VMM 会被判死,容量门是护栏 不是分布式锁)。
  • M8 sshd 加固进构建:build.sh 写入 /etc/ssh/sshd_config.d/99-chv.conf(PasswordAuthentication no、 PermitRootLogin prohibit-password、ChallengeResponseAuthentication no、 KbdInteractiveAuthentication no、PubkeyAuthentication yes), 并经 sshd -t + grep 断言;README 声称与构建一致。

11.3 Low(顺手做)

  • L1 磁盘拷贝符号链接:目标是链接时先删链接本身再拷贝, 不跟随写入目标文件(TOCTOU 窗口已在 disk.py 写清, state_dir 0700 是前提)。
  • L2 磁盘/快照路径白名单:task_disk/seed_disk 须在 state_dir 内(reuse 的基础镜像本身除外);file:// 快照 须在 state_dir 或 CHV_SNAPSHOT_DIR 内,远端协议须显式 CHV_ALLOW_REMOTE_SNAPSHOT=1。
  • L3 失败审计:preflight/容量/配额/net 冲突失败均发 vm_failed 审计(见 M5)。
  • L4 CHV_NET 与 CHV_NET_JSON 双变量:双设时发 net_config_dual 警告审计(不硬拒,CHV_NET 是手动直接 启动命令的透传值);NET_JSON 条目字段白名单 (tap/mac/ip/mask),未知字段直接拒绝。
  • L5 WarmPool 预热盘与任务盘对应关系:定义已写进 pool.acquire 文档——预热盘是干净快照盘,不是任务盘; 本轨定案为实验性(ADR-5),不再作为接线待办。

11.4 宿主防火墙部署清单(代码做不到的部分,缺一不可)

  • 每 VM 独立 tap + 独立 mac + 独立静态 ip(运维分配, 代码在 wake 时校验冲突,但分配本身是运维责任);
  • tap 桥接默认 DROP,只放行到 egress 代理的流量 (iptables/nftables:默认 DROP + 仅代理地址/端口 ACCEPT, 不给 tap 直连外网的 NAT,不把 tap 桥接到宿主内网网段);
  • 东西向隔离:VM 间默认禁通(每 VM 独立 tap + 桥接隔离, 或每 tap 独立网段 + 转发禁令),ARP spoof 场景下 SSH 钉死仍是第二道门,不是唯一门;
  • 宿主进程隔离:worker 用户最小化、VMM api-socket 所在 state_dir 0700 且仅 worker 用户可访、宿主启用 hidepid 收紧 /proc 可见性、进程表采集不记录完整 cmdline;
  • 部署后核对:state_dir 权限、socket 实际权限、 tap 规则实测(见 §11.5 否定测试)。

11.5 仍需真机验证的清单(本轨一律未做,不声称已验)

  1. 否定测试:客内 unset 代理后 curl 外网必须不通; VM A 扫 VM B / spoof B 的 ip 后 SSH 必须被钉死拒绝;
  2. provisioned 首启时序:seed 先应用再起 sshd、不重生成 host key;sshd -T 实际输出与密码登录关闭;
  3. 宿主 SSH argv 实测:真机 ps 下网关 key 不可见;
  4. 长跑/崩溃注入:残留盘总量、快照增长、残留 socket 与 seed/私钥残留的实际表现、GC 覆盖;
  5. 压测:配额与容量门在并发下的实际表现;
  6. 渗透类:任何以“拿到宿主 shell / 读到别租户数据”为 目标的真实攻击尝试——本轨一律未做,静态通过不等于 已证明不可逃逸;
  7. 审计 verdict:以上全部通过后,才能把 verdict 从 No-Go 改为“有条件启用(默认关闭 + 灰度单租户/受信项目)”, 不直接全量多租户。

11.6 本轨明确不做

  • VM 控制按钮、预热池管理页、成本/配额新页面(前端零改动);
  • 真机动态渗透、压测(无 KVM,做不了,只列清单);
  • WarmPool dispatch 前查池接线(已定案不做,见 ADR-5;重启须先 实现任务盘重供给并补不变量测试)。

12. 架构 9.5+ 提升轨 feat-chv-arch95plus(堆叠于 #252 之上)

验证方式标注(本轨新增,逐条不混淆):静态 = 代码 + mock 测试已覆盖;真机 = 须 KVM 宿主验证;压测 = 须并发负载验证。 凡写“代码已做”而未标真机/压测的,一律只代表静态通过。

  • 长函数:cloud/chv/ 全部函数 ≤50 行(静态,mock 测试覆盖 提取后的 stop/wait/restore/清理/接收/校验/组装段)。
  • WarmPool 生产隔离:生产路径只调 reconcile facade(静态); 预热收益仍是实验性,真机收益未验证(真机)。
  • #215 对齐:目录门与 tmpfs 口径已落代码(静态);超时与 美元配额无映射,#215 合并后替换为 import 仍须重跑测试 (合并后,静态)。
  • 审计失败:记日志 + 计数 + 不中断(静态);审计完整性在 DB 故障下的实际表现仍须真机/故障注入验证(真机)。
  • 供应链:BUILDINFO 事后 lockfile 校验脚本已落(静态); 事前 apt/pip 精确 lockfile 仍缺(须构建宿主,外部)。
  • 只读 VM 字段:chv_vm_dead 派生自 worker 已写回的 interrupted_reason(静态,mock 测试覆盖);live VM 状态 仍无字段,VM 控制按钮仍不做(后端无控制端点 + 安全 No-Go)。
  • 合并前 diff 复核清单(合并时逐项打勾,不在本轨内做): server 白名单、worker 断点分支、sandbox re-export、 console.html 构建产物与 src 一致、#215 合并后的 import 替换点。
  • 真机验证清单(沿用 §11.5,另加本轨项):VM 死亡横幅在 真任务中断后是否出现、审计失败计数在 DB 故障时是否 累加且任务不中断、快照白名单目录门在符号链接逃逸时 是否拒绝。
  • 灰度启用清单(启用时逐项打勾):CI 变绿 → 按 #216→#252→ 本 PR 顺序合并 → KVM 宿主 + 防火墙清单 → 镜像构建发布 → §11.5 + 本节真机清单全过 → 默认关闭下单租户/受信项目 灰度 → 全量多租户(须另行决策,不在本轨内默认开启)。