沃盾 warden:一个单文件部署的 Go 反向代理 WAF

一句话结论:warden 是给「被 CC 和扫描器困扰、但用不起商业 WAF、也不想改 DNS 上云 WAF」的小站准备的自托管方案——一个二进制丢到服务器上,把 Nginx 的流量指过去就行。 当前版本 v1.0.1,Apache-2.0 开源,支持 Windows / Linux(amd64 + arm64)。

一、它要解决的是什么问题

企业官网、学校站点、内网业务系统这类小站,常年被两类流量消耗:

  1. CC / 爬虫刷量:大量 IP 高频请求热点页面(新闻列表、详情页、搜索接口), 请求单看每一个都「合法」,合起来把后端打满;
  2. 扫描器探测:/wp-admin、/.env、/phpmyadmin 这类路径扫描,以及 sqlmap、nikto 等工具的特征流量。

可选的现成方案各有各的别扭:商业 WAF 贵;云 WAF 要改 DNS 解析、流量要绕一圈; Nginx 原生的 limit_req 能做限速,但在「怎么区分正常用户和脚本」这件事上手段有限—— 限速要么放行要么拒绝,没有中间态。

warden 的定位是:用一个小巧的自托管程序,把 L4~L7 的防护 + CC 挑战 + 可观测性一次性解决。 它的核心设计取向是「宁可多一次验证码,也不要误杀真实用户」——疑似脚本先弹验证码, 验证通过就发可信凭证,之后走快路径。

二、三个为了「好部署」的选型

这个项目最值得说的其实不是功能,而是它把部署复杂度压到了什么程度。

1. 前端用 go:embed 打进二进制

管理后台是 Vue3 + Element Plus + ECharts 写的单文件应用,但通过 go:embed 直接编进可执行文件,不需要额外部署前端、不需要 Nginx 配静态目录。 第三方库也全部本地化放在 web/vendor/,不走 CDN——内网、离线环境照常打开。

// 改完 web/admin.html 重新 go build 即可,资源自动打进二进制
go build -o warden ./cmd/warden

2. SQLite 用纯 Go 实现,不需要 cgo

用的是 modernc.org/sqlite 而不是需要 cgo 的 mattn 版本。带来的直接好处是 交叉编译没有负担:在 Windows 上直接编出 Linux 产物,不需要装交叉工具链。

CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o warden-linux-arm64 ./cmd/warden

发布包一个 zip 同时含 warden.exe(Windows/amd64)、warden(Linux/amd64)、 warden-linux-arm64 三个产物,平铺在根目录,run.sh 按 uname -m 自动挑对应的二进制。

3. 所有内存状态都有 TTL 和清扫协程

令牌桶、可信 IP、行为状态、违规记录全部带过期时间和后台清扫,长时间运行不会无界增长。 这是自托管程序能不能「丢上去就忘了」的前提。

三、请求是怎么走一遍的

客户端 ──▶ [Nginx / LB,可选] ──▶ warden :81 ──▶ 业务后端 :8002

warden 内部分两层:

TCP 层:ConnLimitListener 在 accept 阶段用令牌桶限流, 超额的直接丢弃,根本不进入 HTTP 处理——这一层挡的是最消耗资源的连接洪水。

HTTP 中间件链,由外到内七道:

1. IP 黑名单      ── 命中 → 拦截
2. URL 白/黑名单  ── 命中 → 放行 / 拦截
3. IP 白名单      ── 命中 → 直通(跳过后续所有检测)
4. CC 防护        ── 泛洪 / 行为 / 限速 → 验证码挑战
5. 频率限制       ── 热点 / 整站 / 子网 → 429
6. Coraza WAF     ── OWASP CRS 规则检测
7. 多站点 Router  ── 按 Host 反代到上游

顺序本身就是策略:白名单在最前面直通,代价高的检测(Coraza)放在后面, 能被前面拦掉的流量不会走到规则引擎。

四、CC 防护怎么判断「这是脚本」

这是 warden 与「单纯限速」最大的区别。它不靠单一阈值,而是几条线索叠加:

手段判断依据
新 IP 泛洪检测滑动窗口统计新 / 老 IP 占比,超过阈值判定泛洪,新 IP 一律先过验证码
行为检测请求间隔高度均匀、或长期只访问 1~2 个路径 → 判为脚本
可信 IP 快路累计访问达阈值即晋升可信,走独立高配额通道;可信列表持久化到 SQLite
可信会话快路验证码通过后签发 Cookie,同一 IP 下多用户互不影响
每 IP 限速未可信 IP 独立令牌桶,超限弹验证码而不是直接断连
共享令牌桶未可信总量保护,拥堵时用验证码自我恢复

几个细节能看出它是真在真实攻击里打磨过的:

  • 静态资源免检:图片 / JS / CSS / 字体直接放行,避免「一篇文章几十个请求」被误伤;
  • 验证码按 (会话, IP) 复用:不会重复生成把用户正在填的验证码冲掉;
  • 爬虫白名单:识别搜索引擎 UA,不弹验证码(改走限速),不影响收录;
  • 违规升级要双条件:违规次数达标 且 持续违规超过 offender_persist_sec 才升级为内核层封禁——避免一次性 IP 也建规则,把系统防火墙规则表撑爆。

此外还有基于离线 xdb 库的 IP 归属拦截:可分别开关「国外 IP」与「云厂商 / IDC IP」。 真实用户几乎不会来自腾讯云、阿里云机房,这个开关对刷量特别有效,且无外部请求, 不依赖第三方 API。

五、三分钟上手

不需要编译的话,直接用 Release 里的 zip:

mkdir -p /opt/warden && cd /opt/warden
unzip ~/warden-v1.0.1.zip
chmod +x run.sh warden warden-linux-arm64   # zip 不保留可执行位
./run.sh

要自己编译的话(Go 1.23+):

# Windows
$env:GOPROXY = "https://goproxy.cn,https://goproxy.io,direct"
go mod tidy
go build -o warden.exe ./cmd/warden

# Linux
export GOPROXY=https://goproxy.cn,https://goproxy.io,direct
go build -o warden ./cmd/warden
./warden -config config.json

最小配置只要两行,把自己放在业务前面:

{
  "listen": ":81",
  "backend": "http://127.0.0.1:8002"
}

验证:

curl http://127.0.0.1:81/healthz   # 健康检查
curl -I  http://127.0.0.1:81/      # 应转发到后端

管理后台默认 http://127.0.0.1:9090,能看到实时拦截指标、QPS / 拦截率趋势、 拦截分类占比、CPU / 内存、攻击日志分页查询与可信 IP 列表。

三种部署形态按需要选:

方案链路适用
A. Nginx 前置(推荐):443 → Nginx(TLS 卸载) → warden:81 → 后端需要 TLS、已有 Nginx
B. WAF 直接对外:80 → warden → 后端想少一跳(Windows 下监听 80 需管理员)
C. 多站点按 Host 路由到不同上游一台机器反代多个站点

长期跑就用 systemd(Linux)或 NSSM(Windows)注册成服务,README 里有现成的 unit 文件。

六、上线前必须知道的五件事

1. 先用 DetectionOnly 观察,再切 On

把 rules/coraza.conf 里的 SecRuleEngine 设成 DetectionOnly,先观察一段时间有没有误报,确认真实业务不受影响后再改成 On。 CC 防护同理:先只开验证码挑战,看真实用户的通过率,再逐步收紧阈值。 直接开 On 上线是 WAF 误杀的头号原因。

2. 改 config.json 不会生效

配置读取顺序是 SQLite config 表 → 回退 config.json。首次启动会用 config.json 初始化数据库,之后一律以 DB 为准。请在管理后台修改并重启。

3. Windows 版 Nginx 有 1024 连接上限

Windows 版 Nginx 用 select() 事件模型,单 worker 最多约 1024 并发连接, worker_connections 设再大也不生效。并发较高时,把 Nginx 放到 Linux / WSL2, 或者让 warden 直接对外(Go 在 Windows 上用 IOCP,无此限制)。

4. 上游 keepalive 超时要对齐

warden 到后端的空闲连接回收时间要短于后端(如 Tomcat 的 connectionTimeout), 否则会复用已被后端关闭的连接,触发 connection reset → 间歇 502。

5. 防火墙封禁默认关闭,别急着开

早期版本会为每个 IP 创建 in/out 两条 netsh 规则,攻击量下能迅速累积到上万条, 而 Windows 防火墙每次增删规则都要重编译整张规则表,操作会越来越慢。 所以现在默认关闭,要开就得配合定期清理。

七、两个有意思的工程细节

管理后台和 WAF 跑在同一个进程里,所以轮询开销直接叠加在业务上,这两处做了优化:

  • 读内存不用 runtime.ReadMemStats:它会触发 STW 停顿,实测单次约 8.7µs;改用 runtime/metrics 读同样指标仅约 0.3µs(快约 27 倍), 而取值一致。前端是按 3 秒轮询 /api/stats 的,这个差别会被放大。
  • CPU 使用率常驻采样:GetSystemTimes / /proc/stat 给的是 自开机以来的累计值,必须两次采样求差。若只在接口被请求时才采样,离开仪表盘再切回来, 第一次读数会是整段空档的平均值(可能是几分钟)。所以后台以 1 秒间隔常驻采样并缓存, 接口只读缓存。

八、适合谁,不适合谁

适合:企业官网、学校 / 政府站点、内网业务系统这类「后端不太健壮、流量模式相对固定、 没人专门做安全运维」的场景;想自建、想完全掌控数据与规则的人。

不适合:需要企业级规则运营、DDoS 大流量清洗(那应该在运营商 / CDN 侧做)、 或者指望装完就不管的场景。README 里也写得很清楚:这是应用层防护手段, 不能替代系统补丁、最小权限和后端自身的安全编码。

九、项目信息

  • 许可证:Apache-2.0(含明确的专利授权,允许商业使用与闭源集成)
  • 语言 / 依赖:Go 1.23+、OWASP Coraza v3、纯 Go SQLite、Vue3 + Element Plus + ECharts
  • 当前版本:v1.0.1(跨平台单包分发)
  • Gitee:gitee.com/jxw1111/warden
  • GitHub:github.com/xwjiang2003/warden

欢迎提交 Issue / PR,贡献默认按 Apache-2.0 授权,建议在提交信息里附 Signed-off-by(DCO)。

相关阅读

问题反馈

本站是纯前端静态站,没有后端也没有账号系统,反馈走 GitHub Issues、讨论区或邮件。

粘贴到 issue 或邮件里能帮我更快定位问题,其中不含你输入的任何内容

其它:查看已有反馈 · 邮件反馈(无需 GitHub 账号):278975598@qq.com