feat(waf): UA 非正常不含爬虫,并支持自定义正则屏蔽

block_abnormal_ua 仅 Other/Unknown;新增 block_custom_ua 与 custom_ua_patterns。
This commit is contained in:
ryan
2026-07-19 11:43:45 +08:00
parent 7476c86976
commit d47ceb9971
15 changed files with 168 additions and 23 deletions
@@ -95,6 +95,7 @@ func compileRuleNodeConfig(node RuleNode) (any, error) {
}
config.Browsers = sortedUniqueStrings(config.Browsers)
config.OperatingSystems = sortedUniqueStrings(config.OperatingSystems)
config.CustomUAPatterns = sortedUniqueStrings(config.CustomUAPatterns)
if config.MatchMode == "" {
config.MatchMode = UACheckMatchModeOr
}
@@ -93,6 +93,8 @@ type UACheckConfig struct {
MatchMode string `json:"match_mode,omitempty"`
BlockCommonBots bool `json:"block_common_bots"`
BlockAbnormalUA bool `json:"block_abnormal_ua"`
BlockCustomUA bool `json:"block_custom_ua"`
CustomUAPatterns []string `json:"custom_ua_patterns,omitempty"`
}
// UA check match modes.
+22 -3
View File
@@ -16,9 +16,11 @@ import (
)
const (
maxRuleGraphNodes = 128
maxRuleGraphEdges = 256
maxRuleGraphBytes = 256 * 1024
maxRuleGraphNodes = 128
maxRuleGraphEdges = 256
maxRuleGraphBytes = 256 * 1024
maxUACustomPatterns = 32
maxUACustomPatternBytes = 256
)
var (
@@ -307,6 +309,23 @@ func validateUACheckNodeConfig(node RuleNode) error {
return fmt.Errorf("节点 %s 的操作系统标签 %s 无效", node.ID, label)
}
}
if len(cfg.CustomUAPatterns) > maxUACustomPatterns {
return fmt.Errorf("节点 %s 的自定义 UA 正则不能超过 %d 条", node.ID, maxUACustomPatterns)
}
for _, pattern := range cfg.CustomUAPatterns {
if strings.TrimSpace(pattern) == "" {
return fmt.Errorf("节点 %s 的自定义 UA 正则不能为空", node.ID)
}
if len(pattern) > maxUACustomPatternBytes {
return fmt.Errorf("节点 %s 的自定义 UA 正则不能超过 %d 字节", node.ID, maxUACustomPatternBytes)
}
if _, err := regexp.Compile(pattern); err != nil {
return fmt.Errorf("节点 %s 的自定义 UA 正则无效: %s", node.ID, pattern)
}
}
if cfg.BlockCustomUA && len(cfg.CustomUAPatterns) == 0 {
return fmt.Errorf("节点 %s 开启屏蔽自定义 UA 时至少需要一条正则", node.ID)
}
return nil
}
@@ -85,6 +85,14 @@ func TestValidateRuleGraph(t *testing.T) {
g.Nodes[1].Type = RuleNodeUACheck
g.Nodes[1].Config = rawConfig(`{"match_mode":"xor"}`)
}, "节点 match-1 的匹配模式必须为 and 或 or"},
{"invalid ua custom regex", func(g *RuleGraph) {
g.Nodes[1].Type = RuleNodeUACheck
g.Nodes[1].Config = rawConfig(`{"block_custom_ua":true,"custom_ua_patterns":["("]}`)
}, "节点 match-1 的自定义 UA 正则无效"},
{"custom ua requires patterns", func(g *RuleGraph) {
g.Nodes[1].Type = RuleNodeUACheck
g.Nodes[1].Config = rawConfig(`{"block_custom_ua":true}`)
}, "节点 match-1 开启屏蔽自定义 UA 时至少需要一条正则"},
{"unknown config field", func(g *RuleGraph) { g.Nodes[1].Config = rawConfig(`{"ips":[],"surprise":true}`) }, "节点 match-1 的配置无效"},
{"null config", func(g *RuleGraph) { g.Nodes[1].Config = rawConfig(`null`) }, "节点 match-1 的配置无效"},
{"too many nodes", func(g *RuleGraph) {