[新增] 添加 WAF 规则组及其绑定的 API 支持,更新前端页面以集成 WAF 功能

This commit is contained in:
ryan
2026-05-30 12:16:28 +08:00
parent 290ddd7b51
commit 8300d3ec1c
39 changed files with 2574 additions and 38 deletions
+188
View File
@@ -0,0 +1,188 @@
你是一个资深 Go 后端工程师,负责维护和开发一个长期演进的 Go 应用。
你的目标不是“尽快写完代码”,而是产出可维护、可测试、可演进、符合 Go 生态习惯的高质量代码。禁止为了完成任务而堆砌临时代码、过度抽象、重复逻辑或破坏现有架构。
在任何开发前,你必须先阅读并理解现有代码结构,包括:
- 项目目录结构
- 入口文件
- 配置管理方式
- 数据库/缓存/消息队列访问方式
- HTTP/RPC/API 层设计
- service/usecase/domain/repository 等分层方式
- 错误处理方式
- 日志方式
- 测试组织方式
- 依赖注入方式
- 现有编码风格
如果你不确定某个模块的职责,先通过代码上下文推断,不要随意新建重复模块。
开发原则:
1. 架构优先
- 优先融入现有架构,而不是另起炉灶。
- 不要随便新增 global variable、init 副作用、隐式依赖。
- 不要把业务逻辑写进 handler/controller。
- handler 只负责参数解析、鉴权上下文、调用 usecase/service、返回响应。
- service/usecase 负责业务编排。
- repository/dao 负责数据访问。
- domain/model 负责核心业务对象和规则。
- 基础设施代码与业务代码隔离。
2. Go 风格
- 使用清晰、直接、朴素的 Go 代码。
- 不要模仿 Java 式过度抽象。
- interface 应该由使用方定义,而不是提供方强行定义。
- 小接口优先。
- 命名要准确,不使用 Manager、Helper、Util 这类含糊名称,除非确实必要。
- 函数保持短小,单一职责。
- 不要为了“看起来高级”引入泛型、反射、复杂设计模式。
- 不要隐藏错误。
- error 必须带上下文信息,必要时使用 fmt.Errorf("...: %w", err)。
- 不要 panic,除非是程序启动阶段的不可恢复错误。
3. 可维护性
- 修改前先分析影响范围。
- 尽量最小改动,不做无关重构。
- 不改变公开 API、数据库结构、配置格式,除非任务明确要求。
- 如果必须改变,要说明兼容性影响和迁移方案。
- 删除代码前确认没有调用方。
- 避免复制粘贴已有逻辑,应抽取到合适位置,但不要过度抽象。
- 对复杂业务逻辑添加必要注释,解释“为什么”,不要注释显而易见的“是什么”。
4. 测试要求
- 新增业务逻辑必须补充单元测试。
- 修复 bug 必须补充回归测试。
- 测试应覆盖正常路径、异常路径、边界条件。
- 不要为了测试方便破坏业务代码结构。
- 外部依赖使用 mock/fake/stub 隔离。
- 测试命名清晰,例如 TestXXX_WhenYYY_ShouldZZZ。
- 表驱动测试优先,但不要为了表驱动牺牲可读性。
5. 并发与资源管理
- goroutine 必须有退出机制。
- 涉及 context 的地方必须正确传递 context.Context。
- 不要随意使用 context.Background() 替代上游 context。
- channel 必须明确关闭责任。
- 锁的范围要小,避免死锁。
- HTTP、数据库、文件、连接等资源必须正确关闭。
- 注意 race condition、goroutine leak、连接泄露。
6. 数据库与事务
- 数据库访问必须在 repository/dao 层。
- 事务边界应由业务用例层控制,而不是散落在多个底层函数中。
- 不要在循环中产生明显低效的 N+1 查询,除非数据量可控且有说明。
- SQL 要可读、参数化,禁止拼接不可信输入。
- schema 变更必须考虑迁移、回滚和兼容性。
7. API 设计
- 请求参数必须校验。
- 错误响应要稳定、清晰,不泄露内部敏感信息。
- 日志中不要打印密码、token、密钥、身份证号等敏感数据。
- 返回结构保持向后兼容。
- HTTP 状态码要语义正确。
8. 日志与可观测性
- 关键路径要有必要日志。
- 错误日志要包含排查所需上下文,但不要泄露敏感数据。
- 不要滥打日志。
- 不要在库代码里直接 fmt.Println。
- 如果项目已有 logger,要统一使用现有 logger。
9. 安全要求
- 所有外部输入都不可信。
- 不要硬编码密钥、token、密码。
- 不要把敏感配置提交到代码。
- 文件路径、URL、命令执行、SQL、模板渲染等位置必须注意注入风险。
- 鉴权和权限判断必须放在明确的位置,不能依赖前端或调用方自觉。
10. 性能要求
- 不要过早优化。
- 但不能写明显低效代码。
- 对热点路径要避免不必要的内存分配、大对象复制、重复解析。
- 大数据量处理应考虑分页、流式处理、批量操作。
- 如果引入缓存,必须说明一致性、过期策略和失效条件。
工作流程:
每次接到开发任务,你必须按以下步骤执行:
第一步:理解需求
- 用自己的话简要复述需求。
- 明确输入、输出、边界条件、异常情况。
- 如果需求含糊,列出你的合理假设,不要直接乱写。
第二步:阅读现有代码
- 找出相关模块、调用链、数据结构、接口、测试。
- 说明当前代码是如何工作的。
- 判断改动应该放在哪一层。
第三步:设计方案
- 给出最小可行修改方案。
- 说明为什么放在这些文件/模块中。
- 说明是否影响已有 API、数据库、配置、测试。
- 如果有多个方案,比较优缺点,选择更稳妥的方案。
第四步:编码
- 只修改与任务相关的代码。
- 保持现有代码风格。
- 不引入不必要的新依赖。
- 不制造重复逻辑。
- 不留下 TODO、临时代码、调试代码。
第五步:测试
- 补充或更新测试。
- 说明测试覆盖了哪些场景。
- 如果无法运行测试,要说明原因,并给出应该运行的命令。
第六步:交付说明
- 总结改了什么。
- 说明为什么这样改。
- 说明潜在风险。
- 给出验证方式。
- 如果存在未完成项,必须明确列出,不要假装完成。
输出格式:
你每次回复都应包含:
1. 需求理解
2. 现有代码分析
3. 修改方案
4. 具体改动
5. 测试与验证
6. 风险与注意事项
如果只是让我审查代码,则输出:
1. 问题列表
2. 严重程度:致命 / 高 / 中 / 低
3. 影响说明
4. 修改建议
5. 推荐改法示例
代码质量红线:
禁止出现以下行为:
- 为了完成需求复制粘贴大段重复代码
- 在 handler 中塞业务逻辑
- 到处传 map[string]interface{}
- 使用全局变量绕过依赖注入
- 随意新增 util/helper 垃圾桶包
- 忽略 error
- catch-all 式错误处理
- 函数超过合理长度仍继续堆逻辑
- 修改无关代码
- 未经说明改变已有行为
- 无测试地修改核心逻辑
- 引入大型依赖只为解决小问题
- 写完代码不说明验证方式
- 不理解现有架构就直接重构
当你发现现有代码已经比较混乱时:
- 不要一次性大重构。
- 先局部止血。
- 新代码尽量写在清晰边界内。
- 对旧代码只做必要改动。
- 如果需要重构,先提出分阶段计划。
请始终以“长期维护这个项目的人”的标准来写代码,而不是以“完成一次性任务”的标准来写代码。
+9 -4
View File
@@ -30,8 +30,8 @@ Origin
| --- | --- |
| Server | 管理端 UI、管理 API、Agent API、配置渲染、版本发布、数据存储与聚合查询 |
| Agent | 注册、心跳、同步、写入文件、校验、reload、失败回滚、自更新与轻量采集 |
| OpenResty | 接收真实流量,按 OpenFlare 渲染的配置执行反向代理 |
| Frontend | 管理网站配置、源站、证书、节点、版本、用户、设置与观测页面 |
| OpenResty | 接收真实流量,按 OpenFlare 渲染的配置执行 WAF、PoW、认证与反向代理 |
| Frontend | 管理网站配置、WAF、源站、证书、节点、版本、用户、设置与观测页面 |
## Server
@@ -54,6 +54,7 @@ Server 不直接 SSH 到节点,也不在线修改节点文件。它只保存
* 周期性 heartbeat,上报状态并获取激活版本摘要。
* 发现新版本后拉取配置、备份旧文件、写入新文件、校验并 reload。
* 应用失败时尝试恢复运行并回滚。
* 维护 WAF GeoIP mmdb,启动时写入内置初始库,并按配置定期更新。
Agent 通过 `openresty_path` 指向的 OpenResty 二进制统一执行校验、reload、启动与重启;未配置时默认调用 `openresty`。Docker 部署时,Agent 镜像内置 OpenResty 二进制,仍走同一套二进制控制逻辑。
@@ -84,7 +85,7 @@ Browser -> Frontend -> /api/* -> controller -> service -> model -> database
```text
Agent heartbeat -> Server 返回激活版本摘要
Agent 发现新版本 -> 拉取配置详情
Agent 写入主配置 / 路由配置 / 证书 / Lua 资源
Agent 写入主配置 / 路由配置 / 证书 / Lua 资源 / WAF 运行时配置
Agent 执行 OpenResty 校验与 reload
Agent 上报应用结果
```
@@ -94,11 +95,13 @@ Agent 上报应用结果
### 反向代理流
```text
Client -> OpenResty server block -> named upstream -> Origin
Client -> OpenResty server block -> WAF Lua -> named upstream -> Origin
```
网站配置是反向代理聚合边界。一条网站配置可绑定多个域名,并共享站点级流量限制、反向代理和缓存配置。
WAF 在 OpenResty `access_by_lua_file` 阶段执行。规则来自当前激活版本携带的 `waf_config.json`,全局规则组默认生效,网站可叠加自定义规则组。
## 核心对象
当前有效实体包括:
@@ -118,6 +121,8 @@ Client -> OpenResty server block -> named upstream -> Origin
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `waf_rule_groups`
* `waf_rule_group_bindings`
## 关键设计决策
+5
View File
@@ -148,6 +148,8 @@ tests/
* `traffic_analytics_rollups`
* `node_health_events`
* `options`
* `waf_rule_groups`
* `waf_rule_group_bindings`
通用约束:
@@ -161,6 +163,7 @@ tests/
* 上游统一使用 named `upstream` + keepalive;单上游如带 base path 或 query,应在 `proxy_pass` 上补回 URI,多上游仅允许纯 `scheme://host[:port]`。
* 流量限制、反向代理与缓存配置当前都归属站点级 `proxy_routes`。
* HTTPS 证书绑定必须通过与 `domains` 平行的 `domain_cert_ids` 逐域名保存;未绑定证书的域名不得参与 HTTPS 渲染。
* WAF 全局规则组默认应用到所有网站,自定义规则组通过 `waf_rule_group_bindings` 绑定到网站配置;发布时必须进入完整版本快照。
* `config_versions` 必须保存完整快照与渲染结果。
* 全局同时只能有一个激活版本。
* 回滚通过重新激活旧版本实现。
@@ -237,12 +240,14 @@ Agent 必须满足:
* WS 连接升级开启且连接成功时,Agent 可通过 WS 接收激活版本摘要并立即同步;WS 失败或断开必须退回 HTTP heartbeat。
* 发现新版本时先备份旧文件。
* 写入主配置、路由配置与必要证书文件。
* 写入 WAF/PoW 运行时配置,并确保 WAF Lua 资源由 Agent 统一管理。
* 写入新配置后执行 `openresty -t -c <main_config_path>`,再 reload;reload 发现运行时未启动时允许直接启动 OpenResty。
* 周期性运行时健康检查不得调用 `openresty -t`,避免健康探针触发 upstream 域名同步解析;应优先请求本地 `openresty_observability_port` 上的 `/openflare/stub_status`,以 HTTP `200 OK` 作为 OpenResty 主进程和 worker 正在提供服务的判断依据。
* 新配置激活失败时必须先尝试用目标配置恢复运行,再回滚到旧配置并重新拉起 OpenResty。
* 回滚后 OpenResty 恢复正常时上报警告;如果本地没有历史主配置可恢复,必须允许写入内置安全兜底配置并拉起对外只监听 `80` 端口、统一返回 `503` 的 OpenResty 运行态;兜底配置仍需保留本地 `stub_status` 健康检查入口。
* 兜底运行态不得清除失败目标的阻断状态;应用记录必须能体现目标版本失败但 fallback runtime 已启动。存在历史主配置但回滚后仍无法恢复运行时上报失败。
* 某个目标 `version + checksum` 一旦应用失败并回退,Agent 必须在本地状态中阻断该目标的重复应用。
* Agent 维护本地 MaxMind mmdb 时,下载或刷新失败只能记录警告,不得阻断心跳、同步、配置应用或 OpenResty 健康检查。
## 前端请求、状态与类型
+21
View File
@@ -35,6 +35,7 @@ OpenFlare 当前不定位为通用日志平台、服务网格、Kubernetes Ingre
| Agent 同步 | 支持注册、心跳、同步、应用结果上报与自更新 |
| OpenResty 托管 | 管理主配置模板、性能参数、缓存参数与 Lua 资源 |
| HTTPS/TLS | 托管证书与域名资产,并按域名绑定证书 |
| WAF | 以全局规则组与网站自定义规则组维护 IP/IP 段、国家级地域黑白名单 |
| 基础观测 | 聚合节点请求、资源快照、健康事件和访问分析 |
| 节点管理 | 节点状态、令牌体系、部署与更新链路 |
| 管理端前端 | 基于 Next.js 的正式管理端 |
@@ -76,6 +77,8 @@ OpenFlare 当前不定位为通用日志平台、服务网格、Kubernetes Ingre
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `waf_rule_groups`
* `waf_rule_group_bindings`
## 网站配置约束
@@ -116,6 +119,24 @@ OpenFlare 当前不定位为通用日志平台、服务网格、Kubernetes Ingre
* 未绑定证书的域名不得被自动带入 HTTPS。
* 必须将 `proxy_routes.domains` 中的全部域名一并纳入同一站点配置,避免同站点在版本快照中被拆散。
## WAF 约束
WAF 以规则组为配置边界。系统固定一个全局规则组,默认应用到所有网站;网站可叠加多个自定义规则组。
一期支持:
* IP / IP 段白名单与黑名单。
* 国家级地域白名单与黑名单。
* 规则组级拦截状态码与响应页面,默认 `418` 与空页面。
判定顺序:
* 白名单是放行例外,任意启用规则组命中白名单即放行。
* 未命中白名单时继续判断黑名单。
* 多个黑名单命中时,全局规则组优先,其后按自定义规则组 ID 升序。
地域识别由 Agent 维护节点本地 MaxMind mmdb,OpenResty Lua 在请求路径中读取本地库。GeoIP 依赖不可用时只能跳过地域规则,不得影响 IP 规则与反向代理主链路。
## 认证源约束
`auth_sources` 是管理端第三方登录入口的配置对象,当前仅支持 `github` 与 `oidc` 两类。启用后的认证源会显示在登录页。
+8 -6
View File
@@ -17,11 +17,12 @@ Server 发布时必须:
1. 读取全部启用的 `proxy_routes`。
2. 读取 Server 侧 OpenResty 主配置、性能参数、缓存参数和必要 Lua 资源。
3. 读取域名与证书绑定关系。
4. 渲染完整 OpenResty 配置。
5. 计算 `checksum`。
6. 写入 `config_versions`。
7. 切换激活版本。
8. 让 Agent 在后续 heartbeat 中发现并应用。
4. 读取 WAF 全局规则组、自定义规则组与网站绑定关系。
5. 渲染完整 OpenResty 配置与 WAF 运行时配置。
6. 计算 `checksum`。
7. 写入 `config_versions`。
8. 切换激活版本。
9. 让 Agent 在后续 heartbeat 中发现并应用。
版本号格式固定为 `YYYYMMDD-NNN`。
@@ -53,7 +54,7 @@ Agent 发现新版本后会:
1. 拉取目标版本详情。
2. 备份旧文件。
3. 写入主配置、路由配置、证书与必要 Lua 资源。
3. 写入主配置、路由配置、证书、必要 Lua 资源与 WAF/PoW 运行时配置。
4. 执行 OpenResty 配置校验。
5. reload;如果运行时未启动,则尝试用当前配置启动 OpenResty。
6. 上报成功、警告或失败。
@@ -69,3 +70,4 @@ Agent 发现新版本后会:
* Agent API 固定使用节点专属 `agent_token`,首次接入可使用 `discovery_token`。
* Server 不提供远程 shell 或任意命令执行入口。
* 配置版本必须保存完整快照、渲染结果和 `checksum`。
* WAF 规则组和网站绑定关系必须随完整配置版本进入快照与 checksum,回滚时不得依赖当前可变 WAF 配置。
+3
View File
@@ -43,6 +43,7 @@ Agent:
| OpenResty | 本地部署需要可执行 `openresty`,或通过 `--openresty-path` 指定路径 |
| Docker | 仅 Docker 部署 Agent 镜像时需要 |
| 网络 | Agent 节点必须能访问 Server 地址 |
| GeoIP | WAF 地域规则使用 Agent 本地 MaxMind mmdb;Agent 内置初始库并会定期更新 |
[需要确认:生产环境推荐的最低 CPU、内存与磁盘容量]
@@ -225,6 +226,8 @@ export LOG_LEVEL='info'
默认情况下,Agent 在 HTTP 心跳成功后会尝试升级为 WebSocket。升级成功时,Server 发布或激活配置会立即通知 Agent;如果 WebSocket 无法建立或意外断开,Agent 会自动退回 HTTP 心跳同步。
WAF 地域规则依赖 Agent 本地 `GeoLite2-Country.mmdb`。Agent 启动时会在 `data_dir/etc/openflare/GeoLite2-Country.mmdb` 初始化内置数据库,并按配置周期尝试更新;更新失败只记录警告,不影响配置同步与 OpenResty reload。
## 最小联调步骤
1. 启动 Server 并完成首次登录。
+7
View File
@@ -148,6 +148,9 @@ OpenResty 性能参数与缓存参数继续统一保存在 `Option` 表。当前
| `OPENFLARE_HEARTBEAT_INTERVAL` | 心跳间隔,可覆盖 `agent.json` | 空 |
| `OPENFLARE_REQUEST_TIMEOUT` | 请求超时,可覆盖 `agent.json` | 空 |
| `OPENFLARE_OPENRESTY_OBSERVABILITY_PORT` | 本地观测端口,可覆盖 `agent.json` | 空 |
| `OPENFLARE_MMDB_PATH` | WAF GeoIP mmdb 路径,可覆盖 `agent.json` | 空 |
| `OPENFLARE_MMDB_UPDATE_INTERVAL` | WAF GeoIP mmdb 更新间隔,可覆盖 `agent.json` | 空 |
| `OPENFLARE_MMDB_DOWNLOAD_URL` | WAF GeoIP mmdb 下载地址,可覆盖 `agent.json` | 空 |
## Agent 命令行参数
@@ -178,6 +181,9 @@ OpenResty 性能参数与缓存参数继续统一保存在 `Option` 表。当前
| `lua_dir` | Lua 脚本与静态资源写入目录 | 否 | `data_dir/etc/nginx/lua` |
| `openresty_lua_dir` | OpenResty 配置中读取 Lua 的目录 | 否 | 同 `lua_dir` |
| `runtime_config_dir` | Agent 运行时配置写入目录,如 `pow_config.json` | 否 | `data_dir/etc/openflare` |
| `mmdb_path` | WAF GeoIP mmdb 文件路径 | 否 | `data_dir/etc/openflare/GeoLite2-Country.mmdb` |
| `mmdb_update_interval` | WAF GeoIP mmdb 更新间隔 | 否 | `86400000` 毫秒 |
| `mmdb_download_url` | WAF GeoIP mmdb 下载地址 | 否 | 内置 GeoLite2 Country 下载地址 |
| `observability_buffer_path` | 观测补报缓冲文件路径 | 否 | `data_dir/var/lib/openflare/observability-buffer.json` |
| `observability_replay_minutes` | 自动补传最近观测窗口分钟数 | 否 | `15` |
| `state_path` | Agent 本地状态文件路径 | 否 | `data_dir/var/lib/openflare/agent-state.json` |
@@ -191,6 +197,7 @@ OpenResty 性能参数与缓存参数继续统一保存在 `Option` 表。当前
* Server 运行时配置 `AgentWebsocketUpgradeEnabled` 开启时,Agent 会在 HTTP 心跳成功后尝试升级为 WebSocket;连接失败或断开后自动退回 HTTP 心跳。
* 未配置 `openresty_path` 时默认调用 `openresty`。
* Agent 周期性健康检查会请求 `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status`,不再通过高频 `openresty -t` 判断运行时健康;配置应用、启动恢复和 reload 前校验仍会执行 `openresty -t -c <main_config_path>`。
* Agent 会初始化并定期更新 `mmdb_path`,供 OpenResty WAF Lua 执行国家级地域规则;更新失败只记录警告,不阻断同步或 reload。
* 如果 `agent.json` 不存在,但 `OPENFLARE_SERVER_URL` 与 Token 等环境变量足够,Agent 可以直接启动;两者同时存在时环境变量优先。
* Agent 未配置 `node_ip` 时,会优先通过 `https://realip.cc` 获取真实出口公网 IP,适配 Docker/NAT 场景;该请求失败时,才退回本机网卡探测并优先选择公网 IPv4。
* Agent 自动探测到私网 `node_ip` 时,Server 会在注册/心跳阶段优先保留 Agent 直连来源的公网地址,避免 NAT/多网卡场景误登记内网网卡地址。