CentOS 7 升级 OpenSSH 9.0p1:源码/RPM与配置迁移
手上还有一批跑着 CentOS 7 的机器sshd 依旧是随系统盘装好的OpenSSH 7.4p1。这个版本是 2016 年底的东西扫描报告里的告警一条压一条运维群里隔三差五就有人问同一件事Linux 上这套 OpenSSH 7.4p 到底怎么干净地升到9.0p1。说实话这个升级不算难但绝对算不上下一步下一步就完事因为 OpenSSH 在 7.5 到 9.0 之间横跨了四五年中间删掉了一批配置指令、关掉了一批老算法、还顺手改了 scp 的底层实现。随便找个网上的教程抄一遍运气好一次过运气不好就是重启完 sshd 起不来、自己也被关在门外。下面这些都是我自己在物理机、虚拟机和几套国产化系统上反复做过之后整理出来的完整链路和踩坑记录。1. 7.4p 为什么要动升级前的动机梳理与风险盘点1.1 老版本真正卡脖子的地方很多人被扫描报告逼着升级其实并不清楚 7.4p1 到底差在哪。我自己的判断标准比较直白安全补丁覆盖、算法代际、以及客户端兼容性这三条线。从安全补丁角度看7.4p1 之后官方陆续修掉的问题里有相当一部分属于远程可达或者本地提权路径而且这些修复只在 8.x、9.x 的新版本里才有靠发行版自己往回移植是补不全的。尤其是某些涉及认证前状态的缺陷暴露在公网 22 端口上的机器风险最高。从算法代际看7.4p1 那个年代的默认协商列表里还保留着不少今天看起来明显老旧的组合比如diffie-hellman-group14-sha1之外的一些 SHA-1 家族密钥交换、以及 3DES、CBC 系列的分组加密。等到 8.x、9.xssh-rsaSHA-1 签名在 8.8 之后直接默认关闭arcfour、blowfish-cbc这类算法干脆从代码里被移除。这套变化带来的直接后果就是升级完之后你那些老旧设备、老版本客户端、老版本 CI 插件可能会连不上——这不是升级失败是新版本的默认策略变了。从客户端兼容性看7.4p1 的问题反而是太宽容。它会老老实实接受一些今天已经不该再用的算法让整条链路的实际安全强度被拉到短板那一侧。所以升级的价值不只是消告警而是把默认协商列表整体抬到现代水平。1.2 升级一次要动的东西比想象中多我见过不少人以为升级 OpenSSH 就是下个 tar 包、configure、make、make install、重启服务。实际上在 CentOS 7 这类系统上完整改动面至少包含下面几块改动对象具体内容容易忽略的程度二进制文件/usr/sbin/sshd、/usr/bin/ssh等一整套低配置文件/etc/ssh/sshd_config里的废弃指令必须清掉高密钥文件主机密钥必须原样保留不能重新生成极高systemd 单元ExecStart 指向的路径要跟着变高PAM 配置/etc/pam.d/sshd必须存在且可用中权限分离目录/var/empty/sshd或/run/sshd的属主和权限中SELinux 标签自定义路径下的二进制和目录要重新打标高子系统路径sftp-server 的实际位置变了高这张表里我标记极高和高的几项基本就是升级后连不上的全部原因来源。主机密钥这一项尤其要注意如果你用make install装到/usr/local新二进制默认去/usr/local/etc找密钥一找不到就自己生成一对新的。用户端下次连接会弹出醒目的主机密钥变更警告所有做严格校验的自动化脚本当场全部失败。1.3 先把回滚路径铺好再动手动手之前我会先做三件事这三件事花不了十分钟但能救命。第一件是备份二进制和配置。不管后面走源码还是走 RPM先把当前的 sshd、ssh、sshd_config、pam.d/sshd 各拷一份带日期后缀的副本例如/usr/sbin/sshd.bak-7.4p1。RPM 路线的情况更省事旧包通常还躺在本地 yum 缓存里rpm -Uvh --oldpackage就能退回去。第二件是确认还有第二条登录通道。如果这台机器是云主机控制台的 VNC 或者串口能进去如果是物理机机房的人能不能帮你接显示器。没有这条退路就不要在远程会话里重启 sshd。第三件是准备一个定时回滚。我习惯用systemd-run挂一个十分钟后执行的回滚脚本起来之后确认新版本工作正常再把它取消掉# 挂一个 10 分钟后自动回滚的定时任务 systemd-run --on-active10min --unitsshd-rollback \ /usr/local/bin/sshd-rollback.sh # 确认没问题后取消这个定时任务 systemctl stop sshd-rollback.timer systemctl reset-failed sshd-rollback.service回滚脚本本身很简单核心就是把备份的二进制和解配置拷回去、重启服务、把日志落一份。这个机制的价值在于万一新的 sshd 起不来或者认证异常你不需要靠第二条通道也能自己恢复十分钟后系统会自己爬起来。2. 编译前的环境体检依赖、OpenSSL 与目录布局2.1 用 ssh -V 和 openssl version -a 摸清家底第一步永远是确认现场。CentOS 7 上执行ssh -V输出通常是OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017。这一行信息量很大前半段是 OpenSSH 版本后半段是它编译时链接的 OpenSSL 版本和编译日期。接着跑openssl version -a重点看OPENSSLDIR和built on两行。很多人会在这里发现一个尴尬情况openssl命令行工具是 1.1.1但 OpenSSH 链接的是 1.0.2k。原因通常是有人额外装过新版本 OpenSSL 到/usr/local但没做全局替换。这种命令行和库版本不一致的环境是后面编译报错的最大来源。关于 9.0p1 对 OpenSSL 的门槛这里说个实话9.0p1 这一代还没有把 OpenSSL 1.1.1 设成硬性要求CentOS 7 自带的 1.0.2k 可以直接编过这一点比后来那几个版本友好得多。再往上的版本门槛会逐步抬高所以我一般建议在 CentOS 7 上落在 9.0p1 到 9.x 中间某一段就行不必非要追最新。顺便把当前服务器支持的算法清单也存一份升级后好做对比ssh -Q key /tmp/algo-before-key.txt ssh -Q cipher /tmp/algo-before-cipher.txt ssh -Q mac /tmp/algo-before-mac.txt ssh -Q kex /tmp/algo-before-kex.txt ssh -Q sig /tmp/algo-before-sig.txt sshd -T /tmp/sshd-effective-before.txtsshd -T这个命令值得单独说一句它会把 sshd 解析配置之后实际生效的完整参数列表打印出来包括主机密钥路径、协商算法、认证方式。排查配置问题时它比翻 sshd_config 文件准确得多因为配置文件里可能有 Include、有默认值、有被覆盖的项。2.2 依赖包清单与离线环境的取包方式CentOS 7 上编译 9.0p1 需要的编译期依赖不多但一个都不能少yum install -y gcc make zlib-devel openssl-devel pam-devel \ libselinux-devel audit-libs-devel krb5-devel \ rpm-build wget逐项解释一下为什么需要它们openssl-devel密钥交换、对称加密、哈希、签名算法全部走 OpenSSL没有它 configure 阶段直接退出。zlib-develSSH 协议里的压缩算法依赖它不装的话编译能过但压缩功能缺失。pam-devel想让 sshd 走系统的 PAM 认证栈密码认证、账号锁定策略、登录审计都靠它就必须带上。不带 PAM 编出来的 sshd 只认公钥和本地密码文件接管系统账号会非常别扭。libselinux-devel让 sshd 能做 SELinux 上下文相关操作配合--with-selinux使用。audit-libs-devel会话审计记录。krb5-devel如果你的环境有 Kerberos 单点登录加上它才能编出 GSSAPI 支持。离线环境的取包方式我一般用这两种在外网同版本同架构的机器上执行yumdownloader --resolve --destdir/tmp/pkgs gcc make zlib-devel openssl-devel pam-devel libselinux-devel audit-libs-devel krb5-devel或者用yum install --downloadonly --downloaddir/tmp/pkgs ...。把这堆 rpm 拷进内网后用rpm -ivh *.rpm或者建个本地仓库一次性装。至于源码包本身openssh-9.0p1.tar.gz和对应的.asc签名文件一起下解压前先做校验gpg --verify openssh-9.0p1.tar.gz.asc openssh-9.0p1.tar.gz sha256sum -c openssh-9.0p1.tar.gz.sha256这一步不是形式主义。源码包在下发到生产环境之前不做校验等于把整条链路的信任基础交给中间任何一个环节。2.3 configure 参数逐项拆解configure 这一步是整个升级里最需要动脑子的地方因为参数选错会导致后面路径全乱。我常用的组合是这样的./configure \ --prefix/usr \ --sysconfdir/etc/ssh \ --with-pam \ --with-selinux \ --with-md5-passwords \ --with-zlib \ --with-ssl-dir/usr \ --with-privsep-path/var/empty/sshd \ --with-privsep-usersshd \ --with-pid-dir/run关键几项说明--sysconfdir/etc/ssh是最重要的一条。OpenSSH 上游默认把配置目录放在$prefix/etc也就是/usr/local/etc。CentOS 的包管理版本放在/etc/ssh你所有的现有配置、主机密钥、known_hosts 都在那儿。不加这一条新 sshd 会去一个空目录里找配置并现场生成密钥前面说过的所有麻烦立刻全来。--prefix/usr是为了让二进制落到/usr/bin、/usr/sbin跟系统原有布局一致systemd 单元基本不用大改--prefix/usr/local则更干净、更容易跟包管理器划清界限代价是要改 systemd 单元、改 sftp 子系统路径、还要处理 SELinux 打标。两种都行我个人在 CentOS 7 上更偏向走 RPM下一节详说源码方式则用/usr/local前缀跟原有文件物理隔离。--with-privsep-path指定权限分离用的空目录。这个目录必须存在、属主是 root、权限 0755sshd 启动时如果发现它不存在或者权限不对会直接拒绝启动。CentOS 7 传统路径是/var/empty/sshdDebian 系习惯用/run/sshd按你的系统习惯来。--with-md5-passwords是为了兼容那些还在$1$格式 crypt 密码的老账号在混合环境里能省掉一堆原密码登不上的投诉。如果你的环境里已经没有这种账号了可以不加。configure 跑完之后仔细看输出最后那一段 summary确认PAM support、SELinux support、Zlib、OpenSSL header version和OpenSSL library version这几行是不是符合预期。尤其是后两行如果不一致编译出来的二进制在运行时会出现版本不匹配的报错。3. 源码编译安装的完整链路与 RPM 打包二选一3.1 源码方式make install 与 systemd 单元的联动改造源码方式的命令本身很直接make -j$(nproc) make tests make installmake tests这一步建议别跳过。OpenSSH 自带一套回归测试会验证密钥生成、算法协商、配置解析等基础功能。它在几分钟内跑完跑不过的话说明编译环境有问题早点发现比上线后发现好。make install之后立刻做两件事。第一件是补主机密钥的位置如果--sysconfdir设的是/etc/ssh那新 sshd 会直接用现成的主机密钥什么都不用做如果设错了ls /usr/local/etc里会多出ssh_host_*_key文件这时候把它们删掉把--sysconfdir改对重新编译安装。第二件是确认权限分离目录和 sshd 用户id sshd || useradd -r -s /sbin/nologin -d /var/empty/sshd sshd mkdir -p /var/empty/sshd chown root:root /var/empty/sshd chmod 0755 /var/empty/sshd然后是 systemd 单元。原来的/usr/lib/systemd/system/sshd.service指向/usr/sbin/sshd现在二进制在/usr/local/sbin/sshd必须改。不要直接改发行版自带的那个文件而是拷一份到/etc/systemd/system/sshd.service覆盖这样以后包管理器更新也不会把你的改动冲掉[Unit] DescriptionOpenSSH server daemon Documentationman:sshd(8) man:sshd_config(5) Afternetwork.target Beforemulti-user.target [Service] Typesimple ExecStart/usr/local/sbin/sshd -D -e ExecReload/bin/kill -HUP $MAINPID KillModeprocess Restarton-failure RestartSec10s [Install] WantedBymulti-user.targetKillModeprocess这一行看着不起眼其实是保命设置。systemd 默认的KillModecontrol-group在停止服务时会把整个 cgroup 里的进程全部杀掉包括你当前这条正在使用的 SSH 会话的 sshd 子进程。也就是说你在 SSH 里执行systemctl restart sshd自己的连接会一起断掉。设置成process之后只杀主进程已有会话的子进程留着重启不影响当前连接。Typesimple是我踩过坑之后的选择。有些发行版单元写的是Typenotify这要求二进制在启动完成后主动向 systemd 发一个就绪通知。你自己编出来的 sshd 如果没带这部分支持systemd 会一直等通知等到超时最后报一个启动失败而实际上进程已经好好跑在那儿了非常误导人。用simple就没这个烦恼。改完之后systemctl daemon-reload systemctl restart sshd sshd -t -f /etc/ssh/sshd_config # 语法检查3.2 RPM 方式从 contrib 里的 openssh.spec 起手源码包解压后有个contrib/redhat/openssh.spec这是打包的起点。我实际操作下来直接拿它编 9.0p1 通常会失败原因基本都出在补丁上——spec 里%patch那一段引用的补丁文件是按老版本代码写的套到 9.0p1 的源码上会冲突。处理办法是把版本宏改掉、把不适用的补丁注释掉# 准备构建目录 mkdir -p ~/rpmbuild/{SOURCES,SPECS,BUILD,RPMS,SRPMS} cp openssh-9.0p1.tar.gz ~/rpmbuild/SOURCES/ # 拷贝并修改 spec cp contrib/redhat/openssh.spec ~/rpmbuild/SPECS/openssh-9.0p1.spec打开 spec把顶部的%global openssh_ver 7.4p1改成9.0p1、%global openssh_rel 21改成1再把Patch定义和%patch调用逐行注释掉。之后开始构建rpmbuild -bb --define _topdir $HOME/rpmbuild \ --without x11-askpass \ --without gnome-askpass \ ~/rpmbuild/SPECS/openssh-9.0p1.spec--without x11-askpass这类开关是为了砍掉 SSH 图形化密码输入框的依赖服务器上完全用不到还能少装一堆 GTK 相关的包。构建完成后~/rpmbuild/RPMS/x86_64/下会生成几个包openssh、openssh-server、openssh-clients、openssh-askpass等。装的时候只需要装前面三个rpm -Uvh openssh-9.0p1-1.el7.x86_64.rpm \ openssh-server-9.0p1-1.el7.x86_64.rpm \ openssh-clients-9.0p1-1.el7.x86_64.rpm3.3 两种方式的取舍我为什么更推荐 RPM源码方式的好处是快、可控、跟包管理器互不干扰适合一次性升级几台机器或者临时验证一个新版本的行为。但只要机器数量超过五台我基本都会转向 RPM。理由有三条。第一是路径和 SELinux 上下文。RPM 安装的文件会按包里的策略自动打上正确的 SELinux 标签/usr/sbin/sshd天然带sshd_exec_t类型不需要手动semanage fcontext加规则再restorecon。源码装到自定义路径这一整套都得自己做漏一步就是端口在听但秒断连排查起来特别绕。第二是配置文件处理。RPM 的%post脚本会帮你处理/etc/ssh/sshd_config的升级如果本地改过这个文件新版配置会被放成sshd_config.rpmnew你原来那份原封不动保留可以自己 diff 后合并。主机密钥也有保护逻辑不会因为装包被重新生成。这些细节手写脚本很容易漏。第三是可回退和可分发。RPM 编出来之后可以直接扔进内网 yum 仓库createrepo一下几百台机器一条yum update openssh全部搞定版本一致、可追溯、能降级。这在有合规审计要求的环境里几乎是硬需求。4. sshd_config 的迁移被移除、被改名、被默认关闭的选项4.1 直接导致 sshd 起不来的废弃指令OpenSSH 对配置文件里的未知关键字是零容忍的一旦遇到不认识的指令sshd 会直接拒绝启动并报错。从 7.4p1 跨到 9.0p1中间被移除的指令不少所以升级前必须把 sshd_config 过一遍。常见的、需要清掉或替换的项老指令状态处理方式UsePrivilegeSeparation已移除整行删除权限分离现在强制开启Protocol已移除整行删除RhostsRSAAuthentication已移除整行删除RSAAuthentication已移除整行删除KeyRegenerationInterval已移除整行删除ServerKeyBits已移除整行删除UseLogin已移除整行删除UseRoaming已移除整行删除ChallengeResponseAuthentication已改名换成KbdInteractiveAuthenticationPubkeyAcceptedKeyTypes已改名换成PubkeyAcceptedAlgorithmsHostbasedKeyTypes已改名换成HostbasedAcceptedAlgorithms最省事的做法是在改配置之前先用新二进制做一次语法检查让它把有问题的行指出来/usr/local/sbin/sshd -t -f /etc/ssh/sshd_config它会一行一行报Bad configuration option: xxx报错的行全部处理掉再跑一次直到没有任何输出。这个命令是升级流程里最应该养成习惯的一步因为它不会真的启动服务只是在解析配置安全得很。注意sshd -t通过不代表认证一定能成功。它只检查语法和文件权限不校验 PAM 是否可用、SELinux 是否放行。这两块要等真正启动后看日志。4.2 8.8 之后 ssh-rsa 默认关闭引发的连锁反应这是升级后投诉量最大的一类问题值得单独拿出来说。从 8.8 开始ssh-rsa这个基于 SHA-1 的签名算法被从默认列表里拿掉了。注意区分两件事RSA 密钥本身没问题出问题的是用 SHA-1 做签名的那套老流程。现在 RSA 密钥走的是rsa-sha2-256和rsa-sha2-512。那为什么会连不上因为服务端和客户端要协商出一套双方都支持的算法。当老客户端只会提ssh-rsa而新服务端的默认列表里没有它握手就失败了。典型报错是Unable to negotiate with 10.0.0.5 port 22: no matching host key type found. Their offer: rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256,ssh-ed25519或者反向的no mutual signature algorithm遇到这种情况处理思路分三层按优先级排第一层升级客户端。这是根本解法。用ssh -V查一下客户端的版本如果还在 7.x 之前升级客户端比放开服务端算法安全得多而且不用担心以后再犯。第二层服务端临时放宽。如果客户端一时升不了老旧网络设备、内嵌系统、第三方厂商的固定客户端可以在 sshd_config 里显式加回去HostkeyAlgorithms ssh-rsa PubkeyAcceptedAlgorithms ssh-rsa前面的是追加到默认列表末尾的意思不是替换。这一点很重要如果你写成HostkeyAlgorithms ssh-rsa那就变成只允许这一种算法了反而把安全性拉得更低。第三层改用 Ed25519 密钥。从根上讲最省心的是给相关账号换成 Ed25519 密钥对。它没有 SHA-1 的历史包袱密钥短、签名快、所有现代客户端都支持。我们的做法是新建 Ed25519 密钥推给用户保留旧 RSA 密钥一段时间做过渡等所有客户端都切过来之后再统一清掉。常用的算法枚举命令出问题时拿它对照两边ssh -Q key # 本机支持的密钥类型 ssh -Q sig # 本机支持的签名算法 ssh -Q kex # 本机支持的密钥交换算法 ssh -Q cipher # 本机支持的加密算法服务端这一侧用sshd -T加条件参数可以看到针对特定连接的生效配置sshd -T -C userroot,host10.0.0.5,addr10.0.0.1 \ | grep -iE hostkeyalgorithms|pubkeyaccepted|ciphers|macs|kexalgorithms4.3 9.0 的 scp 走 SFTP 带来脚本层面的变化9.0 有一个容易被忽略但影响面很广的改动scp命令默认改用 SFTP 协议传输不再走老的 SCP 协议。对日常手工操作来说基本无感但对脚本来说有几个真实差异。第一个差异是子系统依赖。老的 scp 走的是scp子系统或者说直接复用 ssh 通道新的走sftp子系统。如果你在 sshd_config 里把Subsystem那一行注释掉了或者路径写错scp会直接报错subsystem request failed on channel 0 scp: Connection closed处理办法是确保 sshd_config 里有这么一行并且不依赖外部二进制Subsystem sftp internal-sftpinternal-sftp的意思是让 sshd 进程自己处理 sftp 请求不需要外部的 sftp-server 程序。这招的好处是彻底绕开路径问题——后面你会看到源码安装和 RPM 安装的 sftp-server 位置是不一样的用 internal-sftp 就完全不用管这件事。第二个差异是远程通配符的展开位置。老 scp 模式下host:/var/log/*.log里的星号是在远程展开的新 SFTP 模式下部分场景是在本地做匹配判断行为不完全一致。稳妥写法是把远程路径用引号包起来让远程去解释或者干脆改用rsync走 SSH 通道行为更可预测。第三个差异是符号链接。新模式下默认对文件名的检查更严格遇到软链接或者路径里有奇怪字符会直接拒绝提示filename contains unexpected characters。这时候加-T关掉严格检查就能过scp -T -r /data/dir userhost:/backup/如果某个老脚本实在太依赖旧协议还有个应急开关-O强制回到老的 SCP 协议scp -O /tmp/file.tar.gz userhost:/tmp/不过我把-O当临时方案看长期还是建议把脚本改掉因为它在未来的版本里随时可能被彻底删除。5. 上线的安全动作旁路监听、验证与切换5.1 用备用端口先跑一遍新 sshd直接在生产端口上换二进制风险太大。我的做法是先在另一个端口上把新 sshd 跑起来验证通过再切主端口。新 sshd 已经在/usr/local/sbin/sshd直接起一个临时实例/usr/local/sbin/sshd -p 2222 -o PidFile/run/sshd-test.pid -D -e 几个参数的作用-p 2222在命令行指定的端口会覆盖配置文件里的 Port-o PidFile用一个独立的 pid 文件避免和主服务的 pid 文件打架-D保持前台运行-e把日志打到标准错误方便实时观察。注意这里没有加-d。-d是调试模式它会让 sshd 不接受后台运行、处理一个连接后就退出用来做单次握手排错很合适但用来长期监听端口就不行了。起好之后另开一个终端测试ssh -p 2222 -o StrictHostKeyCheckingno localhost sshd -T | head -5 ssh -p 2222 -vvv localhost-vvv的输出里重点看几行协商出来的 KEX 算法、主机密钥类型、加密算法、MAC 算法以及最终是否完成了认证。这几行确认没问题说明新的算法清单和密钥都工作正常。这个阶段还有个小坑防火墙。2222 端口没放行的话从其他机器连会超时容易误判成 sshd 没起来。测试阶段先在 localhost 上验证需要跨机验证的时候临时开一下验证完记得关掉。测试完成杀掉临时实例kill $(cat /run/sshd-test.pid)5.2 sshd -T 与算法审计的交叉验证临时实例验证通过后再做一轮配置层面的比对。安装之前存的那份/tmp/sshd-effective-before.txt现在派上用场了sshd -T | sort /tmp/sshd-effective-after.txt diff /tmp/sshd-effective-before.txt /tmp/sshd-effective-after.txtdiff 出来的每一处变化都值得看一眼。有些是预期内的算法清单变了有些可能是意外某个认证方式被关掉了、某个允许用户列表丢了。算法层面的审计我推荐用社区工具ssh-audit扫一遍注意是扫临时端口或者扫已经切换后的正式服务别对老服务扫ssh-audit localhost:2222它会按 KEX、主机密钥、加密、MAC、认证这几类分别列出每一项算法的状态并且给出建议。我的经验是把它报出的不安全算法逐条对照实际业务需求能用就关确实有老设备依赖的就留着但一定要在配置文件里显式写出来并加注释说明原因这样下次有人接手的时候不会一脸茫然。5.3 真正重启的时机与现场保护生产端口切换的完整顺序是这样# 1. 确认配置语法没问题 /usr/local/sbin/sshd -t -f /etc/ssh/sshd_config # 2. 如果改过 systemd 单元先 reload systemctl daemon-reload # 3. 重启主服务 systemctl restart sshd # 4. 立刻验证状态和版本 systemctl status sshd ssh -V journalctl -u sshd -n 50 --no-pager # 5. 从另一台机器做一次真实登录测试 ssh -o ConnectTimeout5 userthis-host echo ok几个现场保护的细节重启前把回滚定时器挂上前面第 1.3 节那段systemd-run。重启过程中不要在同一个会话里执行最好单独开一个终端只做执行、另一个终端挂着不动。重启后立刻检查journalctl -u sshd。新版本启动时会打印它加载的配置文件路径、主机密钥路径、以及监听的端口这几行能第一时间暴露路径配错的问题。最后确认刚才那些老会话还在。因为单元文件里设了KillModeprocess重启只杀主进程已有会话应该原封不动。如果发现老会话也断了说明单元文件没生效得用systemctl show sshd -p KillMode确认一下实际值。6. 升级后踩到的几个典型故障与排查链路6.1 端口在听但连上就断PAM、SELinux、目录权限三连查这是最典型的一类现象ss -lntp | grep 22能看到 sshd 在监听但客户端一连就断报Connection reset by peer或者卡在SSH2_MSG_KEX_ECDH_REPLY之后没反应。排查按这个顺序走第一步看日志。journalctl -u sshd -n 100加上/var/log/secure的尾部通常直接就有答案。常见的几条报错对应不同原因日志关键字根本原因处理fatal: privsep user not foundsshd 用户不存在useradd -r -s /sbin/nologin sshdfatal: /var/empty/sshd must be owned by root and not group or world-writable权限分离目录权限不对chown root:root加chmod 0755PAM: unable to dlopenPAM 模块路径或配置有问题检查/etc/pam.d/sshd是否存在error: Bind to port 22 on 0.0.0.0 failed: Address already in use老 sshd 没退干净检查进程必要时 kill 掉旧 pidSELinux is preventing sshd from ...SELinux 拒绝见下面第二步第二步查 SELinux。如果getenforce返回Enforcing而二进制装在自定义路径下几乎必然会被拒getenforce ausearch -m avc -ts recent | grep sshd有 AVC 拒绝记录的话先看拒绝的目标类型。如果是因为/usr/local/sbin/sshd被当成普通可执行文件而不是 sshd 程序需要给它打上正确的类型标签semanage fcontext -a -t sshd_exec_t /usr/local/sbin/sshd restorecon -v /usr/local/sbin/sshd同时确认主机密钥和配置目录的上下文restorecon -Rv /etc/ssh ls -Z /etc/ssh/ssh_host_ed25519_key主机密钥文件的上下文类型要正确否则 sshd 读不了表现就是启动时提示找不到主机密钥然后退出。第三步查文件权限。sshd 对权限的检查非常严格主机私钥不能是 0644也不能属于非 root 用户chmod 600 /etc/ssh/ssh_host_*_key chmod 644 /etc/ssh/ssh_host_*_key.pub chown root:root /etc/ssh/ssh_host_*_key*这三步走完绝大多数连上就断都能定位。6.2 sftp 和 scp 报子系统错误前面提过一次这里展开说排查链路。报错形态通常是这两种之一subsystem request failed on channel 0 scp: Connection closed或者 sftp 客户端报Connection closed; transfer aborted根因是 sshd_config 里的 Subsystem 行指向的 sftp-server 路径在新安装下不存在了。RPM 装的路径是/usr/libexec/openssh/sftp-server源码装到/usr/local的话是/usr/local/libexec/sftp-server。如果你是从一种方式切到另一种路径必然对不上。验证方法很直接grep -i subsystem /etc/ssh/sshd_config ls -l $(grep -i subsystem /etc/ssh/sshd_config | awk {print $3})看到No such file or directory就对上了。最干净的修法不是改路径而是换成内置实现Subsystem sftp internal-sftp改完sshd -t检查一遍重启服务再测sftp -P 2222 localhost scp -P 2222 /tmp/1.txt localhost:/tmp/还有一个使用上的坑sftp命令用大写-P指定端口而ssh用小写-pscp也是大写-P。这个大小写差异坑过很多人报错就是连 22 端口失败。6.3 OpenSSL 头文件和库版本不匹配编译阶段报这几句的时候说明头文件和库不是一套configure: error: Your OpenSSL headers do not match your library. Check config.log for details.或者能编过但运行时报OpenSSL version mismatch. Built against 1010100f, you have 100020bf排查方法是把两边的版本都打出来对比openssl version -a cat /usr/include/openssl/opensslv.h | grep -E OPENSSL_VERSION_TEXT|SHLIB_VERSION ldconfig -p | grep libcrypto用openssl version -a里的OPENSSLDIR确认主要的库在哪儿用ldconfig -p确认运行时会加载哪个libcrypto.so。两边版本号对不上就得选一条路走要么统一到系统自带的 OpenSSL也就是 configure 时明确写--with-ssl-dir/usr然后确认/usr/include/openssl和/usr/lib64/libcrypto.so是同一版本要么统一自编译的 OpenSSL装到/opt/openssl-1.1.1之类独立目录configure 时用--with-ssl-dir/opt/openssl-1.1.1并且编译时带上 rpath 让二进制记住库位置export LDFLAGS-Wl,-rpath,/opt/openssl-1.1.1/lib我最不建议的是把系统自带的 OpenSSL 原地覆盖替换。系统里 SUSE、curl、wget、包管理器、其他依赖 crypto 库的程序全都盯着那个路径替换之后出现的问题会远远超出 OpenSSH 的范围而且很难回退。要升级 OpenSSL就用独立目录加 rpath 的方式隔离。6.4 老客户端握手失败的不同报错对照这类问题的报错长得像但原因不同我把常见的几种整理成对照表出问题时直接查报错片段缺失的算法类别服务端临时修补no matching host key type found主机密钥类型HostkeyAlgorithms ssh-rsano mutual signature algorithm用户公钥签名PubkeyAcceptedAlgorithms ssh-rsano matching key exchange method found密钥交换KexAlgorithms diffie-hellman-group1-sha1no matching cipher found对称加密Ciphers aes128-cbcno matching MAC found消息认证码MACs hmac-sha1用的时候注意两点。第一所有都是追加不要写成了替换。第二改完服务端之后有些客户端还需要在它那一侧也放开对应算法比如 PuTTY 的新版本设置界面里要手动勾选ssh-rsaOpenSSH 客户端可以临时用命令指定ssh -o HostKeyAlgorithmsssh-rsa -o PubkeyAcceptedAlgorithmsssh-rsa userhost临时用这个没问题但别把它写进~/.ssh/config就忘了。这种放宽一旦沉淀到配置里过两年就没人记得为什么要写然后新一轮的安全审计又会把这行提出来。7. 离线与国产化系统上的落地细节7.1 离线环境的依赖收集与本地仓库内网机器上不去外网最麻烦的不是编 OpenSSH而是把编译依赖凑齐。我的一般做法是找一台跟目标机器同发行版、同版本、同架构的外网机器一次性把依赖下全yum install -y yum-utils yumdownloader --resolve --destdir/tmp/openssh-build-deps \ gcc make zlib-devel openssl-devel pam-devel \ libselinux-devel audit-libs-devel krb5-devel--resolve会把间接依赖也一并下下来这一步很重要因为openssl-devel本身还依赖openssl-libs之类的包缺一个就装不上。依赖和源码包一起拷进内网之后我倾向于在本地做一个 yum 仓库而不是零散地rpm -ivh。仓库方式的好处是后面升级、补装都不用再操心依赖顺序# 在内网机器上 mkdir -p /opt/localrepo cp /tmp/openssh-build-deps/*.rpm /opt/localrepo/ createrepo /opt/localrepo/然后写一个 repo 文件指过去yum install就能直接用了。等你把自己编好的 OpenSSH RPM 也放进这个仓库几百台内网机器一次性升级就不是什么难事了。7.2 国产化发行版上的几个差异点在麒麟、统信这类国产化系统上做 OpenSSH 升级整体思路不变但有这么几处差异需要提前心里有数。第一是 OpenSSL 的版本基线比 CentOS 7 高。麒麟 V10 这类系统自带的 OpenSSL 通常是 1.1.1 系列这反而是好事意味着 9.0p1 编起来不会有版本门槛问题甚至可以直接考虑更新的 OpenSSH 版本。第二是包名和路径可能有本地化改动。我遇到过 openssl 开发包的名称不是标准的openssl-devel得先用yum search openssl或者rpm -qa | grep openssl看看实际装的是什么。配置目录有时也不完全是/etc/ssh先rpm -ql openssh-server确认一下官方包的文件布局再决定 configure 里的路径参数。第三是 SELinux 策略状态不统一。有的发行版默认是 permissive有的机器被设置为 enforcing。如果源机器是 permissive你把编译好的二进制拷到 enforcing 的机器上就会出现在我这儿好好的到你那儿连不上的情况。分发之前统一getenforce确认一遍最省事。第四是架构多样性。信创环境里 x86_64、aarch64、飞腾、鲲鹏都有。RPM 包是架构绑定的不能在 aarch64 上装 x86_64 的包。要么为每个架构单独构建要么用rpmbuild --target aarch64配合交叉编译工具链。我的经验是架构数量少于三种的话直接每种架构各编一次更省事交叉编译工具链的调试成本往往比多编几次高。最后还有一条容易被忽略的原则尽量不要动系统自带的 OpenSSL 主库。信创系统里很多组件包括桌面环境、办公套件都链接了系统 OpenSSL你把它替换掉出的问题会跑到你完全想不到的地方去。要新版本 OpenSSH 就编 OpenSSHOpenSSL 需要升级就装在独立前缀下用 rpath 隔离两者分开处理。7.3 版本选型的实际考虑热词里经常能看到有人在找「openssh 官网下载」和各种更新的版本号这里给个我自己的选型思路。不必盲目追最新。OpenSSH 版本的迭代节奏大约是半年一个版本每个版本都在收紧默认算法、调整依赖门槛。对于 CentOS 7 这类 1.0.2k 基线的系统9.0p1 是一个比较舒服的落点安全修复覆盖到位编译门槛又没被抬起来。再往上走你会越来越多地遇到要新算法就得先升 OpenSSL升了 OpenSSL 又得处理一堆依赖的连锁反应。对于国产化系统这种 OpenSSL 基线本来就比较新的环境往上多走一两个版本反而没负担可以按官方公告里修复的安全问题范围来决定。不管选哪个版本有两件事是固定动作源码包必须校验签名和哈希上线前必须在临时端口验证一轮完整登录。这两件事做扎实了版本号的具体选择就没那么关键了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →