开发者第一次连入云端 Mac 时,终端通常会询问是否接受未知主机。手工操作里输入 yes 很快,放进 CI 后改用 StrictHostKeyChecking=no 更快,但两种做法都绕过了身份校验。只要 DNS、地址配置或链路被错误导向,凭据和构建材料就可能发送给错误的机器。正确做法是先从可信终端取得主机密钥指纹,再让开发机和自动化任务只接受这把密钥。
先建立独立的信任基准
主机密钥用于证明“当前连接的是预期中的那台 Mac”,它不同于用户登录所用的私钥。以 Ed25519 主机密钥为例,可通过 XcodeVM 控制台提供的 Web VNC 打开本机终端,在目标机器上读取公钥指纹:
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub -E sha256
记录完整的 SHA256: 值,同时记录实例标识、连接地址和读取方式。不要只通过待验证的 SSH 链路获取这个值,否则验证会形成闭环。
随后在开发机读取远端当前提供的公钥:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -T 5 -t ed25519 "$MAC_HOST" > ~/.ssh/xcodevm_known_hosts.new
ssh-keygen -lf ~/.ssh/xcodevm_known_hosts.new -E sha256
逐字符比较两处指纹。匹配后再安装文件:
install -m 600 ~/.ssh/xcodevm_known_hosts.new ~/.ssh/xcodevm_known_hosts
rm -f ~/.ssh/xcodevm_known_hosts.new
ssh-keyscan解决的是“读取公钥”,不是“证明公钥可信”。没有独立指纹作基准时,扫描结果不能直接进入生产信任文件。
将 known_hosts 与项目隔离
全局 ~/.ssh/known_hosts 适合日常交互,但团队项目最好使用独立文件。这样可以明确审查变更,也不会因个人清理记录而影响自动化任务。
在 ~/.ssh/config 中建立别名:
Host xcodevm-build
HostName 192.0.2.10
User builder
IdentityFile ~/.ssh/xcodevm_build_ed25519
IdentitiesOnly yes
UserKnownHostsFile ~/.ssh/xcodevm_known_hosts
StrictHostKeyChecking yes
HashKnownHosts yes
ServerAliveInterval 30
ServerAliveCountMax 3
IdentitiesOnly yes 可避免客户端依次尝试过多身份文件;StrictHostKeyChecking yes 确保未知或变化的密钥直接失败。连接前检查配置展开结果,避免别名被其他规则覆盖:
ssh -G xcodevm-build |
grep -E '^(hostname|user|identityfile|userknownhostsfile|stricthostkeychecking) '
如果同一项目有多台机器,应为每台机器保存独立条目,并将实例标识写入内部变更记录,不要用一个宽泛别名指向会随机变化的地址。
在 CI 中做指纹固定
CI 不应永久继承某位开发者的个人信任文件。更稳妥的方式是把预期 SHA256 指纹作为受保护变量保存,每次任务先扫描、比较,再建立临时 known_hosts。
set -euo pipefail
: "${MAC_HOST:?MAC_HOST is required}"
: "${EXPECTED_ED25519_SHA256:?fingerprint is required}"
scan_file="$(mktemp)"
known_hosts_file="$(mktemp)"
trap 'rm -f "$scan_file" "$known_hosts_file"' EXIT
ssh-keyscan -T 5 -t ed25519 "$MAC_HOST" > "$scan_file" 2>/dev/null
actual="$(ssh-keygen -lf "$scan_file" -E sha256 | awk '{print $2}')"
if [ "$actual" != "$EXPECTED_ED25519_SHA256" ]; then
printf '%s
' "SSH host key verification failed" >&2
exit 1
fi
install -m 600 "$scan_file" "$known_hosts_file"
ssh \
-o UserKnownHostsFile="$known_hosts_file" \
-o StrictHostKeyChecking=yes \
-o IdentitiesOnly=yes \
"$MAC_USER@$MAC_HOST" \
'uname -m && sw_vers -productVersion'
私钥、预期指纹和主机地址应分别管理。日志可以输出实际指纹帮助排查,但不要打印私钥内容、连接密码或未脱敏的环境变量。
正确处理密钥变化
主机密钥变化可能来自系统重置、主机密钥重新生成、连接地址复用,也可能代表连接被导向错误目标。看到 REMOTE HOST IDENTIFICATION HAS CHANGED 时,应先停止自动任务,不能直接执行删除记录命令后重试。
安全轮换按以下顺序进行:
- 在控制台确认实例标识、连接地址和近期操作是否符合预期。
- 通过 Web VNC 的本机终端重新读取 Ed25519 公钥指纹。
- 让另一位维护者复核新旧指纹及变更原因。
- 扫描远端公钥并验证新指纹,生成新的信任文件。
- 先更新测试任务,完成一次只读连接和环境检查。
- 再更新正式任务,并撤销旧指纹。
若使用哈希化记录,可先定位旧条目:
ssh-keygen -F "$MAC_HOST" -f ~/.ssh/xcodevm_known_hosts
ssh-keygen -R "$MAC_HOST" -f ~/.ssh/xcodevm_known_hosts
第二条命令只能在新指纹已经独立确认后执行。删除旧记录只是轮换动作,不是验证动作。
用检查清单完成验收
上线前至少检查以下项目:
| 检查项 | 合格条件 |
|---|---|
| 指纹来源 | 从目标机器本机终端读取 |
| 密钥算法 | 明确固定 Ed25519,不接受任意算法 |
| 文件权限 | .ssh 为 700,信任文件为 600 |
| 客户端策略 | StrictHostKeyChecking=yes |
| CI 隔离 | 每个任务使用独立临时文件 |
| 变化处理 | 先停任务、再核验、后替换 |
| 日志内容 | 保留指纹与失败阶段,不暴露凭据 |
还应执行一次负向测试:临时把预期指纹改错,确认任务在真正运行远端命令前退出。然后恢复正确值,再验证只读命令、仓库访问和构建入口。这样才能证明保护措施确实参与执行,而不是仅存在于配置文件中。
SSH 信任链不需要复杂系统,关键是把“首次接受”和“变化后重连”从临场点击改成可审查流程。可信终端提供基准,项目文件保存固定关系,CI 在每次连接前验证,任何变化都先停下来核对。完成这些步骤后,远程开发和自动构建才能共享同一套明确的主机身份边界。
常见问题
ssh-keyscan 得到的主机密钥可以直接信任吗?
不可以。ssh-keyscan 只负责读取远端当前返回的公钥,必须再与通过本机终端等独立可信路径取得的 SHA256 指纹比较,匹配后才能写入受信任文件。
主机密钥变化后可以删除 known_hosts 记录并重新连接吗?
不能把删除记录当作验证。应先暂停连接,确认实例地址或系统状态是否变化,再从可信终端重新读取指纹;只有新指纹核对一致后才替换旧记录。
CI 应共用开发者个人的 known_hosts 吗?
不建议。CI 应使用权限为 600 的任务专用文件,显式开启 StrictHostKeyChecking,并在任务结束时删除临时文件,避免不同项目共享信任状态。
XcodeVM 云端 Mac
选择适合当前工作负载的独享物理机
核对内存、存储、节点与计费周期后进入下单配置。所有节点全年正常运行,实际可用性以控制台实时返回为准。