沃盾 warden:一个单文件部署的 Go 反向代理 WAF
一句话结论:warden 是给「被 CC 和扫描器困扰、但用不起商业 WAF、也不想改 DNS 上云 WAF」的小站准备的自托管方案——一个二进制丢到服务器上,把 Nginx 的流量指过去就行。 当前版本 v1.0.1,Apache-2.0 开源,支持 Windows / Linux(amd64 + arm64)。
一、它要解决的是什么问题
企业官网、学校站点、内网业务系统这类小站,常年被两类流量消耗:
- CC / 爬虫刷量:大量 IP 高频请求热点页面(新闻列表、详情页、搜索接口), 请求单看每一个都「合法」,合起来把后端打满;
- 扫描器探测:
/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)。