mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 14:26:36 +08:00
d47ceb9971
block_abnormal_ua 仅 Other/Unknown;新增 block_custom_ua 与 custom_ua_patterns。
229 lines
8.2 KiB
Markdown
229 lines
8.2 KiB
Markdown
# WAF 规则节点:UA 检查(ua_check)
|
||
|
||
日期:2026-07-19
|
||
范围:WAF 编排图新节点 `ua_check`(控制面校验/编译 + 边缘 Lua 运行时 + 前端编辑器)
|
||
状态:已确认,待实现
|
||
|
||
## 背景
|
||
|
||
访问日志概览已按 User-Agent 分类浏览器与操作系统(`internal/repository/analytics/browser.go`),但 WAF 规则图尚无基于 UA 的分支节点。运营需要在图中:
|
||
|
||
1. 要求请求必须携带 UA;
|
||
2. 按浏览器 / 操作系统做白名单匹配(and/or 可配);
|
||
3. 优先屏蔽常见爬虫与非正常 UA。
|
||
|
||
## 目标
|
||
|
||
- 新增 match 型节点 **`ua_check`**,输出 `true` / `false` 句柄(与 `ip_match` / `geo_match` 一致)。
|
||
- 属性栏交互与产品草图对齐:开启 UA 检查、匹配多选、屏蔽开关。
|
||
- 边缘分类标签与访问日志概览一致(同一套 token 规则)。
|
||
- 屏蔽逻辑优先级高于白名单匹配。
|
||
|
||
## 非目标
|
||
|
||
- 设备类型(Mobile/Tablet)维度。
|
||
- 原始 UA 正则 / 自由子串列表(PoW 列表已有,不并入本节点)。
|
||
- 在 Server 请求路径上执行 WAF 图(仍仅 Agent OpenResty)。
|
||
- 将 analytics 包直接 import 到 Agent(边缘用 Lua 复刻规则;Go 侧用同一规则表做校验与单测对拍)。
|
||
|
||
## 节点模型
|
||
|
||
### 类型
|
||
|
||
| 字段 | 值 |
|
||
|------|-----|
|
||
| `type` | `ua_check` |
|
||
| 句柄 | `true`, `false` |
|
||
| 可删除 | 是 |
|
||
| 可命名 | 是(`label`) |
|
||
| 可拖放添加 | 是 |
|
||
|
||
### Config(JSON)
|
||
|
||
```json
|
||
{
|
||
"require_ua": false,
|
||
"browsers": [],
|
||
"operating_systems": [],
|
||
"match_mode": "or",
|
||
"block_common_bots": false,
|
||
"block_abnormal_ua": false,
|
||
"block_custom_ua": false,
|
||
"custom_ua_patterns": []
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `require_ua` | bool | 开启后:请求头无 UA(空 / 仅空白)→ **false** |
|
||
| `browsers` | string[] | 白名单浏览器标签;空表示不限制浏览器 |
|
||
| `operating_systems` | string[] | 白名单操作系统标签;空表示不限制 OS |
|
||
| `match_mode` | `"and"` \| `"or"` | **浏览器条件与 OS 条件**之间的组合;默认 `"or"` |
|
||
| `block_common_bots` | bool | 屏蔽常见爬虫:分类 browser 或 os 为 `Bot` → **false** |
|
||
| `block_abnormal_ua` | bool | 屏蔽非正常 UA:browser ∈ `{Other, Unknown}`(**不含** Bot/搜索引擎爬虫)→ **false** |
|
||
| `block_custom_ua` | bool | 屏蔽自定义 UA:原始 UA 命中 `custom_ua_patterns` 任一条 → **false** |
|
||
| `custom_ua_patterns` | string[] | 正则列表(边缘为 Lua 模式);开启 `block_custom_ua` 时至少一条 |
|
||
|
||
默认值:开关全 `false`,列表空,`match_mode: "or"`。
|
||
|
||
### 允许的标签(封闭枚举)
|
||
|
||
与 `ParseBrowserName` / `ParseOSName` 输出对齐:
|
||
|
||
**browsers:**
|
||
`Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Chromium`, `WeChat`, `Postman`, `CLI`, `Bot`, `Unknown`, `Other`
|
||
|
||
**operating_systems:**
|
||
`Android`, `iOS`, `Windows`, `macOS`, `Chrome OS`, `Linux`, `Bot`, `Unknown`, `Other`
|
||
|
||
校验:列表元素必须属于上表;重复项编译时去重排序;未知字符串拒绝保存。
|
||
|
||
## 求值语义(边缘)
|
||
|
||
输入:`ua = http_user_agent`(trim 后判断空)。
|
||
分类:`browser = ParseBrowserName(ua)`,`os = ParseOSName(ua)`(空 UA → 二者均为 `Unknown`,与 analytics 一致)。
|
||
|
||
**严格顺序:**
|
||
|
||
```
|
||
1) if require_ua and ua 为空 → false
|
||
2) browser, os := classify(ua)
|
||
3) if block_common_bots and (browser == "Bot" or os == "Bot") → false
|
||
4) if block_abnormal_ua and browser in {"Other","Unknown"} → false
|
||
5) if block_custom_ua and UA matches any custom_ua_patterns → false
|
||
6) has_browsers := browsers 非空; has_os := operating_systems 非空
|
||
7) if not has_browsers and not has_os → true
|
||
8) browser_hit := browser ∈ browsers; os_hit := os ∈ operating_systems
|
||
9) if has_browsers and not has_os → browser_hit
|
||
10) if has_os and not has_browsers → os_hit
|
||
11) if both lists set:
|
||
match_mode == "and" → browser_hit and os_hit
|
||
match_mode == "or" → browser_hit or os_hit
|
||
```
|
||
|
||
说明:
|
||
|
||
- **屏蔽优先于匹配**:步骤 3–5 在白名单之前。
|
||
- **未配置匹配列表**:步骤 6 直接 true(仅受 require / block 约束)。
|
||
- **仅一侧列表有值**:只校验该侧是否命中;`match_mode` 仅在两侧都有值时生效。
|
||
- 节点本身不 allow/block,仅选句柄;下游连线决定动作。
|
||
|
||
### 示例
|
||
|
||
| 配置摘要 | 请求 | 结果 |
|
||
|----------|------|------|
|
||
| 仅 `require_ua` | 无 UA | false |
|
||
| 仅 `require_ua` | 正常 Chrome | true |
|
||
| `block_common_bots` | Googlebot | false |
|
||
| `block_abnormal_ua` | 无法识别 UA | false |
|
||
| browsers=`[Chrome]`, mode=or | Safari | false |
|
||
| browsers=`[Chrome]`, os=`[iOS]`, mode=and | Chrome Desktop | false(os 未命中) |
|
||
| browsers=`[Chrome]`, os=`[iOS]`, mode=or | Chrome Desktop | true |
|
||
| 列表皆空,无 block | 任意有 UA | true |
|
||
|
||
## 分类规则来源
|
||
|
||
权威实现(analytics):`internal/repository/analytics/browser.go` 中 `browserRules` / `osRules`。
|
||
|
||
实现要求:
|
||
|
||
1. **Lua 运行时**复刻相同 token 顺序与 `contains` / `noneOf` 语义(lower-case 子串)。
|
||
2. **Go 单测**用同一批样例 UA 对拍 `ParseBrowserName` / `ParseOSName` 与 Lua 或共享测试表,防止漂移。
|
||
3. 不强制本迭代抽取共享包;若抽取,须保持 analytics 与 WAF 行为不变。
|
||
|
||
## 控制面
|
||
|
||
### `graph_types.go`
|
||
|
||
- `RuleNodeUACheck RuleNodeType = "ua_check"`
|
||
- `UACheckConfig` 结构体对应上表 JSON 字段
|
||
|
||
### `graph_validate.go`
|
||
|
||
- `requiredHandles`: `true`, `false`
|
||
- `validateUACheckNodeConfig`:
|
||
- `match_mode` 仅 `and`/`or`(缺省按 `or` 或拒绝非法值)
|
||
- browsers / OS 标签 ∈ 封闭枚举
|
||
- 布尔字段默认 false
|
||
- `DisallowUnknownFields`
|
||
|
||
### `graph_compile.go`
|
||
|
||
- 编译进 `RuntimeRuleNode`,列表 `sortedUniqueStrings`
|
||
- 规范化 `match_mode`(非法不得编译成功)
|
||
|
||
### 测试
|
||
|
||
- validate:合法配置、非法标签、非法 mode、缺句柄
|
||
- compile:列表排序去重、默认值
|
||
|
||
## 数据面(Agent)
|
||
|
||
### `waf_runtime.lua`
|
||
|
||
在 `execute_graph` 增加:
|
||
|
||
```lua
|
||
elseif node.type == "ua_check" then
|
||
handle = matches_ua_check(node.config) and "true" or "false"
|
||
```
|
||
|
||
实现 `matches_ua_check` + 本地 classify 函数;读取 `ngx.var.http_user_agent`。
|
||
|
||
### `waf_runtime_spec.lua`
|
||
|
||
覆盖:空 UA + require;bot 屏蔽;abnormal;whitelist and/or;列表空;损坏边 fail-closed。
|
||
|
||
## 前端编辑器
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `types.ts` | `ua_check` 变体 + `UACheckConfig` |
|
||
| `node-factory.ts` | 标签「UA 检查」、默认 config、`AddableNodeType` |
|
||
| `node-library.tsx` | 拖放项 |
|
||
| `rule-node.tsx` | 图标 + `true`/`false` handles |
|
||
| `node-properties.tsx` | 属性 UI(见下) |
|
||
| `graph-validation.ts` | handles + 标签/mode 校验 |
|
||
| `editor-behavior.ts` | connection handles |
|
||
|
||
### 属性栏布局
|
||
|
||
```
|
||
显示名称
|
||
── UA 检查 ──
|
||
[Switch] 开启 UA 检查
|
||
说明:开启后如果请求头不携带 UA 返回 False
|
||
── UA 匹配 ──
|
||
匹配模式 Select: 或(or) / 且(and)
|
||
浏览器 MultiSelect(封闭枚举)
|
||
操作系统 MultiSelect(封闭枚举)
|
||
── 屏蔽 ──
|
||
说明:命中返回 false,优先级高于匹配
|
||
[Switch] 屏蔽常见爬虫 UA
|
||
[Switch] 屏蔽非正常 UA
|
||
```
|
||
|
||
前端选项列表写死与封闭枚举一致;展示可用中文副标题,**写入 config 的值必须是英文标签**(与 analytics / 边缘一致)。
|
||
|
||
## 文档
|
||
|
||
- 更新 `docs/design/waf-orchestration-design.md` 节点表(中文)。
|
||
- `docs/changelog/index.md` `[Unreleased]` 增加用户向说明。
|
||
- 纯设计文档不写 changelog 以外的英文同步。
|
||
|
||
## 验收标准
|
||
|
||
- [ ] 编辑器可拖入 `ua_check`,配置保存再打开一致。
|
||
- [ ] 图校验拒绝非法标签与非法 `match_mode`。
|
||
- [ ] 发布后 Agent Lua 按求值顺序分支;spec 全绿。
|
||
- [ ] 样例 UA 分类与访问日志 `ParseBrowserName`/`ParseOSName` 一致。
|
||
- [ ] `make code-check` 与相关 Go/前端/Lua 测试通过。
|
||
|
||
## 风险与缓解
|
||
|
||
| 风险 | 缓解 |
|
||
|------|------|
|
||
| Go/Lua 分类漂移 | 共享样例表单测对拍 |
|
||
| 「非正常」过严误伤 | 产品定义为 Bot/Other/Unknown;可关 switch |
|
||
| 白名单 + or 过宽 | UI 说明 and/or;默认 or 且列表空不限制 |
|