1971 字
10 分钟
Cloudflare 代理 VaultWarden - Fail2Ban 搭配自定义规则一键封禁恶意 IP

说明#

自部署的 vaultwarden 接入 Cloudflare 后,各类恶意访问与攻击行为随之增多,常见目录扫描、接口探测、账号密码暴力破解等行为层出不穷。即便依靠 Cloudflare 防护策略与 Nginx 访问规则,能够拦截绝大多数基础恶意流量,却难以精准甄别并拦截针对性的登录爆破行为。

在此场景下,借助 Fail2Ban 实时监控运行日志、识别异常访问并自动封禁攻击源,便可高效补齐防护短板,大幅提升服务整体安全性。

需要注意的是在 Cloudflare 代理的情况下,vaultwarden 虽可通过请求头获取访客真实 IP,但若仅依靠 Fail2Ban 在服务器本地封禁 IP,无法起到实际防护效果,因为所有访问流量均经由 Cloudflare 节点转发至服务器,本地封禁仅能拦截节点 IP,无法封禁攻击者真实地址

因此使用 Fail2Ban 识别真实攻击 IP + 触发 Action 调用 Cloudflare API 的组合方案,将封禁动作同步至 Cloudflare 自定义规则,实现 CDN 层面的拦截。

NOTE
  1. Fail2Ban 内置两款 Cloudflare 封禁动作,该动作是每封禁一个 IP 生成一条 IP 封禁规则,不美观也不利于强迫症,因此可以使用自定义规则将所有的封禁记录存于一条自定义规则中。
  2. 该方案存在一定局限:Cloudflare 自定义规则表达式字符上限约 4000 字符,最多仅能容纳170余条封禁 IP,若长期封禁大量 IP 容易导致超出字符以至于无法完成新的封禁动作,因此建议合理设置封禁时长,自动释放过期封禁条目。
  3. 若有长期封禁的需要,后期可能加入封禁 IP 段的实现,以减少多 IP 的字符占用。

一、vaultwarden 配置#

1.1 创建专用用户以及数据目录#

这里以使用 Docker Compose 全新部署 vaultwarden 为例

Terminal window
# 请确保具有 root 权限执行以下命令
# 创建 vaultwarden 用户(禁止登录、无家目录)
useradd -r -s /sbin/nologin -M vaultwarden
# 创建 vaultwarden 数据目录
mkdir /opt/vaultwarden && cd /opt/vaultwarden
# 设置目录权限(仅运行用户可访问)
chown -R vaultwarden:vaultwarden /opt/vaultwarden
chmod -R 700 /opt/vaultwarden
# 创建 docker-compose.yml 文件
touch docker-compose.yml
# 编辑 docker-compose.yml 示例内容见下方
# 部署 Docker 容器
docker compose up -d

1.2 docker-compose.yml 示例内容#

使用 vaultwarden 用户运行容器,本地监听 10000 端口公网无法直接访问
通过 ROCKET_PROXY_HEADER 解析 Cloudflare 传递的真实客户端 IP
开启日志持久化存储,为后续 Fail2Ban 防爆破监控提供日志来源

services:
server:
image: vaultwarden/server
container_name: vaultwarden
restart: always
user: vaultwarden:vaultwarden
environment:
- DOMAIN=https://vaultwarden.example.com # 填写托管在 CloudFlare 的域名,注意协议是https
- SIGNUPS_ALLOWED=true # 是否允许注册 如果仅是自己使用先设置为 true,注册完成后在 admin 管理页面中修改为 false
- ORG_CREATION_USERS=false # 是否允许创建组织 如果仅是自己使用设置为 false
- ADMIN_TOKEN= # 可使用 openssl rand -base64 32 生成,也可根据官方指引生成 argon2
- WEBSOCKET_ENABLED=true # 启用 websocket 支持
- ROCKET_PROXY_HEADER=CF-Connecting-IP # 解析请求头以获取真实用户ip
- TZ=Asia/Shanghai # 设置时区
- LOG_FILE=/data/vaultwarden.log # 设置日志文件位置
ports:
- "127.0.0.1:10000:80" # 设置暴露端口
volumes:
- ./data:/data/ # 设置数据保存目录

部署完成后可通过以下命令查看日志是否生成

Terminal window
cat /opt/vaultwarden/data/vaultwarden.log

二、CloudFlare 配置#

2.1 创建自定义规则#

进入 Cloudflare 后台 → 安全性安全规则 → 点击创建规则自定义规则
输入规则名称,例如Fail2ban vaultwarden
添加至少一个需要 ban 的 IP,如果暂时没有可输入1.2.3.4,点击保存

2.2 获取自定义规则信息#

点击刚刚创建的规则 → 点击通过 API 调用保存
记录下方路径中的关键信息zone_idruleset_idrule_id

/{zone_id}/rulesets/{ruleset_id}/rules/{rule_id}

2.3 创建 API 令牌#

进入管理账户账户 API 令牌创建令牌
设置令牌名称,例如Fail2ban vaultwarden
授予 Rules & Configuration - Account Rulesets 的读写权限
建议设置客户端 IP,提高安全性
点击审核令牌获取cfat_开头的 API 令牌

三、Fail2Ban 配置#

3.1 修改匹配规则#

Fail2Ban 自带的匹配规则因 Vaultwarden 日志格式更新已失效,需要替换
规则文件路径为:/etc/fail2ban/filter.d/vaultwarden.conf
failregex的内容替换为:

^\s*(?:\[\]\s*)?\[vaultwarden::api::(?:identity|admin|core::two_factor::authenticator)?\]\[ERROR\] (?:Invalid admin token|Invalid TOTP code|Username or password is incorrect)[\.!](?:\s+(?!IP:)\S+)* IP: <ADDR>(?:\. Username: <F-USER>\S+</F-USER>)?

3.2 验证匹配规则的有效性#

Terminal window
# 通用系统
fail2ban-regex /opt/vaultwarden/data/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf
# 宝塔面板安装的 Fail2Ban
/www/server/panel/pyenv/bin/fail2ban-regex /opt/vaultwarden/data/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf

出现如下结果表示规则匹配正常,表示890行日志命中拦截规则75行

Lines: 890 lines, 0 ignored, 75 matched, 815 missed

3.3 配置 Jail 规则#

jail.local是 Fail2Ban 的核心防护规则配置文件,新增如下内容:

#vaultwarden-START
[vaultwarden]
enabled = true # 启用 Vaultwarden 防爆破规则
port = 80,443 # 指定监控与封禁的端口
filter = vaultwarden # 日志过滤规则,须与/etc/fail2ban/filter.d/下的匹配规则文件名称一致
banaction = %(banaction_allports)s # 封禁所有端口(全局屏蔽)
logpath = /opt/vaultwarden/data/vaultwarden.log # Vaultwarden 日志路径
backend = pyinotify # 监听文件
maxretry = 3 # 最大失败次数
findtime = 60 # 统计时长 60s
bantime = 60 # 封禁时长 60s
action = cloudflare-vaultwarden # 触发 Action 动作调用 Cloudflare API 进行封禁和解封
#vaultwarden-END

3.4 配置 Action 执行脚本#

Terminal window
# 创建脚本文件夹
mkdir /etc/fail2ban/scripts
# 创建脚本文件
touch /etc/fail2ban/scripts/cloudflare-vaultwarden.sh
# 脚本内容见下方
# 赋予执行权限
chmod -R 700 /etc/fail2ban/scripts/cloudflare-vaultwarden.sh

脚本内容如下:

#!/bin/bash
# 定义变量,接收传入的参数
status="$1"
IP="$2"
CLOUDFLARE_API_TOKEN="$3"
ZONE_ID="$4"
RULESET_ID="$5"
RULE_ID="$6"
# 定义 Cloudflare API 的 URL。Get an account or zone entry point ruleset
cf_api_get_point_ruleset_url="https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/rulesets/phases/http_request_firewall_custom/entrypoint"
# 获取指定 ZONE_ID 的 http_request_firewall_custom 规则的所有规则集
ruleset_data=$(curl -s -X GET "${cf_api_get_point_ruleset_url}" -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}")
# 根据 RULE_ID 获取对应规则集中现在有的表达式
expression=$(echo "$ruleset_data" | jq -r --arg RULE_ID "$RULE_ID" '.result.rules[] | select(.id == $RULE_ID) | .expression')
action=$(echo "$ruleset_data" | jq -r --arg RULE_ID "$RULE_ID" '.result.rules[] | select(.id == $RULE_ID) | .action')
description=$(echo "$ruleset_data" | jq -r --arg RULE_ID "$RULE_ID" '.result.rules[] | select(.id == $RULE_ID) | .description')
# 如果没有找到对应的规则,直接退出
if [ -z "$expression" ]; then
exit 0
fi
# 根据 status 参数执行相应的操作
if [ "$status" = "ban" ]; then
# 检查现有表达式中是否已经包含要添加的IP
if echo "$expression" | grep -q "(ip.src eq ${IP})"; then
exit 0
fi
# 如果不包含要添加的IP则重新构造表达式,添加需要封禁的IP
new_expression="${expression} or (ip.src eq ${IP})"
elif [ "$status" = "unban" ]; then
# 检查现有表达式中是否包含需要取消封禁的IP
if ! echo "$expression" | grep -q "(ip.src eq ${IP})"; then
exit 0
fi
# 如果包含需要取消封禁的IP则重新构造表达式,删除需要取消封禁的IP
new_expression=$(echo "$expression" | sed "s/ or (ip.src eq ${IP})//g; s/(ip.src eq ${IP}) or //g; s/(ip.src eq ${IP})//g")
# 禁止删除最后一个 IP,避免规则失效
if [ -z "$new_expression" ]; then
exit 0
fi
fi
# 调用 API 更新规则
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/rulesets/${RULESET_ID}/rules/${RULE_ID}" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-d "{\"description\":\"${description}\",\"action\":\"${action}\",\"expression\":\"${new_expression}\"}"

3.5 配置 Action 动作#

/etc/fail2ban/action.d中创建 cloudflare-vaultwarden.conf
将获取到的信息填写到 [init] 中留空的位置

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = /etc/fail2ban/scripts/cloudflare-vaultwarden.sh ban <ip> <cloudflare_api_token> <zone_id> <ruleset_id> <rule_id>
actionunban = /etc/fail2ban/scripts/cloudflare-vaultwarden.sh unban <ip> <cloudflare_api_token> <zone_id> <ruleset_id> <rule_id>
[Init]
# Cloudflare API 令牌
cloudflare_api_token =
# 域名区域 ID (zone_id)
zone_id =
# 自定义规则集 ID(ruleset_id)
ruleset_id =
# 规则 ID (rule_id)
rule_id =
# 防火墙规则的目标。默认是 "ip"(IPv4)
cftarget = ip
[Init?family=inet6]
# 启用 IPv6
cftarget = ip6

3.6 重载配置并测试#

Terminal window
# 重新加载配置
fail2ban-client reload
# 手动测试封禁
# 可在自定义规则中查看是否添加成功
fail2ban-client set vaultwarden banip 2.3.4.5
# 检查封禁状态
fail2ban-client status vaultwarden
# 手动测试解除封禁
# 可在自定义规则中查看是否解除成功
root@VM-0-13-ubuntu:~# fail2ban-client set vaultwarden unbanip 2.3.4.5
Cloudflare 代理 VaultWarden - Fail2Ban 搭配自定义规则一键封禁恶意 IP
https://www.self4m.com/posts/cloudflare-vaultwarden-fail2ban/
✍️作者
Self4m
📅发布于
2026-05-21
©️许可协议
CC BY-NC-SA 4.0

商业用途必须事先获得作者授权;
非商业用途可以使用,但必须注明出处;
若有改编需采用相同许可协议发布。