为SSH登录添加双重身份验证(2FA)
1. 概述
SSH 是安全shell,通常用于访问远程 Linux 系统。由于我们经常使用它来连接包含重要数据的计算机,因此建议添加另一个安全层: 双因素身份验证 (2FA)。
什么是双因素身份验证
多重身份验证是一种使用至少两种不同的身份验证方式来确认您的身份的方法。最常见和最容易实现的双因素身份验证示例使用密码(复杂的密码,通常由多个单词组成)和由特殊移动应用程序生成的一次性密码的组合。
我们将使用适用于 Android(在 Play 商店中)和 iOS(在 iTunes)的 Google Authenticator 应用程序来生成身份验证码。
Google Authenticator 使用一次性密码(One-time Passcodes)(OTP)进行两步验证,集成在Linux的 PAM 系统中。
- HOTP:基于 HMAC 计数的一次性密码
- TOTP:基于时间的一次性密码
如果使用过Google Authenticator二次验证器的话你肯定知道,添加密钥的方式有两种:一种是扫描二维码添加密钥,另外一种是手动输入密钥。 若要扫码添加,则需要在终端生成配置二维码,需要额外安装 qrencode 软件包。
参考:
- 谷歌/谷歌身份验证器-libpam
[将 SSH 配置为使用双因素身份验证 Ubuntu](https://ubuntu.com/tutorials/configure-ssh-2fa#1-overview)
关于 PAM
PAM(Pluggable Authentication Modules,可插拔认证模块)提供集中式身份验证机制,系统应用可以使用此机制将身份验证中继到集中配置的框架。
PAM 可插拔,因为存在用于不同类型的身份验证源(如 Kerberos、SSSD、NIS 或本地文件系统)的 PAM 模块。可以优先使用不同的身份验证源。
PAM配置文件
每个支持 PAM 的应用程序或服务在/etc/pam.d/目录中都有一个文件。该目录中的每个文件都与它控制访问的服务同名。例如, login程序将其服务名称定义为login。例如,登录程序将其服务名称定义为 login,并使用 /etc/pam.d/login PAM 配置文件。
每个 PAM 配置文件都包含一组指令,这些指令定义模块(身份验证配置区域)以及与它相关的任何控件或参数。
这些指令都有一个简单的语法,用于标识模块的用途(接口)和模块的配置设置。
1
module_interface control_flag module_name module_arguments
在 PAM 配置文件中,模块接口是定义的第一个字段。例如:
1
2
3
4
5
6
7
8
#简单的 PAM 配置示例:(按顺序执行)
auth required pam_securetty.so
auth required pam_unix.so nullok
auth required pam_nologin.so
account required pam_unix.so
password required pam_pwquality.so retry=3
password required pam_unix.so shadow nullok use_authtok
session required pam_unix.so
PAM 接口本质上是该特定模块可以执行的身份验证作类型。有四种类型的 PAM 模块接口可用,每种接口都对应身份验证和授权过程的不同方面:
auth— 此模块接口对用户进行身份验证。例如,它请求并验证密码的有效性。具有此接口的模块还可以设置凭据,例如组成员身份。account— 此模块接口验证是否允许访问。例如,它检查用户帐户是否已过期,或者是否允许用户在一天中的特定时间登录。password— 此模块接口用于更改用户密码。session— 此模块接口配置和管理用户会话。还可以执行允许访问所需的其他任务,例如挂载用户的主目录和使用户的邮箱可用。
单个模块可以提供任何或所有模块接口。例如,提供所有四个模块接口。pam_unix.so
模块名称(例如 )为 PAM 提供包含指定模块接口的库的名称。省略目录名称,因为应用程序链接到相应版本的 ,可以找到模块的正确版本。pam_unix.solibpam
PAM 模块可按特定顺序 “堆叠”(即依次排列),而 控制标志(flag)会进一步定义:某个模块的成功 / 失败,对 “用户最终能否通过服务认证” 这一整体目标的重要程度(例如是 “必须成功”,还是 “成功即可跳过后续检查”)。
有几个简单的flags:
required— 该模块的认证结果必须成功,认证流程才能继续;若失败,不会立即通知用户,需等当前接口下所有模块都执行完后,才告知认证失败。requisite— 与required类似,模块结果必须成功才能继续认证;但一旦失败,会立即通知用户(无需等其他模块执行),且直接终止认证流程。sufficient— 模块失败时结果会被忽略;若模块成功,且之前所有required模块都未失败,则直接判定认证通过,无需执行后续模块。optional— 默认忽略模块结果,不影响认证流程;仅当当前接口下没有其他模块时,该模块的成功才成为认证通过的必要条件。include— 配置复用,而非结果判断。与required(必须成功)、sufficient(成功则跳过后续)等控制标志不同,include不参与 “判断模块执行结果是否有效” 的逻辑,而是用于引用其他 PAM 配置文件的内容,实现配置代码的复用/追加。
2. 安装 Google Authenticator PAM 模块
以 ubuntu/Debian为例,启动终端会话并键入:
1
2
# ubuntu/Debian
sudo apt install libpam-google-authenticator
3. 配置 SSH
[!warning]
仅适用于ubuntu 20.04 以及之后的系统,其他发行版(如CentOS/RedHat)配置文件有所不同。
(1). 启用Google Authenticator PAM 模块
通常,仅对 SSH 远程登陆需要 2fa 认证,对应的 PAM 配置文件为/etc/pam.d/sshd
添加 auth required pam_google_authenticator.so 到配置文件
1
2
3
4
5
# Google Authenticator (按顺序执行。放在靠前的行,会优先执行)
auth required pam_google_authenticator.so
# Standard Un*x authentication.
@include common-auth
这样将会首先询问二次验证码,验证成功后才会询问密码,二者都要成功验证后才允许登录。
若要 二者任一正确 即可登陆,将配置信息修改为
1
2
3
4
5
# Google Authenticator (按顺序执行。放在靠前的行,会优先执行)
auth sufficient pam_google_authenticator.so
# Standard Un*x authentication.
@include common-auth
此时会先询问二次验证码,若正确则直接登录到系统,否则验证密码,两者任一正确即可登录系统。
[!tip]
如果想在 system 全局范围使用 2FA 认证,需要修改的 PAM 文件为
/etc/pam.d/system-account. (authorization settings common to all services)
/etc/pam.d/common-auth是通用的用户身份配置文件,其他服务会使用@include common-auth配置语句来引用该文件(例如sshd、passwd)
[!tip] PAM 模块的可选参数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 # 确保 counter (对于 HOTP 模式) 不会因失败的尝试而递增。 # 建议设置,因为没有令牌的攻击者的密码尝试失败不会被锁定 授权用户。 auth required pam_google_authenticator.so no_increment_hotp # 如果用户尚未设置OTP,则允许用户在没有OTP的情况下登录 auth required pam_google_authenticator.so nullok # 不自动调整时间偏差,最常见的是 服务器时钟与 Android 上的时钟不一样。 # PAM 模块尝试补偿 time skew,最多偏移3次(每次等待30s),将获得三个不同的 TOTP 代码 auth required pam_google_authenticator.so noskewadj # 将secret文件存放到指定位置, 注意修改目录权限,允许 owner's readonly (0600) auth required pam_google_authenticator.so secret=/var/unencrypted-home/${USER}/.google_authenticator
如果使用 HOTP (基于计数器而不是基于时间),请添加no_increment_hotp选项以确保计数器不会因多次密码尝试失败而递增 。(多次密码尝试失败不会锁定 用户账号)
将以下行添加到 PAM 配置文件中:
1
auth required pam_google_authenticator.so no_increment_hotp
(2). 修改SSH配置项
修改 sshd 配置文件(/etc/ssh/sshd_config), 开启质疑-应答认证选项,找到以下两项:
KbdInteractiveAuthentication注意⚠️: ubuntu 22.04 之前 版本没有这个选项,旧选项为
ChallengeResponseAuthenticationUsePAM
将其修改为 yes
1
2
3
4
5
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
KbdInteractiveAuthentication yes # >> 修改为 yes,开启质疑-应答认证选项
UsePAM yes # >> 启用UsePAM模块
PasswordAuthentication yes # >> 允许密码认证
然后重新启动sshd
1
systemctl restart ssh.service
(3). 使用 Public key 的登陆用户
如果使用密钥登录ssh,还需要做一些配置
编辑 /etc/ssh/sshd_config文件,添加一下行:
1
AuthenticationMethods publickey,keyboard-interactive
接下来重启sshd
1
sudo systemctl restart sshd
然后再编辑 /etc/pam.d/sshd 这个文件,通过 # 注释掉 ` @include common-auth`
1
2
3
4
# PAM configuration for the Secure Shell service
# Standard Un*x authentication.
# @include common-auth # >> 注释掉该行
[!NOTE]
注意⚠️:如果当前设置为使用密钥(public key)登陆,并禁止密码登录, OpenSSH 会忽略如上所有的配置。若想使用密钥登陆 同时开启二次验证的话你需要几个额外步骤: 添加或修改
/etc/ssh/sshd_config配置文件 :
- 配置项
KbdInteractiveAuthentication yes。- 配置项
ChallengeResponseAuthentication yes。- 配置项
AuthenticationMethods publickey,keyboard-interactive:pam。默认的 PAM 认证规则中包含密码认证,既然只用密钥和二次验证码登陆系统,则需要修改 sshd 的 PAM 规则,编辑
/etc/pam.d/sshd文件,关闭密码验证:
1 2 3 4 5 6 7 #%PAM-1.0 #auth required pam_securetty.so #>> disable remote root auth required pam_google_authenticator.so #auth include system-remote-login #>> 在行首添加注释符,关闭密码登录规则。 account include system-remote-login password include system-remote-login session include system-remote-login
4. 生成密钥文件
提示✅:可选安装
qrencode软件包以在屏幕上生成可以扫描的二维码。扫描二维码以自动配置两次验证器。
使用命令 google-authenticator ,会在当前用户的家目录下创建~/.google_authenticator密钥目录
它会问你一系列问题,下面是一个推荐的配置:
- Make tokens “time-base””: YES
- Update the
.google_authenticatorfile: YES - Disallow multiple uses: YES
- Increase the original generation time limit: NO
- Enable rate-limiting: NO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ google-authenticator
Do you want authentication tokens to be time-based (y/n) y
(# 是否基于时间的认证,如果选择n,则为基于计数的认证-HTOP)
Warning: pasting the following URL into your browser exposes the OTP secret to Google:
<这里是自动生成的二维码>
Your new secret key is: J4T4C4HYT2KIA52WGSDJEOLM2I (# 验证器配置密钥)
Enter code from app (-1 to skip): 269371 (# 输入验证器生成的验证码)
Code confirmed
Your emergency scratch codes are: (# 紧急备用令牌码)
93394730
14394073
33491911
86112157
22174783
Do you want me to update your "/home/username/.google_authenticator" file? (y/n) y
(# 是否重新生成 google_authenticator 配置文件?)
(# 如果想停用这个用户的二次验证,只需要删除用户 Home 目录下的该文件就可以了)
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
(# 是否拒绝多次重复使用相同的令牌?这将限制你每30s仅能登录一次,但会提醒/阻止中间人攻击。)
By default, a new token is generated every 30 seconds by the mobile app.
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. This allows for a
time skew of up to 30 seconds between authentication server and client. 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 (the 8 previous codes, the current
code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
between client and server.
Do you want to do so? (y/n) n
(# 是否将验证码有效窗口时间由1分30秒增加到约4分钟?这将缓解时间同步问题。)
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次。)
建议将备用令牌码保存在安全的地方(打印出来并放在一个安全的位置),因为当丢失手机(即你的两步验证器)或其他原因不能使用两步验证器时,只能使用备用令牌码登录。它们同时也被保存在~/.google_authenticator,你可以在登录后随时查看。
google-authenticator命令的 可选参数如下:
1
2
3
4
5
6
# -t: 使用 TOTP 验证
# -f: 将配置保存到 ~/.google_authenticator 文件里面
# -d: 不允许重复使用以前使用的令牌
# -w 3: 使用令牌进行身份验证以进行时钟偏移,偏移3次
# -e 5: 生成 5 个紧急备用代码
# -r 3 -R 30: 限速 - 每 30 秒允许 3 次登录
[!NOTE]
完成此设置后,如果想备份密钥,将
~/.google-authenticator文件复制到其他服务器,可让同一用户在多台不同设备上使用相同的 2FA 动态码登录,无需重复配置。在新服务器上运行
chmod 600 -R ~/.google_authenticator,确保用户对该文件的访问权限正确。
5. 测试验证
在完成测试前请不要断开目前已经的 SSH 连接!如果配置出错或者无法登陆还可以有补救的机会,否则失联的痛楚就要一人默默感受了!
6. 其他配置
用于桌面登陆
谷歌2次认证插件可以同时用于控制台与 GNOME 桌面登录。 只需要在文件 /etc/pam.d/login 或 /etc/pam.d/gdm-password 内加入: auth required pam_google_authenticator.so
配置 备份/迁移
希望能够备份 google-authenticator配置,然后在另一台设备上恢复这些配置,而不是手动运行以重新生成配置文件
配置文件位于用户主目录中 ~/.google_authenticator,将其复制到其他主机上即可。
[!NOTE]
注意: 目录权限需要设置为当前用户只读 :
1 chmod 400 -R ~/.google_authenticator
密钥文件存储位置
如果想要改变密钥存储位置,请使用 --secret 参数:
google-authenticator --secret="/PATH_FOLDER/USERNAME" 然后更改/etc/pam.d/sshd内的路径配置:
1
2
/etc/pam.d/sshd
auth required pam_google_authenticator.so user=root secret=/PATH_FOLDER/${USER}
user=root 用于强制PAM使用root用户权限来搜索文件。 另外请注意,密钥文件的所有者是root,生成文件的用户只能读取文件(chmod: 400):
1
2
chown root.root /PATH_FILE/SECRET_KEY_FILES
chmod 400 /PATH_FILE/SECRET_KEY_FILES