自托管服务器的五层纵深防御:从 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)
暂无评论,来说点什么吧。
登录 后可以发表评论