Fail2ban + Cloudflare 双层防护方案
问题背景
服务器使用 Cloudflare CDN,Fail2ban 能正确检测到原始攻击 IP,但封禁无效。
原因:
流量实际从 Cloudflare IP 进入服务器
iptables 封禁原始 IP 不影响通过 CDN 的流量
同时存在直连服务器 IP 的流量,需要双层防护
解决方案
使用 iptables + Cloudflare API 双层封禁:
iptables:拦截直连服务器 IP 的流量
Cloudflare API:在边缘节点拦截通过 CDN 的流量
实施步骤
1. 获取 Cloudflare Global API Key
滚动到底部 API Keys 区域
找到 Global API Key → 点击 View → 输入密码
复制 API Key(妥善保存)
2. 修复 Cloudflare Action 配置
Fail2ban 自带的 cloudflare.conf 存在 bug,需要修复:
# 备份原文件 cp /etc/fail2ban/action.d/cloudflare.conf /etc/fail2ban/action.d/cloudflare.conf.bak # 编辑配置 vim /etc/fail2ban/action.d/cloudflare.conf
找到 actionunban 部分(约第 40-45 行),将:
actionunban = curl -s -o /dev/null -X DELETE -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \ https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$(curl -s -X GET -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \ 'https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1' | cut -d'"' -f6)
替换为:
actionunban = rule_id=$(curl -s -X GET \ -H 'X-Auth-Email: <cfuser>' \ -H 'X-Auth-Key: <cftoken>' \ 'https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1' \ | jq -r '.result[0].id // empty'); \ if [ -n "$rule_id" ]; then \ curl -s -X DELETE \ -H 'X-Auth-Email: <cfuser>' \ -H 'X-Auth-Key: <cftoken>' \ "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$rule_id"; \ fi
修复说明:
原版使用
cut -d'"' -f6提取 rule ID,依赖 JSON 字段顺序,不稳定新版使用
jq正确解析 JSON,不受字段顺序影响增加
if [ -n "$rule_id" ]判断,避免 ID 为空时误操作
安装 jq(如果没有):
dnf install jq -y
3. 配置 Jail(启用双层封禁)
创建或编辑 /etc/fail2ban/jail.d/nginx-botsearch.local:
vim /etc/fail2ban/jail.d/nginx-botsearch.local
内容:
[nginx-botsearch] enabled = true port = http,https filter = nginx-botsearch logpath = /var/log/nginx/access.log maxretry = 2 findtime = 600 bantime = 86400 # 双层封禁:iptables + Cloudflare action = iptables-multiport[name=nginx-botsearch, port="http,https", protocol=tcp] cloudflare[cfuser="你的cloudflare邮箱", cftoken="你的Global API Key"]
参数说明:
cfuser:Cloudflare 账号邮箱cftoken:第 1 步获取的 Global API Keylogpath:根据实际情况调整 Nginx 日志路径
安全加固:
chmod 640 /etc/fail2ban/jail.d/nginx-botsearch.local
4. 测试与验证
# 测试配置语法 fail2ban-client -t # 重启 Fail2ban systemctl restart fail2ban # 确认 jail 状态 fail2ban-client status nginx-botsearch
功能测试:
# 手动封禁测试 IP fail2ban-client set nginx-botsearch banip 1.1.1.1 # 验证点 1:检查 iptables iptables -L f2b-nginx-botsearch -v -n | grep 1.1.1.1 # 应该看到:1.1.1.1 DROP # 验证点 2:检查 Cloudflare # 访问:https://dash.cloudflare.com/[账号ID]/[域名]/security/waf/tools # 应该看到:IP=1.1.1.1 的 Block 规则 # 测试解封 fail2ban-client set nginx-botsearch unbanip 1.1.1.1 # 验证点 3:双层都解封 iptables -L f2b-nginx-botsearch -v -n | grep 1.1.1.1 # 无输出 # Cloudflare 后台规则也应消失
工作流程
攻击检测:Fail2ban 监控 Nginx 日志,检测到恶意行为
双层封禁:
本地层:iptables 封禁 → 拦截直连服务器 IP 的流量
边缘层:Cloudflare API 封禁 → 拦截通过 CDN 的流量
自动解封:
bantime到期后,两层都自动解除封禁
效果:
从被封 IP 直连服务器 → 连接被拒绝(iptables 生效)
从被封 IP 通过域名访问 → Cloudflare 1020 错误页(CF 生效)
技术细节
为什么原版 actionunban 会失败?
原版实现(使用 cut 提取 ID):
... | cut -d'"' -f6
按双引号分割,取第 6 个字段
假设 API 返回的 JSON 中
id字段位置固定问题:JSON 字段顺序变化或增加字段时,提取失败
示例:
# 正常情况
echo '{"result":[{"id":"abc123"}]}' | cut -d'"' -f6
# 输出:abc123 ✓
# API 返回增加字段
echo '{"result":[{"mode":"block","id":"abc123"}]}' | cut -d'"' -f6
# 输出:id (错误)✗改良版本(使用 jq 解析):
... | jq -r '.result[0].id // empty'
使用 JSON 解析器,直接定位
result[0].id字段不受字段顺序影响
如果 API 返回错误,返回空字符串而不是乱码
API 端点说明
使用 User-level IP Access Rules:
https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules
封禁作用于该账号下所有域名
Free 套餐上限:约 2 万条规则
适用于单域名或需要全局封禁的场景
如果需要 Zone-level(仅影响单个域名),修改 API 端点为:
https://api.cloudflare.com/client/v4/zones/{zone_id}/firewall/access_rules/rules成功标准
触发 nginx-botsearch 时,iptables 和 Cloudflare 同时封禁
直连服务器 IP 的攻击被 iptables 拦截
通过域名(CDN)的攻击被 Cloudflare 拦截
bantime到期后两层都自动解封unbanip 命令能同时清除两层封禁
常见问题
Q1: Free 套餐 API rate limit 够用吗?
A: 1200 请求/5 分钟。每次封禁/解封各 1-2 个 API 请求,可应付正常攻击规模。
Q2: 如何查看当前封禁列表?
# 查看 Fail2ban 封禁状态 fail2ban-client status nginx-botsearch # 查看 iptables 规则 iptables -L f2b-nginx-botsearch -v -n # 查看 Cloudflare 规则 # 访问后台:Security > WAF > Tools
Q3: 如何清除所有封禁?
# 清除 Fail2ban 所有封禁(包括 iptables 和 Cloudflare) fail2ban-client unban --all
Q4: 如果需要永久封禁某个 IP?
不建议用 Fail2ban 做永久封禁,应该:
在 Cloudflare 后台手动添加规则(不设过期时间)
在 Nginx 配置中使用
deny指令
维护建议
定期检查日志:
tail -f /var/log/fail2ban.log | grep -E "Ban|Unban|ERROR"
监控封禁数量:
fail2ban-client status nginx-botsearch | grep "Currently banned"
Cloudflare 规则数量:Free 套餐接近 2 万条时考虑缩短
bantime备份配置:
tar -czf fail2ban-backup-$(date +%Y%m%d).tar.gz \ /etc/fail2ban/jail.d/ \ /etc/fail2ban/action.d/cloudflare.conf
参考资料
Cloudflare API 文档:https://developers.cloudflare.com/api/operations/ip-access-rules-for-a-user-create-an-ip-access-rule
Fail2ban 官方文档:https://fail2ban.readthedocs.io/
发表评论