麒麟V10源码升级OpenSSH10.3p1,附Ansible脚本

0 阅读6分钟

近期,OpenSSH持续爆出高危漏洞,旧版本存在远程命令执行安全风险。生产环境的银河麒麟V10系统,默认自带OpenSSH 版本偏低,手动逐台升级效率低。

今天分享一套完整自动化升级方案:基于 Ansible 批量部署,源码编译升级 OpenSSL 3.0.20 + OpenSSH 10.3p1,适配银河麒麟V10,零基础运维也能直接上手。

一、升级前置说明

1.1.环境信息

  • 操作系统:银河麒麟V10
  • 目标版本:OpenSSL3.0.20和OpenSSH10.3p1
  • 部署方式:本地源码包+AnsiblePlaybook批量执行
  • 适用场景:服务器集群、批量运维、安全合规整改

1.2.升级前必做准备(重要!)

  1. 生产环境先在测试机验证,验证后,先单台在生产环境升级,单台升级无误后扩展到其他服务器节点
  2. 生产环境升级前必须做服务器快照;
  3. 保留至少一个活跃SSH会话,防止升级失败断连
  4. 提前准备源码包:openssl-3.0.20.tar.gz和openssh-10.3p1.tar.gz,放在Ansible剧本同级目录;
  5. 目标主机提前放行22端口,确保Ansible主机间通信正常;
  6. 生产环境严禁业务高峰期执行漏洞修复操作。

二、整体升级逻辑(全自动,零人工干预)

  1. 自动安装编译依赖包(gcc、make、pam-devel等);
  2. 上传、解压本地源码包到远程主机/usr/local/src;
  3. 编译安装新版OpenSSL3,配置动态库路径并刷新ldconfig;
  4. 编译安装新版OpenSSH10.3p1(链接新版OpenSSL,启用PAM);
  5. 自动备份原有SSH配置、主机密钥、PAM文件和二进制程序(带时间戳,方便回滚);
  6. 创建sshd专用系统用户及privsep目录,修复权限;
  7. 替换系统默认的SSH客户端、服务端二进制,补充sftp-server等组件;
  8. 生成安全加固的sshd_config(禁用弱加密算法、启用PAM);
  9. 部署systemd服务文件,测试配置语法后异步重启(避免Ansible连接中断);
  10. 自动修正SELinux上下文(若开启);
  11. 最终校验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工具新建链接验证。

四、补充建议

  1. 建议在测试环境中模拟升级流程,确认业务无影响后再推广到生产。
  2. 若升级后出现连接异常,可通过服务器控制台进入系统,从备份目录恢复旧版本二进制和配置。
  3. 定期跟进OpenSSH 官方安全公告,及时更新版本,筑牢服务器安全防线。