← 返回目录

第 2 卷

第 2 / 5 卷日期:

引擎有黑名单和白名单两种模式,通过 setMode 切换。黑名单模式默认放行,白名单模式默认拒绝。在两种模式之上,deny 和 allow 怎么合成最终结论,有三条规则:

deny 优先于 allow。无论黑名单还是白名单模式,一条请求同时命中 deny 和 allow 时,结果都是拒绝。为什么 deny 必须压过 allow?因为 deny 通常是“窄而强”的拦截,

allow 通常是“宽而弱”的放行:白名单常配一条 allow path starts_with /api 放行全部接口,再配一条 deny path contains /api/admin 拦掉敏感子路径,若

allow 优先,/api/admin 会被放行,那条 deny 失效。

allow 只在白名单模式生效。黑名单模式下 allow 被忽略;白名单模式下,命中 allow 且没命中 deny 才放行。

顺序决定的是 first hit,也就是第一条命中的规则。多条 deny 命中时取第一条命中的 deny,白名单模式下多条 allow 命中时取第一条命中的 allow,first hit 的 ruleName

就是返回的规则名。调换顺序,first hit 变了,返回的规则名跟着变。

unless 也需要说清楚,它的语义是:命中 when 且命中 unless 时,这条规则不触发,把 “例外”直接写进那条 deny 规则本身,而不是另写 allow 去抵消,因为 deny 优先。上面

那条 block admin scan 就带了 unless: { path eq /admin/health },所以 /admin/users 被拦、/admin/health 放行。

引擎怎样把规则变成可靠、快速、可解释的判定

下面我们来看解决这个问题的关键机制。超长请求在入口就拦,不进引擎。即使匹配本身很快,一个几 MB 的请求体仍要“看一遍”,这个代价本身就不划算。所以 isOverlong 在交给

引擎前,先拒绝 path / body / ua 三处长度超过 8192 的字段。

注意:这道边界只挡超大载荷,挡不住正常长度的攻击,短小但恶意的请求照样进匹配路径,靠规则来拦。

些统一还原,包括解码百分号、规整 // 和 ..、统一 Unicode 写法,让“看起来不同,其实是同一个”的请求现出原形。

正则用 RE2,匹配不会被卡死。规则里唯一走正则的是 matches 算子。普通正则有个致命问题:精心构造的输入能让它“回溯爆炸”,把 CPU 卡死,也就是 ReDoS 攻击。这一讲我们

用 re2-matcher,RE2 是 Google 出的正则引擎,用线性时间算法、不做回溯,所以再恶意的输入也卡不死它。

规则先编译好,请求时直接执行。compileRule 在加载阶段就把整条规则,连同 matches 用的正则一起,编译成可直接执行的形式,并按规则 id 缓存。比如这条叶子条件,来自

rules.json 里的 when:

复制代码 1{ "field": "path", "op": "starts_with", "value": "/admin" }

编译后得到这样一个闭包,来自 rules.ts 的 compileCondition 叶子分支。真实代码用的是变量,这里代入了具体值:

复制代码 1{ test: (r) => evalLeaf('path', 'starts_with', '/admin', r) }

其中 evalLeaf 对 starts_with 的求值就是 r.path.toLowerCase().startsWith(/admin),value 已在编译期小写化,

matches 的正则也已在编译期预编译。请求到来时,evaluate 直接匹配,不再重新编译,既省下每次请求重新编译的开销,也把坏规则挡在加载期,编不过去就直接拒绝,不会留到请

求时才出错。

截或放行,这让每次判定都可解释,而不是一个黑盒。

引擎怎样接入 dsh:seam 拆能力,插件装配

规则引擎接入 dsh,就是把它做成 dsh 里的能力,本讲主角是 firewall。分两步:先按第 5 讲介绍的能力 seam 方法,把 firewall 能力拆成 Definition / Provider / Consumer 三

角色;再用 ctx.plugin() 把角色对应的插件挂上树。

第一步:seam 拆能力。把 firewall 拆成三角色:

角色职责本讲对应
Service Definition定契约:这个能力长什么样Firewall(firewall.ts里的抽象类)
Service Provider给实现:真正干活,可替换FirewallEngine(firewall.ts里的实现类

ingress-authz(拦请求的入口)、rules Consumer用能力:只认契约,不认实现来源)、tool-firewall(只读工具)

上面表格里的三角色,落到 cookbook 代码里是这样,这里略去 import 和实现细节:

复制代码 1// Service Definition(firewall.ts)——契约

2exportabstractclassFirewallextendsService{
3constructor(ctx:Context) {
4super(ctx,'firewall')
5}
6abstractevaluate(request:HttpRequest):Decision|null
7abstractlistRules():Rule[]
8abstractaddRule(input:RuleInput,state?:RuleState):string
9// ……还有removeRule / setMode / getMode / loadRules
10}
12// Service Provider(firewall.ts)——实现
全站分卷索引 · 5 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷
  5. 5第 5 卷