近期,OpenSSH持续爆出高危漏洞,旧版本存在远程命令执行安全风险。生产环境的银河麒麟V10系统,默认自带OpenSSH 版本偏低,手动逐台升级效率低。
今天分享一套完整自动化升级方案:基于 Ansible 批量部署,源码编译升级 OpenSSL 3.0.20 + OpenSSH 10.3p1,适配银河麒麟V10,零基础运维也能直接上手。
一、升级前置说明
1.1.环境信息
- 操作系统:银河麒麟V10
- 目标版本:OpenSSL3.0.20和OpenSSH10.3p1
- 部署方式:本地源码包+AnsiblePlaybook批量执行
- 适用场景:服务器集群、批量运维、安全合规整改
1.2.升级前必做准备(重要!)
- 生产环境先在测试机验证,验证后,先单台在生产环境升级,单台升级无误后扩展到其他服务器节点;
- 生产环境升级前必须做服务器快照;
- 保留至少一个活跃SSH会话,防止升级失败断连;
- 提前准备源码包:openssl-3.0.20.tar.gz和openssh-10.3p1.tar.gz,放在Ansible剧本同级目录;
- 目标主机提前放行22端口,确保Ansible主机间通信正常;
- 生产环境严禁业务高峰期执行漏洞修复操作。
二、整体升级逻辑(全自动,零人工干预)
- 自动安装编译依赖包(gcc、make、pam-devel等);
- 上传、解压本地源码包到远程主机/usr/local/src;
- 编译安装新版OpenSSL3,配置动态库路径并刷新ldconfig;
- 编译安装新版OpenSSH10.3p1(链接新版OpenSSL,启用PAM);
- 自动备份原有SSH配置、主机密钥、PAM文件和二进制程序(带时间戳,方便回滚);
- 创建sshd专用系统用户及privsep目录,修复权限;
- 替换系统默认的SSH客户端、服务端二进制,补充sftp-server等组件;
- 生成安全加固的sshd_config(禁用弱加密算法、启用PAM);
- 部署systemd服务文件,测试配置语法后异步重启(避免Ansible连接中断);
- 自动修正SELinux上下文(若开启);
- 最终校验SSH版本并输出结果。
三、升级步骤
3.1.准备相关文件
所有文件需统一放在同一个目录下,目录结构如下:
/root/ansible-upgrade-ssh/
├── inventory # Ansible 主机清单
├── openssh-10.3p1.tar.gz # OpenSSH 源码包
├── openssl-3.0.20.tar.gz # OpenSSL 源码包
├── start.sh # 一键执行脚本
└── upgrade_ssh_kylin.yml # Ansible 升级剧本各文件说明:
各文件说明:
inventory(主机清单) :用于定义目标服务器的 IP、账号和密码,示例内容:
[ssh_nodes]
192.168.1.6 ansible_ssh_user=root
如需批量升级多台主机,可在[ssh_nodes]下按格式继续添加
源码包文件
openssl-3.0.20.tar.gz:OpenSSL 3.0.20 源码包,用于编译依赖环境
openssh-10.3p1.tar.gz:OpenSSH 10.3p1 源码包,本次升级目标版本
注意:两个源码包必须与剧本在同一目录,剧本中已通过 stat 任务自动校验文件是否存在。
start.sh(一键执行脚本)
#!/bin/bash
ansible-playbook -i inventory upgrade_ssh_kylin.yml -k
执行前需添加执行权限:
chmod +x start.sh
upgrade_ssh_kylin.yml(升级剧本)
完整的 Ansible 自动化升级脚本,包含:
- 源码包上传、解压
- OpenSSL/OpenSSH 编译安装
- SSH 配置备份与恢复
- 服务重启与版本校验
文件内容如下:
- name: 本地包升级OpenSSL3+OpenSSH10.3p1 Kylin V10
hosts: ssh_nodes
gather_facts: true
vars:
openssl_ver: 3.0.20
openssh_ver: 10.3p1
openssl_prefix: /usr/local/openssl
ansible_python_interpreter: /usr/bin/python3
openssh_prefix: "/usr/local/openssh{{ openssh_ver }}"
bak_suffix: "{{ ansible_date_time.year }}{{ ansible_date_time.month }}{{ ansible_date_time.day }}{{ ansible_date_time.hour }}{{ ansible_date_time.minute }}"
local_pkg_dir: "{{ playbook_dir }}"
remote_src_dir: /usr/local/src
tasks:
- name: 检查本地源码包是否存在
stat:
path: "{{ item }}"
loop:
- "{{ local_pkg_dir }}/openssl-{{ openssl_ver }}.tar.gz"
- "{{ local_pkg_dir }}/openssh-{{ openssh_ver }}.tar.gz"
register: pkg_stat
- name: 若源码包缺失则报错
fail:
msg: "缺少源码包:{{ item.item }}"
loop: "{{ pkg_stat.results }}"
when: not item.stat.exists
- name: 安装编译依赖
yum:
name:
- gcc
- gcc-c++
- make
- pam-devel
- perl-IPC-Cmd
- perl-Time-Piece
- zlib-devel
- krb5-devel
- libedit-devel
- wget
- tar
state: present
- name: 上传openssl源码包
copy:
src: "{{ local_pkg_dir }}/openssl-{{ openssl_ver }}.tar.gz"
dest: "{{ remote_src_dir }}/"
mode: 0644
- name: 上传openssh源码包
copy:
src: "{{ local_pkg_dir }}/openssh-{{ openssh_ver }}.tar.gz"
dest: "{{ remote_src_dir }}/"
mode: 0644
- name: 解压openssl
unarchive:
src: "{{ remote_src_dir }}/openssl-{{ openssl_ver }}.tar.gz"
dest: "{{ remote_src_dir }}"
remote_src: true
- name: 编译安装OpenSSL3
shell: |
cd {{ remote_src_dir }}/openssl-{{ openssl_ver }}
./config --prefix={{ openssl_prefix }} --openssldir={{ openssl_prefix }} shared zlib
make -j$(nproc)
make install
args:
creates: "{{ openssl_prefix }}/bin/openssl"
- name: 配置动态库(麒麟V10 64位使用lib64)
copy:
content: "{{ openssl_prefix }}/lib64\n"
dest: /etc/ld.so.conf.d/openssl3.conf
- name: 刷新动态库缓存
command: ldconfig
- name: 验证OpenSSL版本
shell: "{{ openssl_prefix }}/bin/openssl version"
register: openssl_ver_out
- name: 输出OpenSSL版本
debug:
msg: "OpenSSL 安装完成:{{ openssl_ver_out.stdout }}"
- name: 解压openssh
unarchive:
src: "{{ remote_src_dir }}/openssh-{{ openssh_ver }}.tar.gz"
dest: "{{ remote_src_dir }}"
remote_src: true
- name: 备份旧ssh配置和密钥(保留密钥)
block:
- name: 创建备份目录
file:
path: "/etc/ssh_bak_{{ bak_suffix }}"
state: directory
- name: 复制配置和密钥到备份目录
shell: |
if [ -d /etc/ssh ]; then
cp -a /etc/ssh/* "/etc/ssh_bak_{{ bak_suffix }}/" 2>/dev/null || true
fi
if [ -f /etc/pam.d/sshd ]; then
cp /etc/pam.d/sshd "/etc/pam.d/sshd_bak_{{ bak_suffix }}"
fi
- name: 创建sshd组
group:
name: sshd
system: yes
- name: 创建sshd用户
user:
name: sshd
uid: 50
group: sshd
home: /var/lib/sshd
shell: /sbin/nologin
system: yes
create_home: no
- name: 初始化sshd目录
file:
path: /var/lib/sshd
state: directory
mode: '0700'
owner: root
group: root
- name: 创建/etc/ssh目录
file:
path: /etc/ssh
state: directory
mode: '0755'
- name: 恢复主机密钥(从备份中)
shell: |
if [ -d "/etc/ssh_bak_{{ bak_suffix }}" ]; then
cp -a /etc/ssh_bak_{{ bak_suffix }}/ssh_host_*_key* /etc/ssh/ 2>/dev/null || true
fi
- name: 编译安装openssh(禁用FIDO2,适配lib64)
shell: |
cd {{ remote_src_dir }}/openssh-{{ openssh_ver }}
export PKG_CONFIG_PATH="{{ openssl_prefix }}/lib64/pkgconfig"
export CFLAGS="-I{{ openssl_prefix }}/include"
export LDFLAGS="-L{{ openssl_prefix }}/lib64 -Wl,-rpath,{{ openssl_prefix }}/lib64"
export LD_RUN_PATH="{{ openssl_prefix }}/lib64"
./configure --prefix={{ openssh_prefix }} \
--sysconfdir=/etc/ssh \
--with-pam \
--with-ssl-dir={{ openssl_prefix }} \
--with-md5-passwords \
--with-privsep-path=/var/lib/sshd \
--disable-fido2
make -j$(nproc)
make install
args:
creates: "{{ openssh_prefix }}/sbin/sshd"
- name: 生成sshd_config
copy:
dest: /etc/ssh/sshd_config
content: |
Port 22
PermitRootLogin yes
PasswordAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
PermitEmptyPasswords no
ClientAliveInterval 60
ClientAliveCountMax 10
PrintMotd no
AllowTcpForwarding yes
Subsystem sftp /usr/libexec/openssh/sftp-server
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521
Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-256,hmac-sha2-512
- name: 修复密钥权限
shell: |
chmod 600 /etc/ssh/ssh_host_*_key 2>/dev/null
chmod 644 /etc/ssh/ssh_host_*_key.pub 2>/dev/null
- name: 检查PAM备份文件是否存在
stat:
path: "/etc/pam.d/sshd_bak_{{ bak_suffix }}"
register: pam_backup_stat
- name: 恢复备份的PAM配置
copy:
src: "/etc/pam.d/sshd_bak_{{ bak_suffix }}"
dest: /etc/pam.d/sshd
remote_src: yes
when: pam_backup_stat.stat.exists
- name: 创建默认PAM配置(当无备份时)
copy:
dest: /etc/pam.d/sshd
content: |
#%PAM-1.0
auth required pam_unix.so nullok try_first_pass
account required pam_unix.so
session required pam_unix.so
password required pam_unix.so
when: not pam_backup_stat.stat.exists
- name: 检查加密策略配置文件是否存在
stat:
path: /etc/crypto-policies/back-ends/openssh.config
register: crypto_policy_file
- name: 注释不兼容的 GSSAPIKexAlgorithms 选项
lineinfile:
path: /etc/crypto-policies/back-ends/openssh.config
regexp: '^GSSAPIKexAlgorithms'
line: '#\g<0>'
backrefs: yes
when: crypto_policy_file.stat.exists
- name: 测试sshd配置语法(使用旧二进制测试新配置)
command: /usr/sbin/sshd -t
register: sshd_test
ignore_errors: yes
- name: 若配置测试失败则中止
fail:
msg: "sshd 配置测试失败:{{ sshd_test.stderr }}"
when: sshd_test.rc != 0
- name: 替换系统ssh相关命令
shell: |
for bin in ssh ssh-keygen ssh-add ssh-keyscan sftp ssh-keysign ssh-pkcs11-helper sftp-server; do
if [ -f /usr/bin/$bin ]; then
mv /usr/bin/$bin /usr/bin/${bin}_bak_{{ bak_suffix }}
fi
done
if [ -f /usr/sbin/sshd ]; then
mv /usr/sbin/sshd /usr/sbin/sshd_bak_{{ bak_suffix }}
fi
if [ -f /usr/libexec/openssh/sftp-server ]; then
mv /usr/libexec/openssh/sftp-server /usr/libexec/openssh/sftp-server_bak_{{ bak_suffix }}
fi
\cp -f {{ openssh_prefix }}/bin/ssh /usr/bin/
\cp -f {{ openssh_prefix }}/bin/ssh-keygen /usr/bin/
\cp -f {{ openssh_prefix }}/bin/ssh-add /usr/bin/
\cp -f {{ openssh_prefix }}/bin/ssh-keyscan /usr/bin/
\cp -f {{ openssh_prefix }}/bin/sftp /usr/bin/
\cp -f {{ openssh_prefix }}/libexec/ssh-keysign /usr/bin/ 2>/dev/null || true
\cp -f {{ openssh_prefix }}/libexec/ssh-pkcs11-helper /usr/bin/ 2>/dev/null || true
\cp -f {{ openssh_prefix }}/sbin/sshd /usr/sbin/
mkdir -p /usr/libexec/openssh
\cp -f {{ openssh_prefix }}/libexec/sftp-server /usr/libexec/openssh/
- name: 部署sshd.service
copy:
dest: /usr/lib/systemd/system/sshd.service
content: |
[Unit]
Description=OpenSSH Server Daemon
After=network.target syslog.target
[Service]
Type=simple
ExecStart=/usr/sbin/sshd -D
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
- name: 处理SELinux上下文(若启用)
shell: |
if command -v restorecon >/dev/null 2>&1; then
restorecon -Rv /usr/sbin/sshd /etc/ssh /usr/libexec/openssh 2>/dev/null || true
fi
- name: 异步重启sshd(防止连接中断)
systemd:
name: sshd
daemon_reload: yes
enabled: yes
state: restarted
async: 5
poll: 0
- name: 等待sshd启动完成
pause:
seconds: 3
- name: 检查新sshd进程是否运行
shell: systemctl is-active sshd
register: sshd_status
retries: 5
delay: 2
until: sshd_status.stdout == "active"
- name: 验证新安装的SSH版本
shell: "{{ openssh_prefix }}/bin/ssh -V 2>&1"
register: ssh_ver
- name: 输出升级结果
debug:
msg: "✅ 升级成功:{{ ssh_ver.stdout }}"
升级后,需执行sshd -V命令验证升级结果,并在ssh工具新建链接验证。
四、补充建议
- 建议在测试环境中模拟升级流程,确认业务无影响后再推广到生产。
- 若升级后出现连接异常,可通过服务器控制台进入系统,从备份目录恢复旧版本二进制和配置。
- 定期跟进OpenSSH 官方安全公告,及时更新版本,筑牢服务器安全防线。