本文由DeepSeek总结,经过人工修改

前言

在学习安全这方面的知识以前,我开一个VPS都是直接使用root账户+密码+默认22端口的形式登录SSH。虽然以前用滴滴云的时候,滴滴云强制要求用户使用普通账户登录SSH再通过命令使用root权限,但是当时只是感到麻烦更多罢了。其实使用一个普通账户登录SSH再用命令使用root权限会比直接登录root账户稍微安全一些,不过这只能算是基础的安全防御手段。

随着网络安全威胁日益复杂,仅靠静态密码已经难以抵御暴力破解、凭证泄露等攻击。为了达到真正的安全标准,我们需要引入多层次的防御机制。

本文操作主要在AlmaLinux 10环境下验证通过,理论上同样适用于RHEL、Rocky Linux等RHEL衍生发行版。

特别提醒: 以下涉及大量SSH和PAM配置,修改不当可能导致无法登录。强烈建议通过VNC等带外控制台进行操作,以防配置错误无法恢复。

基础安全配置

1.1 添加普通用户

为了安全,平时我们应该以普通用户的身份操作VPS,而不是直接使用root。添加用户有两个命令,adduser和useradd,在不同系统中的定义以及用法上有区别,这里提供一个通用添加方法(Ubuntu/Debian/RHEL/RHEL衍生均可用):

# 以添加用户名为 ec2-user 的普通用户为例子
useradd -m -s /bin/bash ec2-user

# 对该用户设置密码
passwd ec2-user

1.2 授予普通用户sudo权限

有时需要使用root权限,比如安装软件、启动服务等操作时就需要用到sudo命令来提升权限。授予用户sudo权限最简单的方法是把用户添加到sudo组。如果系统中没有sudo,需要先安装:

# 安装sudo(如已安装可跳过)
dnf install sudo -y
#apt install sudo -y

# 以添加 ec2-user 到 wheel 组为例(RHEL系列使用wheel组)
usermod -aG wheel ec2-user
#usermod -aG sudo ec2-user

1.3 配置SSH密钥登录

密码的缺点很明显:容易被暴力破解,而且需要记忆,使用起来不是特别安全便捷。密钥的好处是,你只需要一对密钥文件:公钥和私钥,公钥相当于门锁,装在VPS上,私钥相当于钥匙,放在本地计算机上,登录的过程就像用钥匙去开锁,有钥匙的人才能打得开,不仅安全且方便。

在服务器上操作(切换到新建的普通用户):

# 切换到 ec2-user并创建 .ssh 目录并设置正确权限
su - ec2-user && mkdir -p ~/.ssh && chmod 700 ~/.ssh

在本地计算机上生成密钥对(以Windows为例):

# 生成ED25519密钥对(推荐,安全性高且性能好)
ssh-keygen -t ed25519 -C "your_email@example.com"

依据命令提示输入密钥的生成路径以及密钥的通行短语(Passphrase)。如果设置了保密口令则必须牢记,否则无法找回或更改。

生成后,在生成路径找到公钥xxx.pub(这是一个纯文本文件),将其内容复制到服务器的~/.ssh/authorized_keys中:

# 在服务器上,将公钥内容写入 authorized_keys
nano ~/.ssh/authorized_keys

# 设置文件正确权限
chmod 600 ~/.ssh/authorized_keys

1.4 禁用不安全的登录方式

前面的一系列操作都是铺垫,为禁止root账户登录和密码登录以及修改SSH端口做准备,这才是提升VPS安全性的主要目的。

打开/etc/ssh/sshd_config文件进行修改:

sudo nano /etc/ssh/sshd_config

主要配置以下的项

配置项推荐值说明
Port自定义端口(如6022)避免默认22端口被扫描
LoginGraceTime2m登录认证超时时间
PermitRootLoginno禁止root直接登录
PasswordAuthenticationno禁止密码登录
PubkeyAuthenticationyes启用密钥登录
ChallengeResponseAuthenticationyes为后续2FA做准备
UsePAMyes启用PAM认证
KbdInteractiveAuthenticationyes启用键盘交互认证
AuthenticationMethodspublickey,keyboard-interactive强制双因素认证

修改完成后检查并重启SSH服务:

sudo sshd -t
sudo systemctl restart sshd

注意: 如果是通过SSH进行配置,请勿直接断开连接。应先打开一个新终端测试能否使用私钥登录普通用户,确认成功后再关闭旧会话。

SSH双因素认证(2FA)配置

即使我们禁用了密码登录,只允许使用公钥和私钥进行SSH连接,如果未经授权的用户窃取了你的密钥,他仍然可以借此访问系统。双因素认证(2FA)通过增加一层验证(如手机上的验证器应用生成的动态验证码),使仅凭密钥无法登录,极大提升了安全性。

AlmaLinux使用PAM(Pluggable Authentication Modules,可插拔认证模块)来集成OTP认证。Google Authenticator是其中最常用的方案。

2.1 安装Google Authenticator PAM模块

Google Authenticator PAM模块需要从EPEL仓库安装。qrencode用于在终端显示二维码,方便手机扫描:

# 启用EPEL仓库
sudo dnf install -y epel-release

# 安装google-authenticator和qrencode
sudo dnf install -y google-authenticator qrencode
#sudo apt update && sudo apt install -y libpam-google-authenticator

关于AlmaLinux 10的特别说明: 目前AlmaLinux 10的官方仓库和EPEL 10尚未完全稳定,google-authenticator包可能暂时不可用。如果你在使用AlmaLinux 10时遇到此问题,可以考虑启用EPEL 10的测试仓库或改用OATH Toolkit方案

2.2 为用户生成OTP密钥

务必以目标用户身份(而非root)执行以下命令。本文以ec2-user为例:

# 切换到目标用户
su - ec2-user

# 运行 google-authenticator
google-authenticator

执行后,终端会显示一个二维码,同时输出以下关键信息:

  • Secret key:密钥字符串(备用)
  • Verification code:当前验证码
  • Emergency scratch codes:5个紧急备用码

⚠️ 极其重要:请务必将紧急备用码保存在安全的地方!这些备用码是你在丢失OTP设备后唯一的恢复途径。

根据提示回答以下问题:

提示建议选择中文翻译
Do you want me to update your "~/.google_authenticator" file?y您是否希望我更新“~/.google_authenticator”文件?
Do you want to disallow multiple uses of the same token?y您是否希望禁止重复使用同一认证令牌?此设置会将登录间隔限制为大约每30秒一次,但能提高您发现甚至阻止中间人攻击的可能性
Do you want to increase the original time window?n默认情况下,移动应用每 30 秒生成一个新令牌。为了弥补客户端与服务器之间可能存在的时间偏差,我们在当前时间的前后各允许一个额外的令牌。这使得认证服务器与客户端之间的时间偏差最多可达 30 秒。如果您遇到时间同步不佳的问题,可以将时间窗口从默认的 3 个有效代码(前一个代码、当前代码和下一个代码)扩展至 17 个有效代码(前 8 个代码、当前代码和后 8 个代码)。这样可以容忍客户端与服务器之间最多 4 分钟的时间偏差。您要这样做吗?
Do you want to enable rate-limiting?y如果您要登录的计算机未针对暴力破解登录尝试进行强化防护,您可以为身份验证模块启用速率限制。默认情况下,此功能将限制攻击者每 30 秒最多只能进行 3 次登录尝试。您要启用速率限制吗?

2.3 配置PAM强制OTP认证

编辑SSH服务的PAM配置文件:

sudo nano /etc/pam.d/sshd

修改要点:

  1. 在文件开头添加OTP认证行:

    auth required pam_google_authenticator.so
  2. 注释掉原有的密码认证行(跳过系统密码验证):

    #auth substack password-auth
    #auth include postlogin

配置文件示例:

#%PAM-1.0
auth       required     pam_google_authenticator.so
#auth       substack     password-auth
#auth       include      postlogin

account    required     pam_sepermit.so
account    required     pam_nologin.so
account    include      password-auth
# ... 其余session配置保持不变

这样配置后,SSH登录将只要求OTP验证,不再询问系统密码

2.4 配置SSH服务端强制双因素认证

编辑/etc/ssh/sshd_config(注意AlmaLinux中/etc/ssh/sshd_config.d/50-redhat.conf可能会覆盖主配置):

sudo nano /etc/ssh/sshd_config

确保以下配置存在:

# 强制双因素认证:必须同时提供SSH密钥和OTP
AuthenticationMethods publickey,keyboard-interactive

⚠️ 特别注意事项:

检查/etc/ssh/sshd_config.d/50-redhat.conf中的配置,确保没有覆盖上述设置:

# 检查是否存在冲突配置
sudo cat /etc/ssh/sshd_config.d/50-redhat.conf

如果发现ChallengeResponseAuthentication noKbdInteractiveAuthentication no,请将其改为yes。这个配置冲突在实际操作中经常出现,是导致AuthenticationMethods指令失效的常见原因。

2.5 重启服务

# 测试配置语法是否正确
sudo sshd -t

# 如果没有错误,重启SSH服务
sudo systemctl restart sshd

# 查看服务状态
sudo systemctl status sshd

2.6 测试登录

建议保持现有SSH会话,打开新终端测试

# 客户端连接命令(注意参数)
ssh -o PreferredAuthentications=publickey,keyboard-interactive -o RequestTTY=yes 用户名@服务器地址

预期的登录流程

stateDiagram-v2 [*] --> 密钥认证 密钥认证 --> OTP认证: 密钥通过 密钥认证 --> [*]: 密钥失败(拒绝连接) OTP认证 --> 登录成功: OTP通过 OTP认证 --> [*]: OTP失败(拒绝连接) 登录成功 --> [*]

如果遇到Connection closed by ... port 22错误,通常是服务器端keyboard-interactive认证被禁用导致的,请检查上一步的配置文件。

为 system-auth 和 sudo 命令启用 OTP 认证

3.1 修改 system-auth

/etc/pam.d/system-auth 被 sudo、su、login 等多个服务引用,修改一处即可影响全局。
Debian系则为/etc/pam.d/common-auth
备份并编辑:

sudo cp /etc/pam.d/system-auth /etc/pam.d/system-auth.bak
sudo nano /etc/pam.d/system-auth
#sudo cp /etc/pam.d/common-auth /etc/pam.d/common-auth.bak
#sudo nano /etc/pam.d/common-auth

在 auth 部分开头添加 OTP 模块:

auth        required      pam_google_authenticator.so
# ... 其余保持不变

# 部分发行版可能有下面这一行,但也需要注释
#@include common-auth

如果希望完全弃用密码(仅 OTP):

auth        required      pam_google_authenticator.so
#auth        sufficient    pam_unix.so nullok try_first_pass   # 注释掉
auth        required      pam_deny.so

3.2 验证 sudo 继承配置

/etc/pam.d/sudo 默认已 include system-auth,无需额外修改:

# 确认 sudo 引用了 system-auth
cat /etc/pam.d/sudo | grep system-auth
# 应输出:auth       include      system-auth
#cat /etc/pam.d/sudo | grep common-auth

3.3 测试

# 测试 sudo
sudo whoami
# 预期:Verification code: [输入6位OTP]

# 测试 su
su - 用户名
# 预期:Verification code: [输入6位OTP]

3.4 影响范围

命令/服务是否受影响
sudo✅ 是
su✅ 是
本地登录 (login)✅ 是
sshd⚠️ 已单独配置(见第二部分)

3.5 紧急恢复

配置错误导致无法提权时:

# 通过物理控制台或保留的 root SSH 会话执行
sudo cp /etc/pam.d/system-auth.bak /etc/pam.d/system-auth

常见问题排查指南

A.1 SSH连接被拒绝:Connection closed

症状: 添加AuthenticationMethods publickey,keyboard-interactive后SSH服务无法启动,客户端报Connection closed。

原因: 服务器端禁用了keyboard-interactive认证。

解决方法: 检查/etc/ssh/sshd_config/etc/ssh/sshd_config.d/*.conf,确保:


KbdInteractiveAuthentication yes
ChallengeResponseAuthentication yes

A.2 SSH服务启动失败:Disabled method "keyboard-interactive"

症状: sudo systemctl status sshd显示上述错误。

解决方法: 检查/etc/ssh/sshd_config.d/50-redhat.conf,将ChallengeResponseAuthentication no改为yes,然后重启服务。

A.3 OTP验证后仍然询问密码

原因: PAM配置中pam_unix.so仍在生效。

解决方法: 在/etc/pam.d/sshd中注释掉auth substack password-authauth include postlogin行。

A.4 客户端连接后没有OTP输入提示

解决方法:客户端添加-o RequestTTY=yes参数或在~/.ssh/config中配置RequestTTY yes

A.5 SELinux阻止pam_google_authenticator.so写入文件

检查方法:

sudo ausearch -m avc -ts recent | grep google_authenticator

解决方法:

sudo restorecon -R -v /home/

A.6 在AlmaLinux 10上找不到google-authenticator

解决方法: 使用OATH Toolkit作为替代方案。

参考连接:Linux VPS 服务器基础安全设置