Axum + Nginx + Fail2ban 构建动态 IP 自动封禁系统
在 Rust Axum 后端开发中,我们常面临这样的困境:代码里检测到了恶意爬虫或暴力破解,但仅仅返回 403 Forbidden 是不够的——攻击者依然占用着带宽和连接数。
虽然我们可以用 iptables 库在 Rust 里直接封禁 IP,但这会导致权限耦合和性能损耗。最优雅的方案是“各司其职”:Axum 负责业务逻辑判断,Nginx 负责流量清洗,而 Fail2ban 负责充当“自动保镖”,根据日志动态封禁 IP。
本文将手把手教你如何通过 日志驱动 的方式,实现毫秒级响应的 IP 自动封禁。
🏗️ 核心架构原理
我们采用 日志驱动(Log-Driven) 的解耦架构:
- Axum (应用层):检测到恶意行为(如撞库、越权),打印一条特定格式的日志。
- Fail2ban (监控层):实时监控 Nginx/Axum 的日志文件。
- Iptables (内核层):一旦匹配到特定日志模式,Fail2ban 调用系统防火墙直接丢弃该 IP 的数据包。
优势:Axum 无需 Root 权限,无需关心网络层细节,只需专注于“什么是恶意请求”。
🛠️ 第一步:Axum 端埋点
首先,我们需要在 Axum 中定义“恶意行为”。为了不让 Fail2ban 误判,我们最好输出一个独特的标识。
假设你使用 tracing crate,我们可以自定义一个中间件或在路由处理器中增加逻辑:
use axum::{
extract::ConnectInfo,
http::StatusCode,
response::IntoResponse,
routing::post,
Router,
};
use std::net::SocketAddr;
use tower_http::trace::TraceLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
// 模拟登录接口
async fn login(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
// 这里应该注入你的业务逻辑 State
) -> impl IntoResponse {
// 模拟检测到恶意行为(例如:密码错误超过5次)
let is_malicious = true;
if is_malicious {
// 🔴 关键点:打印特定格式的日志
// 格式建议:[BLOCK_ME] IP:xxx 这样方便正则匹配且不易误伤
tracing::warn!(target: "security", "[BLOCK_ME] IP: {}", addr.ip());
return StatusCode::UNAUTHORIZED;
}
StatusCode::OK
}
#[tokio::main]
async fn main() {
// 初始化日志,确保日志输出到文件或 stdout 被 Nginx 捕获
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.init();
let app = Router::new()
.route("/login", post(login))
.layer(TraceLayer::new_for_http())
.into_make_service_with_connect_info::<SocketAddr>();
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app)
.await
.unwrap();
}
⚙️ 第二步:Nginx 配置
Fail2ban 需要读取日志。如果你的 Axum 运行在 Docker 或后端端口,Nginx 作为反向代理,我们需要确保 Nginx 记录了真实的客户端 IP,并且日志格式包含我们需要匹配的内容。
编辑 /etc/nginx/nginx.conf 或站点配置文件:
http {
# 1. 定义日志格式,确保记录 $remote_addr (真实IP)
# 注意:如果你使用了 Docker,确保 Nginx 配置了 set_real_ip_from 以获取真实 IP
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
server {
listen 80;
server_name your_domain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # 传递真实 IP
}
}
}
关键点:Axum 打印的日志通常会通过 Docker 的 stdout 或单独的文件输出。
- 情况 A(推荐):Axum 日志输出到
/var/log/axum/app.log。Fail2ban 直接监控这个文件。 - 情况 B:Axum 日志被 Nginx 代理并写入
/var/log/nginx/access.log。Fail2ban 监控 Nginx 日志。
下文以 情况 A(直接监控应用日志) 为例,因为这样更精准,不受 Nginx 格式干扰。
🕵️ 第三步:编写 Fail2ban 过滤器
我们需要告诉 Fail2ban 什么样的日志算“恶意”。
创建文件:/etc/fail2ban/filter.d/axum-malicious.conf
[Definition]
# failregex 是正则表达式,用于匹配恶意日志
# ^<HOST> 是 Fail2ban 的占位符,自动匹配行首的 IP 地址
# 我们匹配 Axum 打印的 "[BLOCK_ME]" 标识
failregex = ^<HOST>.*$$BLOCK_ME$$.*$
# ignoreregex 用于排除误判,通常留空或排除自己的监控 IP
ignoreregex =
🚔 第四步:配置监狱
这是 Fail2ban 的核心,定义了“怎么封”、“封多久”。
创建文件:/etc/fail2ban/jail.d/axum-local.conf
[axum-malicious]
enabled = true
port = http,https
# 指定刚才创建的过滤器
filter = axum-malicious
# 🔴 关键:指向 Axum 的日志文件路径
logpath = /var/log/axum/app.log
# 也可以监控 Nginx 日志,如果是这种情况:
# logpath = /var/log/nginx/access.log
# 封禁策略:
# maxretry: 在 findtime 秒内超过多少次触发
maxretry = 3
# findtime: 时间窗口(秒)
findtime = 60
# bantime: 封禁时长(秒),3600 = 1小时
bantime = 3600
# 白名单:永远不要封禁自己!
ignoreip = 127.0.0.1/8 ::1 YOUR_SERVER_IP
🚀 第五步:启动与验证
-
重启 Fail2ban:
sudo systemctl restart fail2ban -
检查状态:
查看监狱是否启动成功:sudo fail2ban-client status axum-malicious输出应显示
Status为OK,且Currently banned为 0。 -
实战模拟:
在你的本地机器(非白名单 IP)上,快速请求 3 次恶意接口:# 模拟攻击 for i in {1..5}; do curl -X POST http://your-server/login; done -
观察结果:
再次运行状态命令:sudo fail2ban-client status axum-malicious你应该能看到你的攻击者 IP 出现在
Banned IP list中。此时,该 IP 的所有请求(包括正常请求)都会被服务器直接丢弃(Connection Timed Out)。
💡 进阶技巧 与 避坑指南
1. Fail2ban 的 安装 和 启动
在 CentOS 系统上安装 Fail2ban 非常直接,它的主要依赖(如 Python)通常已预装,核心步骤是启用 EPEL 软件仓库。
📦 安装 Fail2ban
CentOS 的官方软件源不包含 Fail2ban,因此我们需要先安装 EPEL (Extra Packages for Enterprise Linux) 仓库。
请根据你的 CentOS 版本选择对应的命令:
对于 CentOS 7
# 1. 安装 EPEL 仓库
sudo yum install epel-release -y
# 2. 安装 Fail2ban
sudo yum install fail2ban -y
对于 CentOS 8 / Stream / Rocky Linux / AlmaLinux
# 1. 安装 EPEL 仓库
sudo dnf install epel-release -y
# 2. 安装 Fail2ban
sudo dnf install fail2ban -y
⚙️ 启动与配置
安装完成后,Fail2ban 服务默认不会自动启动,需要手动操作。
1. 启动服务并设置开机自启
# 启动 Fail2ban 服务
sudo systemctl start fail2ban
# 设置开机自动启动
sudo systemctl enable fail2ban
# 检查服务状态,确认是否为 "active (running)"
sudo systemctl status fail2ban
2. 创建 自定义配置文件 (关键步骤)
重要原则:永远不要直接修改 /etc/fail2ban/jail.conf,因为软件更新会覆盖你的修改。
所有自定义配置都应放在 /etc/fail2ban/jail.local 文件中。
我们可以 复制默认配置文件 作为 模板 来开始:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
然后,使用你喜欢的编辑器(如 vim 或 nano)打开这个文件:
sudo vim /etc/fail2ban/jail.local
2. 邮件告警
不想时刻盯着控制台?配置 Fail2ban 发送邮件。
在 jail.d/axum-local.conf 的 [DEFAULT] 部分添加:
destemail = admin@yourcompany.com
sender = fail2ban@your-server.com
mta = sendmail
# action_mwl = 封禁 + 发送带日志的邮件
action = %(action_mwl)s
3. 误封怎么办?
如果测试时把自己封了,或者规则太严误伤了用户:
# 手动解封指定 IP
sudo fail2ban-client set axum-malicious unbanip 192.168.1.50
4. Docker 环境特别注意
如果你的 Axum 运行在 Docker 容器里,日志文件在容器内部。Fail2ban 运行在宿主机,无法直接读取。
解决方案:
在 docker run 或 docker-compose.yml 中,将容器内的日志文件挂载到宿主机:
services:
axum-app:
image: my-axum-app
volumes:
# 将容器内的日志挂载到宿主机的 /var/log/axum/app.log
- ./app.log:/var/log/axum/app.log
5. 配置 SSH 防护
在配置文件中找到 [sshd] 部分,并修改或确保包含以下内容:
[DEFAULT]
# 白名单:务必将你自己的 IP 地址加入,防止把自己锁在外面!
ignoreip = 127.0.0.1/8 ::1 你的公网IP地址
# 全局默认设置
bantime = 3600 # 封禁时长(秒),这里是1小时
findtime = 600 # 检测时间窗口(秒)
maxretry = 5 # 最大失败尝试次数
# SSH 服务防护配置
[sshd]
enabled = true
# CentOS 的 SSH 日志文件路径
logpath = /var/log/secure
配置完成后,保存并退出编辑器。
重新加载配置
使你的新配置生效:
sudo systemctl restart fail2ban
🧪 验证与使用
配置完成后,你可以通过以下命令来管理和验证 Fail2ban。
1. 查看 SSH 防护状态
sudo fail2ban-client status sshd
这个命令会显示当前有多少 IP 被封禁,以及封禁列表。
2. 手动解封 IP
如果你不小心把自己封禁了,或者需要解封某个 IP,可以使用:
sudo fail2ban-client set sshd unbanip <被封禁的IP地址>
3. 测试封禁规则
你可以从另一台机器故意输错 5 次 SSH 密码,然后回到服务器上使用 status 命令查看该 IP 是否已被成功封禁。
📝 总结
通过这套方案,我们实现了一个低成本、高内聚的安全防御体系:
- Axum 保持纯粹,只负责识别业务层面的恶意行为。
- Fail2ban 负责脏活累活,在系统内核层阻断连接。
- Nginx 继续高性能地处理合法流量。
这比在 Rust 代码里手写 iptables 命令要稳健得多,也更符合云原生时代的运维标准。
更多推荐



所有评论(0)