# WAF 可编排规则设计 本文定义 OpenFlare WAF 从固定判定链重构为可视化有向无环图(DAG)的目标架构、数据模型、执行语义、发布模型与迁移边界。IP 组的来源与成员计算仍遵循 [WAF 设计](./waf-design.md),本文只改变规则如何组合和执行。 ## 目标与边界 用户新增 WAF 规则时只输入名称。Server 随即创建一张合法的默认图 `开始 → 通过`,前端进入基于 React Flow 的独立编排页面。用户通过添加处理单元、配置节点并连接分支构建策略,不再填写固定顺序的黑白名单与 PoW 表单。 第一阶段支持以下节点: | 节点 | 数量约束 | 输入 | 输出 | 配置 | | --- | --- | --- | --- | --- | | 开始 | 每张图恰好一个 | 无 | `next` | 无 | | 通过 | 每张图恰好一个 | 一个或多个 | 无 | 无 | | 阻止 | 可创建多个 | 一个或多个 | 无 | HTTP 状态码、HTML 响应体 | | IP 匹配 | 可创建多个 | 一个或多个 | `true`、`false` | IP、CIDR、IP 组 ID | | 地域匹配 | 可创建多个 | 一个或多个 | `true`、`false` | 国家代码、地区代码 | | UA 检查 | 可创建多个 | 一个或多个 | `true`、`false` | 要求携带 UA、浏览器/OS 白名单与 and/or、屏蔽爬虫/非正常 UA(不含爬虫)/自定义正则 | | 安全防护 | 可创建多个 | 一个或多个 | `true`、`false` | 基础特征检测(路径穿越/文件包含默认开;SQL/XSS/命令注入/SSRF/上传/XXE/CRLF 可开关);命中任一已启用规则为 false | | PoW | 可创建多个 | 一个或多个 | `next` | 算法、难度、会话 TTL、挑战 TTL | IP 匹配、地域匹配、UA 检查与安全防护不区分黑名单或白名单。`true` 只表示请求通过该节点判定,`false` 只表示未通过;放行或阻止的业务含义完全由连线决定。UA 检查的求值顺序为:要求携带 UA → 屏蔽爬虫/非正常 UA → 白名单匹配。安全防护在请求 Path/Query/Header/Cookie/Body(有限)上做特征匹配。PoW 验证完成后沿 `next` 继续,未完成时由挑战页面接管当前请求,不产生 `false` 分支。 不在第一阶段实现循环、脚本节点、任意表达式节点、子图调用和跨规则跳转。 ## 控制面架构 规则图采用控制面编辑态和数据面运行态分离的双模型: 1. React Flow 编辑器提交版本化图 JSON,其中包含节点 ID、节点类型、显示名称、坐标、类型化配置和连线。 2. Server 对整张图执行权威校验,通过后以单个事务保存图并递增修订号。 3. 配置发布时,Server 再次校验所有启用规则,将图编译为不含坐标、标签等 UI 字段的紧凑运行时 DAG,并收集被引用的 IP 组 ID。 4. Agent 原子落盘完整发布快照并 reload OpenResty。新 Worker 启动时只加载和解析一次规则 JSON。 5. 请求热路径只遍历 Worker 内存中的不可变运行时图,不读取文件、不计算 checksum、不解析 JSON。 编辑态 JSON 使用明确的 `schema_version`。节点配置使用按节点类型区分的结构,不允许用无约束键值对象绕过 Server 校验。初始安全上限为每条规则 128 个节点、256 条边和 256 KiB 编辑态 JSON;这些限制由 API 和发布编译器共同执行。 ## 图结构约束 规则保存与发布必须满足全部约束: * 图是有向无环图,禁止自环和任意循环。 * 恰好存在一个开始节点和一个通过节点;阻止节点可以存在多个。 * 开始节点无入边且恰好有一个 `next` 出口;通过和阻止节点无出口。 * IP 匹配、地域匹配、UA 检查与安全防护的 `true`、`false` 出口必须各连接一次;PoW 的 `next` 必须连接一次。 * 除终止节点外不得存在悬空出口;每个非开始节点至少有一条入边。 * 所有节点都必须从开始节点可达,且从每个可执行节点出发都能抵达通过或阻止。 * 边的源端口必须属于源节点类型;同一源端口不得连接多个目标。 * 节点 ID 在图内唯一,边 ID 在图内唯一,所有边引用的节点必须存在。 * 节点配置必须通过对应类型的字段、范围、引用存在性和体积校验。 前端提供即时校验和连线限制以改善体验,但 Server 是唯一权威校验方。删除节点时前端同步删除关联边并将规则标记为未保存;图恢复合法前禁止保存。 ## 多规则执行语义 一个路由可以绑定多条自定义规则。绑定关系是有序列表,并遵循以下顺序: 1. 启用的全局规则固定最先执行,不参与路由侧排序。 2. 路由绑定的启用规则按绑定顺序依次执行。 3. 当前规则抵达阻止节点时立即输出该节点配置的响应并终止请求。 4. 当前规则抵达通过节点时,只表示当前规则执行完成;若仍有后续规则则继续执行。 5. 全部规则均抵达通过节点后,请求才真正放行并进入后续 OpenResty/回源链路。 运行时图在发布前已经过完整校验。若 Lua 执行器仍遇到未知节点、未知端口、缺失目标或超过节点步数上限,则记录限频错误并阻止请求,避免损坏的安全配置意外放行。 ## IP 组内存刷新 规则拓扑只在发布并 reload OpenResty 时生效;IP 组成员仍可由手动、订阅或自动任务独立更新,不要求发布或 reload。 IP 组采用协调 Worker、共享快照和 Worker 本地对象的两级缓存: 1. 请求始终读取当前 Worker 内存中的 IP 组对象,不访问文件或共享字典中的 JSON。 2. 每 5 秒只有一个取得共享锁的 Worker 读取轻量 checksum 文件。 3. checksum 未变化时立即结束,不读取完整 `waf_ip_groups.json`。 4. checksum 变化时,协调 Worker 读取并验证一次完整 JSON,再把原始快照按 checksum 写入独立的 64 MiB `ngx.shared.openflare_waf_ip_groups`,最后更新提交指针。 5. 其他 Worker 发现共享版本变化后,从共享内存取得快照、解析并原子替换各自的本地对象,不重复读取磁盘。 6. 刷新失败时继续使用上一份有效对象,限频记录错误,并在下一周期重试。 Agent 必须先原子替换 IP 组 JSON,最后原子更新 checksum,使 Worker 永远不会把半写入文件识别为新版本。Server 发布/同步和 Agent 落盘共同执行 20 MiB 聚合快照上限;共享字典使用不会强制淘汰旧键的安全写入,失败时保留当前与上一代不可变快照。 ## API 与编辑器 创建接口只接受规则名称,创建成功后返回带默认图的规则详情。规则元数据、图保存和路由绑定使用独立操作,避免修改启用状态或绑定时覆盖画布。 图详情包含 `revision`。保存请求提交 `revision + graph`,Server 仅在修订号匹配时更新并递增修订号;不匹配时返回冲突,前端提示重新加载,禁止静默覆盖其他页面的修改。路由绑定接口接受有序规则 ID 数组。 React Flow 编辑页采用全宽画布和固定右侧属性栏: * 顶部提供返回、规则名称、启用状态、校验状态和保存操作。 * 画布使用紧凑高度和较小的首次适配缩放,支持缩放、平移、框选、删除、自动布局和 MiniMap/Controls 等必要导航能力;节点拖动由 React Flow 本地受控状态实时处理,拖动结束后才把坐标写回编辑图。 * “添加处理单元”提供 IP 匹配、地域匹配、UA 检查、安全防护、PoW 和阻止;开始与通过由默认图提供且不可删除或重复添加。 * 选中普通节点或连线后可使用画布删除按钮或 Delete/Backspace 删除;删除节点时同步移除关联连线。 * 右侧属性栏默认隐藏,选中节点后才显示并用于编辑配置;点击连线或画布空白处时收起。 * 地域匹配属性使用完整国家与 ISO 3166-2 一级行政区数据;国家选项同时显示本地化名称与代码,行政区支持按国家名、行政区名或代码搜索,避免一次渲染数千个选项。 * 离开存在未保存变更的页面前必须提示;保存冲突和 Server 校验错误应定位到相关节点或边。 WAF 列表展示规则名称、启用状态、节点数量、应用路由数量和更新时间。新建规则的对话框只有名称字段,成功后立即导航到编排页面。 ## 持久化与迁移 规则记录增加版本化图 JSON 与修订号;绑定记录增加执行顺序。图作为一个聚合整体保存,不拆成节点表和边表,以保证编辑操作的事务边界,并让新增节点类型不必频繁扩展数据库 Schema。 升级现有安装时: * 保留规则名称、全局标记、启用状态及路由绑定关系。 * 所有规则图重置为 `开始 → 通过`,不迁移旧 IP/地域名单、PoW 或拦截响应配置。 * 现有绑定按稳定顺序写入顺序字段;全局规则仍固定前置。 * 新图和运行时稳定后移除旧规则字段、固定顺序编译逻辑和旧前端表单,不长期维护双执行器。 该迁移会让旧防护配置停止生效,升级说明必须显著提示管理员在发布下一版本前重新编排规则。 ## 发布、失败与回滚 规则图只在配置发布时生效。发布前校验或编译失败时拒绝发布,当前活动版本保持不变。Agent 写入、OpenResty 配置检查或 reload 失败时,应用流程失败并恢复上一份有效发布版本。 新 Worker 只接受完整且可解析的规则运行态配置。旧 Worker 在 OpenResty 优雅 reload 期间继续使用旧内存图,新 Worker 使用新图,因此请求不会观察到半更新状态。 地域数据库不可用时,地域匹配返回 `false` 并限频告警,保持现有行为。IP 组刷新失败时保留旧内存快照。PoW 未完成由挑战模块接管请求,不视为执行错误;PoW 节点配置先以短期键写入 OpenResty 共享内存,再通过 `ngx.exec` 的显式参数传给内部挑战处理器,不能依赖内部重定向保留 `ngx.ctx` 或隐式继承请求参数。发布快照中的空规则绑定必须编码为 JSON 空数组;运行时将旧快照中的 `null` 可选数组按空数组处理,禁止因 `cjson` 的 `ngx.null` userdata 中断请求。 ## 测试与验收 * Go 单元测试覆盖图结构、端口、可达性、终止性、节点配置、体积限制、编译结果、修订冲突和绑定顺序。 * 数据库测试覆盖 PostgreSQL/SQLite 迁移、默认图、旧绑定稳定排序和回滚。 * Lua 测试覆盖所有节点出口、多规则顺序、全局规则前置、多个阻止响应、PoW 接管和损坏运行时图保护。 * Agent/OpenResty 测试覆盖发布 reload、加载一次、失败回滚、IP 组五秒 checksum 刷新和旧快照保留。 * 前端测试覆盖创建后导航、特殊节点唯一性、连线限制、属性编辑、即时校验、未保存提示和并发冲突。 * 集成测试从控制面创建并编排规则,发布后用真实请求验证放行、阻止、PoW 和 IP 组热刷新。 * API 变更后运行 `make swagger`;完成实现后运行前端检查与构建以及 `make code-check`。