OpenSSH的安装与配置

hacking skills
作者

zenggyu

发布日期

2023-01-11

修改的

2023-02-15

摘要
介绍如何安装和配置OpenSSH。

背景

OpenSSH是远程登录主机系统并进行日常管理操作的最重要的软件工具,一旦发现新漏洞应及时升级版本以进行修复。但另一方面,由于最新版本往往只能通过源代码编译安装、且该过程涉及较多依赖软件和配置,如果操作不当会造成主机系统无法访问等严重问题。

为了在日后工作中避免和解决OpenSSH升级时可能遇到的相关问题,我最近进行了一些研究和测试,并将节果和经验记录成此文以备参考。本文将介绍在Debian和Red Hat Enterprise Linux (RHEL) 系列Linux发行版上使用源代码编译安装OpenSSH所涉及到的主要操作和配置,并进行充分说明。

在进入正文前,需要先区分几个容易混淆的名词概念:

  • SSH:一种计算机通信协议
  • OpenSSH:基于SSH协议的一系列软件的开源实现
  • sshd:OpenSSH的服务端软件
  • ssh:OpenSSH的客户端软件

本文所介绍的配置与问题排查部分主要针对sshd,并且假设系统原先自带的sshd能够正常工作(这样可以跳过一些额外的配置,例如用户以及权限分离等)。如需了解如何配置ssh以实现秘钥验证和跳转等,可以看这里

编译安装

源代码的编译需要用到一系列工具软件,这些软件可以通过以下命令安装:

# 安装编译工具-Debian系列发行版使用以下命令
sudo apt install build-essential

# 安装编译工具-RHEL系列发行版使用以下命令(RHEL 8之后使用dnf命令替代yum命令)
sudo yum group install 'Development Tools'

在正式对OpenSSH进行编译之前,最好先根据需要确定软件依赖,以便有针对性地添加依赖和配置、避免增加运维负担。OpenSSH本身并不严格依赖于特定软件;但为了确保OpenSSH能够在常见的系统(间)环境正常运作,需要重点考虑是否添加以下几个依赖:

  • OpenSSL:可以为ssh/sshd提供多种高级的加密算法。由于多数系统上的ssh/sshd均默认使用前述算法,因此最好添加该依赖,以确保不同系统间能够顺利进行访问。
  • zlib:可以为ssh/sshd间的数据传输提供压缩和解压功能,可以用于缩短在低带宽网络中进行大体积数据传输的时间 1 。如果主机所在网络的带宽不低、或者没有经常性通过ssh/sshd传输大体积数据的需求,可以不添加该依赖
  • PAM:可以为sshd提供用户认证功能。如果不具有相关安全要求,或者说当前环境没有为sshd启用PAM 2 、且希望继续保持该状态,那么可以不添加该依赖
  • SELinux:可以被操作系统用于进一步控制与ssh/sshd相关的权限。如果不具有相关安全要求,或者说当前环境没有启用SELinux 3 、且希望继续保持该状态,那么可以不添加该依赖

1 完成OpenSSH安装后,需要通过修改sshd配置文件的Compression参数、或在使用ssh建立连接时添加-C选项来启用该功能。

2 PAM启用状态查询方法:查看原来的sshd的配置文件(一般是/etc/ssh/sshd_config)的UsePAM参数值,如果取值是yes,表示已启用;如果取值是no,则表示未启用。

3 SELinux启用状态查询方法:在命令行中执行getenforce命令,如果返回结果为Enforcing,表示已启用;如果返回结果为Disabled或提示命令不存在,则表示未启用;命令还可能返回Permissive这种结果,表示SELinux会对越权操作进行记录、但不会禁止。这种情况提示系统可能正在调试之中、之后可能会再启用SELinux,因此在编译OpenSSH时最好添加SELinux支持。

4 也可以通过编译安装,本文不展开介绍。

在决定需要添加的依赖后,可以先通过包管理器安装依赖 4 ,以便顺利完成后续步骤。

在完成以上准备工作后,就可以下载OpenSSH的源代码、并进行编译配置和安装了。值得注意的是,OpenSSH原本是为OpenBSD系统开发的软件,如需在Linux等其他系统上进行编译安装,需要使用可移植版(Portable Release)的源代码。在官网页面上进入合适的镜像网点和软件版本后即可下载源代码包;下载完成后,需要对源代码包进行解压、并进入解压后的目录,才可进行编译配置和安装等操作。

编译配置由OpenSSH源代码包里的./configure脚本完成。该脚本可以接受选项并据此决定是否使用有关依赖,其中:OpenSSL、zlib是默认添加的依赖,但可以分别通过--without-openssl--without-zlib选项排除;PAM、SELinux是默认排除的依赖,但可以分别通过--with-pam--with-selinux选项添加。另外,为了保证在新的OpenSSH软件在安装或配置失败后能继续使用原来的软件和配置、不至于无法登录系统,最好同时通过./configure--prefix选项指定不同的安装目录 5

5 如果只希望将软件安装到新目录、但仍使用原来的配置文件,可以在--prefix选项的基础上追加--sysconfdir选项、并指定原来的配置文件目录,例如--sysconfdir=/etc/ssh

假设要下载和安装OpenSSH 9.2可移植版、为其添加前面讲到的所有依赖(OpenSSL、zlib、PAM、SELinux)、并将其安装到/usr/local/目录下,那么需要执行的命令如下:

# 安装依赖-Debian系列发行版使用以下命令
sudo apt install libssl-dev zlib1g-dev libpam0g-dev libselinux1-dev

# 安装依赖-RHEL系列发行版使用以下命令(RHEL 8之后使用dnf命令替代yum命令)
sudo yum install openssl-devel zlib-devel pam-devel libselinux-devel
# 下载OpenSSH源代码包
curl -o openssh-9.2p1.tar.gz https://mirrors.aliyun.com/pub/OpenBSD/OpenSSH/portable/openssh-9.2p1.tar.gz

# 解压源代码包
tar -xzf openssh-9.2p1.tar.gz

# 进入源代码所在目录
cd openssh-9.2p1

# 编译配置
./configure --prefix=/usr/local --with-pam --with-selinux

# 编译
make

# 安装
sudo make install

基本配置

按照上述方式顺利完成安装后,新的sshd服务将默认使用/usr/local/etc/sshd_config这个配置文件,可以根据需要对其进行修改,然后重启sshd使配置生效。例如,修改端口号、是否允许root用户通过ssh远程登录等。

Port <port>
PermitRootLogin { yes | no }

sshd的开机自启动可以通过systemd实现。为了避免和原来的sshd相关文件或服务冲突,可以使用不同的名称创建链接/usr/sbin/sshd2、使其指向新安装的sshd(/usr/local/sbin/sshd),并在后续配置中使用该链接作为启动命令 6

6 对于配置systemd而言,为新安装的sshd创建链接并取用不同的名称并不是必要的;但由于PAM是直接通过文件的名称来识别进程的,因此如果启用了PAM、且希望在不修改原规则的前提下使用新规则进行用户认证,那么可以采用前述方法。

# 创建链接
sudo ln -s /usr/local/sbin/sshd /usr/sbin/sshd2

然后,创建对应的systemd的unit配置文件/etc/systemd/system/sshd2.service,并在其中添加以下内容(注意,Debian发行版和RHEL发行版分别使用$SSHD_OPTS$OPTIONS作为sshd选项参数的变量,需要根据实际情况进行选择) 7

7 对sshd服务来说,最好令Type=notify,以便使systemd能够准确地追踪服务的状态;但要使该配置生效,需要在编译OpenSSH时额外加入一个补丁。如果不希望加入该补丁,可以采用Type=simple,但systemd可能会在某些情况下无法准确识别sshd的运行状态并进行相应的管理操作。

[Unit]
Description=Custom sshd service
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
Type=simple
# sshd参数变量(二选一;Debian系列发行版选$SSHD_OPTS,RHEL系列发行版选$OPTIONS)
ExecStart=/usr/sbin/sshd2 -D { $SSHD_OPTS | $OPTIONS }
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=multi-user.target

完成上述配置后,执行以下命令使之生效:

# 载入systemd配置文件
sudo systemctl daemon-reload

# 启动新的sshd服务
sudo systemctl start sshd2.service

# 使新的sshd服务开机自启动
sudo systemctl enable sshd2.service

在新的sshd2服务正常启动后,最好尝试从另一台主机用ssh发起访问请求,并在确定能够顺利建立连接、通过用户认证、完成登录后,再关掉原来的sshd服务、并取消开机自启动:

# 停止原来的sshd服务
sudo systemctl stop sshd.service

# 使原来的sshd服务不再开机自启动
sudo systemctl disable sshd.service

PAM相关配置

如需启用PAM,首先要在sshd配置文件(/usr/local/sbin/sshd)中将UsePAM参数设为yes并重启sshd。

然后,创建PAM配置文件(/etc/pam.d/sshd2),并向其中添加用户认证相关规则。注意,PAM配置文件的名称必须与sshd服务(/usr/sbin/sshd2)进程的启动命令名称保持一致,否则规则无法正确对应到服务进程并生效,可能会导致用户认证无法通过!

以下是一个能够满足基本用户认证要求的PAM配置文件内容示例:

auth required pam_unix.so
account required pam_unix.so
password required pam_unix.so
session required pam_unix.so

除上述例子外,也可参考系统原有的配置文件(/etc/pam.d/sshd)或源代码包提供的配置文件(在源代码目录的contrib/子目录下)。

SELinux相关配置

备注:SELinux似乎不会直接对新安装的sshd进行权限限制,因此如果只希望顺利启动和使用sshd,以下配置似乎可以跳过。

如需启用SELinux,首先要确认/etc/selinux/config文件的SELINUX参数已设为enforcing、并且getenforce返回结果为Enforcing(否则可在命令行中执行setenforce Enforcing)。然后,再针对ssh/sshd进行SELinux的相关配置。

如果只考虑能否正常启动sshd,那么通常只需关注监听端口规则即可。默认情况下,SELinux只允许sshd监听22端口;如果需要改用其他端口,除了要修改sshd的配置,还需要让SELinux允许sshd绑定该端口:

sudo semanage port -a -t ssh_port_t -p tcp <port>

常见问题的排查与处理

防火墙阻止连接

有的Linux发行版可能会通过防火墙限制ssh访问非默认端口;如果对非默认端口发起连接时出现以下情形,提示防火墙可能阻止了连接:

  • Connection timed out
  • Connection refused
  • No route to host

如需进一步明确上述问题是否由防火墙导致,可以直接通过sudo iptables -Lsudo iptables -S命令分析是否存在相关限制;另外,也可以先确认sshd正在监听指定的端口、再对该端口进行网络抓包(这种方法适合防火墙规则比较复杂、难以分析的情形):

# 确认sshd正在监听指定的端口
lsof -P -i -n | grep -P 'sshd.*LISTEN'

# 查询网络设备
tcpdump --list-interfaces

# 进行网络抓包(须同时从另一台主机对当前主机发起ssh连接请求)
tcpdump -nn -Q inout -i <interface> port <port>

如果另一台主机使用ssh对当前主机发起连接期间,后者只探测到入访流量、而没有出访流量,那么基本可以确定是防火墙阻止了访问。

如需令防火墙放行,可以将规则插入到iptables中靠前的位置(确保优先级较高 8 ):

8 有时iptables的最后一条规则可能是阻止所有连接,如果在其之后追加放行规则,并不能真正起到放行作用。

# 如需删除对应规则,将-I替换成-D再执行即可
sudo iptables -I INPUT -p tcp -m tcp --dport <port> -j ACCEPT
sudo iptables -I OUTPUT -p tcp -m tcp --sport <port> -j ACCEPT

注意,通过上述命令添加的规则在系统重启后会丢失;如需持久化规则,可以通过systemd实现。具体方法是,创建/etc/systemd/system/insert-iptables-rules.service文件,并加入以下内容:

[Unit]
Description=Insert additional rules into iptables
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/iptables -I INPUT -p tcp -m tcp --dport <port> -j ACCEPT
ExecStart=/usr/sbin/iptables -I OUTPUT -p tcp -m tcp --sport <port> -j ACCEPT
TimeoutSec=60
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

然后,使上述配置生效:

sudo systemctl daemon-reload
sudo systemctl start insert-iptables-rules.service
sudo systemctl enable insert-iptables-rules.service

无法使用root用户登录

在root用户通过密码验证的方式发起连接时,如果输入密码后未返回其他提示信息(比如密码错误等)、但仍反复要求输入密码,那么可能是sshd不允许使用root用户登录。

如需对root用户放行,可以在sshd配置文件中将PermitRootLogin设为yes,然后重启sshd。

无法通过密码认证

如果sshd启用了PAM,并且在确认用户密码输入无误的情况下,出现了Permission denied, please try again.等提示密码错误的信息,那可能是PAM配置文件缺失或出错导致的。

如需继续使用PAM,那么需要检查配置文件是否存在、内容是否符合配置规范、且配置文件名称是否与sshd启动命令名称一致(详见前文PAM部分)。

如不再需要使用PAM,那么也可以将sshd配置文件的UsePAM参数设为no,然后重启sshd。

SELinux相关问题

备注:目前我还不太清楚SELinux的规则配置方法和起效条件,无法重现相关问题、指出具体原因并给出对应的解决方法。在排查SELinux相关问题时,可以采用的一个通用策略是:在命令行中执行setenforce Permissive,然后再尝试问题操作,如果问题消失,那么可以确认是SELinux的问题,然后再通过相关日志文件进一步了解问题原因。

如果使用systemd启动sshd服务失败、且提示sshd无法监听所指定的非默认端口,那么可能是SELinux没有允许sshd监听该端口。要解决这个问题,可以将该端口加入SELinux的规则中:

semanage port -a -t ssh_port_t -p tcp <port>

另外,我发现如果ssh登录时失败、且出现/bin/bash: Permission denied.这样的提示信息,也可以通过上述方式解决。

参考材料

Morgan, Andrew G, 和 Thorsten Kukuk. 2006. 《The Linux-PAM System AdministratorsGuide. 2006年. http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_SAG.html.
RedHat. 2023. 《Using SELinux - Basic and Advanced Configuration of Security-Enhanced Linux (SELinux)》. 2023年1月11日. https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/using_selinux/index.
The OpenBSD Foundation. 2023. OpenSSH Portable Release Installation Instructions. 2023年1月11日. https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/INSTALL.