1. 项目概述与核心价值
最近在给几台生产环境的Rocky Linux 9.6服务器做安全加固,SSH密码登录虽然方便,但总感觉像把大门钥匙放在门垫下面,心里不踏实。尤其是在当前环境下,多一层防护总是好的。于是,我决定给这些服务器的SSH登录加上第二道锁——双因素认证。Google Authenticator(谷歌身份验证器)就成了我的首选方案,它不依赖网络,离线也能生成动态验证码,对于服务器这种关键基础设施来说,可靠性是第一位的。
这个项目说白了,就是在Rocky 9.6系统上,让SSH登录过程从“输入密码”变成“输入密码+输入手机App上实时变化的6位数字”。即使你的密码不幸泄露,攻击者没有你手机上的那个动态码,依然无法登录。这对于保护那些暴露在公网、存放着重要数据和服务的服务器来说,意义重大。无论你是运维工程师、开发者,还是自己折腾家庭服务器的爱好者,只要你关心服务器的访问安全,这个配置都值得你花上半小时深入研究并部署。整个过程不复杂,但里面的细节和坑,我踩过不少,今天就把最完整、最稳妥的配置路径分享给你。
2. 环境准备与依赖安装
2.1 系统环境确认与更新
动手之前,第一件事是确认你的系统环境。我用的是一台全新的Rocky Linux 9.6 Minimal安装,确保环境干净。你可以通过cat /etc/redhat-release命令来查看系统版本。确认是Rocky Linux 9.x系列就没问题,9.3、9.4、9.5、9.6甚至9.7,核心步骤都是相通的。
接下来,我强烈建议你先更新系统到最新状态。这不是走形式,因为后续安装的PAM模块和开发工具包,其版本与系统内核和基础库的版本紧密相关,更新能避免很多因版本不匹配导致的诡异问题。
sudo dnf update -y更新完成后,通常需要重启系统以确保所有更新生效,特别是内核更新。你可以使用sudo reboot重启。重启后再次登录,我们继续。
2.2 安装核心依赖包
Google Authenticator的PAM模块在默认仓库里没有,我们需要通过EPEL(Extra Packages for Enterprise Linux)仓库来获取。EPEL是社区维护的,为RHEL及其衍生版(如Rocky、CentOS)提供大量高质量附加软件包。
首先,安装EPEL仓库:
sudo dnf install epel-release -y安装成功后,我们就可以安装本次配置的核心软件包了:
sudo dnf install google-authenticator qrencode -y这里解释一下这两个包:
google-authenticator: 这是主角。它包含了生成密钥、管理用户令牌的命令行工具,以及最重要的——与系统PAM(可插拔认证模块)集成的库文件。PAM是Linux系统身份验证的框架,我们就是通过配置PAM,让SSH在验证密码后,再去调用Google Authenticator进行二次验证。qrencode: 这是一个非常实用的辅助工具,用于生成二维码。在为用户初始化认证器时,手动输入一长串密钥既麻烦又容易出错。用这个工具生成二维码,用户用手机App一扫即可绑定,体验好得多。
除了这两个,我们还需要确保系统安装了必要的开发工具和PAM开发库,因为从源码编译或某些底层交互可能会用到(虽然dnf安装的二进制包通常不需要,但有备无患):
sudo dnf groupinstall "Development Tools" -y sudo dnf install pam-devel -y安装完成后,可以验证一下google-authenticator命令是否可用:
which google-authenticator如果返回/usr/bin/google-authenticator这样的路径,说明安装成功。
注意: 如果你在安装过程中遇到类似“无法找到 google-authenticator 包”的错误,请先确认EPEL仓库是否成功启用。可以运行
sudo dnf repolist | grep epel查看。如果没列出,可能需要手动下载对应版本的EPEL RPM包进行安装。
3. 为用户配置Google Authenticator令牌
软件装好了,接下来要为需要启用双因素认证的每个用户单独生成一个“种子密钥”。这个密钥是独一无二的,会保存在用户的家目录下,并需要被录入到用户的手机Authenticator App中(比如Google Authenticator, Microsoft Authenticator, Authy等)。
3.1 生成初始配置
切换到你要配置的用户。非常重要:一定要在目标用户的登录会话下执行以下命令,而不是在root下直接运行。因为生成的配置文件.google_authenticator会保存在当前用户的家目录下,并且文件权限是600(仅用户自己可读)。如果以root身份为其他用户生成,权限和归属可能会出问题。
# 假设要为用户 ‘alice’ 配置 sudo su - alice # 执行初始化命令 google-authenticator执行命令后,会进入一个交互式的配置向导。你会看到几个问题,我的选择和理由如下:
Do you want authentication tokens to be time-based (y/n)
- 输入
y。这是默认和推荐的方式,基于时间(TOTP)。另一种是基于计数器(HOTP),每次登录后计数器递增,但需要服务器和客户端同步状态,对于SSH场景,基于时间的更简单可靠。
- 输入
接着会显示一个大大的二维码(如果你的终端支持),以及你的“密钥”(secret key)、验证码(verification code)和紧急备用码(emergency scratch codes)。请立即、妥善保存这些信息!尤其是备用码,万一手机丢失,可以用它来登录。你可以用手机App扫描二维码,或者手动输入密钥来添加账户。
Do you want me to update your “/home/alice/.google_authenticator” file? (y/n)
- 输入
y。这会将配置写入家目录下的隐藏文件。
- 输入
Do you want to disallow multiple uses of the same authentication token? This restricts you to one login about every 30s, but it increases your chances to notice or even prevent man-in-the-middle attacks. (y/n)
- 输入
y。禁止重复使用同一个令牌。这能有效防止重放攻击。虽然意味着30秒内不能连续用同一个码登录两次,但对安全性的提升是值得的。
- 输入
By default, tokens are good for 30 seconds. In order to compensate for possible time-skew between the client and the server, we allow an extra token before and after the current time. If you experience problems with poor time synchronization, you can increase the window from its default size of 3 permitted codes (one previous code, the current code, the next code) to 17 permitted codes (for example). Do you want to do so? (y/n)
- 输入
n。默认的“时间容差窗口”是前后各一个码(共3个有效码)。如果你的服务器和手机时间同步良好(通过NTP),保持默认即可。扩大窗口会降低安全性。只有在你确认时间不同步且无法解决时,才考虑选y。
- 输入
If the computer that you are logging into isn’t hardened against brute-force login attempts, you can enable rate-limiting for the authentication module. By default, this limits attackers to no more than 3 login attempts every 30s. Do you want to enable rate-limiting? (y/n)
- 输入
y。启用速率限制。这是非常重要的安全措施,可以防止攻击者暴力尝试验证码。默认每30秒最多3次尝试,足够你正常登录,又能极大增加攻击成本。
- 输入
配置完成后,退出当前用户回到有sudo权限的账户:
exit3.2 关键文件解读与备份
现在,查看一下用户alice的家目录,会发现一个名为.google_authenticator的隐藏文件。
sudo cat /home/alice/.google_authenticator你会看到类似以下内容(密钥是打乱的示例):
JBSWY3DPEHPK3PXP " RATE_LIMIT 3 30 " WINDOW_SIZE 3 " DISALLOW_REUSE " TOTP_AUTH 12345678 23456789 34567890 45678901 56789012我来逐行解释:
- 第一行:
JBSWY3DPEHPK3PXP这就是核心的“种子密钥”(Secret Key)。它被编码为Base32字符串。手机App和服务器都拥有这个相同的密钥,结合当前时间,各自独立计算出相同的6位数字。这个密钥必须绝对保密! - 以
"开头的行: 这些是配置选项,对应我们刚才交互式设置的选择。RATE_LIMIT,WINDOW_SIZE,DISALLOW_REUSE,TOTP_AUTH。 - 最后的5行数字: 这些就是“紧急备用码”。每个码只能用一次。用掉一个,这个文件里对应的行会被删除。请务必将这些备用码抄写在安全的地方,比如密码管理器或离线记事本。
实操心得: 在为生产环境多个用户配置时,我习惯在初始化后,立即将这个
.google_authenticator文件进行加密备份。同时,将二维码图片和备用码单独保存。这样,即使服务器硬盘损坏或用户家目录误删,也能快速恢复用户的2FA配置,避免用户被锁死在系统外。备份命令示例:sudo tar -czf /secure_backup/2fa_backup_alice.tar.gz -C /home/alice .google_authenticator,记得将备份文件放到安全的位置并设置严格权限。
4. 配置SSH服务使用PAM认证
这是最关键的一步,我们要修改两个配置文件:PAM的SSH配置和SSH服务本身的配置。在修改前,请务必为当前SSH会话开一个“逃生窗口”!
4.1 保持一个活动的SSH连接不要关闭
在你进行以下配置并测试成功之前,请确保至少有另一个已经通过密码或密钥登录的SSH会话保持连接。这样,万一配置出错导致所有新登录被阻断,你还可以通过这个“逃生窗口”回滚配置,而不用去机房接显示器键盘。
4.2 配置PAM(/etc/pam.d/sshd)
PAM配置文件定义了SSH登录时的认证栈(authentication stack)。我们需要在密码认证之后,追加Google Authenticator验证。
编辑PAM配置文件:
sudo vi /etc/pam.d/sshd找到与auth相关的行。通常,你会看到类似auth substack password-auth这样的行。我们需要在这一类认证行的末尾,添加Google Authenticator的配置。
一个安全且常见的添加位置是在auth required pam_sepermit.so之后,auth substack password-auth之前或之后。为了清晰,我选择在password-auth这一行之后添加。修改后的相关段落看起来像这样:
#%PAM-1.0 auth required pam_sepermit.so auth substack password-auth auth required pam_google_authenticator.so nullok关键解释:
auth: 表示这是一个认证类模块。required: 表示此模块必须认证成功,但如果失败,整个认证栈不会立即终止,会等所有模块执行完再返回失败。这比requisite(失败立即终止)更友好,便于调试。pam_google_authenticator.so: 这就是我们安装的PAM模块。nullok:这个参数至关重要!它的意思是“如果用户没有配置.google_authenticator文件,则跳过此模块,视为成功”。这允许你为部分用户启用2FA,而其他用户仍仅用密码登录。在初次部署时,务必加上nullok,并先为一个测试用户配置。等所有用户都配置好后,可以考虑移除nullok以强制所有用户必须使用2FA。
4.3 配置SSH服务(/etc/ssh/sshd_config)
接下来,我们需要告诉SSH服务,在认证时要使用PAM。
编辑SSH服务配置文件:
sudo vi /etc/ssh/sshd_config找到并确保以下两个参数被设置:
ChallengeResponseAuthentication yes UsePAM yesChallengeResponseAuthentication yes: 启用质询-应答认证。Google Authenticator的动态码就是通过这种方式交互的。UsePAM yes: 启用PAM模块。这通常是默认开启的,但检查一下总没错。
另一个重要设置:确认PasswordAuthentication的设置符合你的预期。
- 如果你希望只允许“公钥+2FA”,而完全禁用密码登录(最安全),那么保持
PasswordAuthentication no。这样,首次认证是公钥,第二次认证(如果配置了)是PAM(即Google Authenticator)。 - 如果你希望允许“密码+2FA”,则设置
PasswordAuthentication yes。这样,首次认证是密码,第二次是Google Authenticator。
我个人在生产环境推荐“公钥+2FA”的组合,因为它避免了密码在网络上传输,且公钥认证本身就更强。但“密码+2FA”对于不习惯管理密钥的用户来说更容易上手。
修改完成后,保存文件。在重启SSH服务前,先检查配置语法是否正确,这是一个好习惯:
sudo sshd -t如果没有任何输出,表示语法正确。如果有错误,它会提示你哪一行有问题,请根据提示修正。
现在,可以安全地重启SSH服务了:
sudo systemctl restart sshd重启后,千万不要立即关闭你当前的“逃生窗口”SSH会话!你需要用一个新的SSH连接来测试配置是否生效。
5. 登录测试与故障排查
5.1 测试新的登录流程
打开一个新的终端窗口,尝试用你配置了2FA的用户(如alice)登录:
ssh alice@your_server_ip登录过程会发生变化:
- 首先,会提示你输入密码(如果
PasswordAuthentication是yes)或者进行公钥认证。 - 认证通过后,你会看到一个新的提示:
这时,打开你手机上的Google Authenticator App,找到对应你服务器(或你设置的名字)的账户,输入当前显示的6位数字。Verification code: - 输入正确的验证码,回车。如果一切顺利,你就会成功登录。
恭喜!双因素认证已经成功启用。
5.2 常见问题与排查技巧实录
在实际部署中,你可能会遇到一些问题。下面是我踩过坑后总结的排查清单:
问题1:登录时没有出现Verification code:提示,直接进去了。
- 可能原因A: PAM配置未生效。检查
/etc/pam.d/sshd文件,确认pam_google_authenticator.so行已添加且拼写正确。确保文件末尾没有空格等异常字符。 - 可能原因B: SSH配置未生效。检查
/etc/ssh/sshd_config中的ChallengeResponseAuthentication和UsePAM是否都为yes。修改后必须重启sshd服务。 - 可能原因C: 当前测试用户的家目录下没有
.google_authenticator文件。用sudo ls -la /home/username/检查。如果没有,需要以该用户身份运行google-authenticator命令生成。 - 排查命令:
# 检查PAM配置 sudo grep google-authenticator /etc/pam.d/sshd # 检查SSH配置 sudo grep -E “^(ChallengeResponseAuthentication|UsePAM)” /etc/ssh/sshd_config # 检查用户配置文件 sudo ls -la /home/alice/.google_authenticator
问题2:提示Permission denied或Invalid verification code。
- 可能原因A: 服务器与手机时间不同步。这是最常见的原因。动态码基于精确的30秒时间片计算,时间差超过30秒(默认窗口)就会失败。
- 解决: 确保服务器时间同步。安装并启用NTP服务:
sudo dnf install chrony -y sudo systemctl enable --now chronyd sudo chronyc sources # 查看时间源状态 - 同时,检查手机时间是否设置为“自动从网络获取时间”。
- 解决: 确保服务器时间同步。安装并启用NTP服务:
- 可能原因B: 在Authenticator App中添加账户时,密钥输入错误或二维码扫描不完整。
- 解决: 删除App中的旧账户,重新用
google-authenticator命令生成新的二维码和密钥进行绑定。注意:重新生成会作废旧的备用码。
- 解决: 删除App中的旧账户,重新用
- 可能原因C: 速率限制触发。如果你在短时间内连续输错多次验证码,会被临时阻止。
- 解决: 等待30秒再试。
- 可能原因D:
.google_authenticator文件权限不对。该文件必须仅对所属用户可读(权限600)。- 解决:
sudo chmod 600 /home/alice/.google_authenticator
- 解决:
问题3:为root用户配置后,通过su或sudo切换用户时也要求验证码。
- 原因: 如果你在
/etc/pam.d/su或/etc/pam.d/sudo文件中也添加了pam_google_authenticator.so模块,那么这些操作也会触发2FA。 - 应对: 这取决于你的安全策略。如果你希望本地切换用户也需要2FA,那就保留。如果觉得麻烦,可以不要修改这些文件,仅让远程SSH登录需要2FA。
问题4:紧急情况下,备用码也用完了,如何登录?
- 这是最危险的情况,意味着你被完全锁在服务器外。
- 预防优于补救: 务必妥善保管备用码,并考虑为至少一个管理账号(如root)保留一个未启用2FA的SSH密钥对,将该私钥加密存储在极度安全的地方,仅用于紧急救援。
- 最后的物理手段: 如果服务器有物理控制台(KVM或本地终端),你可以从控制台登录,然后修改相应用户的PAM配置或删除其
.google_authenticator文件以暂时禁用2FA。这就是为什么“逃生窗口”和备份如此重要。
6. 生产环境进阶配置与优化
基础功能跑通后,我们可以考虑一些更贴合生产环境的优化措施。
6.1 为不同用户组设置不同策略
你可能不想为所有用户强制2FA,比如一些用于自动化任务的系统账户。利用PAM的灵活性和nullok参数,我们可以实现精细控制。
一种方法是使用pam_succeed_if模块。例如,我们想让除了deploy用户外的所有用户都使用2FA,可以这样修改/etc/pam.d/sshd:
auth [success=1 default=ignore] pam_succeed_if.so user = deploy auth required pam_google_authenticator.so nullok这段配置的意思是:如果用户是deploy,则跳过下一行(success=1);对于其他所有用户,则执行pam_google_authenticator.so检查。
6.2 与SSH公钥认证结合的最佳实践
最安全的组合是:SSH公钥认证 + Google Authenticator。这样,第一次认证(公钥)解决了“你有什么”(私钥)的问题,第二次认证(动态码)解决了“你知道什么”(随时间变化的码)的问题。配置如下:
/etc/ssh/sshd_config:PasswordAuthentication no # 完全禁用密码登录 PubkeyAuthentication yes ChallengeResponseAuthentication yes UsePAM yes AuthenticationMethods publickey,keyboard-interactive:pamAuthenticationMethods publickey,keyboard-interactive:pam这一行是关键。它指定了认证方法顺序:先公钥,后键盘交互式(即PAM/Google Authenticator)。两者都必须成功。用户侧: 用户需要先将自己的SSH公钥上传到服务器的
~/.ssh/authorized_keys文件中。登录流程: 用户连接时,首先自动使用私钥认证。成功后,系统会弹出
Verification code:提示,要求输入Google Authenticator的动态码。
这种模式下,即使私钥泄露,没有动态码依然无法登录;反之亦然。安全性极高。
6.3 日志记录与监控
启用2FA后,监控认证日志很重要。日志位于/var/log/secure(Rocky Linux 9)。你可以看到成功的2FA登录和失败的尝试。
sudo tail -f /var/log/secure | grep google-authenticator失败日志会显示“Invalid verification code”或“Failed to read “/home/user/.google_authenticator”等信息。定期检查这些日志,可以帮助你发现暴力破解尝试或配置问题。
6.4 批量部署与自动化
如果需要为大量用户配置,手动运行google-authenticator命令不现实。你可以编写一个脚本,自动化这个过程。核心思路是:
- 为每个用户生成一个随机密钥(可以用
openssl rand -base64 20生成,然后转换为Base32)。 - 用
qrencode生成二维码图片。 - 按照固定格式创建
.google_authenticator文件。 - 将密钥、二维码和备用码通过安全渠道分发给相应用户。
但请注意,自动化生成密钥并分发,本身引入了密钥管理的新风险。务必确保脚本运行环境的安全,以及分发过程的安全(如使用加密邮件、内部安全通讯工具等)。对于超大规模或合规要求严格的环境,可能需要考虑更专业的集中式双因素认证解决方案(如FreeRADIUS + Google Authenticator模块,或商业产品),而非这种基于本地文件的分散式管理。