fabriziosalmi/caddy-waf
GitHub: fabriziosalmi/caddy-waf
面向 Caddy Web 服务器的 Web 应用防火墙中间件,提供正则规则检测、IP/DNS/GeoIP 过滤、Tor 阻断和速率限制等防护能力。
Stars: 791 | Forks: 30
# Caddy WAF
一个用 Go 编写的、面向 [Caddy](https://caddyserver.com/) Web 服务器的 Web Application Firewall 中间件。
[](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/test.yml)
[](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/github-code-scanning/codeql)
[](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/build-run-validate.yml)
- **模块 ID**:`http.handlers.waf`
- **Go module 路径**:`github.com/fabriziosalmi/caddy-waf`
- **当前版本**:`v0.3.4`(见 [`caddywaf.go`](caddywaf.go) — `const wafVersion`)
- **许可证**:AGPL-3.0
## 概述
`caddy-waf` 是一个 HTTP handler 中间件,它通过四个定义明确的阶段检查请求和响应,应用带有异常评分的正则表达式规则集,执行 IP/DNS/ASN/国家黑名单和白名单,执行 token-bucket 风格的速率限制,并暴露一个 JSON 指标 endpoint。
该中间件作为单个 Caddy 模块实现,注册 ID 为 `http.handlers.waf`。它可以通过 Caddyfile 或直接通过 JSON 进行配置。
## 功能特性
| 功能特性 | 实现方式 |
|---|---|
| 正则规则引擎 | Go [`regexp`](https://pkg.go.dev/regexp) 包(RE2,线性时间保证)。编译后的模式按规则 ID 进行缓存。 |
| 多阶段检查 | 阶段 1(请求标头和请求前检查),阶段 2(请求体),阶段 3(响应标头),阶段 4(响应体)。 |
| 异常评分 | 每条规则将其 `score` 贡献到单次请求的总分中;当总分达到 `anomaly_threshold` 时,请求将被阻止。 |
| 显式动作 | 规则 `mode` 为 `block` 或 `log`。`block` 短路请求;`log` 记录匹配并继续。 |
| IP 黑名单 | 存储 prefix trie([`go-iptrie`](https://github.com/phemmer/go-iptrie))中的纯 IP 和 CIDR 范围(IPv4 和 IPv6)。 |
| DNS 黑名单 | 精确匹配(不区分大小写)的主机查找。 |
| GeoIP 国家阻止 / 白名单 | MaxMind GeoLite2 Country MMDB。 |
| ASN 阻止 | MaxMind GeoLite2 ASN MMDB。 |
| Tor 出口节点阻止 | 定期从 `https://check.torproject.org/torbulkexitlist` 获取。 |
| 速率限制 | 基于单 IP 的滑动窗口,可选的正则表达式 path 匹配。 |
| 自定义阻止响应 | 针对每个状态码响应自定义 Content-Type、标头和主体(内联或来自文件)。 |
| 敏感数据脱敏 | 可选地对敏感查询参数和日志字段进行脱敏。 |
| 热重载 | 对规则文件、IP 黑名单和 DNS 黑名单使用 `fsnotify` 监视器。 |
| 指标 endpoint | 在配置的 `metrics_endpoint` 路径暴露的 JSON 文档。 |
| 异步日志记录 | 带有同步回退机制的缓冲日志通道(当缓冲区满时)。 |
## 工程说明
- **线性时间正则表达式**:规则由 Go 的 `regexp` (RE2) 编译。不会发生灾难性回溯。
- **无等待计数器**:每条规则的命中计数使用存储在 `sync.Map` 中的 `atomic.Int64`。
- **有界主体读取**:请求体通过 `io.LimitReader`(`max_request_body_size`,默认 10 MiB)读取,并通过 `io.MultiReader` 恢复,因此下游 handler 仍能看到完整的主体。
- **有界响应缓冲**:仅当存在阶段 4 规则对其进行检查时,响应体才会被保存在内存中,并且绝不会超过 `max_response_body_size`(默认 10 MiB)。超过此限制——或在上游刷新时——WAF 会释放其持有的内容并 stream 剩余部分,因此内存绝不会随着响应大小而增加。
- **零拷贝主体字符串**:主体通过 `unsafe.String` 暴露给规则匹配,以避免每次请求产生内存分配。
- **GeoIP 的熔断器**:`geoip_fail_open` 控制 GeoIP 查找失败是阻止请求还是允许其通过。
- **Panic 恢复**:`ServeHTTP` 安装了一个延迟恢复机制,在发生 panic 时返回 `500 Internal Server Error`。
## 快速开始
实现可运行构建的最快途径:
```
curl -fsSL -H "Pragma: no-cache" \
https://raw.githubusercontent.com/fabriziosalmi/caddy-waf/refs/heads/main/install.sh | bash
```
该脚本确保安装了 Go 和 `xcaddy`,克隆仓库,下载 GeoLite2 数据库,使用 `caddy-waf` 模块构建 Caddy,并启动服务器。
一份典型的配置日志:
```
INFO Provisioning WAF middleware {"log_level":"info","log_path":"debug.json","log_json":true,"anomaly_threshold":20}
INFO http.handlers.waf Tor exit nodes updated {"count":1093}
INFO WAF middleware version {"version":"v0.3.4"}
INFO Rate limit configuration {"requests":100,"window":10,"cleanup_interval":300,"paths":["/api/v1/.*"],"match_all_paths":false}
WARN GeoIP database not found. Country blacklisting/whitelisting will be disabled {"path":"GeoLite2-Country.mmdb"}
INFO IP blacklist loaded {"path":"ip_blacklist.txt","valid_entries":223770,"invalid_entries":0,"total_lines":223770}
INFO DNS blacklist loaded {"path":"dns_blacklist.txt","valid_entries":854479,"total_lines":854479}
INFO WAF rules loaded successfully {"total_rules":33,"rule_counts":"Phase 1: 17 rules, Phase 2: 16 rules, Phase 3: 0 rules, Phase 4: 0 rules, "}
INFO WAF middleware provisioned successfully
```
## 安装说明
### 要求
- Go **1.25** 或更高版本([`go.mod`](go.mod) 声明了 `go 1.25`)
- Caddy **v2.11.x** 或更高版本(当前构建使用 `github.com/caddyserver/caddy/v2 v2.11.2`)
- [`xcaddy`](https://github.com/caddyserver/xcaddy) 用于构建带有插件的 Caddy
### 选项 1 — 使用 xcaddy 构建(推荐)
```
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/fabriziosalmi/caddy-waf
./caddy list-modules | grep waf # expect: http.handlers.waf
```
### 选项 2 — 快速脚本
```
curl -fsSL -H "Pragma: no-cache" \
https://raw.githubusercontent.com/fabriziosalmi/caddy-waf/refs/heads/main/install.sh | bash
```
### 选项 3 — 从源码构建
```
git clone https://github.com/fabriziosalmi/caddy-waf.git
cd caddy-waf
go mod tidy
wget https://git.io/GeoLite2-Country.mmdb # optional, only for GeoIP
xcaddy build --with github.com/fabriziosalmi/caddy-waf=./
./caddy fmt --overwrite
./caddy run
```
### `caddy add-package`
此模块在 Caddy 的官方包注册表中**未注册**;`caddy add-package github.com/fabriziosalmi/caddy-waf` 将失败并返回 `HTTP 400: ... is not a registered Caddy module package path`。请使用上述构建选项之一。详情请见 [`docs/add-package-guide.md`](docs/add-package-guide.md)。
## 最小化 Caddyfile
```
{
auto_https off
admin localhost:2019
}
:8080 {
log {
output stdout
format console
level INFO
}
route {
waf {
metrics_endpoint /waf_metrics
rule_file rules.json
ip_blacklist_file ip_blacklist.txt
dns_blacklist_file dns_blacklist.txt
}
@wafmetrics path /waf_metrics
handle @wafmetrics {
# Falls through to the WAF handler, which serves metrics as JSON.
}
handle {
respond "Hello world!" 200
}
}
}
```
[`Caddyfile`](Caddyfile) 和 [`caddyfile.example`](caddyfile.example) 中提供了带有完整注释的示例。
## 文档
| 文档 | 主题 |
|---|---|
| [`docs/installation.md`](docs/installation.md) | 所有安装方法。 |
| [`docs/configuration.md`](docs/configuration.md) | Caddyfile 和 JSON 指令、请求生命周期、阻止优先级。 |
| [`docs/rules.md`](docs/rules.md) | `rules.json` schema、目标标识符、正则表达式语义。 |
| [`docs/blacklists.md`](docs/blacklists.md) | IP 和 DNS 黑名单文件格式。 |
| [`docs/ratelimit.md`](docs/ratelimit.md) | 速率限制块、路径匹配、行为。 |
| [`docs/geoblocking.md`](docs/geoblocking.md) | 国家阻止 / 白名单、ASN 阻止、回退行为。 |
| [`docs/attacks.md`](docs/attacks.md) | 内置规则集解决的攻击类别。 |
| [`docs/dynamicupdates.md`](docs/dynamicupdates.md) | 文件监视器、重载语义、每次重载的范围。 |
| [`docs/metrics.md`](docs/metrics.md) | `/waf_metrics` JSON schema。 |
| [`docs/prometheus.md`](docs/prometheus.md) | 将 JSON 指标桥接到 Prometheus 和 Grafana。 |
| [`docs/caddy-waf-elk.md`](docs/caddy-waf-elk.md) | 使用 Filebeat 将日志发送到 ELK stack。 |
| [`docs/scripts.md`](docs/scripts.md) | 用于生成规则和黑名单的辅助 Python 脚本。 |
| [`docs/testing.md`](docs/testing.md) | 运行内置的 `test.py` 测试套件。 |
| [`docs/docker.md`](docs/docker.md) | 使用 Docker / Docker Compose 构建和运行。 |
| [`docs/add-package-guide.md`](docs/add-package-guide.md) | `caddy add-package` 注册状态。 |
| [`docs/caddytest.md`](docs/caddytest.md) | `caddytest.py` 流量生成工具。 |
## 项目结构
```
.
├── caddywaf.go Module registration, lifecycle, file watchers, metrics endpoint
├── handler.go ServeHTTP, phase dispatch, blocking decisions
├── config.go Caddyfile directive parsing
├── rules.go Rule loading, validation, regex caching, hit accounting
├── request.go Target extraction (URI, headers, body, JSON paths, ...)
├── response.go Response recorder, block writer
├── blacklist.go IP and DNS blacklist loaders and lookups
├── ratelimiter.go Per-IP / per-path sliding-window rate limiter
├── geoip.go MaxMind country and ASN lookups, with optional cache
├── tor.go Periodic Tor exit-node list fetcher
├── logging.go Async log worker, sensitive-data redaction
├── helpers.go IP parsing helpers
├── types.go Public types (Middleware, Rule, RateLimit, ...)
├── doc.go Package documentation
├── rules.json Default rule set (used by Caddyfile)
├── rules/ Modular rule files by category
├── ip_blacklist.txt Default IP blacklist
├── dns_blacklist.txt Default DNS blacklist
├── tor_blacklist.txt Tor exit-node cache (auto-managed)
├── docs/ Documentation
└── *_test.go Unit and integration tests
```
## 测试
```
make test # go test -v ./...
make it # go test -v ./... -tags=it (integration)
make lint # golangci-lint run
make test-integration # runs test.py inside a python:3.9-slim container
```
该仓库还附带了 Python 套件,涵盖恶意 payload(`test.py`)、流量生成(`caddytest.py`)和基准测试(`benchmark.py`)。请参见 [`docs/testing.md`](docs/testing.md) 和 [`docs/caddytest.md`](docs/caddytest.md)。
## 安全
漏洞披露请参见 [`SECURITY.md`](SECURITY.md)。报告可以发送至 `fabrizio.salmi@gmail.com` 或作为私密的 GitHub Security Advisory 提交;请不要公开提 issue。
## 许可证
AGPL-3.0。见 [`LICENSE`](LICENSE)。
标签:AppImage, Caddy, CISA项目, EVTX分析, Go, Ruby工具, WAF, Web应用防火墙, 中间件, 密码管理, 日志审计, 流量过滤, 网络安全, 配置错误, 隐私保护