feat(chv): 安全审计修复 High/Medium/Low(堆叠于 #239)
审计报告 ~/workspace/chv-security-audit/REPORT.md(No-Go, Critical 0 / High 7 / Medium 8 / Low 5)代码层修复,mock 测试覆盖,真机动态验证一律未做、只列清单(见 docs/chv-production-readiness.md §11)。 High: - H1 egress 代码门:代理地址校验、直连显式允许+审计、 每 VM 独立 tap/mac/ip 校验、net 字段白名单;宿主防火墙 列为部署清单,代码门+防火墙缺一不可。 - H2 网关 key 移出 argv:provisioned seed 盘客内文件 /etc/chv/guest-env(0600)投递,传输层密钥进 argv 直接 ValueError;keyscan 模式密钥投递 fail-closed。 - H3 任务盘删除:stop/recycle/cleanup 删 disk.raw(保留期 0)+ 残留盘 GC,删失败审计+脏标记。 - H4 默认值改 provisioned:keyscan 只作显式 opt-in。 - H5 seed.iso 生成即 0600;宿主私钥 seed 盘建好后即删; 崩溃残留扫描 cleanup_stale_secrets。 - H6 state_dir 默认出 /tmp(~/.local/share/.../chv-vms, preflight 保 0700);api-socket 组/其它可写即 fail-fast。 - H7 tap 隔离校验:net.json 登记+stop 释放,共享即拒绝审计。 Medium:M1 shlex 远程转义;M2 清理失败即拒起(脏标记); M3 配额默认 50GB + 快照数/总量/保留期上限;M4 SHA256 必填 + BUILDINFO versions 实测记录(事前 lockfile 列清单); M5 host key/ preflight/失败审计补齐;M6 reuse 须声明单租户 项目;M7 容量计数只认连通 socket;M8 sshd 加固进 build.sh 断言。Low:L1 链接不跟随;L2 路径白名单;L3 失败审计; L4 双变量警告+字段白名单;L5 预热盘/任务盘对应关系定义。 旧测试:实验室默认值与生产默认值区分标注;行为已变的旧 测试按新语义更新(容量死 socket、快照白名单、磁盘路径 白名单、网关 argv、reuse 租户声明)。 测试:新 test_chv_secfix 39 项;旧 117 项 + 2 项新增断言 = 158 项;worker 相关 129 项;前端 vitest 9 项;cloud 全量 1588 项(skipped 4)。
This commit is contained in:
1 parent
003ea357f9
commit
baf26beb78
16 files changed
+2106
-132
No files matched your search
+46
-4
@@ -24,6 +24,7 @@ timeouts 模块)、门禁类(preflight fail-fast、容量门、磁盘
|
||||
→ sandbox_policy 模块)。见 PR 描述的复用清单。
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
@@ -65,7 +66,8 @@ def build_chv_config(params, env):
|
||||
cpus = str(params.get("cpus") or env.get("CHV_CPU") or "2")
|
||||
memory = params.get("memory") or env.get("CHV_MEM") or "2G"
|
||||
state_dir = params.get("state_dir") or env.get("CHV_STATE_DIR") \
|
||||
or "/tmp/chv-vms"
|
||||
or os.path.join(os.path.expanduser("~"),
|
||||
".local", "share", "codex-app-server", "chv-vms")
|
||||
ssh_key = params.get("ssh_key") or env.get("CHV_SSH_KEY")
|
||||
if params.get("ssh_user") is not None \
|
||||
and params.get("ssh_user") == "":
|
||||
@@ -117,15 +119,49 @@ def build_chv_config(params, env):
|
||||
raise ValueError(
|
||||
"CHV_DISK_MODE 只能是 copy 或 reuse:%s" % disk_mode)
|
||||
disk_gb_raw = env.get("CHV_DISK_GB")
|
||||
disk_gb = float(disk_gb_raw) if disk_gb_raw else None
|
||||
# feat-chv-secfix(M3):配额默认开启。CHV_DISK_GB 未设时
|
||||
# 不再是 None(配额门直接返回),而是 50 GB 总量预算;
|
||||
# 显式设 0 或负数同样非法——配额门是 fail-closed,不是
|
||||
# “设个小值绕过”。check_disk_quota 仍接受 None(直接
|
||||
# 调用者的第二道门),但执行器路径永远带默认值。
|
||||
try:
|
||||
disk_gb = float(disk_gb_raw) if disk_gb_raw else 50.0
|
||||
except ValueError:
|
||||
raise ValueError("CHV_DISK_GB 必须是数字")
|
||||
if disk_gb <= 0:
|
||||
raise ValueError("CHV_DISK_GB 必须大于 0")
|
||||
egress_proxy = env.get("CHV_EGRESS_PROXY")
|
||||
allow_direct_egress = env.get("CHV_ALLOW_DIRECT_EGRESS") == "1"
|
||||
host_key_mode = params.get("host_key_mode") \
|
||||
or env.get("CHV_HOST_KEY_MODE") or "keyscan"
|
||||
or env.get("CHV_HOST_KEY_MODE") or "provisioned"
|
||||
if host_key_mode not in ("provisioned", "keyscan"):
|
||||
raise ValueError(
|
||||
"CHV_HOST_KEY_MODE 只能是 provisioned 或 keyscan:%s"
|
||||
% host_key_mode)
|
||||
# feat-chv-secfix(M6):reuse 实验放行须同时声明单租户
|
||||
# 项目(部署声明 + 审计,代码无法从环境变量验证数据库
|
||||
# 里是否真的只有一个租户——这一点在文档与 preflight 的
|
||||
# kind 里明写,不假装已校验)。
|
||||
single_tenant_project = env.get("CHV_SINGLE_TENANT_PROJECT")
|
||||
# feat-chv-secfix(M3 快照上限 / L2 快照目录白名单):
|
||||
# 快照数、总量、保留期均有默认值;file:// 快照只允许在
|
||||
# state_dir 或 CHV_SNAPSHOT_DIR 内,远端协议须显式放行
|
||||
# (见 hardening.validate_snapshot_url)。
|
||||
try:
|
||||
snapshot_max_count = int(env.get("CHV_SNAPSHOT_MAX_COUNT")
|
||||
or 10)
|
||||
snapshot_max_total_gb = float(
|
||||
env.get("CHV_SNAPSHOT_MAX_TOTAL_GB") or 20.0)
|
||||
snapshot_retention_days = float(
|
||||
env.get("CHV_SNAPSHOT_RETENTION_DAYS") or 30.0)
|
||||
except ValueError:
|
||||
raise ValueError("CHV_SNAPSHOT_* 必须是数字")
|
||||
if snapshot_max_count < 0 or snapshot_max_total_gb < 0 \
|
||||
or snapshot_retention_days < 0:
|
||||
raise ValueError("CHV_SNAPSHOT_* 不能为负数")
|
||||
snapshot_dir = env.get("CHV_SNAPSHOT_DIR")
|
||||
allow_remote_snapshot = env.get("CHV_ALLOW_REMOTE_SNAPSHOT") \
|
||||
== "1"
|
||||
task_timeout_raw = env.get("CHV_TASK_TIMEOUT")
|
||||
task_timeout = float(task_timeout_raw) \
|
||||
if task_timeout_raw else None
|
||||
@@ -166,4 +202,10 @@ def build_chv_config(params, env):
|
||||
host_key_mode=host_key_mode,
|
||||
task_timeout=task_timeout, snapshot_url=snapshot_url,
|
||||
warm_pool_size=warm_pool_size, api_timeout=api_timeout,
|
||||
ready_timeout=ready_timeout, ready_interval=ready_interval)
|
||||
ready_timeout=ready_timeout, ready_interval=ready_interval,
|
||||
single_tenant_project=single_tenant_project,
|
||||
snapshot_max_count=snapshot_max_count,
|
||||
snapshot_max_total_gb=snapshot_max_total_gb,
|
||||
snapshot_retention_days=snapshot_retention_days,
|
||||
snapshot_dir=snapshot_dir,
|
||||
allow_remote_snapshot=allow_remote_snapshot)
|
||||
Reference in new issue
Block a user