merge(chv-muse-round11): 合入 P3 authd 替身凭证 + Sentinel 出口审批 (a90cf78)

冲突解决(3 处,均为并集保留,双方改动无一丢弃):
- cloud/chv/executor.py _copy_config_fields:P1 的 balloon/
  vsock/vsock_cid 与 P3 的 sentinel_allow_hosts 合并进同一元组
- cloud/tests/test_chv_arch_maint_fe.py EXPECTED_FIELDS:P1 三字段
  + P3 sentinel_allow_hosts;len 断言 41/39 → 42(并集)
- cloud/chv/config.py 自动合入:P3 sentinel_allow_hosts(网络
  片段内)+ P1 _balloon_vsock_fragment 均保留;仅更新
  build_chv_config docstring 注明字段集含 P3 字段
This commit is contained in:
host committed 2026-11-06 21:36:56 +00:00
commit 304707b372
15 files changed
+2376 -95

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
# 对齐 Muse P3 差距表:authd 替身凭证 + Sentinel 式出口审批
依据:`~/workspace/research_notes/meta-muse-ai-architecture-20261101-0755/report.md`
(官方以 Meta security.muse.ai 2026-09-08 为准;第三方拆解只作参考,
逆向细节不得写成官方架构)。Muse 模型一句话:凭证由 VM 内独立服务
authd 保管、代理只见替身令牌;所有连接器动作与网络出口由 Sentinel
唯一审批(allow/deny/ask,L4+L7 评估,SSRF 防护,严格能力绑定);
审批 UI 不在对话内;连接器执行走 privsep worker,按连接器做凭证白名单。
本轨范围只限 P3:authd 式替身凭证 + Sentinel 式出口审批。
不碰 P1(常驻 VM、WarmPool、气球、vsock)、不碰 P2(客机 nspawn 单元、
浏览器子代理独立)。分支 `feat-chv-muse-p3`(从 `feat-chv-cover100`
分出,基线 HEAD `06c3e17`)。
## 1. 现状 vs Muse
| 项 | 现状(实测/代码为准) | Muse 目标 | 本轨做法 |
|---|---|---|---|
| Gitea token 可见面 | 真 token 经 seed 写进 `/etc/chv/guest-env`(0600 root);`build_ssh_command` 在每个任务的远程命令前 `source` 该文件,codex 进程环境里直接含真值;`chv-sync boot` 再把它写进 `/home/chv/.git-credentials` 落盘 | authd 保管凭证,代理只见替身令牌;凭证存入运行时单元之外的安全凭证存储,代理不可见;仅在需要时注入 | 真 token 改走 seed 里的独立文件 `/etc/chv/authd-seed`(0600 root),只给客内 authd 守护进程读;guest-env 不再含 GITEA_TOKEN;任务期 `build_ssh_command` 不再把 GITEA_TOKEN source 进 codex 环境;`chv-sync` 经 git credential helper 按次向 authd 取凭证,只进 git 子进程内存,不落盘、不进 agent 环境/argv/日志 |
| authd 位置 | 无 | 存于用户 VM 内(非 Meta 集中基础设施),在运行时单元之外 | 客内 authd 守护进程(`chv-guest/authd.py`,标准库 Unix socket 服务),以 root 身份跑在单元外;宿主侧不存任何 VM 的 token。选客内不选宿主的理由:Muse 的 authd 就在用户 VM 里;宿主侧方案要走 vsock(P1 范围、未就绪),且会把全部 VM 的 token 集中到宿主,违背“存于用户 VM”的口径 |
| 替身令牌 | 无(真值直给) | authd 签发替身令牌,带作用域与有效期 | 两档:① 本期默认——按次释放:每次 git 操作向 authd 取一次凭证,绑定 {连接器=gitea, 主机, 仓库路径, 操作},单次有效 + 300 秒 TTL,过期即废,释放全程审计(审计里无值);② 真铸 token:authd 可经 Gitea API 铸短期专用 token(`POST /api/v1/users/{user}/tokens`),默认关闭,见诚实边界第 2 条 |
| 凭证白名单(privsep) | 无连接器概念;`_CHV_SECRET_ENV_NAMES` 只有“密钥/非密钥”二分 | 每个 privsep worker 有明确凭证白名单(日历 worker 不能仅改参数就索取邮件凭证) | authd 只认一个连接器 `gitea`,白名单只含 GITEA_TOKEN;未知连接器/越权主机/仓库/操作一律拒绝;SUB2API_API_KEY 仍走 guest-env(见诚实边界第 3 条) |
| 出口审批 | 只有代码门:preflight 要求代理或显式直连 + `validate_egress_proxy` 格式校验;真正的隔离靠 Tinyproxy 白名单 + 宿主防火墙(第三棒落地中);客内 root 绕过代理直连在代码层无第二道门 | Sentinel 是连接器动作与全部网络出口的唯一权限机构;L4+L7 评估(主机名、解析后 IP、端口、协议、HTTP 方法、路径、解码后请求);SSRF 防护;审批是严格能力(绑定连接器/目的地/用例,一次性/会话级/任务级/时限级) | `cloud/chv/sentinel.py` 决策引擎:每动作审批钩子 allow/deny/ask;L4(解析后 IP、端口、协议)+ L7(主机、方法、路径)评估;SSRF 挡截(公开主机名解析到私有/回环/链路本地/CGNAT/保留地址即 deny);能力绑定 {连接器, 主机, 方法, 路径, 有效期, 次数};ask 记入待审批队列、执行停止。本期两个接线点:① wake 时 Gitea 出口预审(解析失败/私网 IP/不在白名单 → 起 VMM 前 fail-closed);② authd 放凭证前先过 Sentinel 评估 |
| 审批 UI | 无 | Sentinel 直接向客户端发审批请求,弹窗在客户端 UI 内,答复直接回 Sentinel;目的:防对话内容伪造审批 | 本期只做设计约束 + 待审批队列(`sentinel.ApprovalQueue`,内存态 pending 记录):ask 出现即停止执行、记入队列,不自动放行;UI 不在对话内这一条记为约束,不在本期实现任何对话内审批入口 |
| 污点出口(eBPF) | 无 | eBPF cgroup 程序做网络拦截与进程归因、LSM hook 做污点传播 | 列为后续,不冒充已做。见诚实边界第 4 条 |
| 审计 | `_emit` 审计只含 VM 名/路径/URL/kind,不含 key 值(M5) | 审计轨迹展示已做与计划做的事 | 沿用 `_emit` 契约:新增 `sentinel_egress_allow/deny/ask`、`authd_credential_released/refused` 事件,detail 只含连接器/主机/路径/ verdict/原因,不含任何凭证值;authd 客内审计文件 `/var/log/chv-authd.log`(0600)同款口径 |
## 2. 改了什么(本轨交付物)
- `cloud/chv/authd.py`(新,叶子模块,标准库):凭证保险库(只活内存,
`__repr__` 不含值)+ 按次释放(作用域绑定、单次有效、300 秒 TTL、
过期即废)+ 连接器白名单(本期只 `gitea`)+ Gitea API 铸 token
辅助函数(可注入 HTTP 客户端,默认关闭)+ 无值审计记录。
- `cloud/chv/sentinel.py`(新,叶子模块,标准库):决策引擎
`evaluate()`(allow/deny/ask)+ L4/L7 评估 + SSRF 挡截
(解析器可注入,默认 `socket.getaddrinfo`)+ 严格能力
`Capability`(连接器/主机/方法/路径/有效期/次数绑定)+
`ApprovalQueue`(ask 待审批队列,内存态)+ 无值审计记录。
- `cloud/chv/hostkeys.py`:`write_seed` 加 `authd_env` 参数——authd
专用密钥写进独立的客内文件 `/etc/chv/authd-seed`(0600 root),
不再与 guest-env 混写;`AUTHD_SEED_PATH` 常量。
- `cloud/chv/gitea_sync.py`:`AUTHD_SECRET_KEYS = {"GITEA_TOKEN"}` +
`split_authd_env()`(把 GITEA_TOKEN 从密钥集里拆出来走 authd 通道)。
- `cloud/chv/ssh_transport.py`:`_CHV_AUTHD_SECRET_NAMES`;`remote_command`
语义不变(仍只 source guest-env 文件)。
- `cloud/chv/executor.py`:`provision_host_key`/`_provision_seed_disk`
透传 `authd_env`;新增 `_authd_delivered` 投递集(与
`_guest_env_delivered` 同锁)——authd 投递的密钥名在
`build_ssh_command` 里不报错、但不 source 进 codex 环境;
`_wake_prechecks` 加 Gitea 出口 Sentinel 预审(deny/ask 即
`sentinel_egress_denied`,起 VMM 前 fail-closed)。
- `cloud/chv/config.py`:`CHV_SENTINEL_ALLOW_HOSTS`(逗号分隔主机名白名单,
默认只放行配置的 Gitea 主机)。
- `chv-guest/authd.py`(新,标准库守护进程):读 `/etc/chv/authd-seed`
持 token 于内存、Unix socket `/run/chv-authd/authd.sock`(0700 目录、
0600 socket)、SO_PEERCRED 只放行 root 与 chv 用户、每次放凭证前先过
Sentinel 评估、审计落盘 0600。
- `chv-guest/git-credential-chv-authd`(新,标准库 helper):git
credential 协议(stdin 读 `protocol/host/path`,stdout 回用户名/口令),
口令只进 git 子进程内存。
- `chv-guest/chv-authd.service`(新):systemd 单元(root 运行,单元外)。
- `chv-guest/chv-sync.sh`:`boot` 在 authd socket 存在时改配
`credential.helper` 走 authd,不再把真 token 写进
`.git-credentials`;同时配 `credential.useHttpPath=true`
(git 默认不把 path 发给 helper,没有 path helper 无法推断
仓库与操作、每次都拒绝——新测试抓到的真问题);socket 不存在
时走旧路径 + stderr 警告(过渡兼容,见诚实边界第 5 条)。
- `cloud/tests/test_chv_muse_p3.py`(新):替身过期、真 token 泄露面、
SSRF 挡截、deny/ask 路径、seed 拆分、wake 预审、helper 端到端
(真 git `credential fill` + 假 authd socket);全量 cloud 套件全过。
- 镜像集成(只留接入说明,不改 `chv-guest/build.sh`,与 P1 同款规矩,
合并时由 P2 一并带进镜像):
`--copy-in chv-guest/authd.py:/usr/local/bin/`、
`--copy-in chv-guest/git-credential-chv-authd:/usr/local/bin/`、
`--copy-in chv-guest/chv-authd.service:/etc/systemd/system/`、
`--copy-in cloud/chv/authd.py:/usr/local/lib/chv-authd/cloud/chv/`、
`--copy-in cloud/chv/sentinel.py:/usr/local/lib/chv-authd/cloud/chv/`、
`--copy-in cloud/__init__.py:/usr/local/lib/chv-authd/cloud/`、
`--copy-in cloud/chv/__init__.py:/usr/local/lib/chv-authd/cloud/chv/`、
`--run-command 'chmod +x /usr/local/bin/chv-authd /usr/local/bin/git-credential-chv-authd'`、
`--run-command 'systemctl enable chv-authd || true'`、
`99-chv-sync.cfg` 的 runcmd 在 `chv-sync boot` 之前加
`systemctl start chv-authd || true`。
## 3. 诚实边界(本轮不宣称的东西)
1. **seed 文件仍是 0600 root 落盘**:真 token 在 `/etc/chv/authd-seed`
里仍以文件形式存在(cloud-init 的唯一投递通道就是写文件),且 seed
ISO 本身挂在客机上。真正让它对 agent 不可见的是 P2 的 nspawn 单元
隔离(单元内 root 映射为非特权用户,读不到单元外的 0600 文件);在
P2 落地之前、codex 仍以客内 root 跑的镜像上,root agent 理论上仍能
读到它。本轮堵住的是“进程环境/argv/日志/`.git-credentials` 落盘”
这条泄露面,不是“客内 root 万能”——后者是 P2 的边界,不是 P3 能修的。
2. **本期默认的“替身”不是 Gitea 侧可独立校验的新 token**:按次释放给
git 子进程的值仍是真 token,只是只进 git 进程内存、不进 agent。
真铸 token(Gitea API 铸短期专用 token、到期吊销)已实现辅助函数
与假客户端测试,但默认关闭——它要求真 token 有建 token 的权限,
现有测试 token 只有 `write:repository`,铸 token 会失败;开它须另配
有铸 token 权限的 token,并做真机验证。不得把本期默认写成“Muse 同款
替身令牌已落地”。
3. **SUB2API_API_KEY 不在本轮**:网关 key 仍走 guest-env 进 codex 环境
(H2 既有行为)。按 Muse 模型它也该走 privsep worker(由 worker 代
codex 调网关、codex 不见 key),那是网关调用链改造,超出本轮范围,
列为后续,不静默声称已覆盖。
4. **eBPF 污点传播未做**:Sentinel 本轮是决策层(wake 预审 + authd 放
凭证前评估),不是网络层强制。客内任意进程的直连仍由 Tinyproxy
白名单 + 宿主防火墙(第三棒)兜底;eBPF cgroup 拦截 + LSM 污点传播
是后续工作,不得写成“出口已全部经 Sentinel”。
5. **过渡兼容留了旧路径**:`chv-sync boot` 在 authd socket 不存在时仍走
旧的 `.git-credentials` 落盘(只多一句 stderr 警告),为的是 lite9/
lite10 旧镜像不被一次改断。旧镜像退役后应删掉该回落,改为无 socket
即 fail-closed;删除前不得声称“旧路径已关闭”。
6. **无真机验证**:本轮全部是代码 + 本地单测(含真 git helper 端到端,
但 authd 守护进程本身未在真 VM 里跑过)。真机待验证清单:authd 在
lite11 镜像里启动时序(须在 `chv-sync boot` 之前就绪)、SO_PEERCRED
放行 chv 用户、helper 在 pull/push 下的实际表现、wake 预审对真实
Gitea 主机解析的判定、铸 token 的真实 API 行为。测试机(WSL)须等
第三棒 V-1~V-9 让出后再做。