fix(chv-guest): seed user-data 不再自带 runcmd,防顶掉镜像钩子
sync-test 首轮实测:seed 的 runcmd 整体替换了镜像 /etc/cloud/cloud.cfg.d/99-chv-sync.cfg 的 runcmd(cloud-init 不跨配置类型合并,merge_how 救不了 conf.d),chv-sync boot 钩子静默不跑、启动 clone 没跑。 - hostkeys.build_seed_user_data 不再发 runcmd;chmod 600 已由 write_files 0600 保证;sshd 重启搬到镜像自己的 runcmd(cloud-final,sshd 首次启动之后)。 - build.sh:99-chv-sync.cfg 的 runcmd 增加 restart ssh 一行(在 chv-sync boot 之前)。 - README §5 同步硬性要求;回归测试 2 项(无 runcmd、 带 secret 仍单 write_files 列表无 runcmd)。 - 针对性 147 项全过(test_chv_to10/gaps/secfix/fix/ arch95plus)。 本地提交,未推未部署。
This commit is contained in:
1 parent
cbd5ab7c0b
commit
67ca0b86cd
4 files changed
+71
-10
No files matched your search
@@ -15,6 +15,8 @@ import unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import yaml
|
||||
|
||||
try:
|
||||
from cloud.chv.hostkeys import (
|
||||
build_seed_user_data,
|
||||
@@ -330,6 +332,45 @@ class HostKeyProvisionTest(ChvGapCase):
|
||||
with self.assertRaises(ValueError):
|
||||
build_seed_user_data("", "ssh-ed25519 AAAAx")
|
||||
|
||||
def test_seed_user_data_has_no_runcmd(self):
|
||||
# 根因回归(sync-test 残留,2026-11-06 实测):seed
|
||||
# 的 user-data 里绝不能带 runcmd——cloud-init 不跨配
|
||||
# 置类型合并,user-data 的 runcmd 会整体替换镜像
|
||||
# /etc/cloud/cloud.cfg.d/ 里的 runcmd,旧版 seed 的
|
||||
# 两行就这样顶掉了 99-chv-sync.cfg 的 chv-sync boot
|
||||
# 钩子,首轮启动 clone 静默不跑。sshd 重启已搬到
|
||||
# 镜像自己的 runcmd(build.sh 写 99-chv-sync.cfg)。
|
||||
text = build_seed_user_data("PRIV\n", "ssh-ed25519 AAAAx\n")
|
||||
parsed = yaml.safe_load(text)
|
||||
self.assertNotIn("runcmd", parsed)
|
||||
self.assertNotIn("runcmd:", text)
|
||||
self.assertNotIn("systemctl restart", text)
|
||||
self.assertNotIn("merge_how", parsed)
|
||||
# write_files 两条 key 条目仍在,chmod 靠其 0600 保证。
|
||||
self.assertEqual(len(parsed["write_files"]), 2)
|
||||
self.assertEqual(
|
||||
parsed["write_files"][0]["permissions"], "0600")
|
||||
|
||||
def test_write_seed_with_secret_has_no_runcmd(self):
|
||||
# 带 secret_env 时 guest-env 条目仍须并进同一个
|
||||
# write_files 列表(单 write_files 键),且 user-data
|
||||
# 里仍不能出现 runcmd。
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
seed_dir = os.path.join(tmp, "seed")
|
||||
write_seed(seed_dir, "vm1", "PRIV", "ssh-ed25519 AAAAx",
|
||||
secret_env={"GITEA_TOKEN": "s3cr3t v"})
|
||||
with open(os.path.join(seed_dir, "user-data")) as fh:
|
||||
content = fh.read()
|
||||
self.assertEqual(content.count("write_files:"), 1)
|
||||
parsed = yaml.safe_load(content)
|
||||
self.assertNotIn("runcmd", parsed)
|
||||
self.assertEqual(len(parsed["write_files"]), 3)
|
||||
paths = [e["path"] for e in parsed["write_files"]]
|
||||
self.assertIn("/etc/chv/guest-env", paths)
|
||||
# 密钥值经 shlex.quote 引用后才进文件(值本身在 guest-env
|
||||
# 条目内是设计内的,见 hostkeys.write_seed)。
|
||||
self.assertIn("GITEA_TOKEN='s3cr3t v'", content)
|
||||
|
||||
def test_keyscan_is_weak_fallback_only(self):
|
||||
# provisioned 模式下 wake 前必须预置;keyscan 不应被
|
||||
# 当成生产路径:配置值本身可解析,且文档/代码里标弱。
|
||||
|
||||
Reference in new issue
Block a user