Files
OpenFlare/docs/superpowers/specs/2026-07-19-waf-ua-check-node-design.md
T
ryan d47ceb9971 feat(waf): UA 非正常不含爬虫,并支持自定义正则屏蔽
block_abnormal_ua 仅 Other/Unknown;新增 block_custom_ua 与 custom_ua_patterns。
2026-07-19 11:43:45 +08:00

8.2 KiB
Raw Blame History

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)

{
  "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 增加:

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 且列表空不限制