3个版本 (重大更新)

0.4.1 2021年12月30日
0.4.0 2021年12月30日
0.3.0 2021年12月27日
0.2.0 2021年12月25日
0.1.4 2021年12月23日

#923HTTP服务器

每月下载量 26次

MIT许可证

25KB
419

RuES - 表达式评估作为服务

RuES是一个使用JMESPath的最小表达式评估side-car,它可以处理任意JSON。这实际上使它成为一个通用逻辑表达式评估引擎,就像一些用于评估逻辑表达式的Python库。这反过来又允许您实现复杂的规则引擎、RBAC或策略引擎等。

以下是RuES的独特之处

  • 轻量级且快速 - 检查以下初始基准。在单个CPU下,只需20MB即可轻松做到10K RPS。
  • 无需重启 - 通过在20 MB中进行更改,无需重启即可动态添加/删除规则。
  • HTTP & JSON - 通用!无需自定义协议,无需麻烦。
  • UNIX哲学 - 只评估规则,没有花哨的钩子或集成。非常简单!

为什么?

可能一个很明显的问题就是,为什么选择RuES而不是直接使用库?在以下场景中,RuES可以带来以下好处,使其受益:

  • 统一且一致的规则 - 无需处理库之间的差异,特别是在多语言堆栈中,您不必担心任何不一致性、性能问题或库维护。
  • 隔离且可扩展 - 虽然嵌入式库可能有更广泛的攻击面,但隔离的过程为您提供了沙箱,由于它是轻量级的,因此作为side-car为您提供亚毫秒级延迟。这不仅允许开发人员将安全责任转交给正确的团队,而且还允许您与系统一起进行扩展。
  • 集中管理 - 允许您拥有集中管理的部署和规则。更改规则甚至不需要新的部署。规则是实时重新加载的。这意味着您可以在0停机时间内动态添加/修改规则。

使用方法

在启动 rules 时,请确保您的当前工作目录中存在 rules.hjson。以下是一个示例规则:

{
  example_one: "value == `2`"
  example_two: "a.b"
}

每个规则都通过 /eval/{rule_name} 作为 POST 端点公开,您可以将有效负载发布到该端点以评估表达式。简单使用 curl 进行测试。

> curl -X POST http://localhost:8080/eval/example_one -H 'Content-Type: application/json' -d '{"value": 2}'
{"Success":{"expression":"value == `2`","name":"example_one","is_truthy":true,"value":true}}
> curl -X POST http://localhost:8080/eval/example_two -H 'Content-Type: application/json' -d '{"a": {"b": "Hello"}}'
{"Success":{"expression":"a.b","name":"example_two","is_truthy":true,"value":"Hello"}}

如果评估成功,响应对象将包含 Success。例如:

{
   "Success": {
      "name": "filter_active",
      "expression": "[?isActive] | length(@)",
      "is_truthy": true,
      "value": 2
   }
}

如果表达式中有错误或在评估表达式时存在某些违规行为(在这种情况下,reason 将包含一个原因),则响应将包含一个 Error

{
   "Error": {
      "name": "filter_registered",
      "expression": "[?matched('^201\\d', registered)] | length(@)",
      "reason": "Runtime error: Call to undefined function matched (line 0, column 9)\n[?matched('^201\\d', registered)] | length(@)\n         ^\n"
   }
}

如果未找到指定的规则,则响应将包含一个 NotFound

{
   "NotFound": {
      "name": "filter_register"
   }
}

批量规则 API

许多时候您需要针对有效负载评估一组规则。RuES 支持使用批量 API 评估上下文与多个规则。给定以下规则文件:

{
  example_one: "c == `2`"
  example_two: "a.b"
}

可以通过简单调用 /eval 并使用 POST 数据来调用批量 API。

{
   "context": {
      "c": 3,
      "a": {
         "b": true
      } 
   },
   "rules": ["example_one", "example_two", "example_three"]
}

规则将按它们被传递的顺序顺序评估,服务器将返回一个数组响应。

[
   {"Success":{"expression":"c == `2`","name":"example_one","is_truthy":false,"value":false}},
   {"Success":{"expression":"a.b","name":"example_two","is_truthy":true,"value":true}},
   {"NotFound":{"name":"example_three"}}
]

附加功能

除了 JMES 的内置函数之外,还有以下附加功能

  • string[] match(expref string $regex, string $element) - 返回所有正则表达式匹配的组数组或如果没有匹配则返回一个 null。正则表达式规范可以在 这里 找到。正则表达式按 LRU 顺序编译和缓存。给定的正则表达式必须是字符串字面量表达式,例如 &'\d+' 这是为了确保正则表达式始终是字符串字面量,而不是变量,从而消除通过变量进行正则表达式注入的可能性,防止任何漏洞或正则表达式模式的意外爆炸。示例
    [?match(&'^[a-z0-9_-]{3,16}$', username)]
    [?match(&'^[a-z0-9_-]{3,16}$', 'user_123')]
    [?match(&'([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))', date)]
    
  • bool valid_email(string $element) (尚未实现) - 根据电子邮件格式返回 truefalse。除了格式之外,它还排除了临时电子邮件地址。示例
    [?valid_email('user123@gmail.com')]
    [?!valid_email('janette@guerrillamailblock.com')]
    [?valid_email(contact.email)]
    
  • 🚧 number parse_datetime(string $element, string $format = 'rfc3339') (尚未实现) - 将给定格式的日期时间转换为时间戳。然后可以使用该时间戳进行比较或重新格式化。
  • 🚧 string to_datetime(number $element, , string $format = 'rfc3339') (尚未实现) - 将时间戳转换为指定的字符串格式。
  • 🚧 bool in_geo_fence(number[] $center, number $radius, number[] $element) (尚未实现)- 如果 $element 位于 $center$radius 范围内,则返回 truefalse
  • 🚧 number[][] filter_in_geo_fence(number[] $center, number $radius, number[][] $elements) (尚未实现)- 返回所有位于给定半径和中心地理围栏内的元素。
  • 🚧 bool match_glob(string $pattern, string $element) (尚未实现)- 如果 $element$pattern 的 glob 匹配,则返回 truefalse

配置变量

  • CONFIG_PATH - 规则文件路径,文件可以是 .json.yaml.hjson。默认:rules.hjson
  • BIND_ADDRESS - 绑定的服务地址。默认:0.0.0.0:8080

基准测试

我的简要压力测试表明,在一个 CPU 核心下(单个工作进程),3 条规则和 1.6 KB 的有效负载大小。服务器轻松处理了 10K RPS(即使在高负载下),RSS 内存占用仅为 19 MB,p99 为 4ms。

$ cat vegeta_attack.txt | vegeta attack -duration=10s -rate=10000 | vegeta report 
Requests      [total, rate, throughput]         100000, 10000.20, 9999.80
Duration      [total, attack, wait]             10s, 10s, 394.927µs
Latencies     [min, mean, 50, 90, 95, 99, max]  107.266µs, 811.954µs, 285.329µs, 2.128ms, 2.654ms, 4.517ms, 12.373ms
Bytes In      [total, mean]                     9566673, 95.67
Bytes Out     [total, mean]                     166000000, 1660.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:100000  
Error Set:

在两个 CPU 核心下(两个工作进程),结果甚至更好

$ cat vegeta_attack.txt | vegeta attack -duration=10s -rate=10000 | vegeta report
Requests      [total, rate, throughput]         100000, 10000.30, 10000.08
Duration      [total, attack, wait]             10s, 10s, 217.653µs
Latencies     [min, mean, 50, 90, 95, 99, max]  111.479µs, 270.125µs, 219.274µs, 413.215µs, 564.181µs, 1.021ms, 8.184ms
Bytes In      [total, mean]                     9566673, 95.67
Bytes Out     [total, mean]                     166000000, 1660.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:100000  
Error Set:

所有规则和数据都已打包在 stress_test 下。欢迎分享您的结果,我将非常乐意将其包含在内。

依赖项

~27–38MB
~669K SLoC