OpenWrt SSH双因素认证配置指南:TOTP与备用端口方案

OpenWrt SSH双因素认证配置指南:TOTP与备用端口方案

1. 项目概述:为什么要在OpenWrt上折腾SSH双因素认证?

如果你和我一样,把家里的路由器刷成了OpenWrt,那它大概率已经成了你网络的核心枢纽。除了路由,你可能还用它跑了Docker、挂载了硬盘做轻量NAS,或者部署了一些自用的服务。这时,SSH就是通往这个“数字堡垒”的管理员通道。默认的密码登录,在如今撞库、暴力破解横行的网络环境下,就像给大门只上了一把普通的挂锁,安全感实在有限。

双因素认证(2FA)就是给这把锁再加一道指纹或者动态密码锁。Google Authenticator实现的基于时间的一次性密码(TOTP)是其中非常经典和可靠的一种方式。它不依赖短信(避免了SIM卡劫持风险),也不需要额外的硬件令牌,只需你手机上的一个App。这个项目的核心,就是在OpenWrt的SSH服务上集成这套机制,让登录时除了密码,还必须输入手机上Google AuthenticatorApp生成的、每30秒变化一次的6位数字码。

但这里有个很实际的痛点:万一你手机丢了、没电了,或者App数据没了,岂不是把自己锁在门外了?所以,一个成熟的方案必须包含“逃生通道”。这就是标题里“附密码 fallback 端口”的由来。它的思路很巧妙:我们配置两个SSH服务实例,监听不同的端口。比如,主端口22强制要求密码+TOTP双因素认证;而备用端口2222(或其他任意端口)则回退到传统的密码认证。平时我们只用端口22,享受双因素的安全;真到了紧急情况,我们可以通过端口2222,仅用密码登录进去修复配置。这既提升了安全性,又避免了“作茧自缚”的风险。

接下来,我会带你一步步实现这个配置。整个过程涉及OpenWrt的包管理、SSH服务配置、PAM(可插拔认证模块)集成以及防火墙规则调整。我会把每个步骤的原理、可能遇到的坑以及我的实操心得都讲清楚,让你不仅能配通,更能理解背后的逻辑。

2. 核心组件与原理浅析

在动手之前,花几分钟了解一下核心组件是如何协同工作的,能帮你更好地排查问题。整个认证流程涉及三个关键部分:OpenSSH-ServerPAMGoogle Authenticator的PAM模块。

2.1 OpenSSH, PAM与Google Authenticator的关系

OpenSSH是提供SSH服务的软件。当有连接尝试时,它负责处理加密、会话建立,并将认证请求转发给系统的认证机制。

PAM是一个强大的、模块化的认证框架。你可以把它想象成一个认证“流水线”或“插件系统”。SSH服务并不直接处理密码或密钥,而是把“用户XXX想登录,提供了凭证YYY”这个任务交给PAM。PAM则根据配置文件(/etc/pam.d/sshd),按顺序调用一系列模块来验证这个请求。每个模块负责一项具体的认证工作,比如检查密码(pam_unix.so)、限制尝试次数(pam_tally2.so),或者就是我们今天要用的,验证TOTP码(pam_google_authenticator.so)。

Google Authenticator PAM模块就是这个流水线上的一个特殊工位。它的工作流程是:

  1. 用户首次运行时,模块会生成一个密钥(Secret Key)和一组备用代码(Scratch Codes),并显示给用户。用户需要把这个密钥录入到手机App(如Google Authenticator、Authy、Microsoft Authenticator等)中。
  2. 认证时,用户除了输入密码,还需要输入手机App根据当前时间和共享密钥计算出的6位动态码。
  3. PAM模块收到动态码后,使用本地存储的密钥和当前时间进行相同的计算,比对结果。一致则通过该模块的认证。

2.2 TOTP协议与备用端口方案设计

TOTP原理简述:它基于HMAC-SHA1算法。你和服务器(这里指OpenWrt)共享一个密钥。认证时,服务器和你的手机App都取当前的Unix时间戳(通常以30秒为一个时间窗口),用这个密钥和当前时间窗口值通过算法计算出一个哈希值,再从中截取出6位数字。由于服务器和手机的时间大致同步,且算法相同,所以算出的结果在同一个30秒窗口内是一致的。这保证了动态码的一次性和时效性。

备用端口(Fallback Port)方案设计:这是本方案的精髓,也是安全性与可用性的平衡点。我们通过运行两个dropbear(OpenWrt默认的轻量级SSH服务器)实例来实现。

  • 主实例(端口22):使用完整的PAM认证栈,其中包含了pam_google_authenticator.so模块。这意味着登录时必须同时满足密码正确TOTP码正确。
  • 备用实例(端口2222):使用一个简化版的PAM配置,移除了pam_google_authenticator.so模块,只进行传统的密码认证。这个端口不应该对公网开放,仅在内部网络或紧急情况下使用。

这样设计的好处是,日常所有外部访问都经过坚固的双因素认证。而备用通道的存在,确保了管理员在极端情况下(如手机丢失且未备份密钥)不会永久失去访问权限,避免了整个系统“变砖”的风险。你需要做的,就是通过防火墙规则严格限制对备用端口的访问来源。

3. 环境准备与软件包安装

首先,通过SSH登录到你的OpenWrt路由器。确保你有root权限,并且系统软件源已正确配置。不同版本的OpenWrt软件源地址可能不同,如果安装失败,可能需要检查/etc/opkg/distfeeds.conf文件。

3.1 安装必需软件包

我们需要安装三个核心包:

  1. google-authenticator-libpam: 提供PAM模块pam_google_authenticator.so
  2. libqrencode: 用于在终端生成二维码,方便将密钥录入手机。
  3. nanovim: 一个你熟悉的文本编辑器。OpenWrt默认的vi可能比较简陋。
opkg update opkg install google-authenticator-libpam libqrencode nano

注意opkg update是更新软件包列表,非常重要。如果遇到“Package google-authenticator-libpam not found”的错误,可能是因为你的OpenWrt版本较旧或软件源架构不匹配。可以尝试在OpenWrt官方软件包仓库网站查找对应版本的正确包名。

安装完成后,可以验证PAM模块是否存在:

ls /lib/security/pam_google_authenticator.so

如果能看到这个文件,说明模块安装成功。

3.2 为用户初始化Google Authenticator

这个步骤需要为每一个需要通过SSH双因素登录的系统用户单独执行。通常我们只为root用户配置,因为管理OpenWrt主要使用root。

  1. 切换到root用户(如果当前不是):
    su -
  2. 运行初始化命令:
    google-authenticator
    接下来会有一系列交互式提问,这是最关键的一步:
    • Do you want authentication tokens to be time-based (y/n) y: 输入y,选择基于时间的令牌(TOTP),这是标准模式。
    • 屏幕上会显示一个大大的二维码(如果终端支持),以及一行secret key(密钥)和Verification code(当前验证码)。同时还会给出5个emergency scratch codes(紧急备用码)。
    • Do you want me to update your “/root/.google_authenticator” file? (y/n) y: 输入y,将配置保存到用户家目录下的隐藏文件中。
    • Do you want to disallow multiple uses of the same authentication token? (y/n) y: 输入y,禁止重复使用同一个令牌,提升安全性。
    • By default, tokens are good for 30 seconds... Do you want to do so? (y/n) n: 输入n,不延长窗口期。保持30秒的默认值,在时间稍有偏差时,系统会自动尝试前后一个窗口(共90秒),通常足够。
    • Do you want to enable rate-limiting? (y/n) y: 输入y,启用速率限制。这能防止暴力破解动态码,最多每30秒允许3次尝试。

实操心得

  • 立即扫码/备份: 在程序退出前,务必用你的认证App(如Google Authenticator)扫描屏幕上的二维码,或者手动输入那串secret key。同时,把5个紧急备用码抄下来,保存在安全的地方(比如密码管理器)。这是你丢失手机后的救命稻草。
  • 密钥文件: 所有配置(密钥、备用码、设置)都保存在/root/.google_authenticator文件中。备份这个文件就相当于备份了你的双因素认证。你可以把它加密后存到别处。
  • 测试: 初始化完成后,先不要急着修改SSH配置。打开手机App,看到生成的6位数字码在跳动。在终端里运行google-authenticator -d -r 3 -w 3可以再次显示二维码和密钥信息,方便你核对。

4. 配置PAM与双SSH实例

这是整个配置的核心部分,我们需要修改PAM配置来启用Google Authenticator,并配置两个Dropbear实例。

4.1 配置PAM启用Google Authenticator

OpenWrt中SSH的PAM配置文件通常是/etc/pam.d/sshd。但为了不影响其他可能依赖PAM的服务,更稳妥的做法是为SSH创建一个独立的配置,或者直接修改它。我们先备份原文件:

cp /etc/pam.d/sshd /etc/pam.d/sshd.backup

然后编辑/etc/pam.d/sshd文件。你需要理解PAM配置的语法:每一行定义一个模块及其控制标志、参数。控制标志如required表示本模块必须成功,但失败不会立即返回,会继续执行后续模块;sufficient表示本模块成功即足以通过认证。

我们需要在密码认证模块之后,添加Google Authenticator模块。找到包含pam_unix.so(负责密码验证)的那一行,通常长这样:

auth required pam_unix.so

在它下面添加一行:

auth required pam_google_authenticator.so

修改后的相关部分看起来像这样:

# Standard Un*x authentication. auth required pam_unix.so # Google Authenticator for 2FA auth required pam_google_authenticator.so

重要提示pam_google_authenticator.so模块必须放在pam_unix.so模块之后。因为PAM是按顺序执行的,我们需要先验证密码是否正确,再验证动态码。如果顺序反了,会导致即使用户不存在,也会要求输入动态码,这可能泄露用户存在信息。

4.2 配置主SSH实例(端口22,启用2FA)

OpenWrt的SSH服务器是dropbear,它的配置文件是/etc/config/dropbear。这个文件使用UCI(统一配置接口)语法。

  1. 首先,查看当前配置:

    uci show dropbear

    通常会显示一个名为dropbear.@dropbear[0]的配置段,里面包含了PortPasswordAuth等选项。

  2. 我们需要确保主实例监听22端口,并启用密码认证(因为2FA包含了密码环节):

    uci set dropbear.@dropbear[0].PasswordAuth='on' uci set dropbear.@dropbear[0].RootPasswordAuth='on' uci set dropbear.@dropbear[0].Port='22' # 确保接口监听所有地址,或者根据需求调整 uci set dropbear.@dropbear[0].Interface='lan'

    Interface选项可以限制SSH监听在哪个网络接口上,例如只在内网(lan)接口监听,这样外网就无法直接连接SSH,安全性更高。这取决于你的网络拓扑。

4.3 创建备用SSH实例(Fallback端口)

UCI允许配置多个dropbear实例。我们将创建一个新的实例专门用于备用端口。

  1. 添加一个新的dropbear配置段:

    uci add dropbear dropbear uci rename dropbear.@dropbear[-1]=fallback

    第一条命令添加一个新段,@dropbear[-1]指代刚添加的最后一个段。第二条命令给它起个名字叫fallback方便识别。

  2. 配置这个备用实例:

    uci set dropbear.fallback.enable='1' uci set dropbear.fallback.PasswordAuth='on' uci set dropbear.fallback.RootPasswordAuth='on' uci set dropbear.fallback.Port='2222' # 可以换成任何非22的端口 uci set dropbear.fallback.Interface='lan' # 同样建议只监听内网 # 关键:指定一个不使用Google Authenticator的PAM配置文件 uci set dropbear.fallback.PamAuthentication='on'

    这里的关键是PamAuthentication='on',它告诉dropbear使用PAM。但默认它还是会去找/etc/pam.d/sshd。为了让这个实例跳过2FA,我们有两种方案

    方案A(推荐):创建独立的PAM配置文件

    1. 复制原SSH PAM配置并移除Google Authenticator行:
      cp /etc/pam.d/sshd /etc/pam.d/sshd_fallback sed -i '/pam_google_authenticator.so/d' /etc/pam.d/sshd_fallback
    2. 修改dropbear.fallback的配置,指定使用这个新的PAM文件。但标准的UCIdropbear配置可能没有直接指定PAM服务名的选项。这时我们需要更底层的方法。

    方案B:通过启动参数指定(更可靠)Dropbear的启动参数可以在UCI中通过GatewayPortsExtraArgs(取决于版本)传递。更通用的方法是直接修改启动脚本。但OpenWrt 21.02之后,我们可以利用/etc/config/dropbear中的option BannerFile或自定义选项,并通过/etc/rc.local或自定义initscript来启动第二个实例。不过,这变得复杂了。

    一个更简洁的实践方案:我们为备用实例配置PAM,而是利用Dropbear的-s参数禁用PAM,使其仅使用系统密码文件(/etc/shadow)进行认证。这样它就会完全忽略PAM配置,自然也绕过了Google Authenticator。

    uci set dropbear.fallback.PamAuthentication='off' # 或者保持默认的'off'

    这样,备用实例就变成了一个纯密码认证的SSH服务。这正是我们需要的“逃生通道”。

  3. 提交所有UCI更改:

    uci commit dropbear
  4. 重启dropbear服务以应用主实例配置,并启动备用实例:

    service dropbear restart service dropbear enable # 检查备用实例是否启动,可能需要手动启用 /etc/init.d/dropbear start_fallback # 如果存在这样的脚本,或者 service dropbear start # 通常会启动所有enable的实例

    你可以用netstat -tlnp | grep :22netstat -tlnp | grep :2222来检查两个端口是否都在监听。

5. 防火墙与安全加固配置

配置好了服务,下一步就是通过防火墙严格控制访问,这是安全架构中不可或缺的一环。

5.1 配置OpenWrt防火墙(firewall)

OpenWrt使用firewall4(nftables)作为防火墙后端,配置通过/etc/config/firewall进行。

  1. 为主SSH端口(22)放行:通常,为了从外网管理,我们会在WAN区域打开22端口。但既然我们已经有了更安全的2FA,可以继续保留。如果你希望更安全,可以关闭WAN对22端口的访问,只通过VPN连接内网后再使用SSH。这里假设你需要在WAN上开放。

    uci add firewall rule uci set firewall.@rule[-1].name='Allow-WAN-SSH-2FA' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].proto='tcp' uci set firewall.@rule[-1].dest_port='22' uci set firewall.@rule[-1].target='ACCEPT'
  2. 严格限制备用端口(2222)的访问:这是安全的关键。绝对不要在WAN区域开放2222端口。我们应该只允许来自受信任IP地址(比如你的家庭内网网段,或者你固定公网IP)的连接。

    uci add firewall rule uci set firewall.@rule[-1].name='Allow-LAN-Fallback-SSH' uci set firewall.@rule[-1].src='lan' # 仅限lan区域,即内网 # 或者使用src_ip指定一个更精确的IP段,如 '192.168.1.0/24' # uci set firewall.@rule[-1].src_ip='192.168.1.0/24' uci set firewall.@rule[-1].proto='tcp' uci set firewall.@rule[-1].dest_port='2222' uci set firewall.@rule[-1].target='ACCEPT'

    如果你有固定的公网IP,并且想在紧急情况下从外部访问备用端口,可以添加一条非常具体的规则:

    uci add firewall rule uci set firewall.@rule[-1].name='Allow-Trusted-IP-Fallback-SSH' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].src_ip='你的.公网.IP.地址' uci set firewall.@rule[-1].proto='tcp' uci set firewall.@rule[-1].dest_port='2222' uci set firewall.@rule[-1].target='ACCEPT'

    警告:谨慎使用此条规则。暴露备用端口会增加风险面。确保你的root密码强度极高,并考虑定期更换。

  3. 提交并应用防火墙配置:

    uci commit firewall service firewall restart

5.2 其他安全建议

  1. 禁用Root的密码登录(可选但推荐):对于主端口22,既然我们已经有了2FA,可以保留root密码登录。但更激进的安全策略是,在主端口上禁用root密码登录,强制使用SSH密钥+2FA。这需要先生成SSH密钥对,并在/etc/ssh/sshd_config(Dropbear是/etc/dropbear/authorized_keys)中配置公钥,然后将dropbear.@dropbear[0].RootPasswordAuth设为'off'。这样,攻击者即使猜到密码,没有密钥也无法登录。备用端口2222则保持root密码登录,作为最后的保障。
  2. 使用非标准端口:将主SSH端口从22改为一个高位端口(如50222),可以显著减少来自互联网的自动化扫描和攻击噪音。只需修改dropbear.@dropbear[0].Port的值,并相应调整防火墙规则即可。备用端口也可以用一个不常见的高位端口。
  3. Fail2ban:在OpenWrt上安装配置fail2ban,可以自动封锁多次登录失败的IP地址,有效抵御暴力破解。这对于暴露在公网上的端口尤其有用。

6. 完整功能测试与验证

配置完成后,必须进行全面的测试,确保双因素认证生效,且备用通道工作正常。

6.1 测试主SSH端口(2FA认证)

从另一台电脑(比如你的笔记本电脑)尝试通过主端口登录。

ssh root@你的OpenWrt路由IP -p 22

你会看到和以前不一样的提示:

Password:

输入root密码后,不会直接登录,而是会看到:

Verification code:

这时,打开你手机上的Google Authenticator App,找到对应OpenWrt的条目,输入当前显示的6位数字码。

测试场景与预期结果

  1. 密码正确,动态码正确: 成功登录。✅
  2. 密码正确,动态码错误(或超时): 登录被拒绝,提示“Permission denied”。❌
  3. 密码错误: 登录被拒绝,不会进入动态码输入环节。❌

实操心得

  • 时间同步是关键: TOTP依赖于时间。确保你的OpenWrt路由器时间准确。可以安装ntpclientchrony来同步时间:opkg install chrony && service chronyd start && service chronyd enable。手机时间也请确保是自动同步的。
  • “窗口”问题: 如果你输入动态码时总是提示错误,但确认密码和动态码都正确,可能是时间不同步超出了允许的窗口。google-authenticator初始化时如果回答了“n” to “Do you want to do so?”,它会使用默认的“窗口漂移”为1(即允许前后一个30秒周期)。你可以尝试在输入动态码时稍等几秒,或者快速连续输入两次(当前周期和下一个周期的码)。

6.2 测试备用SSH端口(密码认证)

同样从测试电脑上,尝试通过备用端口登录。

ssh root@你的OpenWrt路由IP -p 2222

这次,你应该只会被要求输入密码:

Password:

输入正确的root密码后,应该能直接登录,不会要求输入动态码。

重要:这个测试最好从你预设的允许访问备用端口的IP地址进行(例如,你的内网IP)。如果你设置了只允许特定IP访问,从其他IP尝试连接应该会被防火墙拒绝。

6.3 模拟“逃生”场景

这是最重要的测试。假设你手机无法使用(没电、丢失),你需要通过备用端口登录来重新配置或禁用2FA。

  1. 使用备用端口2222和密码成功登录。
  2. 登录后,你可以选择:
    • 为root用户重新初始化2FA: 运行google-authenticator,会生成新的密钥和二维码。你需要用新手机扫描。注意:这会覆盖旧的.google_authenticator文件,旧的令牌立即失效。
    • 临时禁用某个用户的2FA: 直接编辑对应用户的PAM配置。最快速的方法是注释掉/etc/pam.d/sshdpam_google_authenticator.so那一行(在行首加#),然后重启dropbear服务。但这会全局禁用2FA。
    • 更精细的控制: PAM支持非常复杂的规则。你可以创建另一个PAM配置文件,通过pam_succeed_if.so模块判断来源IP或用户,来决定是否要求2FA。但这属于高级用法。

7. 故障排查与日常维护指南

即使按照步骤操作,也可能会遇到问题。这里列出一些常见问题及解决方法。

7.1 常见问题速查表

问题现象可能原因排查步骤与解决方案
SSH连接主端口,输入密码后直接“Permission denied”,没要求动态码。1. PAM配置中模块顺序错误。
2.pam_google_authenticator.so模块未正确安装或路径不对。
3. 对应用户的~/.google_authenticator文件不存在或权限错误。
1. 检查/etc/pam.d/sshd,确保pam_google_authenticator.sopam_unix.so之后。
2. 运行ls /lib/security/pam_google_authenticator.so确认文件存在。
3. 检查/root/.google_authenticator文件是否存在(ls -la /root/),权限应为600(-rw-------)。
输入正确的密码和动态码,仍提示“Permission denied”。1. 系统时间不同步。
2. 手机App中添加的条目密钥错误。
3. PAM模块的“窗口”设置太窄。
1. 在OpenWrt上运行date,在手机上查看时间,对比是否相差超过1分钟。安装时间同步服务。
2. 在OpenWrt上运行google-authenticator -d -r 3 -w 3显示密钥,与手机App中录入的比对。或者用备用码登录后重新初始化。
3. 初始化时如果选择了不延长窗口,默认是允许前后1个周期的。尝试在动态码刚刷新时立即输入。
备用端口无法连接。1. 防火墙未放行该端口或限制了来源IP。
2. 第二个dropbear实例未成功启动。
3. 端口冲突。
1. 运行netstat -tlnp | grep :2222查看端口是否监听。运行logread | grep dropbear查看服务日志。
2. 检查UCI配置uci show dropbear,确认fallbackenable='1'
3. 运行service dropbear restart并再次检查。确认2222端口未被其他程序占用。
运行google-authenticator命令提示“not found”。软件包未成功安装。运行opkg list-installed | grep google-authenticator确认。确保运行了opkg update,并检查软件源配置。
登录时提示“PAM unable to dlopen(...)”或“PAM symbol not found”。PAM模块依赖的库缺失或架构不匹配。安装完整的PAM支持包:opkg install libpam。确保安装的google-authenticator-libpam版本与系统架构(如aarch64_cortex-a53, mipsel_24kc等)匹配。

7.2 日常维护与备份建议

  1. 备份密钥文件: 定期(或在每次重置后)将/root/.google_authenticator文件加密备份到其他安全位置。这个文件包含了所有秘密。
  2. 保管好紧急备用码: 将初始化时生成的5个紧急备用码妥善保存。它们是最后的保障。
  3. 考虑使用兼容的认证器: 除了Google Authenticator,像AuthyMicrosoft AuthenticatorBitwarden1Password等都支持扫描TOTP二维码添加账户。Authy的优势是多设备同步且备份在云端(需权衡安全性)。
  4. 路由器重置/升级后的恢复: 如果你需要重置OpenWrt或升级固件,在操作前:
    • 确保你有备用端口密码登录的能力。
    • 备份/root/.google_authenticator文件和/etc/pam.d/sshd以及/etc/config/dropbear/etc/config/firewall配置文件。
    • 升级后,重新安装软件包,恢复配置文件,并放回密钥文件(注意权限为600)。
  5. 审计日志: 定期检查logread/var/log/auth.log(如果启用),关注失败的登录尝试,尤其是针对备用端口的尝试,以便及时发现异常。

配置完成后,你的OpenWrt路由器的SSH管理入口安全性就得到了质的提升。双因素认证有效抵御了密码泄露或暴力破解的风险,而精心设计的备用端口方案又确保了管理的可靠性,不会因为认证设备的故障而将自己锁在门外。这套组合拳,对于将OpenWrt作为家庭网络核心或边缘服务器的用户来说,是一个非常值得投入的安全实践。