RuES - Expression Evaluation as Service

Related tags

Miscellaneous rues
Overview

RuES - Expression Evaluation as Service

RuES is a minimal JMES expression evaluation side-car, that uses JMESPath, and it can handle arbitrary JSON. Which effectively makes it general purpose logical expression evaluation engine, just like some Python libraries that used to evaluate logical expression. This in turn can allow you implement complex stuff like Rule engine, RBAC, or Policy engines etc.

Here is what makes RuES special:

  • Lean and Zippy - Checkout initial benchmarks below. Under 20 MB with single CPU one will easily do 10K RPS.
  • Zero restarts - Add/remove rules on fly by making changes in rules.hjson without restarting.
  • HTTP & JSON - Ubiquitous! No custom protocols, no shenanigans.
  • UNIX philosophy - Only evaluates rules, no fancy hooks or integrations. Dead simple!

Why?

A very obvious question to ask might be, why RuES and why not just use a library? RuES can be beneficial in large scale scenarios with following benefits:

  • Unified and consistent rules - No need to deal with library differences, specially in a polyglot stack you won't have to worry about any inconsistencies, performance issues, or library maintenances.
  • Isolated and scalable - While embedded libraries can have a broader attack surface the isolated process gives you sandbox, and due to being lightweight have it as a sidecar giving you sub-millisecond latencies. This not only allows developers to hand off security to the right team, but also allows you to scale with your system.
  • Centrally managed - Allowing you to have centrally managed deployments, and rules. Changing rules doesn't even require a new deployment. The rules are live reloaded. That means with 0 downtime you can add/modify rules on the fly.

Usage

Make sure you have rules.hjson in your current working directory when launching rues. Given following example rules:

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

Each rule is exposed under /eval/{rule_name} as POST endpoint, which in turn can be posted payload to evaluate the expression. Simple use curl to test:

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"}}">
> 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"}}

Response object contains Success if evaluation was successful. e.g.

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

Response will have an Error if there was an error in expression or there was some violation while evaluating the expression (in which case reason will contain a reason):

{
   "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"
   }
}

Response will have a NotFound if the specified rule is not found:

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

Batch Rules API

Many times you need evaluate a set of rules against a payload. RuES supports evaluating a context against multiple rules using batch API. Given the rules file:

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

One can invoke batch api by simply invoking /eval with POST data of:

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

The rules will be evaluated in sequence of order they were passed in, and server will return an array response:

[
   {"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"}}
]

Additional functions

In addition to built-in functions of JMES, there additional are following additional functions:

  • string[] match(expref string $regex, string $element) - Returns an array of all groups of regex matching or a null if there is no match. Regex specs can be found here. Regexes are compiled and cached in LRU order. The given Regex has to be an expression with string literal e.g. &'\d+' this is required so that regexes are always string literal and never variables eliminating any possibility of regex injection via variables, preventing any exploits or accidental explosion of regex patterns. Examples:
    [?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) - Returns true or false based on email format. In addition to formatting it also excludes temporary email addresses. Examples:
    [?valid_email('[email protected]')]
    [?!valid_email('[email protected]')]
    [?valid_email(contact.email)]
    
  • number now() - Returns current time unix timestamp as 64-bit float with number of seconds since 1970-01-01 the fractional part of timestamp contains upto microseconds of the timestamp. Examples:
    now()
    > 1641257813.803243
    
  • number duration(string $element) - Parses a duration string with same specs as systemd.time and returns a unix timestamp just like now. This will allow anyone to perform addition/subtraction operations on timestamps, Examples:
    duration('1min100ms')
    > 60.1
    duration('1h')
    > 360.0
    
  • 🚧 number parse_datetime(string $element[, string $format = 'rfc3339']) (To be implemented yet) - Converts datetime in given format to a timestamp. The timestamp then in turn can be used to do comparisons or reformatting.
  • 🚧 string to_datetime(number $element[, string $format = 'rfc3339']) (To be implemented yet) - Converts timestamp to a given string format.
  • 🚧 bool in_geo_fence(number[] $center, number $radius, number[] $element) (To be implemented yet) - Returns true or false if the $element lies within the $radius of $center.
  • 🚧 number[][] filter_in_geo_fence(number[] $center, number $radius, number[][] $elements) (To be implemented yet) - Returns all elements that lie within geo fence of given radius and center.
  • 🚧 bool match_glob(string $pattern, string $element) (To be implemented yet) - Returns true or false if the $element is a glob match of the $pattern.

Configuration variables

  • CONFIG_PATH - path to rules file, file can be .json, .yaml, or .hjson. Default: rules.hjson
  • BIND_ADDRESS - service address to bind to. Default: 0.0.0.0:8080

Benchmarks

My brief stress testing shows with a single CPU core (single worker), 3 rules, and payload size of 1.6 KB. Server was easily able to handle 10K RPS (even with sustained load) under 19 MB of RSS memory footprint, and a p99 of 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:

With two CPU cores (two workers), the results were even better:

$ 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:

All the rules, and data has been shipped under stress_test. Feel free to share your results, and I will be more than happy to include your results.

You might also like...
A rust library for toornament.com service

A rust library for toornament.com service

Web service generating images of Japanese (Riichi) Mahjong hands.
Web service generating images of Japanese (Riichi) Mahjong hands.

chombo-gen ChomboGen is a web service that allows to generate images of Japanese (Riichi) Mahjong hands. The hands are provided in a text format and a

Memory.lol - a tiny web service that provides historical information about social media accounts

memory.lol Overview This project is a tiny web service that provides historical information about social media accounts. It can currently be used to l

Satoru keeper service 🦀.
Satoru keeper service 🦀.

Satoru keeper service 🦀 📝 Description The keeper is an offchain service for Satoru protocol. It is responsible for: Watching the user initiated acti

🧮 Boolean expression evaluation engine. A Rust port of boolrule.

coolrule My blog post: Porting Boolrule to Rust Boolean expression evaluation engine (a port of boolrule to Rust). // Without context let expr = coolr

Wait Service is a pure rust program to test and wait on the availability of a service.

Wait Service Wait Service is a pure rust program to test and wait on the availability of a service.

Fast dense evaluation of Green's function kernels in Rust

Fast evaluation of Greens functions in Rust This library allows the fast evaluation of Greens functions and potential sums for Laplace, Helmholtz, and

Simple POC for the Micriμm STM32F107 Evaluation Board

My motivation for this project was very simple: I wanted to blink some LEDs at "some rate(tm)" using entirely rust. I also did not want to use any of the embedded-std libs that were available, because I wanted to really get a feel for bit-banging the registers (including documenting where I grabbed the info from).

Evaluation metrics for machine learning

eval-metrics Evaluation metrics for machine learning Design The goal of this library is to provide an intuitive collection of functions for computing

An evaluation context for Rust.

Evcxr An evaluation context for Rust. This project consists of several related crates. evcxr_jupyter - A Jupyter Kernel evcxr_repl - A Rust REPL evcxr

An alternative broken buggy Nix implementation in Rust + Java (for evaluation)

An alternative broken buggy Nix implementation in Rust + Java (for evaluation)

Rewrite Redis in Rust for evaluation and learning.

Drill-Redis This library has been created for the purpose of evaluating Rust functionality and performance. As such, it has not been fully tested. The

An investigation to enable ethernet with smoltcp on the SAM E70 XPLAINED ULTRA EVALUATION KIT

An investigation to enable ethernet with smoltcp on the SAM E70 XPLAINED ULTRA EVALUATION KIT

An implementation of Code Generation and Factoring for Fast Evaluation of Low-order Spherical Harmonic Products and Squares

sh_product An implementation of Code Generation and Factoring for Fast Evaluation of Low-order Spherical Harmonic Products and Squares (paper by John

Supporting code for the paper "Optimized Homomorphic Evaluation of Boolean Functions" submitted to Eurocrypt 2024

This repository contains the code related to the paper Optimized Homomorphic Evaluation of Boolean Functions. The folder search_algorithm contains the

Parsing Expression Grammar (PEG) parser generator for Rust

Parsing Expression Grammars in Rust Documentation | Release Notes rust-peg is a simple yet flexible parser generator that makes it easy to write robus

A typed parser generator embedded in Rust code for Parsing Expression Grammars

Oak Compiled on the nightly channel of Rust. Use rustup for managing compiler channels. You can download and set up the exact same version of the comp

Text Expression Runner – Readable and easy to use text expressions
Text Expression Runner – Readable and easy to use text expressions

ter - Text Expression Runner ter is a cli to run text expressions and perform basic text operations such as filtering, ignoring and replacing on the c

Extreme fast factor expression & computation library for quantitative trading in Python.

Extreme fast factor expression & computation library for quantitative trading in Python.

Releases(v0.4.1)
Owner
Zohaib Sibte Hassan
Yet another engineer
Zohaib Sibte Hassan
An investigation to enable ethernet with smoltcp on the SAM E70 XPLAINED ULTRA EVALUATION KIT

An investigation to enable ethernet with smoltcp on the SAM E70 XPLAINED ULTRA EVALUATION KIT

James Munns 2 Mar 10, 2022
An implementation of Code Generation and Factoring for Fast Evaluation of Low-order Spherical Harmonic Products and Squares

sh_product An implementation of Code Generation and Factoring for Fast Evaluation of Low-order Spherical Harmonic Products and Squares (paper by John

Simon Brown 7 Dec 2, 2022
Extreme fast factor expression & computation library for quantitative trading in Python.

Extreme fast factor expression & computation library for quantitative trading in Python.

Weiyuan Wu 22 Dec 8, 2022
lightweight and customizable rust s-expression (s-expr) parser and printer

s-expr Rust library for S-expression like parsing and printing parser keeps track of spans, and representation (e.g. number base) number and decimal d

Vincent Hanquez 5 Oct 26, 2022
LaaS: Life as a Service

LaaS: Life as a Service $ curl life-as-a-service.herokuapp.com/-1x0~0x0~1x0 0x-1~0x0~0x1 let previous = '0x-1~0x0~0x1' for (let i = 0; i < 5; i++) {

Brandon Smith 5 Nov 1, 2021
A discord bot that safely executes whatever rust you throw at it. Remote code execution as a service

RustBot Bot is still under development and not ready for production use RustBot is a discord bot that executes whatever rust code you throw at it. In

Conner Bradley 7 Jan 3, 2022
Web service for Firefox Suggest

Merino A service to provide address bar suggestions to Firefox. For more details, see the service docs. About the Name This project drives an importan

Mozilla Services 27 Sep 23, 2022
Runit service management wrappers

void-svtools Basic wrappers for managing services for runit,

Isaac Hung 1 Aug 3, 2022
A Rust client for the NOAA Weather Wire Service Open Interface.

nwws-oi A Rust client for the NOAA Weather Wire Service Open Interface. NWWS-OI is one of several platforms through which the National Weather Service

Will Glynn 3 Sep 15, 2022
Matrix Bot for Bitcoin Push Notification Service

BPNS Matrix Bot Description Matrix Bot for Bitcoin Push Notification Service. Requirements Rust (1.57.0+) BPNS Server Matrix account Build cargo build

BPNS 0 Mar 14, 2022