返回博客

自托管服务器的五层纵深防御:从 SSH 加固到统一认证门户

Lazybones Follow 0 2026年09月07日 运维与监控 74 次阅读

自托管服务器的五层纵深防御:从 SSH 加固到统一认证门户

自托管服务器直接暴露在公网,安全不是「加个防火墙」就够了。本文讲如何用五层纵深防御把攻击面压到最小——SSH 密钥认证、fail2ban 自动封禁、Authelia 统一认证门户、端口收敛,以及它们之间如何联动。

纵深防御的思路

单层防御总有被绕过的可能。纵深防御的核心是每一层都能独立挡住一类攻击,即使某层被突破,下一层依然有效:

┌─────────────────────────────────────────────┐
│ 第1层: SSH 密钥认证 + 禁密码登录             │  ← 挡暴力破解
├─────────────────────────────────────────────┤
│ 第2层: fail2ban 自动封禁                     │  ← 挡高频扫描
├─────────────────────────────────────────────┤
│ 第3层: 端口收敛(UFW 只放行必要端口)        │  ← 缩小攻击面
├─────────────────────────────────────────────┤
│ 第4层: Authelia 统一认证门户                 │  ← 挡 Web 暴力破解
├─────────────────────────────────────────────┤
│ 第5层: Traefik ForwardAuth + 路由隔离        │  ← 挡未授权访问
└─────────────────────────────────────────────┘

第1层:SSH 密钥认证

SSH 是服务器的「后门」,一旦被突破,后面所有层都形同虚设。

配置

/etc/ssh/sshd_config 关键配置:

PubkeyAuthentication yes          # 启用密钥认证
PasswordAuthentication no         # 禁用密码登录
PermitRootLogin no                # 禁止 root 直接登录
MaxAuthTries 3                    # 最多尝试 3 次
LoginGraceTime 30                 # 30 秒内必须完成认证

禁用密码登录后,暴力破解字典攻击直接失效——没有密码可以猜。

SSH 探针监控

为了确保 SSH 服务持续可用(被 fail2ban 误封、sshd 崩溃等),可以设置一个受限的探针用户:

# 创建受限用户 probe,只允许执行 /bin/true
# authorized_keys 限制行:
command="/bin/true",no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-user-rc ssh-ed25519 AAAA...

探针脚本每 2 分钟用这个受限用户连一次 SSH,成功后向 Uptime Kuma 的 Push 端点发送心跳。如果连续几次失败,Uptime Kuma 会告警。这个设计的好处是:

  • 探针用户只能执行 /bin/true,即使私钥泄露也无法做任何操作。
  • no-pty + no-port-forwarding 等限制进一步收窄能力。
  • 探针验证的是完整的 SSH 认证链路(网络连通 + sshd 运行 + 密钥认证正常)。

第2层:fail2ban 自动封禁

fail2ban 监控认证日志,同一 IP 短时间内多次失败就自动封禁。

SSH jail 配置

# /etc/fail2ban/jail.local
[DEFAULT]
# 白名单:本机、宿主机内网 IP、固定公网 IP
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
findtime = 600
bantime = 3600
banaction = nftables

关键点:SSH jail 用 banaction = nftables,只封锁 SSH 端口(22)。这意味着被封禁的 IP 仍然可以访问 Web 服务——这是有意为之的设计,后面会讲为什么。

Web 服务 jail(Authelia)

[authelia]
enabled = true
port = http,https
filter = authelia
logpath = /var/log/authelia.log
maxretry = 3
findtime = 120
bantime = 300
banaction = nftables[type=allports]

Web jail 用 nftables[type=allports]——封锁所有端口,包括 SSH。这里有一个安全不对称:

Jail 封禁范围 效果
sshd 仅 SSH 端口 攻击者仍可访问 Web
authelia 所有端口 攻击者完全被挡在外面

为什么 Web jail 更严格?因为Web 暴力破解往往是 SSH 暴力破解的前奏——攻击者先试 Web 面板弱密码,拿到 shell 权限后再横向。Web jail 把所有端口一封,直接切断后续攻击路径。

Authelia 内置防暴力破解

Authelia 自带 regulation 模块,独立于 fail2ban:

# Authelia 配置
regulation:
  max_retries: 3        # 2 分钟内最多失败 3 次
  find_time: '2 minutes'
  ban_time: '5 minutes' # 触发后封禁 5 分钟

这意味着攻击者面对的是双层封禁:Authelia 先在应用层封 5 分钟,如果频繁触发,fail2ban 在系统层封更长时间。两层的计数器和封禁时长独立,形成递进式防御。

排坑:banned vs 密码错

调试 Authelia 登录失败时,必须先区分两种原因:

  • banned:被 regulation 封禁,即使密码正确也返回 401。重启容器即可清除。
  • do not match:密码真的不对。

短时间反复跑自检脚本很容易把自己锁死,然后误判成「配置坏了」。排查登录失败的第一步永远是看容器日志。

第3层:端口收敛

容器化最大的安全收益之一是端口收敛。以前裸机部署时,每个服务都想监听自己的端口,防火墙规则越加越乱。容器化后,只有反向代理的 80/443 对外,其余服务全走 Docker 内部网络。

对外暴露的端口清单:

端口 用途 必要性
22 SSH 必须
80 HTTP 入口(ACME 验证 + HTTP→HTTPS 跳转) 必须(所有 Web 服务入口)
443 HTTPS 入口 必须(所有 Web 服务入口)
自定义高位端口 确有需要的非 HTTP 服务 按需,越少越好

对比重构前暴露十几个端口,攻击面可以缩小 70% 以上。

第4层:Authelia 统一认证门户

所有管理面板和监控工具统一走 Authelia 认证,不再各自维护 BasicAuth 或弱密码。

架构

浏览器 → Traefik → (ForwardAuth) → Authelia
                      │ 认证通过
                      └→ 后端服务
                      注入 Remote-User / Remote-Groups / Remote-Email

未登录时 302 跳转到登录门户,登录成功后回跳原地址。密码用 argon2id 哈希存储,支持 TOTP 二步验证。

哈希生成踩坑

users.yml 里的密码字段是 argon2id 哈希,满是 $ 特殊字符。用 sed 替换会静默失败:

# ❌ sed 把 $ 当成正则锚点,替换失败
sed -i 's/$argon2id$.../$argon2id$.../' users.yml

# ✅ 用 Python 正则 + re.escape 处理特殊字符
python3 -c "
import re
path = '/srv/stacks/authelia/config/users.yml'
old, new = OLD_DIGEST, NEW_DIGEST
text = open(path).read()
open(path, 'w').write(re.sub(re.escape(old), new, text))
"

第5层:Traefik 路由隔离

每个受保护域名的路由只在 File Provider 里定义一次,带 authelia-forward 中间件。关键教训是混用 Docker Provider 和 File Provider 会导致认证被绕过——这是反向代理配置里最常见的一类坑。

验证认证是否生效的方法:

# 无凭据访问必须 302/401,绝不能 200
curl -s -o /dev/null -w '%{http_code}\n' https://logs.example.com/

# 检查是否有 @docker 幽灵路由
docker exec traefik wget -qO- http://127.0.0.1:8080/api/http/routers | grep '@docker'

这个检查可以固化为回归护栏脚本 authelia-check.sh,定期跑一次确保认证没被意外绕过。

SSH 探针 + Uptime Kuma 联动

前面提到的 SSH 探针不只是「能连就行」,它和 Uptime Kuma 形成了完整的监控闭环:

┌──────────┐    每2分钟SSH连接    ┌──────────┐
│ crontab  │ ──────────────────→ │ sshd     │
│ 探针脚本 │                     └──────────┘
└────┬─────┘
     │ 成功 → push heartbeat
     ▼
┌──────────┐    告警             ┌──────────┐
│ Uptime   │ ─────────────────→ │ 通知渠道 │
│ Kuma     │    连续失败         │ (邮件等) │
└──────────┘                    └──────────┘

探针脚本验证的是完整的 SSH 认证链路:网络连通 → sshd 运行 → 密钥认证正常 → 命令执行成功。任何一环断裂都会被 Uptime Kuma 捕获。

小结

五层防御各司其职:

层 防什么 技术
SSH 密钥 暴力破解密码 禁密码登录
fail2ban 高频扫描 自动封禁 IP
端口收敛 不必要的暴露 UFW + Docker 内部网络
Authelia Web 面板弱密码 argon2id + TOTP + regulation
路由隔离 未授权访问 ForwardAuth + 路由规则

核心经验:安全不是一层就够,而是层层叠加。每一层假设前面的层可能被突破,自己仍然独立有效。Web jail 封全端口、SSH jail 只封 SSH,这种不对称设计是有意为之——Web 攻击者比 SSH 攻击者更危险,因为 Web 面板通常有更高的系统权限。

下一篇讲自动备份与恢复演练。

评论 (0)

暂无评论,来说点什么吧。

登录 后可以发表评论