服务器被暴力破解是运维人员最头疼的问题之一。当你查看/var/log/auth.log,发现每分钟都有来自全球各地IP的SSH登录尝试时,仅靠修改端口或禁用密码登录已经不够了。攻击者会消耗服务器带宽和CPU资源,更危险的是万一哪天你误操作留下弱口令,机器瞬间就会被拿下。fail2ban是经典的防御工具,但它默认只在本地iptables层面封禁,攻击流量依然能抵达你的服务器。把封禁动作推到Cloudflare的CDN/WAF层面,让恶意请求在距离你服务器最近的边缘节点就被丢弃,这才是更彻底的方案。本文详细拆解如何让fail2ban联动Cloudflare API,实现自动化的云端IP封禁。
整体架构与工作流程这套系统的核心逻辑非常清晰:fail2ban监控服务器日志,当某个IP在设定时间内失败次数达到阈值,触发action脚本,该脚本调用Cloudflare API将该IP添加到指定Zone的IP Access Rules(IP访问规则)中,封禁方式可以选择block(阻止)、challenge(质询)或js_challenge(JS质询)。同时,封禁到期后fail2ban会自动发起unban动作,脚本再调用API删除该规则。整个过程完全自动化,无需人工干预。
你需要准备的东西:一台运行Ubuntu的服务器(20.04/22.04/24.04均可),一个托管在Cloudflare上的域名(需要获取Zone ID和API Token),以及root或sudo权限。
创建Cloudflare API Token并获取Zone ID登录Cloudflare控制台,点击右上角头像进入“我的个人资料”,选择左侧“API令牌”。不要使用全局API Key,那个权限太大,一旦泄露后果严重。点击“创建令牌”,选择“自定义”,按最小权限原则配置:权限项选择“Zone - Firewall Services - Edit”,区域资源选择你具体的域名。创建后立即复制保存Token,它只会显示这一次。
获取Zone ID:在Cloudflare仪表盘选中域名,右侧边栏底部“API”部分就能看到Zone ID,直接复制。把这两样东西记好,后续脚本会用到。
安装并配置fail2banUbuntu官方仓库自带fail2ban,直接安装即可:
sudo apt update && sudo apt install fail2ban -y
安装完成后不要直接修改/etc/fail2ban/jail.conf,这个文件会被更新覆盖。标准做法是创建/etc/fail2ban/jail.local文件,只写你需要覆盖的配置项。先创建一个基础配置:
sudo tee /etc/fail2ban/jail.local << 'EOF' [DEFAULT] bantime = 3600 findtime = 600 maxretry = 5 banaction = cloudflare banaction_allports = cloudflare action = %(action_)s [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 86400 EOF
这里把SSH监牢的封禁时间设为86400秒(24小时),最大重试次数3次,检测窗口600秒。banaction指定为cloudflare,这是我们马上要自定义的action名称。
编写Cloudflare封禁与解封脚本fail2ban的action机制依赖外部脚本来执行实际的封禁操作。我们需要写一个能调用Cloudflare API的bash脚本。创建/usr/local/bin/cloudflare-ban.sh:
sudo tee /usr/local/bin/cloudflare-ban.sh << 'EOF'
#!/bin/bash
# Cloudflare API配置
CF_API_TOKEN="你的API_TOKEN"
CF_ZONE_ID="你的ZONE_ID"
CF_EMAIL="你的Cloudflare账号邮箱" # 使用API Token时此字段可留空,但保留变量以防兼容问题
ACTION=$1 # ban 或 unban
IP=$2 # 要操作的IP地址
if [ "$ACTION" == "ban" ]; then
# 创建IP访问规则,模式为block,你也可以改为challenge或js_challenge
RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/firewall/access_rules/rules" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"mode\":\"block\",\"configuration\":{\"target\":\"ip\",\"value\":\"${IP}\"},\"notes\":\"fail2ban auto ban\"}")
SUCCESS=$(echo "$RESPONSE" | grep -o '"success": true')
if [ -n "$SUCCESS" ]; then
echo "$(date): Banned $IP via Cloudflare API" >> /var/log/fail2ban-cloudflare.log
else
echo "$(date): FAILED to ban $IP - Response: $RESPONSE" >> /var/log/fail2ban-cloudflare.log
fi
elif [ "$ACTION" == "unban" ]; then
# 先根据IP查询规则ID
RULE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/firewall/access_rules/rules?match=all&configuration.target=ip&configuration.value=${IP}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -n "$RULE_ID" ]; then
DELETE_RESPONSE=$(curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/firewall/access_rules/rules/${RULE_ID}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
DELETE_SUCCESS=$(echo "$DELETE_RESPONSE" | grep -o '"success": true')
if [ -n "$DELETE_SUCCESS" ]; then
echo "$(date): Unbanned $IP (Rule ID: $RULE_ID) via Cloudflare API" >> /var/log/fail2ban-cloudflare.log
else
echo "$(date): FAILED to unban $IP - Response: $DELETE_RESPONSE" >> /var/log/fail2ban-cloudflare.log
fi
else
echo "$(date): No rule found for $IP, nothing to unban" >> /var/log/fail2ban-cloudflare.log
fi
else
echo "Usage: $0 [ban|unban] <IP>" >> /var/log/fail2ban-cloudflare.log
fi
EOF
给脚本执行权限:
sudo chmod +x /usr/local/bin/cloudflare-ban.sh
这里有几个关键点需要说明。封禁模式我写的是block,这是最严格的直接拦截,用户会看到Cloudflare的1020错误页面。如果你的业务需要区分爬虫和恶意攻击,可以改为challenge(质询页面,用户需点击验证)或js_challenge(自动JS验证,对用户体验影响较小)。解封时脚本会先通过GET请求列出匹配该IP的规则,提取规则ID后再执行DELETE删除。所有操作都记录到/var/log/fail2ban-cloudflare.log,方便排查问题。
测试脚本是否正常工作:
sudo /usr/local/bin/cloudflare-ban.sh ban 1.2.3.4 # 然后去Cloudflare控制台 -> 安全性 -> WAF -> 工具,查看IP访问规则是否出现该IP sudo /usr/local/bin/cloudflare-ban.sh unban 1.2.3.4 # 确认规则被删除
如果测试不通过,检查API Token权限是否正确、Zone ID是否对应、网络是否能访问api.cloudflare.com。
定义fail2ban的cloudflare actionfail2ban的action定义文件放在/etc/fail2ban/action.d/目录下。创建一个名为cloudflare.conf的文件:
sudo tee /etc/fail2ban/action.d/cloudflare.conf << 'EOF' [Definition] actionstart = actionstop = actioncheck = actionban = /usr/local/bin/cloudflare-ban.sh ban <ip> actionunban = /usr/local/bin/cloudflare-ban.sh unban <ip> [Init] init = Cloudflare API action for fail2ban EOF
这个配置极其简洁。actionstart和actionstop留空是因为我们不需要在fail2ban启动或停止时做什么额外操作。actioncheck也留空,fail2ban默认通过检查IP是否已被封禁来判断,但Cloudflare API查询有速率限制,频繁检查反而不好,让fail2ban自己维护封禁列表就行。actionban和actionunban分别调用我们的脚本,<ip>是fail2ban自动传入的变量。
扩展监控范围:Nginx、Apache及其他服务只保护SSH远远不够。如果你的服务器运行着Web服务,攻击者会扫描后台登录页、XML-RPC接口、API端点等。在/etc/fail2ban/jail.local中添加更多监牢配置:
[nginx-http-auth] enabled = true filter = nginx-http-auth logpath = /var/log/nginx/error.log maxretry = 5 bantime = 3600 [nginx-botsearch] enabled = true filter = nginx-botsearch logpath = /var/log/nginx/access.log maxretry = 3 bantime = 7200 [nginx-limit-req] enabled = true filter = nginx-limit-req logpath = /var/log/nginx/error.log maxretry = 5 bantime = 3600
这些监牢默认使用我们在[DEFAULT]段定义的cloudflare banaction,所以都会走Cloudflare封禁。需要注意的是,nginx-botsearch会匹配访问日志中返回404的可疑请求,如果你的站点有大量合法404(比如RESTful API),误封风险较高,建议根据实际情况调整filter正则或者降低maxretry。
进阶优化:速率限制与API调用保护Cloudflare API对免费套餐有速率限制,每分钟约1200次请求。正常fail2ban触发频率远低于此,但如果你管理的服务器数量多,或者遭受大规模分布式攻击,短时间内可能触发大量ban/unban操作。建议在脚本中加入简单的速率控制:
# 在cloudflare-ban.sh开头加入简单的锁机制
LOCKFILE="/tmp/cf_ban_lock"
exec 200>"$LOCKFILE"
flock -n 200 || { echo "$(date): Another instance is running, exiting" >> /var/log/fail2ban-cloudflare.log; exit 1; }
另外,Cloudflare的IP Access Rules列表上限是50000条(免费套餐),对于个人或中小型站点完全够用。但如果你预期封禁列表会很大,建议设置更合理的bantime,让过期IP及时自动解封,避免列表膨胀。也可以在脚本的ban部分加入计数逻辑,当列表接近上限时通过邮件或Webhook告警。
监控与日志审计部署完成后,持续监控是保证系统健康运行的关键。除了脚本自带的日志/var/log/fail2ban-cloudflare.log,fail2ban本身也有详细日志在/var/log/fail2ban.log。你可以用以下命令实时观察封禁动态:
sudo tail -f /var/log/fail2ban.log /var/log/fail2ban-cloudflare.log
建议设置一个简单的cron任务,每天统计封禁数量并清理旧日志:
# 每天凌晨2点统计并发送报告(需配置邮件系统) 0 2 * * * echo "Daily fail2ban Cloudflare report: $(grep 'Banned' /var/log/fail2ban-cloudflare.log | wc -l) bans today" | mail -s "Fail2ban Report" your@email.com
如果发现某个正常用户IP被误封,你可以直接在Cloudflare控制台删除对应规则,或者用脚本手动unban:sudo /usr/local/bin/cloudflare-ban.sh unban 被误封的IP。
常见问题与排错指南问题一:fail2ban状态显示已封禁,但Cloudflare控制台看不到规则。首先检查/var/log/fail2ban-cloudflare.log,看脚本执行是否有报错。常见原因是API Token权限不足,请确认Token拥有Zone Firewall Services的Edit权限。另一个原因是Zone ID填错,注意Zone ID是32位十六进制字符串,不是域名本身。
问题二:封禁生效了,但攻击者仍然能访问服务器。这是因为Cloudflare的IP Access Rules只对经过Cloudflare代理的流量生效(DNS记录旁的小云朵必须是橙色点亮状态)。如果你的源站IP被直接暴露(比如某些子域名没开代理,或者攻击者通过历史DNS记录找到了源站),Cloudflare的规则自然拦不住。解决办法是确保所有流量都经过Cloudflare,并在服务器防火墙层面只允许Cloudflare的IP段入站。
问题三:解封不及时或解封失败。检查脚本中unban部分的API调用,有时因为API响应延迟或网络波动,curl可能超时。可以在curl命令后加--max-time 10设置超时时间。另外,fail2ban的bantime到期后会触发unban,但如果fail2ban服务重启,内存中的封禁列表会丢失,已封禁的IP不会自动解封。建议定期手动清理Cloudflare上由fail2ban创建且过期的规则,或者在脚本中增加定期巡检逻辑。
问题四:IPv6兼容性。Cloudflare的IP Access Rules完全支持IPv6地址,脚本中无需特殊处理,直接传入IPv6地址即可。但要注意fail2ban的日志解析是否正确识别IPv6格式,部分老旧filter可能需要调整正则。
这套方案的实际价值把fail2ban的封禁从本地提升到云端,本质上是将防御纵深前移。攻击IP在Cloudflare边缘就被拦截,不仅保护了源站带宽,还减少了服务器iptables规则数量(大量规则会影响网络性能)。更重要的是,Cloudflare的威胁情报网络会标记这些恶意IP,如果你开启了相关功能,这些IP在访问其他Cloudflare保护的站点时也会受到更严格的审查,某种程度上算是“行业联防”。
对于个人开发者或小团队运维来说,这套方案成本为零(Cloudflare免费套餐完全够用),部署一次即可长期受益。配合Cloudflare的其他安全功能(如Bot Management、速率限制规则、托管规则集),能构建起相当坚固的防护体系。最关键的是,整个过程自动化运行,你只需要在部署完成后偶尔检查一下日志,就能把精力放在更重要的事情上。
