尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CentOS 7 源码编译升级 OpenSSH 7.4p1 到 9.0p1 实战

CentOS 7 源码编译升级 OpenSSH 7.4p1 到 9.0p1 实战 OpenSSH 7.4p 这个版本号干过 CentOS 7 运维的人基本都眼熟——系统装完打眼一看ssh -V出来的就是OpenSSH_7.4p1, OpenSSL 1.0.2k-fips。这个组合在当年很稳但架不住时间久了安全基线扫描一跑一堆中高危全指着它。更麻烦的是 7.4p 里还留着ssh-rsaSHA-1 签名、diffie-hellman-group1-sha1这些早就被认为不安全的算法放在内网还好说只要机器对外暴露过端口日志里天天有人拿着字典撞你的 22 端口。所以把 OpenSSH 从 7.4p 拉到 9.0p本质上不是升级而是一次换血。它不是yum update openssh一条命令就完事的操作因为 CentOS 7 官方源里最高就给到 7.4p1想要 9.0 只能自己编译或者找第三方 RPM 包。而编译安装这事踩坑的地方远比敲命令的地方多OpenSSL 版本不够、PAM 没编进去导致密码登录全挂、Subsystem sftp路径还指着老文件、systemd 的Typenotify让服务起不来、SELinux 上下文不对把 sshd 直接拦死……这篇内容就是把我自己在一批 CentOS 7 / 类 CentOS 7 机器上把 OpenSSH 7.4p1 升到 9.0p1 的整个过程、参数选择理由、连不上的排查套路以及最终的回滚方案完整摊开讲一遍。适合手上有一批老系统、又必须过安全合规检查的运维同学也适合想搞明白源码编译替换系统组件这套玩法的人。整篇按评估—准备—编译—配置—验证—批量的顺序推进每一步我都会说清楚为什么这么做而不是只丢一条命令。1. 升级前的整体评估与方案选型1.1 为什么 7.4p 必须动漏洞和算法两线夹击先说清楚驱动力。7.4p1 是 2016 年底发布的版本到 9.0p1 之间隔了差不多五年半中间修掉的安全问题数量相当可观。其中最出名的一类是认证绕过和整数溢出比如CVE-2018-15473那个用户名枚举问题攻击者可以通过响应时间差异判断某个账号到底存不存在给后续爆破省了一大半力气。再比如CVE-2020-15778scp的参数处理可以被注入命令只要服务端允许 scp配合某些场景就能执行非预期指令。但比单个 CVE 更麻烦的是算法层面。7.4p1 的默认算法集里ssh-rsa用的还是 SHA-1 签名SHA-1 的碰撞攻击在学术上早就实锤了diffie-hellman-group1-sha1这种 1024 位群的密钥交换算力稍微宽裕一点就能被拿下。等保测评、行业安全基线、甲方自己的扫描报告基本上都会把这几条列成高危项。你可以手工在sshd_config里把算法白名单收紧来规避但那相当于在旧地基上不断打补丁越补越乱最终还是得换版本。还有一个容易被忽略的点新版本带来的不只是安全还有可用性。9.0p1 默认启用了sntrup761x25519-sha512openssh.com这个抗量子计算的混合密钥交换虽然现在谈量子威胁有点早但它在握手性能上并不吃亏另外scp在 9.0 里默认改走 SFTP 协议传输大目录时对断点、权限的处理比老的 rcp 协议干净得多。注意升级理由要写清楚最好在变更单里明确列出修复高危漏洞和禁用弱算法两条不要只写升级到最新版。前者是合规语言后者是技术语言两边都站得住。1.2 三条升级路线的取舍yum 源、第三方 RPM、源码编译真正动手前路线选择比技术细节更重要。我整理过三条路各自的适用场景差别很大。第一条路是换 yum 源。有些第三方源确实提供了较新版本的 openssh RPM安装体验最好yum install一条命令依赖自动解决回滚也简单。但问题在于这类源的可信度参差不齐包里的编译参数你无法确认而且一旦这个源哪天不维护了后续再升级就断了。对于生产环境尤其是要过合规审计的环境用来源不明的二进制包替换核心登录组件风险比收益大。第二条路是自己打 RPM 包。从源码编译出二进制再用 rpmbuild 或 fpm 打成 RPM最后rpm -Uvh安装。这条路兼顾了可控性和可管理性编译参数是你自己定的包是你自己签的升级和回滚都走标准包管理流程rpm -qa能查到版本后面批量分发也方便。缺点是要多花时间写 spec 文件第一次搭起来有点折腾。第三条路是纯源码编译覆盖安装。直接在机器上./configure make make install把二进制覆盖到/usr/sbin/sshd、/usr/bin/ssh这些位置。最快、最直接适合机器数量少、或者没有内网 yum 仓库的场景。但回滚只能靠提前备份的二进制没有包管理记录。我自己的选择是小规模十台以内用源码编译中大规模先打一个 RPM 再用 Ansible 分发。原因很实际——十台以内手工搞一遍也就半天为它单独搭 RPM 构建环境不划算但机器一多手工编译时任何一台的参数敲错都可能导致差异用统一的 RPM 才能保证环境一致性。1.3 动手前的环境摸底清单不管走哪条路先摸底。我吃过一次亏在一台机器上编译到一半才发现 gcc 版本太老configure直接报错退出之前装的依赖全白装。所以下面这张表我基本每次都跑一遍。检查项命令期望结果不满足时的处理当前版本ssh -VOpenSSH_7.4p1记录作为回滚基线系统版本cat /etc/redhat-releaseCentOS 7.x确认包管理方式OpenSSL 版本openssl version1.0.2k 或更高低于 1.1.1 时需单独编译gcc 版本gcc --version4.8.5 以上升级或换构建机PAM 开发库rpm -q pam-devel已安装yum install -y pam-develzlib 开发库rpm -q zlib-devel已安装yum install -y zlib-develSSH 端口ss -lntp | grep sshd22 或自定义端口记录别改错端口SELinux 状态getenforceEnforcing / PermissiveEnforcing 时准备好 restorecon控制台通道云厂商 VNC / IDRAC / IPMI可用没这个别开始升级最后一行是我最看重的。升级 OpenSSH 有一个铁律必须有一条不依赖 SSH 的登录通道。云主机用厂商控制台的 VNC物理机用带外管理虚拟机用宿主机的 console。原因很简单——你在升级 sshd 的过程中任何一步出错都会导致 SSH 登录不上如果这时候你只有 SSH 这一条路那这台机器就彻底失联了只能走工单让机房的人重启进救援模式。2. 动手前的准备备份与依赖2.1 回滚预案要落实到命令级别很多人做变更时的备份就是cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak这远远不够。源码编译安装会覆盖至少四个二进制文件和一批头文件、库文件回滚必须能把这些都还原回去。我习惯在/root下建一个固定目录把所有需要还原的东西按原路径结构存好mkdir -p /root/ssh_rollback_$(date %Y%m%d) cd /root/ssh_rollback_$(date %Y%m%d) # 配置目录整体备份保留权限和时间戳 cp -a /etc/ssh ./etc_ssh # 二进制备份 cp -a /usr/sbin/sshd ./sshd.bin cp -a /usr/bin/ssh ./ssh.bin cp -a /usr/bin/scp ./scp.bin cp -a /usr/bin/ssh-keygen ./ssh-keygen.bin # 记录包信息方便必要时用 yum reinstall 恢复 rpm -qa | grep -i openssh openssh_packages.txt rpm -ql openssh-server openssh_server_files.txt # 记录当前算法协商结果作为对比基线 ssh -Q kex kex_before.txt ssh -Q key key_before.txt ssh -Q cipher cipher_before.txtssh -Q这几个子命令特别有用它能把当前版本支持的算法族全列出来。升级前后各存一份对比一下就知道哪些算法没了后面配兼容性白名单时心里有数。回滚时的操作就是把备份的文件拷回去cp -a /root/ssh_rollback_YYYYMMDD/sshd.bin /usr/sbin/sshd cp -a /root/ssh_rollback_YYYYMMDD/etc_ssh/* /etc/ssh/ restorecon -Rv /etc/ssh systemctl restart sshd注意备份二进制时一定要用cp -a保留原来的 owner 和权限。/usr/sbin/sshd是 root:root 755如果拷回来变成别的权限sshd 会因为安全校验直接拒绝启动。2.2 依赖包和工具链准备CentOS 7 的默认最小化安装缺不少东西编译 OpenSSH 之前先把下面这些装上yum install -y gcc gcc-c make perl \ zlib-devel pam-devel \ openssl-devel \ wget tar \ rpm-build # 如果要打 RPM 包其中zlib-devel和pam-devel是两个必装项而且必须在configure之前装好。原因在于 OpenSSH 的configure脚本是探测式的——它发现不到pam相关头文件就会静默地把 PAM 支持关掉编译过程不会报任何错一路绿灯到make install完成。等你重启 sshd 准备试密码登录时才会看到Permission denied日志里写着PAM: none或者干脆不提 PAM。这时候你只能重装 pam-devel 然后重新编译白折腾一轮。openssl-devel是系统自带的那套版本是 1.0.2k。这里有个关键决策点要不要单独编译 OpenSSL 1.1.1我的建议是分开看如果你只升级到 9.0p1官方给出的最低要求是 OpenSSL 1.0.1 以上系统自带的 1.0.2k 在configure阶段是能通过的可以先用系统版本编译。但如果你后续还要往 9.4、9.6 这些版本走它们对 OpenSSL 的要求提到了 1.1.1 以上那时候系统自带的 1.0.2k 就不够用了。所以如果时间允许我建议一次性把 OpenSSL 1.1.1 也编译好放到/usr/local/ssl后面升级 OpenSSH 就是纯增量操作。单独编译 OpenSSL 时注意一点只装到/usr/local/ssl绝对不要覆盖系统自带的/usr/lib64/libssl.so.10和/usr/lib64/libcrypto.so.10。系统里 yum、curl、rpm 这些工具全依赖老版本一旦被覆盖整个包管理系统可能直接罢工那台机器就只能重装了。tar -zxf openssl-1.1.1w.tar.gz -C /usr/local/src cd /usr/local/src/openssl-1.1.1w ./config --prefix/usr/local/ssl --openssldir/usr/local/ssl shared zlib make -j$(nproc) make install # 让新库能被找到但不动系统老库 echo /usr/local/ssl/lib /etc/ld.so.conf.d/openssl-111.conf ldconfig--openssldir/usr/local/ssl这个参数容易被漏掉。不写的话默认会指向编译目录将来openssl命令找配置文件openssl.cnf时会找不到导致某些证书操作报错。2.3 关于 host key 的一个冷知识/etc/ssh/ssh_host_*_key这些主机密钥升级过程中不要动。有些人为了干净会把它们删掉让新版本重新生成这会导致一个直接后果所有客户端的known_hosts里的指纹全部失效下次连接报一大段WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!几台机器还好说几百台客户端逐个ssh-keygen -R就够你受的。只有一种情况需要重新生成你的老主机密钥里包含 DSA 类型。OpenSSH 9.0 已经完全移除对 DSA 的支持如果/etc/ssh/下存在ssh_host_dsa_key新的 sshd 读取时会报错并拒绝启动。处理方式很简单ls -l /etc/ssh/ssh_host_*_key # 如果有 ssh_host_dsa_key先移走 mv /etc/ssh/ssh_host_dsa_key /root/ssh_rollback_backup/ mv /etc/ssh/ssh_host_dsa_key.pub /root/ssh_rollback_backup/移走之后不用重新生成因为ssh_host_rsa_key、ssh_host_ecdsa_key、ssh_host_ed25519_key这三对还在9.0 会正常使用它们。3. 源码编译安装 9.0p1 全流程3.1 源码包的下载与校验官方发布包在 OpenSSH 官网和各大开源镜像站都能找到。包名是openssh-9.0p1.tar.gz注意那个p1后缀它表示这是 9.0 系列的第一个补丁版本别把p1漏掉只下载openssh-9.0.tar.gz那个包在官方列表里是不存在的。下载之后强烈建议校验尤其是从镜像站拉的时候cd /usr/local/src wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.0p1.tar.gz wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.0p1.tar.gz.asc # 计算 SHA256和官网公布的对比 sha256sum openssh-9.0p1.tar.gz校验这一步很多人跳我觉得在升级核心登录组件这事上不值得省。如果内网有自建的文件服务器把源码包放上去所有机器从同一份文件拉能避免不同机器下载到不同版本或者被中间篡改。解压tar -zxf openssh-9.0p1.tar.gz cd openssh-9.0p13.2 configure 参数逐项拆解configure是整个编译过程里最需要动脑的一步。参数写错后面要么功能缺失要么路径错乱。我用的这套参数是在多个环境验证过的./configure \ --prefix/usr \ --sysconfdir/etc/ssh \ --with-pam \ --with-zlib \ --with-ssl-dir/usr/local/ssl \ --with-privsep-path/var/lib/sshd \ --with-md5-passwords \ --with-ssl-engine \ --with-selinux逐条说明为什么这么选--prefix/usr是最关键的一个。CentOS 7 的sshd.service里写死了ExecStart/usr/sbin/sshd -D $OPTIONSssh、scp、sftp这些客户端命令也都在/usr/bin/下。如果prefix用默认的/usr/local那么编译出来的 sshd 会装到/usr/local/sbin/sshd而 systemd 还是去/usr/sbin/sshd找结果就是systemctl start sshd报status203/EXEC找不到可执行文件。把 prefix 设成/usr编译产物正好覆盖掉老版本systemd 不用改一行配置。--sysconfdir/etc/ssh让配置文件路径和系统默认保持一致。默认的 sysconfdir 是$prefix/etc也就是/usr/etc/ssh这个路径既不符合 CentOS 习惯也会让现有的/etc/ssh/sshd_config被忽略。设成/etc/ssh之后新 sshd 直接读你原来的配置改动量最小。--with-pam必须加。CentOS 的密码认证、账号锁定、登录审计全走 PAM 模块。不加这个参数编译出来的 sshd 不支持 PAMPasswordAuthentication yes形同虚设所有用密码登录的用户都会被拒。前面说过这个问题在编译阶段完全静默只有实际登录时才暴露。--with-zlib打开压缩支持。默认情况下 configure 会自动探测 zlib但显式写上更保险尤其在zlib-devel装得比较晚的情况下。--with-ssl-dir/usr/local/ssl指定 OpenSSL 路径。如果你决定用系统自带的 1.0.2k把这个参数去掉。用独立编译的 1.1.1w 就按这个写。注意这里填的是前缀目录不是 lib 目录。--with-privsep-path/var/lib/sshd是指定权限分离用的空目录。OpenSSH 在 7.x 之后默认用/var/empty但某些系统上/var/empty的权限被改过比如被别的软件弄成了 755sshd 启动时会因为权限不满足安全要求而失败。改成/var/lib/sshd更可控mkdir -p /var/lib/sshd chmod 711 /var/lib/sshd chown root:root /var/lib/sshd权限 711 是硬性要求sshd 会检查这个目录的属主和权限太宽松直接拒绝启动。--with-md5-passwords是为了兼容老密码哈希。有些历史遗留账号的密码用的是 MD5-crypt 格式不加这个参数的话这些账号会登录失败。--with-selinux在 SELinux 处于 Enforcing 的系统上建议加上。它让 sshd 在运行期能正确处理 SELinux 上下文减少权限拒绝。configure跑完后终端最后几行会有一个汇总。这一步一定要看完不要直接拉到最后敲 make。重点检查三个字段PAM 是不是yesOpenSSL 版本是不是你期望的那个PrivSep 路径对不对。我见过configure因为找不到 OpenSSL 头文件而静默降级使用内置实现的情况最后登录时各种加密套件不匹配。3.3 编译、安装与二进制替换configure通过之后就顺了make -j$(nproc)编译大概几分钟中间如果有 warning 一般可以忽略但如果出现 error基本都是依赖没装全。常见的两个fatal error: openssl/opensslv.h: No such file—— 缺openssl-devel或者--with-ssl-dir路径写错。fatal error: security/pam_appl.h—— 缺pam-devel。编译完之后我强烈建议先跑一次完整的自检再安装make tests这套测试会本地起一个临时 sshd验证密钥交换、认证、文件传输这些核心链路。跑一遍大概几十秒能提前发现问题。特别是在跨大版本升级时它能帮你确认新编译的二进制本身是健全的。确认没问题再安装make install安装过程会自动做几件事其中一件是生成新的 host key——但前提是/etc/ssh/下不存在同名文件。因为我们已经有了老密钥这一步实际上会被跳过密钥保持不散客户端的known_hosts不受影响。安装完之后立刻验证二进制版本/usr/sbin/sshd -V # 期望输出OpenSSH_9.0p1, OpenSSL 1.1.1w ... which ssh scp ssh-keygen sftp ssh -V如果sshd -V报unknown option说明/usr/sbin/sshd还是老版本make install没覆盖成功。检查一下是不是prefix写错了或者安装时有权限报错被忽略了。3.4 补齐 sftp-server 路径和权限这一步是 9.0 升级里最容易被漏掉的坑。/etc/ssh/sshd_config里通常有这么一行Subsystem sftp /usr/libexec/openssh/sftp-server这是 CentOS 打 RPM 包时的路径sftp-server被放在/usr/libexec/openssh/下。而源码编译安装的sftp-server默认装到/usr/libexec/sftp-server两个路径对不上。结果就是 SSH 登录正常但一执行sftp或者用支持 SFTP 的客户端比如 WinSCP、FileZilla就报subsystem request failed on channel 0。两个解决办法选一个就行# 方法一改配置指向新路径 sed -i s#Subsystem\s\sftp\s\/usr/libexec/openssh/sftp-server#Subsystem sftp /usr/libexec/sftp-server# /etc/ssh/sshd_config # 方法二建个软链接老路径继续有效 ln -sf /usr/libexec/sftp-server /usr/libexec/openssh/sftp-server我更倾向方法一因为 9.0 的scp默认走 SFTP 协议sftp-server的路径正确与否直接影响 scp 能否工作。改配置的时候顺手确认一下新装的sftp-server确实在ls -l /usr/libexec/sftp-server顺手还要检查/var/lib/sshd权限、/etc/ssh下密钥文件权限chmod 600 /etc/ssh/ssh_host_*_key chmod 644 /etc/ssh/ssh_host_*_key.pub chown root:root /etc/ssh/ssh_host_*4. sshd_config 配置改造与算法兼容4.1 9.0p1 到底弃用了哪些算法配置改造前先把账算清楚。从 7.4p1 到 9.0p1被默认关掉或者彻底移除的算法主要是这几类类别被弃用/移除的算法生效版本影响主机密钥ssh-dss9.0 完全移除老 DSA 主机密钥无法加载签名算法ssh-rsaSHA-18.8 默认禁用老客户端公钥认证失败密钥交换diffie-hellman-group1-sha17.6 起默认禁用老设备协商失败密钥交换diffie-hellman-group14-sha18.x 起降级部分老客户端不兼容加密3des-cbc、blowfish-cbc、cast128-cbc7.x 起默认禁用老客户端无法连接MAChmac-ripemd160、umac-64openssh.com7.x 起默认禁用老客户端协商失败这里最需要关注的是ssh-rsa。很多企业的自动化脚本、老款网络设备、甚至一些老版本的 Java 客户端比如 JSch用的还是ssh-rsa做公钥签名。升级到 9.0p1 之后服务端默认不接受这种签名客户端连接时会看到Unable to negotiate with 10.0.0.1 port 22: no matching host key type found. Their offer: ssh-rsa或者Permission denied (publickey).注意第二种情况特别隐蔽——协商阶段过了但认证阶段被拒日志里看不出明显的算法错误。区分方法是在客户端加-vvv看详细日志搜索send_pubkey_test附近的内容如果出现using ssh-rsa然后紧跟着Authentications that can continue: publickey基本就是这个问题。4.2 一个稳妥的服务端配置模板我的做法是默认算法全部收紧只为确实需要的老客户端开最小白名单。直接把ssh-rsa全局打开是不可取的那等于把安全升级的意义抵消了一半。# 备份原配置 cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date %Y%m%d) # 显式声明算法白名单 cat /etc/ssh/sshd_config EOF # 9.0p1 算法加固配置 # 密钥交换只要现代算法 KexAlgorithms curve25519-sha256,curve25519-sha256libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256,sntrup761x25519-sha512openssh.com # 主机密钥算法去掉 ssh-rsa HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256 # 加密套件 Ciphers chacha20-poly1305openssh.com,aes256-gcmopenssh.com,aes128-gcmopenssh.com,aes256-ctr,aes192-ctr,aes128-ctr # MAC MACs hmac-sha2-512-etmopenssh.com,hmac-sha2-256-etmopenssh.com,umac-128-etmopenssh.com # 公钥认证算法 PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256 EOF如果确实有老客户端必须用ssh-rsa用前缀追加而不是替换HostKeyAlgorithms ssh-rsa PubkeyAcceptedAlgorithms ssh-rsa表示在默认集合基础上追加-表示从默认集合里移除直接写算法列表则是完全替换。搞清这三个语义能省很多事——直接写列表容易漏掉某个必要算法导致自己都连不上。另外提醒一下PubkeyAcceptedAlgorithms在 8.5 之前的名字叫PubkeyAcceptedKeyTypes。9.0p1 里老名字还能作为别名使用但会有 warning。如果你在配置里看到老名字顺手换成新的。4.3 客户端侧的老设备兼容处理服务端收紧之后客户端侧也要跟着调整。对于 Linux 客户端最省事的办法是在~/.ssh/config里针对特定主机做例外Host old-switch-10.0.0.88 HostKeyAlgorithms ssh-rsa PubkeyAcceptedAlgorithms ssh-rsa KexAlgorithms diffie-hellman-group14-sha1 Ciphers aes128-cbc这样做的好处是只有连这台老设备时才放宽连其他主机的安全策略不受影响。比在全局配置里放开要干净得多。Windows 上如果用 PuTTY它的新版已经支持rsa-sha2-256和rsa-sha2-512一般不用特殊处理如果是很老的版本需要升级客户端。用 WinSCP 的话在站点设置的高级选项里可以手工指定算法优先级。至于scp9.0p1 默认已经走 SFTP 协议了。如果对端是很老的 sshd6.x 及以下SFTP 子系统可能不可用这时要用-O参数强制回退到老协议scp -O local_file.tar.gz userold-server:/tmp/这个小改动坑了不少人——升级完客户端之后原本正常的 scp 脚本突然全部失败报subsystem request failed排查半天才发现是协议变了。4.4 sshd 配置校验与平滑重启改完配置千万别直接systemctl restart。先用-t做语法检查/usr/sbin/sshd -t返回空表示配置没问题。如果报错它会明确指出哪一行、什么问题比如Bad SSH2 cipher spec 3des-cbc,aes128-cbc—— 指定的加密算法里有 9.0 已移除的需要删掉。Unsupported option UsePrivilegeSeparation—— 这个关键字在 8.0 就被移除了配置里有残留就要删掉。Missing privilege separation directory: /var/lib/sshd—— 目录不存在按前面说的方法创建。还可以用sshd -T输出最终生效的完整配置合并了默认值和文件配置确认算法白名单确实按预期生效/usr/sbin/sshd -T | grep -E kexalgorithms|hostkeyalgorithms|ciphers|macs确认无误后重启服务但一定不要在当前 SSH 会话里重启完就关窗口systemctl restart sshd systemctl status sshd紧接着在另一个终端窗口里开新连接测试ssh -p 22 rootlocalhost echo connection_ok新连接成功了再关掉老会话。这个顺序看起来啰嗦但它是唯一能保证你不会把自己锁在门外的操作习惯。5. 验证、回滚与常见问题排查5.1 一份可执行的验证清单重启完成后我会按固定顺序跑一遍验证。这套清单是踩坑之后总结出来的覆盖面比较全。# 1. 版本确认 /usr/sbin/sshd -V ssh -V # 2. 服务状态 systemctl is-active sshd systemctl is-enabled sshd # 3. 端口监听 ss -lntp | grep sshd # 4. 本机回环连接公钥 ssh -o BatchModeyes root127.0.0.1 uname -r # 5. 密码认证用一个普通账号测 ssh -o PreferredAuthenticationspassword -o PubkeyAuthenticationno testuser127.0.0.1 id # 6. SFTP 子系统 echo hello /tmp/t.txt sftp -o BatchModeyes root127.0.0.1:/tmp/t.txt /tmp/t2.txt # 7. scp新协议 scp -o BatchModeyes /tmp/t.txt root127.0.0.1:/tmp/t3.txt # 8. 算法协商实际结果 ssh -vvv root127.0.0.1 exit 21 | grep -E kex:|host key algorithm:|cipher: # 9. 日志检查 journalctl -u sshd --since 10 minutes ago | grep -iE error|fail|denied第 8 条特别值得做它能告诉你实际协商用的是哪套算法。看到rsa-sha2-512或者ssh-ed25519就对了如果还是ssh-rsa说明配置没生效需要检查是不是有别的配置文件覆盖比如/etc/ssh/sshd_config.d/下的片段文件。提示sshd -T是排查配置问题的利器它把最终生效的配置全量输出比反复读 sshd_config 猜测默认值快得多。养成改配置后先-t查语法、再-T看结果的习惯。5.2 常见问题速查表下面这些是我在多个环境里真实遇到过的按现象归类方便对照排查。现象大概率原因处理方式systemctl start sshd卡住后超时服务文件是Typenotify新编译的 sshd 不支持 sd_notify改/usr/lib/systemd/system/sshd.service为Typesimpledaemon-reloadstatus203/EXECExecStart路径下没有 sshd确认--prefix/usr或修正 unit 里的路径sshd 启动就退出日志提 DSAhost key 里有ssh_host_dsa_key移走该密钥文件密码登录全部失败编译时漏了--with-pam装 pam-devel 后重新 configure、make、installSubsystem request failedsftp-server 路径不一致改Subsystem行或建软链接老客户端提示no matching host key type客户端只支持ssh-rsa服务端加HostKeyAlgorithms ssh-rsa受控使用Permission denied (publickey)但密钥没问题PubkeyAcceptedAlgorithms不含客户端签名算法用ssh -vvv确认签名算法按需加白名单SELinux 环境下 sshd 被拒绝新二进制没有正确的安全上下文restorecon -Rv /etc/ssh /usr/sbin/sshd必要时用semanage fcontext登录后scp报错9.0 的 scp 走 SFTP对端不支持加-O参数回退sshd 提示Missing privilege separation directory/var/lib/sshd不存在或权限不对创建目录并设chmod 711服务重启后连接被立即断开配置里残留UsePrivilegeSeparation等已删关键字删掉这些行重新sshd -t5.3 systemd 服务文件那个坑单独把Typenotify这个问题拎出来说因为它在源码编译升级里出现概率很高。CentOS 7 的/usr/lib/systemd/system/sshd.service里写的是Typenotify意思是 sshd 启动后会主动给 systemd 发一个就绪通知。RHEL 官方的 openssh 包打过补丁支持这个通知但上游源码编译出来的 sshd 默认不带这个能力于是 systemd 一直等不到通知等到超时就把服务标记为失败。表现就是systemctl restart sshd卡在那不动几十秒后返回失败但此时 sshd 进程其实已经在跑了。处理方式有两种# 方式一改成 simple保留 -D 前台运行 sed -i s/^Typenotify/Typesimple/ /usr/lib/systemd/system/sshd.service systemctl daemon-reload systemctl restart sshd# 方式二确认 -D 参数存在配合 Typesimple 或 Typeexec grep ExecStart /usr/lib/systemd/system/sshd.service # ExecStart/usr/sbin/sshd -D $OPTIONS改完记得daemon-reload否则改动不生效你还是会看到超时。这个坑我前后踩过两次第二次才意识到是 unit 文件的问题之前一直以为是 sshd 本身启动失败。6. 生产环境批量升级与加固经验6.1 用 Ansible 做可控的批量分发机器多了之后手工升级的问题不在于慢而在于每台可能不一样。你在 A 机上多敲了一个空格B 机上少改了一行配置半年后排查问题时根本对不上。所以批量场景下我会把整个升级流程固化成脚本用 Ansible 统一下发。核心思路是编译只做一次产物统一分发。在一台和线上同架构的机器上编好把二进制打包推送过去覆盖。这样能保证所有机器的二进制完全一致。# upgrade_openssh.yml 片段 - hosts: ssh_targets serial: 5 # 分批每次 5 台控制爆炸半径 gather_facts: yes tasks: - name: 备份现有 sshd shell: cp -a /usr/sbin/sshd /root/sshd.bak.$(date %s) args: creates: /root/sshd.bak.flag - name: 分发新二进制 copy: src: {{ item.src }} dest: {{ item.dest }} owner: root group: root mode: {{ item.mode }} backup: yes loop: - { src: files/sshd, dest: /usr/sbin/sshd, mode: 0755 } - { src: files/ssh, dest: /usr/bin/ssh, mode: 0755 } - { src: files/scp, dest: /usr/bin/scp, mode: 0755 } - { src: files/sftp-server,dest: /usr/libexec/sftp-server, mode: 0755 } - name: 分发配置文件 template: src: templates/sshd_config.j2 dest: /etc/ssh/sshd_config backup: yes notify: restart sshd - name: 配置语法校验 command: /usr/sbin/sshd -t changed_when: false handlers: - name: restart sshd systemd: name: sshd state: restarted daemon_reload: yesserial: 5这一行是我加的最重要的一行。它让 Ansible 每次只处理 5 台处理完一批再下一批。如果某批出了问题后面的机器不会被波及你有充足的时间去排查。全量并发推下去一旦配置文件有误整个集群同时失联那场面相当难看。另外ssh和scp客户端二进制要不要一起换这个取决于场景。如果服务端和客户端是同一批机器互相连接建议一起换如果客户端是异构环境只换服务端更安全。6.2 打成 RPM 包的好处前面提到中大规模建议走 RPM。这里说一下打包的关键点。用fpm打包最省事fpm -s dir -t rpm \ -n openssh \ -v 9.0p1 \ --iteration 1.el7 \ --epoch 1 \ -a x86_64 \ --rpm-autoreqprov false \ --depends pam --depends zlib \ --prefix /usr \ -C /usr/local/src/openssh-9.0p1 \ sbin/sshd bin/ssh bin/scp bin/sftp libexec/sftp-server--epoch 1很关键。CentOS 官方的 openssh 包 epoch 就是 1如果你打的包不带 epoch版本号比较时会被判为比官方包旧yum update就会拒绝升级或者被 yum 源里的老版本反向覆盖。加上 epoch 之后9.0p1 才会被认为比 7.4p1 新。--rpm-autoreqprov false关掉自动依赖探测。因为编译出来的二进制依赖libcrypto.so.1.1这类非系统库自动探测会往里写一堆找不到的依赖导致安装时Failed dependencies。手工声明--depends更可控。打包的目的不只是安装方便更重要的是版本可追溯。半年后你在一台机器上看到rpm -qa | grep openssh输出openssh-9.0p1-1.el7.x86_64立刻就知道这台机器升过级而源码安装的机器上这个命令查不到任何东西得靠ssh -V去猜。在几十上百台机器的环境里这种可追溯性价值很高。6.3 升级之外的加固动作版本升上去只是第一步顺手把几项配置一起改了收益更大。关闭 root 直接登录。如果业务允许把PermitRootLogin设成no改成用普通账号登录后sudo。这个改动会直接影响一批自动化脚本动之前先确认脚本里用的是哪个账号。限制登录用户。用AllowUsers或者AllowGroups做白名单只允许特定账号登录。这样即使有人爆破出某个系统账号的密码只要它不在白名单里照样连不上。开启登录审计。9.0p1 支持sshd -T输出完整配置配合journalctl -u sshd做日志收集可以比较方便地把登录事件汇总到日志平台。关键字段是Accepted publickey for、Failed password for、Connection closed by。配置文件权限。/etc/ssh/sshd_config建议设成 600 root:root。有些加固基线会检查这一项。定期做算法扫描。用nmap --script ssh2-enum-algos -p 22 ip可以从外部确认服务端实际开放的算法集合比看配置文件更权威因为它是真实协商结果。这些都是和升级配套的动作。单独做某项效果有限但组合起来整个主机的远程访问面会收得很紧。7. 一些我踩过的坑和实际体会讲到这里技术和流程基本讲完了。剩下几条是文档里通常不会写、但实际干活时很有用的经验我单独拎出来说说。关于 OpenSSL 的选择别急着一次到位。我第一次升级时想着反正要动把 OpenSSL 也一起换成 1.1.1 吧结果在一台装了自编译软件的机器上新库的ld.so.conf顺序影响了别的程序导致某个业务进程启动异常。后来我的做法改成先只升 OpenSSH用系统的 OpenSSL 1.0.2k 编译验证通过后再评估是否升级 OpenSSL。一次变更只动一个变量出问题时排查范围小得多。sshd -t必须成为肌肉记忆。我有一次因为赶时间改完配置直接systemctl restart结果配置里有个拼写错误的算法名sshd 启动失败而当时我只有这一个会话一重启连接就断了。那次是靠云控制台救回来的。从那之后sshd -t sshd -T | grep ...变成我改配置后的固定动作一步都不省。老会话不要急着关。如果你是通过 SSH 连上去做升级的那在你成功用新连接登录之前老会话绝对不能关。因为 sshd 重启时已建立的会话不会立即断开sshd 的进程模型是这样设计的你可以利用这个特性重启服务然后用新窗口测试成功了再关老窗口失败了直接用老窗口回滚。这是最实用的安全网。备份要检查可恢复性。备份的动作做完了不代表备份能用。我建议在升级前把备份的sshd.bin复制到一个临时位置加上可执行权限跑一下./sshd.bin -V确认它确实能启动。有些情况下文件权限没保留好回滚时才发现二进制跑不起来那就晚了。记录基线数据。ssh -Q kex、ssh -Q key、ssh -Q cipher、ssh -Q mac这四条命令在升级前后各跑一次存成文本文件对比。将来排查客户端兼容问题的时候这份差异表能帮你快速定位是哪个算法消失了。最后一条也是最重要的升级窗口和通知。OpenSSH 是登录入口任何闪失都会导致整台机器不可达。所以升级一定要安排在维护窗口内提前通知到相关方并确保有带外管理通道可用。技术再熟也架不住一次意外。至于后续还能怎么扩展我自己的路线是把手动流程脚本化脚本 Ansible 化Ansible 流程接入内网的发布审批最后形成安全基线扫描 → 自动生成变更单 → 分批升级 → 自动验证 → 报告归档的闭环。这套东西搭起来要花点时间但对于管理规模超过几十台的团队来说省下来的排查时间远超投入。
返回列表