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 -L
或sudo 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.
这样的提示信息,也可以通过上述方式解决。