mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 22:46:38 +08:00
feat(waf): complete composable rule orchestration
Add the React Flow rule editor, ordered graph APIs and runtime DAG execution.\n\nPublish rules only on OpenResty reload and reconcile checksum-driven IP group snapshots in bounded shared memory.
This commit is contained in:
+8
-125
@@ -1,132 +1,15 @@
|
||||
# WAF 设计文档
|
||||
# WAF 设计
|
||||
|
||||
> WAF 规则正在从固定的白名单、黑名单、PoW 判定链演进为可视化 DAG。新的图模型、执行顺序、发布加载和迁移边界以 [WAF 可编排规则设计](./waf-orchestration-design.md) 为准;本文保留 IP 组、GeoIP 与现有运行时背景说明。
|
||||
OpenFlare WAF 的现行规则模型是可视化 DAG。节点语义、图约束、多规则顺序、发布编译与迁移边界统一以 [WAF 可编排规则设计](./waf-orchestration-design.md) 为准。
|
||||
|
||||
你会学到:OpenFlare 边缘 Web 应用防火墙(WAF)的核心架构、动态 IP 组异步差分同步模型、OpenResty Lua 高性能缓存方案以及完整的请求过滤与判定逻辑。
|
||||
## 系统边界
|
||||
|
||||
---
|
||||
Server 保存带坐标和修订号的编辑图,发布时再次校验并编译为紧凑运行图;Agent 原子写入快照并 reload OpenResty;请求热路径只遍历 Worker 内存中的不可变图。
|
||||
|
||||
## 需求分析
|
||||
IP 组独立于规则拓扑更新。手动、订阅和自动 IP 组由控制面维护,Agent 先原子替换 JSON、最后更新 checksum。协调 Worker 每 5 秒检查 checksum,仅变化时读取完整快照并分发给其它 Worker;失败时保留上一份有效数据。完整运行时快照上限为 20 MiB,Server 发布/同步与 Agent 落盘使用同一序列化校验;OpenResty 使用独立的 64 MiB 共享字典和非淘汰写入,容量不足时拒绝新版本而不破坏已提交快照。
|
||||
|
||||
在互联网公开环境中,Web 应用程序面临着各种各样的安全威胁(如扫描器踩点、刷接口、针对特定地域的恶意网络爬虫、勒索攻击及 CC 攻击等)。如果直接把恶意请求放行给源站(Origin Server),会导致:
|
||||
1. **源站负载飙升**:高频的数据库查询与 CPU 运算极易耗尽服务器资源。
|
||||
2. **敏感接口被刷**:登录、注册、短信验证码接口容易被恶意滥用导致财产损失。
|
||||
3. **数据泄露风险**:恶意的通用漏洞探测行为无法被提前拦截。
|
||||
地域节点使用 Country 与 City MMDB。数据库不可用时地域匹配返回 `false` 并限频告警,不允许因数据损坏意外放行其它执行错误。
|
||||
|
||||
因此,OpenFlare 需要在最前端的数据面(OpenResty)构建一套 **高性能、可弹性伸缩的 WAF 过滤引擎**。该引擎能够在最接近用户的边缘层以毫秒级的极低开销对恶意请求进行深度过滤,减轻源站压力,并提供防 CC(PoW 挑战)、IP 黑白名单与地域级别拦截等核心安全防护能力。
|
||||
## 安全顺序
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
OpenFlare WAF 包含以下核心防护维度:
|
||||
|
||||
* **IP 级拦截(IP 黑白名单)**:支持单 IP、CIDR 网段过滤,支持将上万 IP 聚合为 IP 组进行高效比对。
|
||||
* **地域黑白名单(GeoIP 限制)**:集成 MaxMind 数据库,支持针对国家(Country)和省份/地区(Region)执行精准准入控制。
|
||||
* **自定义拦截响应**:支持针对不同的过滤规则自定义阻断状态码(如 403, 418)以及个性化的 HTML 拦截页面。
|
||||
* **人机挑战(PoW CC 防护)**:支持无感人机挑战,通过计算 Hash 碰撞防止自动化脚本和僵尸网络(Botnet)对接口进行并发冲击。
|
||||
|
||||
---
|
||||
|
||||
## IP 组设计与动态异步同步
|
||||
|
||||
IP 组是 WAF 进行高效黑白名单管控的核心容器。OpenFlare 将 IP 组根据更新频率与产生渠道分为三类:
|
||||
|
||||
### 1. IP 组类型
|
||||
* **手动 IP 组(Manual)**:由管理员在控制面板上手动输入 IP 或 CIDR 列表。主要用于静态的信任 IP 或长期的封禁。
|
||||
* **订阅 IP 组(Subscription)**:配置远程文本(按行分隔)或标准的 JSON 订阅地址。Server 侧的定时任务会周期性抓取远程订阅源并自动解析导入。主要用于集成开源的威胁情报库、云厂商的 IP 范围等。
|
||||
* **自动 IP 组(Automatic)**:**最具弹性的动态防护通道**。控制面的定时扫描任务会读取所有节点的访问日志,按照设定的 Expr 规则(例如:“5分钟内请求 `/api/login` 接口触发 401 超过 50 次”)进行聚合分析,一旦匹配,自动将该恶意源 IP 写入封禁组,并指定封禁时长。
|
||||
|
||||
### 2. 异步差分同步设计 (不触发 Nginx Reload)
|
||||
在传统的 Nginx WAF 设计中,IP 黑名单的更新通常需要重写配置并 reload。如果恶意 IP 封禁以秒级或分钟级高频触发,频繁 reload 会导致 Nginx 频繁新建 Worker 进程并销毁老进程,导致性能骤降。
|
||||
|
||||
OpenFlare 采用 **动态 IP 组异步差分同步设计**:
|
||||
|
||||
```text
|
||||
WAF IP 成员更新 (手动/订阅/自动自动触发)
|
||||
|
|
||||
v
|
||||
Server 更新数据库并计算该 IP 组的全新 MD5 Checksum
|
||||
|
|
||||
+----------------------------------------+
|
||||
| (WebSocket 实时广播) | (心跳兜底比对)
|
||||
v v
|
||||
Server 立即向所有 Agent 推送变更组的完整成员 Agent 心跳上报本地所有 IP 组的 Checksum 映射表
|
||||
| |
|
||||
| v
|
||||
| Server 发现 Checksum 不一致,下发变更的 IP 组成员
|
||||
v |
|
||||
Agent 接收成员数据,将其以 JSON 形式写入本地磁盘路径:waf_ip_groups.json
|
||||
|
|
||||
v (Lua 内存感知)
|
||||
OpenResty Lua 引擎通过 MD5 校验和秒级感知文件变化并热更新内存,无需 reload 进程
|
||||
```
|
||||
|
||||
通过这一架构,上万个高频变动的动态黑名单 IP 的落地和生效,**全程无需 reload 任何 Nginx 进程**,极大地保护了网关的高并发性能。
|
||||
|
||||
---
|
||||
|
||||
## 规则组与网站绑定
|
||||
|
||||
* **WAF 规则组(Rule Group)**:WAF 过滤政策的最小逻辑集合。一条规则组内可以包含 IP 黑白名单、IP 组引用、地域限制及防 CC 挑战配置。
|
||||
* **全局规则组(Global)**:当规则组被标记为 `is_global = true` 时,该规则组对节点上托管的**所有网站路由**默认生效。
|
||||
* **网站绑定绑定(Site Binding)**:网站路由(Proxy Route)可以绑定一个或多个非全局规则组。判定时,会执行 `全局规则组 + 绑定规则组` 的并集逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 实现方案与高性能缓存
|
||||
|
||||
WAF 在 OpenResty 的 `access_by_lua` 阶段被触发,核心由 Lua 文件与本地落地的 JSON 配置构成。
|
||||
|
||||
### 1. 物理结构
|
||||
* `waf_config.json`:包含所有规则组的元数据、国家地域限制、以及网站(Site)与规则组的关联映射。
|
||||
* `waf_ip_groups.json`:包含所有同步下来的 IP 组与对应的 IP 列表。
|
||||
* `waf/runtime.lua`:WAF 规则比对的实际运行时引擎。
|
||||
* `waf/check.lua`:接入层入口,负责包引入与 check() 触发。
|
||||
|
||||
### 2. 共享内存字典 (ngx.shared) 高性能缓存设计
|
||||
在每次 Web 请求进来时都读取磁盘上的 JSON 文件并进行解码,会导致磁盘 I/O 成为严重的性能瓶颈。
|
||||
|
||||
OpenFlare 利用 **OpenResty 共享内存字典 (ngx.shared.openflare_waf_config)** 设计了二级缓存机制:
|
||||
|
||||
1. **零文件 I/O 路径**:
|
||||
在 Lua 中,每次执行 `check()` 时,首先利用 `ngx.md5` 瞬间计算本地磁盘 JSON 文件的 MD5 哈希(这一操作几乎为零耗时,因为文件已被操作系统 Page Cache 缓存)。
|
||||
2. **哈希比对与热加载**:
|
||||
比对共享内存中存储的缓存哈希键(`_config_hash`)。
|
||||
* **若哈希未发生变化**:直接从共享内存字典中读取已解码、存在内存中的 Lua Table 配置,整个校验过程完全基于**共享内存操作**,耗时在 **微秒级** 级别。
|
||||
* **若哈希不一致**:说明 Agent 刚刚落地了新的 WAF 规则或 IP 组,Lua 自动读取磁盘文件并使用 `cjson.decode` 解码,解码后的数据及全新的 MD5 写入共享内存,供后续 Worker 进程无缝读取。
|
||||
|
||||
---
|
||||
|
||||
## 应用流程与判定判定控制逻辑
|
||||
|
||||
当一个 HTTP/HTTPS 请求到达 OpenResty 后,WAF 会在 `access` 阶段按下图所示的漏斗判决链进行逐步匹配拦截:
|
||||
|
||||
### 1. WAF 判定流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[请求进入 access 阶段] --> B[获取当前请求的 Site Name]
|
||||
B --> C[在共享内存中加载与此 Site 绑定的所有活跃规则组]
|
||||
C --> D{匹配到 IP 白名单 / 白名单 IP 组?}
|
||||
D -- 是 (匹配成功) --> E[放行请求 - ALLOW]
|
||||
D -- 否 --> F{匹配到国家/地区地域白名单?}
|
||||
F -- 是 (匹配成功) --> E
|
||||
F -- 否 --> G{匹配到 IP 黑名单 / 黑名单 IP 组?}
|
||||
G -- 是 (匹配成功) --> H[阻断请求 - BLOCK]
|
||||
G -- 否 --> I{匹配到国家/地区地域黑名单?}
|
||||
I -- 是 (匹配成功) --> H
|
||||
I -- 否 --> J{是否启用了防 CC PoW 验证?}
|
||||
J -- 是 --> K[转交防 CC 模块处理]
|
||||
J -- 否 --> L[无安全风险,正常放行]
|
||||
|
||||
H --> M[退出并返回规则组配置的自定义状态码与拦截响应体]
|
||||
```
|
||||
|
||||
### 2. 判决步骤细则
|
||||
1. **白名单前置**:
|
||||
为了防止误杀以及保障核心回源流量(如搜索引擎蜘蛛、CDN 回源 IP、办公区出口)的顺畅,WAF **优先匹配 IP 白名单与地域白名单**。一旦白名单匹配成功,直接绕过后续的所有黑名单检测和 CC 挑战,立刻放行。如果请求未命中白名单,则继续向下进行黑名单检测及其他后续判定。
|
||||
2. **黑名单强力阻断**:
|
||||
如果在白名单判定中未被捕获,请求将进入黑名单漏斗。一旦请求源 IP 命中 IP 黑名单、命中引用的黑名单 IP 组、或是处于被禁止的国家/地区范围内,Lua 引擎立即将 `ngx.ctx.openflare_waf_blocked` 标记设为 `true`。
|
||||
3. **输出响应**:
|
||||
命中黑名单后,Lua 提取匹配到规则组的 `block_status_code`(默认返回 418 / 403)和 `block_response_body`(拦截页面 HTML),通过 `ngx.say()` 输出响应体并执行 `ngx.exit(status)` 平滑退出请求,防止请求继续向后透传。
|
||||
启用的全局规则固定前置;路由规则按绑定 sequence 执行。阻止节点立即终止,通过节点仅结束当前规则,全部规则通过后才进入回源链路。未知节点、缺失出口或步数超限一律阻止请求。
|
||||
|
||||
@@ -70,11 +70,11 @@ IP 组采用协调 Worker、共享快照和 Worker 本地对象的两级缓存
|
||||
1. 请求始终读取当前 Worker 内存中的 IP 组对象,不访问文件或共享字典中的 JSON。
|
||||
2. 每 5 秒只有一个取得共享锁的 Worker 读取轻量 checksum 文件。
|
||||
3. checksum 未变化时立即结束,不读取完整 `waf_ip_groups.json`。
|
||||
4. checksum 变化时,协调 Worker 读取并验证一次完整 JSON,再把原始快照与新版本写入 `ngx.shared`。
|
||||
4. checksum 变化时,协调 Worker 读取并验证一次完整 JSON,再把原始快照按 checksum 写入独立的 64 MiB `ngx.shared.openflare_waf_ip_groups`,最后更新提交指针。
|
||||
5. 其他 Worker 发现共享版本变化后,从共享内存取得快照、解析并原子替换各自的本地对象,不重复读取磁盘。
|
||||
6. 刷新失败时继续使用上一份有效对象,限频记录错误,并在下一周期重试。
|
||||
|
||||
Agent 必须先原子替换 IP 组 JSON,最后原子更新 checksum,使 Worker 永远不会把半写入文件识别为新版本。
|
||||
Agent 必须先原子替换 IP 组 JSON,最后原子更新 checksum,使 Worker 永远不会把半写入文件识别为新版本。Server 发布/同步和 Agent 落盘共同执行 20 MiB 聚合快照上限;共享字典使用不会强制淘汰旧键的安全写入,失败时保留当前与上一代不可变快照。
|
||||
|
||||
## API 与编辑器
|
||||
|
||||
|
||||
Reference in New Issue
Block a user