SerenityOS SSH Server 每次重启后客户端提示主机密钥变化,如何用静态主机密钥修复
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
如果你通过 SSH 连接 SerenityOS 的 SSH 服务器,会发现一个反复出现的问题:每次重启系统(或重新构建磁盘镜像)后,SSH 客户端都会报告服务器主机身份发生了变化(如WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED),必须再次清除本地known_hosts里的旧记录才能连接。原因是 SerenityOS 自带的 SSHServer 目前只支持生成 ephemeral(临时)主机密钥:每次启动时现场生成一对新的 ED25519 密钥作为服务器身份,客户端检测到身份变化后会将其视为中间人攻击的迹象并中止连接。
修复方法来自官方文档 Documentation/SSHServer.md:手动生成一对 ED25519 密钥对作为固定主机密钥,通过sync-local.sh构建脚本把它写进磁盘镜像的/etc/ssh/目录。重启后服务器加载的始终是同一对密钥,客户端就不会再提示身份变化。
为什么每次重启主机密钥都会变
服务端实现可以印证这一行为。ServerConfiguration.cpp 中ensure_ssh_ed25519_keys()的逻辑是:
- 优先从
/etc/ssh/host_ed25519(私钥)和/etc/ssh/host_ed25519.pub(公钥)加载静态主机密钥; - 两个文件都不存在时,回退到
Crypto::Curves::Ed25519::generate_private_key()现场生成一对临时密钥——这就是每次重启密钥都变化的根源; - 如果文件存在但加载失败(例如公私钥不匹配),服务器会打印
Unable to use the host key from /etc/ssh/:加具体错误,同样回退到临时密钥。其中公私钥不匹配对应 源码 中的Corrupted host key错误,服务器启动时会在控制台输出对应的dbgln信息,可作为排查依据。
因此只要把正确的密钥对放进镜像的/etc/ssh/,问题就能根治。
准备条件
- 已按 BuildInstructions.md 完成 SerenityOS 构建,并习惯用
Meta/serenity.sh run等方式运行/重建磁盘镜像; - 宿主机上有
ssh-keygen(用于生成密钥对); - 项目根目录下可以创建
sync-local.sh。AdvancedBuildInstructions.md 说明该脚本的用途:构建时向磁盘镜像的文件系统中添加、修改或删除文件,且变更可以在镜像重建后持久生效。Meta/build-root-filesystem.sh 在每次构建根文件系统时执行它:
# Run local sync script, if it exists if [ -f "${SERENITY_SOURCE_DIR}/sync-local.sh" ]; then sh "${SERENITY_SOURCE_DIR}/sync-local.sh" fisync-local.sh在构建环境中以mnt目录为磁盘镜像根目录执行,脚本内的mnt/...路径对应 SerenityOS 内的/...路径,无需替换其他变量。
第一步:在宿主机生成 ED25519 主机密钥对
Documentation/SSHServer.md 明确只支持 ED25519 密钥,其他类型(RSA、ECDSA 等)不可用。主机密钥与客户端认证用的密钥对相互独立,文档建议不要放在~/.ssh下,而是单独存一个目录,例如:
ssh-keygen -t ed25519 -f path/to/serenity/ssh_keys/host_ed25519命令中的path/to/serenity/ssh_keys/host_ed25519是文档给出的示例路径,你可以换成任何自己指定的位置,但后文sync-local.sh里的路径必须与这里一致。执行后会生成两个文件:host_ed25519(私钥)和host_ed25519.pub(公钥),两者都要放进镜像。
第二步:配置 sync-local.sh,把密钥对写入镜像
如果项目根目录还没有sync-local.sh,先创建它(参照 AdvancedBuildInstructions.md 的示例加上#!/bin/sh与set -e),然后把下面这段文档给出的命令追加进去。cp后面的*会同时拷走私钥和公钥两个文件;chown/chmod将文件属主设为 root、权限收敛为属主只读(u=r,g=,o=),因为镜像中 root 的 umask 相关配置由构建脚本统一管理,这一步按文档原样保留即可:
mkdir -p mnt/etc/ssh/ cp path/to/serenity/ssh_keys/host_ed25519* mnt/etc/ssh/ chown root:root mnt/etc/ssh/host_ed25519* chmod u=r,g=,o= mnt/etc/ssh/host_ed25519*mkdir -p保证目录已存在时不会报错,所以这段内容在后续镜像重建时反复执行是安全的。
第三步:重建磁盘镜像
保存好sync-local.sh后,按你平时的方式重建镜像(例如Meta/serenity.sh run)。镜像构建过程中,各build-image-*.sh脚本都会调用 Meta/build-root-filesystem.sh,其中会在安装完基础系统后执行你的sync-local.sh,密钥对随之写入镜像的/etc/ssh/目录。
注意:该机制依赖每次重建都执行sync-local.sh。如果镜像是在没有该脚本的情况下构建的,/etc/ssh/host_ed25519不存在,服务器仍会回退到临时密钥,问题依旧。
验证:重启后客户端不再提示身份变化
- 重建镜像后启动系统,用与之前相同的方式建立 SSH 连接。
- 重启 SerenityOS,再次连接。预期现象:不再出现“远程主机身份已变化”的警告,无需再删除客户端
known_hosts中的旧条目。这正是文档描述的目标状态——“If this identity changes between two connections, clients will detect it and abort”,而静态密钥让身份在多次连接之间保持不变。 - 如果警告依旧出现,检查服务器启动时的控制台输出:出现
Unable to use the host key from /etc/ssh/: ...说明密钥文件缺失或不合法(例如Corrupted host key表示公钥与私钥不匹配),此时服务器已悄悄回退为临时密钥。对照确认两点:sync-local.sh中的路径是否指向真实存在的密钥文件;私钥与公钥是否出自同一次ssh-keygen。
限制与边界
- 主机密钥只支持 ED25519;
ServerConfiguration在读取静态密钥后会对密钥类型做SSH_ED25519校验,公钥文件还要求恰好一行、且公钥必须能由私钥推导出来,否则按加载失败处理并回退临时密钥。 - 客户端认证是另一个独立问题:该服务器只支持
publickey认证,客户端公钥需要安装到用户主目录的.config/ssh/authorized_keys(例如通过sync-local.sh追加mnt/home/anon/.config/ssh/authorized_keys)。本文只处理主机密钥这一侧;如果连接在认证阶段失败,属于客户端授权密钥的问题,需按 Documentation/SSHServer.md 的 “Authentication” 一节另行配置。
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考