diff --git a/chv-guest/build.sh b/chv-guest/build.sh index 2ccf755..e33c10e 100755 --- a/chv-guest/build.sh +++ b/chv-guest/build.sh @@ -19,9 +19,10 @@ # # Gitea 双向同步(用户工作区口径,2026-11-06 追加):镜像里加 # git + gt 1.0.0(官方 install.sh + SHA256 校验,跟 170/172 -# 同款),配好 git user 与 credential helper 占位,不含真实 -# token;客机启动时 /usr/local/bin/chv-sync boot 按 seed 盘 -# /etc/chv/guest-env(0600)把用户选定的仓 clone 到 +# 同款),配好 git user,不含真实 token,不设 credential helper +#(round-11 真机修复层 5:store helper 已去掉,authd helper 是 +# 唯一凭证 helper,且自身绝不落盘);客机启动时 chv-sync boot +# 按 seed 盘 /etc/chv/guest-env(0600)把用户选定的仓 clone 到 # ~/workspace,后续 pull/push 由用户显式同步,分叉/冲突时 # 失败关闭(不 --force、不自动 rebase)。token 绝不进镜像、 # 代码、日志。 @@ -309,7 +310,7 @@ VC_ARGS=(-a "$OUT/disk.raw" --run-command 'mv /usr/local/bin/authd.py /usr/local/bin/chv-authd && chmod +x /usr/local/bin/chv-authd /usr/local/bin/git-credential-chv-authd' --run-command 'systemctl enable chv-authd || true' --run-command 'printf "runcmd:\n - systemctl restart ssh || service ssh restart || true\n - systemctl start chv-authd || true\n - test -f /etc/chv/guest-env && chown chv:chv /etc/chv/guest-env || true\n - /usr/local/bin/chv-sync boot >>/var/log/chv-sync.log 2>&1 || true\n" > /etc/cloud/cloud.cfg.d/99-chv-sync.cfg' - --run-command 'git config --system user.name chv && git config --system user.email chv@localhost && git config --system credential.helper store && git config --system init.defaultBranch main' + --run-command 'git config --system user.name chv && git config --system user.email chv@localhost && git config --system init.defaultBranch main' --run-command "$GT_INSTALL_CMD" --run-command '/usr/local/bin/gt version > /app/guest-versions-gt.txt' --run-command 'printf "ssh_deletekeys: false\nbootcmd:\n - \"[ -f /etc/ssh/ssh_host_ed25519_key ] || ssh-keygen -A\"\n" > /etc/cloud/cloud.cfg.d/99-chv-hostkey.cfg; systemctl enable ssh || true' diff --git a/chv-guest/chv-sync.sh b/chv-guest/chv-sync.sh index 81963ea..a2d1964 100755 --- a/chv-guest/chv-sync.sh +++ b/chv-guest/chv-sync.sh @@ -14,11 +14,12 @@ # 只给客内 authd 守护进程读)。 # boot(root,cloud-init runcmd 调)读 guest-env,authd 在时配 # git credential helper 走 authd 按次取凭证(只进 git 子进程 -# 内存,不落盘);authd 不在时(旧镜像)才回落到把 token 写进 -# /home/chv/.git-credentials(0600,chv 所有,git credential -# store 标准机制)并警告。pull/push/status 以 chv 身份跑,用 -# helper 或已落盘的 credential,不再读 guest-env;token 不会 -# 出现在 git remote URL 或镜像里。 +# 内存,不落盘);authd 不在时直接失败(round-11 真机修复层 5: +# 旧镜像的落盘回落已废止——它在真机测试里把 authd 释放的长效 +# token 写进了客内 ~/.git-credentials;authd helper 是唯一凭证 +# helper,且自身绝不落盘)。pull/push/status 以 chv 身份跑,用 +# helper,不再读 guest-env;token 不会出现在 git remote URL 或 +# 镜像里。 # # 用法: # chv-sync boot # 启动时 clone(已 clone 则静默退出,不自动 pull) @@ -65,19 +66,16 @@ cmd_boot() { : "${GITEA_USER:?guest-env 缺 GITEA_USER}" : "${SYNC_REPO:?guest-env 缺 SYNC_REPO}" [ "$(id -u)" = "0" ] || die "boot 须以 root 运行(cloud-init runcmd)" - local host="${GITEA_URL#https://}" - host="${host#http://}" - host="${host%%/*}" local name="${SYNC_REPO##*/}" local dir="$WORKSPACE/$name" mkdir -p "$WORKSPACE" chown "$SYNC_USER:$SYNC_USER" "$WORKSPACE" # feat-chv-muse-p3:authd 路径——socket 存在时凭证走 helper, # 不再把真 token 写进 .git-credentials 落盘;helper 按次向 - # authd 取凭证,只进 git 子进程内存。socket 不存在(旧镜像) - # 时走旧路径 + 警告(过渡兼容,旧镜像退役后改为 fail-closed)。 + # authd 取凭证,只进 git 子进程内存。socket 不存在时直接失败 + #(round-11 真机修复层 5:落盘回落已废止,见下)。 # 注意:authd 路径下 GITEA_TOKEN 不在 guest-env 里(它只进 - # authd-seed 给守护进程),所以这里不要求它;旧路径才要求。 + # authd-seed 给守护进程),所以这里不要求它。 local authd_socket="${CHV_AUTHD_SOCKET:-/run/chv-authd/authd.sock}" local helper="/usr/local/bin/git-credential-chv-authd" if [ -S "$authd_socket" ] && [ -x "$helper" ]; then @@ -88,14 +86,13 @@ cmd_boot() { runuser -u "$SYNC_USER" -- env HOME="$USER_HOME" git config --global credential.useHttpPath true info "凭证走 chv-authd(不落盘)" else - : "${GITEA_TOKEN:?guest-env 缺 GITEA_TOKEN(旧路径)}" - echo "chv-sync: 警告:chv-authd 未运行,凭证走旧落盘路径(须用含 authd 的新镜像)" >&2 - # token 只落盘进用户家目录的 credential store(0600), - # 不进 git remote URL、不进镜像、不进日志(此处不打印 token)。 - local creds="$USER_HOME/.git-credentials" - printf 'https://%s:%s@%s\n' "$GITEA_USER" "$GITEA_TOKEN" "$host" > "$creds" - chmod 0600 "$creds" - chown "$SYNC_USER:$SYNC_USER" "$creds" + # round-11 真机修复(层 5):authd 不在时直接失败,不再 + # 把真 token 写进 ~/.git-credentials 落盘——旧路径在 + # round-11 真机测试里把 authd 释放的长效 token 写进了 + # 客内磁盘(/home/chv/.git-credentials,0600)。authd + # helper 是唯一凭证 helper,且自身绝不落盘;旧镜像退役 + # 后过渡兼容即废止,不再留落盘逃生口。 + die "chv-authd 未运行(socket 或 helper 缺失),拒绝同步:凭证绝不落盘,请用含 authd 的镜像" fi if [ -d "$dir/.git" ]; then info "已 clone:$dir(不自动 pull,改动由用户显式同步)" diff --git a/cloud/tests/test_chv_gaps.py b/cloud/tests/test_chv_gaps.py index a193eac..1717511 100644 --- a/cloud/tests/test_chv_gaps.py +++ b/cloud/tests/test_chv_gaps.py @@ -732,6 +732,31 @@ class ChvSyncTest(unittest.TestCase): r = self._sync("boot", env_extra={"CHV_GUEST_ENV": env_file}) self.assertNotEqual(r.returncode, 0) + def test_boot_without_authd_fails_closed_no_credentials_file(self): + # round-11 真机修复(层 5):authd 不在时 boot 直接失败, + # 绝不把 token 写进 ~/.git-credentials(旧落盘回落已废止; + # 即使 guest-env 里有 token 也不写)。 + if os.getuid() != 0: + self.skipTest("boot 须以 root 运行") + env_file = os.path.join(self.dir, "guest-env") + with open(env_file, "w") as fh: + fh.write("GITEA_URL=https://gitea.tose.top\n" + "GITEA_USER=admin\n" + "SYNC_REPO=admin/chv-sync-test\n" + "GITEA_TOKEN=fake-token-for-test\n") + home = os.path.join(self.dir, "home") + os.makedirs(home, exist_ok=True) + r = self._sync("boot", env_extra={ + "CHV_GUEST_ENV": env_file, + "CHV_USER_HOME": home, + "CHV_USER": "root", + "CHV_AUTHD_SOCKET": os.path.join(self.dir, "no-authd.sock"), + }) + self.assertNotEqual(r.returncode, 0) + self.assertIn("chv-authd 未运行", r.stderr) + self.assertFalse( + os.path.exists(os.path.join(home, ".git-credentials"))) + # -- (6) 中途死亡 → interrupted → 断点续跑闭环 --------------------------- class MidDeathLoopTest(ChvGapCase): diff --git a/cloud/tests/test_chv_muse_p2.py b/cloud/tests/test_chv_muse_p2.py index 4b4e505..8d8d9d9 100644 --- a/cloud/tests/test_chv_muse_p2.py +++ b/cloud/tests/test_chv_muse_p2.py @@ -632,6 +632,10 @@ class BuildDryRunTest(unittest.TestCase): self.assertIn(needle, out, needle) # P1 文件不在本分支:干跑只警告、不失败。 self.assertIn("vsock_notify.py 不在仓库里", out) + # round-11 真机修复(层 5):镜像不再设 store helper + #(authd helper 是唯一凭证 helper),git user 仍配。 + self.assertNotIn("credential.helper store", out) + self.assertIn("git config --system user.name chv", out) if __name__ == "__main__": diff --git a/docs/chv-muse-p3-gap.md b/docs/chv-muse-p3-gap.md index 63f8477..7173785 100644 --- a/docs/chv-muse-p3-gap.md +++ b/docs/chv-muse-p3-gap.md @@ -106,10 +106,16 @@ authd 保管、代理只见替身令牌;所有连接器动作与网络出口 凭证前评估),不是网络层强制。客内任意进程的直连仍由 Tinyproxy 白名单 + 宿主防火墙(第三棒)兜底;eBPF cgroup 拦截 + LSM 污点传播 是后续工作,不得写成“出口已全部经 Sentinel”。 -5. **过渡兼容留了旧路径**:`chv-sync boot` 在 authd socket 不存在时仍走 - 旧的 `.git-credentials` 落盘(只多一句 stderr 警告),为的是 lite9/ - lite10 旧镜像不被一次改断。旧镜像退役后应删掉该回落,改为无 socket - 即 fail-closed;删除前不得声称“旧路径已关闭”。 +5. **过渡兼容已关闭(round-11 真机修复,层 5)**:此前 `chv-sync + boot` 在 authd socket 不存在时仍走旧的 `.git-credentials` 落盘 + (只多一句 stderr 警告),为的是 lite9/lite10 旧镜像不被一次 + 改断。round-11 真机测试证实该回落把 authd 释放的长效 token 写 + 进了客内磁盘,修复轮已改为无 socket 即 fail-closed(`die`), + 同时镜像 `/etc/gitconfig` 的 `credential.helper store` 也已删 + 除——authd helper 是唯一凭证 helper,且自身绝不落盘。旧镜像 + (lite10 及更早)上 boot 现在会直接失败,须用含 authd 的新 + 镜像;真机复测须验证跑完一次完整 Gitea 链后客内 + `~/.git-credentials` 不存在。 6. **无真机验证**:本轮全部是代码 + 本地单测(含真 git helper 端到端, 但 authd 守护进程本身未在真 VM 里跑过)。真机待验证清单:authd 在 lite11 镜像里启动时序(须在 `chv-sync boot` 之前就绪)、SO_PEERCRED