luberan/cloudflare-waf-log
GitHub: luberan/cloudflare-waf-log
一个基于 Cloudflare Worker 的可视化面板,支持在免费套餐下跨多个账户集中展示和分析 WAF 安全事件与 HTTP 流量数据。
Stars: 0 | Forks: 0
# Cloudflare WAF 仪表盘
一个 Cloudflare Worker,用于可视化跨**多个 Cloudflare 账户**(您自己的账户 + 客户账户)的 WAF / 安全事件 **以及 HTTP 流量分析**。
在**免费套餐**下完全可用(保留 24 小时的 WAF 事件)。
UI 包含两个选项卡,它们共享账户 / Zone / 时间范围选择器:
- **🛡 WAF** — 防火墙 / 安全事件(原始仪表盘,见下文)。
- **📊 HTTP 流量** — 全 Zone 的 HTTP 分析:请求、数据传输、访问量、状态码、缓存命中率、热门国家 / 主机名 / 路径、内容类型、HTTP 版本,以及边缘/源站性能(TTFB 和源站响应时间)。数据范围限定为 `requestSource: eyeball`,因此它们与 Cloudflare 自己的 HTTP 流量仪表盘保持一致。根据时间范围的广度,底层会自动适配三种数据集,这与 Cloudflare 仪表盘切换分辨率的方式相同:较短的范围(约 24 小时)使用 **`httpRequestsAdaptiveGroups`**(细粒度,包含所有细分项;适用于免费计划,无需 Pro+);中等范围(约 3 天)切换为按小时的 **`httpRequests1hGroups`** 汇总;较长范围(最长约 30 天)使用按天的 **`httpRequests1dGroups`** 汇总(保留时间最长)。在汇总数据的时间范围下,无法使用按路径 / 主机名 / 性能的细分(相关面板会隐藏)——这些数据仅存在于细粒度的自适应数据集中。时间范围下拉菜单是根据每个 Zone 下所有三种数据集的真实限制(通过 GraphQL Settings 节点)构建的。
## 功能
- **在 Cloudflare 账户之间切换** — 每个账户都有自己作为 Worker secret 存储的 API token
- 在选定账户内**切换 Zone**
- 筛选条件:
- **action**(标签:`block`、`managed_challenge`、`jschallenge`、`challenge`、`allow`、`log`、`skip`)
- **hostname**(例如 `www.example.com`)
- **path**(例如 `/wp-login.php`)
- **Rule ID**(防火墙规则 UUID,以逗号分隔)
- **country**(ISO2 代码:`US,DE,RU,...`)
- **ASN**(AS 编号,以逗号分隔:`13335,15169`)
- **User-Agent**(精确匹配)
- 时间范围(WAF 选项卡):过去 **1 / 6 / 24 小时**(免费套餐 — Cloudflare 不会保留超过此时间长度的 WAF 事件)
- **KPI 卡片**:总计 / 已阻止 / 验证 / 允许+记录
- **图表**:
- 堆叠时间序列(每小时的事件数,按 action 进行颜色区分)
- 按 action 划分的环形图
- 前 15 个国家/地区(水平条形图)
- 前 15 个主机名
- 按来源划分的环形图(`waf`、`firewallrules`、`botManagement`、…)
- **表格**:
- 前 30 个 Rule ID *(点击 = 按 Rule ID 筛选)*
- 前 50 个路径 *(点击 = 按路径筛选)*
- 前 50 个 ASN *(点击 = 按 ASN 筛选)*
- 前 50 个 User Agent *(点击 = 按 UA 筛选)*
- 最新约 500 个事件(时间、action、IP、ASN、国家/地区、主机、路径、方法、规则、ray ID)
- **清除筛选** — 一个按钮即可清除所有标签和输入
- **CSV 导出** — 下载当前筛选条件下的原始事件(最多 10,000 行,UTF-8 + BOM,可直接在 Excel 中打开)
## 架构
| 部分 | 文件 | 用途 |
|---|---|---|
| Worker (TypeScript) | [src/index.ts](src/index.ts) | API endpoints — 代理至 Cloudflare GraphQL Analytics API |
| 仪表盘标记 | [public/index.html](public/index.html) | 原生 HTML + CSS,无需构建步骤 |
| 仪表盘逻辑 | [public/app.js](public/app.js) | 所有客户端 JS(fetch、图表、分面筛选) |
| Chart.js (本地集成) | `public/vendor/chart.umd.min.js` | 自托管的 Chart.js — 不使用 CDN,因此 CSP 可以使用 `script-src 'self'` |
| 配置 | [wrangler.jsonc](wrangler.jsonc) | Worker 入口 + assets binding + 禁用默认域名 |
### API endpoints
| Endpoint | 描述 |
|---|---|
| `GET /api/accounts` | 已配置账户列表(仅包含 `id` + `label`,绝不包含 token) |
| `GET /api/zones?account=` | 指定账户下的 Zone 列表 |
| `GET /api/stats?account=&zone=&...filters` | WAF 聚合数据 + 最新 500 个事件(所有数据在一个请求中返回) |
| `GET /api/http-stats?account=&zone=&since=&until=` | HTTP 流量 + 边缘性能聚合(服务器端分组;短时间范围使用 `httpRequestsAdaptiveGroups`,较长范围使用按小时/天的汇总) |
| `GET /api/http-settings?account=&zone=` | 该 Zone 的 HTTP 数据集限制(保留时间 + 最大查询窗口),用于填充时间范围下拉菜单 |
| `GET /api/log?account=&zone=&...filters` | 仅获取事件(可通过 `&limit=` 控制) |
| `GET /api/export.csv?account=&zone=&...filters` | 原始事件的 CSV 导出(最多 10,000 行,`Content-Disposition: attachment`) |
### 关于免费套餐的说明 — 聚合在 Worker 中运行
Cloudflare 的 `firewallEventsAdaptiveGroups` 数据集(服务器端聚合)**需要 Pro+ 计划**。在免费计划下,只能使用 `firewallEventsAdaptive`(原始事件,24 小时,每次请求最多 10,000 行)。因此,Worker 会获取原始事件并在 JS 中计算所有统计数据(`byAction`、`byCountry`、`byHost`、`byPath`、`byRule`、`bySource`、`byAsn`、`byUserAgent`、`series`)。响应中包含 `totalSampled` 和 `truncated` — 如果某个 Zone 在 24 小时内超过 10,000 个事件,仪表盘会警告您统计数据是基于样本的。
### 下钻分面 + 缓存
`country / host / path / rule / asn / ua` 等筛选条件采用**分面式多选**:
- 点击表格 / 条形图中的某项会将其添加到筛选条件中,再次点击则会移除。
- 当前选中的项会被高亮显示,其他项保持可见(置灰)— 经典的分面搜索 UX。
- Worker 在 JS 中应用这些筛选,而不是在 GraphQL 查询中。对 CF 的请求仅包含 `action + source + zone + datetime`。
外部 GraphQL 获取的结果缓存在 Worker Cache API 中(TTL 为 5 分钟,以 `acc + zone + 5分钟时间桶 + action + source` 作为键)。因此,各项之间切换分面是**瞬间完成**的(缓存命中)— 您将体验到 <50 ms 的延迟,而不是 CF 带来的 500–2000 ms 延迟。仪表盘头部会显示 `⚡ cache HIT / ☁ cache MISS` 指示器及请求延迟。
## Secret 配置
**所有敏感数据仅存在于 Worker secrets 中。**没有任何敏感信息被提交到代码库或 `wrangler.jsonc` 中。
为**每个 CF 账户**创建三个 secret:
| Secret 名称 | 描述 | 示例 |
|---|---|---|
| `CFACC__LABEL` | UI 下拉菜单中显示的标签 | `My account` |
| `CFACC__ACCOUNT` | Cloudflare Account ID(32 位十六进制字符) | `00000000000000000000000000000000` |
| `CFACC__TOKEN` | Cloudflare API token(只读,见下文) | `cf_xxx...` |
`` 可以是任何简短标识符(`PERSONAL`、`ACME`、`NOVA`、…)。它会出现在 URL 中,例如 `?account=`。Worker 在内部会将其统一转换为小写。
**添加新账户** = 创建三个新的 secret。现有的任何内容都不会改变,您也无需知道旧的 token。
**Token 轮换** = 只需覆盖 `CFACC__TOKEN`。
**移除账户** = 删除其对应的三个 secret。
### Cloudflare API token — 如何创建
1. **My Profile → API Tokens → Create Token → Custom token**
2. **Token 名称**:例如 `waf-log-personal`
3. **权限策略** — 在左上角的 Resources 选择器中选择 **All Domains**
*(不要选 "Entire Account" — 该范围不包含诸如 Zone:Read 和 Zone Analytics:Read 之类的 Zone 级别权限)*
4. 勾选以下类别:
- **DNS & Zones → Zone : Read**
- **Analytics & Logs → Analytics : Read**
5. *(可选)* 客户端 IP 过滤、TTL — 保持默认
6. **Continue → Create Token** → 复制它(仅显示一次)
## 设置
### 本地开发
```
npm install
# 创建 .dev.vars(请勿提交——它已在 .gitignore 中)。
@'
CFACC_PERSONAL_LABEL=My account
CFACC_PERSONAL_ACCOUNT=00000000000000000000000000000000
CFACC_PERSONAL_TOKEN=cf_xxx
'@ | Out-File -Encoding utf8 .dev.vars
npm run dev
```
打开 。
### 生产环境 — Worker secrets
在仪表盘中:**Workers & Pages → 您的 Worker → Settings → Variables and Secrets → Add → Type: Secret**
为每个账户添加三个 secret(`CFACC__LABEL`、`CFACC__ACCOUNT`、`CFACC__TOKEN`)。
添加完所有内容后,点击 **Deploy**(只需一次 — 一次性应用所有更改)。
或者通过 CLI:
```
"My account" | npx wrangler secret put CFACC_PERSONAL_LABEL
"abc123..." | npx wrangler secret put CFACC_PERSONAL_ACCOUNT
"cf_xxx..." | npx wrangler secret put CFACC_PERSONAL_TOKEN
```
### 通过 GitHub → Cloudflare Workers Builds 部署
1. 将代码库推送到 GitHub
2. **Workers & Pages → Create → Workers → Connect to Git**,选择该代码库
3. 构建设置:
- **Build command**:*(留空 — 无需构建)*
- **Deploy command**:`npx wrangler deploy`
- **Non-production deploy command**:`npx wrangler versions upload`
- **Builds for non-production branches**:保持禁用(无论如何,预览 URL 在配置中都已禁用)
4. 首次部署后添加 secret(见上文)
5. 之后每次推送到 `main` 分支都会触发自动部署
### 自定义域名
`*.workers.dev` URL 已被禁用([wrangler.jsonc](wrangler.jsonc) — `workers_dev: false`),因此该 Worker 只能通过自定义域名访问。设置步骤:
1. **Worker → Settings → Domains & Routes → Add → Custom Domain**
2. 输入域名,例如 `waf.example.com`(必须是位于与 worker 相同 CF 账户上的 Zone)
3. CF 会自动创建 `CNAME` 并签发 TLS 证书
### 访问保护 — Cloudflare Access (Zero Trust)
如果没有保护,该 Worker 将公开暴露,并会泄露所有客户账户的数据。**它必须位于 Access 之后:**
1. **Zero Trust 仪表盘 → Access → Applications → Add application → Self-hosted**
2. 应用程序域名:`waf.example.com`(上一步中的自定义域名)
3. 路径:留空(保护包括 `/api/*` 在内的整个主机名)
4. **Policy** → Add policy:
- 操作:`Allow`
- 包含:`Emails: you@example.com`(或者一个 IdP 群组,等等)
5. 保存并部署应用程序
如果没有有效的 Access 会话,Worker 将返回 302 重定向至 CF Access 登录页面。
#### 可选 — 在代码中验证 Access JWT(纵深防御)
默认情况下,Worker 信任位于其前方的 Access(网络层强制执行)。您还可以让 Worker **自行验证 Access JWT**,这样配置错误或被移除的 Access 应用程序就不会悄无声息地暴露数据。设置另外两个变量(普通变量,非 secret):
| 变量 | 描述 | 示例 |
|---|---|---|
| `CF_ACCESS_TEAM_DOMAIN` | 您的 Zero Trust 团队域名 | `https://yourteam.cloudflareaccess.com` |
| `CF_ACCESS_AUD` | Access 应用程序的 Application Audience (AUD) 标签 | `0a1b2c…` |
当**两者**都设置好后,每个 `/api/*` 请求都必须携带有效的 Access token(`Cf-Access-Jwt-Assertion` header 或 `CF_Authorization` cookie);Worker 会根据您团队的公钥以及受众、签发者和过期时间检查 RS256 签名,如果不则返回 `403`。如果其中任何一个未设置,则会跳过代码内验证(从而确保 `wrangler dev` 继续正常工作)。
## 重要提示 — 禁用默认域名
[wrangler.jsonc](wrangler.jsonc) 中硬编码了以下内容:
- `workers_dev: false` — 禁用 `..workers.dev`
- `preview_urls: false` — 禁用由 `wrangler versions upload` 产生的预览 URL
如果不这样做,Wrangler 会在每次部署时重新启用默认域名,这将绕过 Access(预览 URL 没有附加 Access 策略)。如果您以后需要再次启用默认域名,请从配置中删除这些行。
## 免费套餐限制
- **WAF 事件保留时间**:24 小时(Pro 72 小时,Biz 30 天,Ent 6 个月)— 这是 Cloudflare 的限制,并非此代码的限制
- **每次请求最多 10,000 个事件** — 如果 Zone 超过此限制,统计数据将基于样本得出(您会在响应中看到 `truncated: true`)
- **Worker 配额**:每天 100,000 次请求,10 ms CPU 时间
- **GraphQL 速率限制**:每个 token 每 5 分钟约 1,200 次请求(每个账户都有自己独立的限制 → 随账户数量扩展)
- **`firewallEventsAdaptiveGroups`(服务器端聚合)仅限 Pro+** — 这就是 Worker 在 JS 中聚合原始事件的原因
## 安全说明
- Token 具有只读权限 — 即使 secret 泄露,也无法更改 CF 账户中的任何内容
- 前端永远不会接收到 token — `GET /api/accounts` 仅返回 `id` + `label`
- Worker 必须位于 Cloudflare Access 之后 — 否则仪表盘将完全公开。作为纵深防御,它也可以选择在代码中[验证 Access JWT](#optional--verify-the-access-jwt-in-code-defense-in-depth)
- 所有资源都带有严格的 `Content-Security-Policy`(`script-src 'self'`、`frame-ancestors 'none'` 等)以及 `X-Content-Type-Options`、`X-Frame-Options` 和 `Referrer-Policy`。所有脚本均为自托管(Chart.js 位于 `public/vendor/`,应用逻辑位于 `public/app.js`) — 不加载任何第三方源的资源
- CSV 导出会对触发公式的字符(`= + - @`)进行转义,以防止 CSV/Excel 公式注入,并且下载文件名已经过净化处理
- 发往 Cloudflare API/GraphQL 的上游调用具有硬超时限制,因此如果上游停滞将返回 `504`,而不是让 Worker 一直挂起
- `.dev.vars` 已被列入 [.gitignore](.gitignore) 中
## 可能的扩展
- Cron Trigger → 将聚合数据存储在 D1/R2 中,以保留超过 24 小时的历史记录
- 当超过被阻止请求的阈值时触发告警 Webhook (Slack/Discord)
## 许可证
[MIT](LICENSE)
标签:WAF分析, Web安全, 互联网扫描, 可视化仪表盘, 程序员工具, 自动化攻击, 蓝队分析, 调试辅助, 运维监控, 高对比度