diff --git a/cloud/chv/hostkeys.py b/cloud/chv/hostkeys.py index e6e716d..b118857 100644 --- a/cloud/chv/hostkeys.py +++ b/cloud/chv/hostkeys.py @@ -74,13 +74,23 @@ def delete_host_private_key(state_dir: str, vm_name: str) -> str: def known_hosts_line(port: int, public_key_text: str, host: str = "127.0.0.1") -> str: """公钥文件内容转 known_hosts 行:只取前两列(类型+数据)。 - 公钥文件形如 'ssh-ed25519 AAAA... [comment]',known_hosts - 行形如 '[]: ssh-ed25519 AAAA...'(host 缺省 - 回环,仅作兼容;客机 IP 方案下 host 应为解析出的客机 IP)。 - 非法内容 ValueError,绝不把陌生行原样写进文件。""" + 公钥文件形如 'ssh-ed25519 AAAA... [comment]'。22 端口 + (OpenSSH 默认端口)时行形如 ' ssh-ed25519 + AAAA...'(裸 host 名);非 22 端口时才用 + '[]: ssh-ed25519 AAAA...'。OpenSSH 查 + known_hosts 时,默认端口只查裸 host 名、非默认端口 + 只查括号形式,写错即永远匹配不上(2026-11-07 WSL + 真机实测:provisioned 模式钉死 key 写成 + '[192.168.10.10]:22',22 端口 ssh 报 Host key + verification failed;keyscan TOFU 输出裸行所以 + lite9 没暴露)。host 缺省回环,仅作兼容;客机 IP + 方案下 host 应为解析出的客机 IP。非法内容 + ValueError,绝不把陌生行原样写进文件。""" parts = (public_key_text or "").strip().split() if len(parts) < 2 or not parts[0].startswith("ssh-"): raise ValueError("host 公钥格式非法") + if int(port) == 22: + return "%s %s %s" % (host, parts[0], parts[1]) return "[%s]:%s %s %s" % (host, port, parts[0], parts[1]) diff --git a/cloud/tests/test_chv_gaps.py b/cloud/tests/test_chv_gaps.py index c7d067b..88eb9b2 100644 --- a/cloud/tests/test_chv_gaps.py +++ b/cloud/tests/test_chv_gaps.py @@ -318,6 +318,17 @@ class HostKeyProvisionTest(ChvGapCase): self.assertFalse(os.path.exists(r["paths"]["private"])) self.assertTrue(os.path.exists(r["known_hosts"])) + def test_known_hosts_line_port_22_is_bare(self): + # 22 端口写裸 host 行:OpenSSH 默认端口只查裸名, + # '[host]:22' 永远匹配不上(2026-11-07 WSL 真机)。 + self.assertEqual( + known_hosts_line(22, "ssh-ed25519 AAAAx comment", + host="192.168.10.10"), + "192.168.10.10 ssh-ed25519 AAAAx") + self.assertEqual( + known_hosts_line(2222, "ssh-ed25519 AAAAx comment"), + "[127.0.0.1]:2222 ssh-ed25519 AAAAx") + def test_known_hosts_line_rejects_bad_key(self): with self.assertRaises(ValueError): known_hosts_line(2222, "not-a-key")