Compare commits

...

59 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
209a534b7c Initial plan 2025-11-25 00:43:19 +00:00
dependabot[bot]
ed360879a6 build(deps): bump prettydiff from 0.8.1 to 0.9.0 (#502)
Bumps [prettydiff](https://github.com/romankoblov/prettydiff) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/romankoblov/prettydiff/releases)
- [Changelog](https://github.com/oli-obk/prettydiff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/romankoblov/prettydiff/commits/0.9.0)

---
updated-dependencies:
- dependency-name: prettydiff
  dependency-version: 0.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 15:02:35 -06:00
dependabot[bot]
4988bda647 build(deps): bump indexmap from 2.12.0 to 2.12.1 (#503)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.12.0 to 2.12.1.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.12.0...2.12.1)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 12:16:12 -06:00
dependabot[bot]
92b9ec8fa8 build(deps): bump clap from 4.5.52 to 4.5.53 (#505)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.52 to 4.5.53.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.52...clap_complete-v4.5.53)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 12:15:17 -06:00
Anand Krishnamoorthi
a3a20a1235 feat!: Rego -> RVM Compiler and extensive testsuite (#506)
# RVM compiler test cases

Coverage:
- arithmetic
- arrays
- chained lookups
- comparisons
- comprehensions
- default rules
- destructuring
- function rules
- loops/quantifiers
- multiple entrypoints
- objects/sets
- variables
- negative/edge scenarios such as data/rule conflicts
- virtual data lookups
- etc

 # Modify interpreter and compiled policy for RVM Compilation

- Interpreter::eval_default_rule_for_compiler:
   evaluates a named default rule in isolation - allows compiler to emit a constant value instead of instructions
   for the default value

#  feat: Rego Compiler Scaffolding

- Introduce the rego::compiler module surface and entry point wiring
- Add the core compiler concepts:
  - register allocator
  - scope tracking
  - literal/builtin tables
  - rule worklists
  - instruction emit helpers
  - compiler-specific error types
  - context structs for rules, comprehensions, and loops to support later lowering passes.

# feat: Compile Rules/Queries

- add compiler::compile_from_policy workflow plus rule worklist, entry-point wiring, and recursion checks
- implement query lowering:
  - scheduling-aware statement ordering
  - loop hoisting
  - “every/some” semantics
  - context yields
  -  literal assertions
- finalize Program construction

# feat: Expression Lowering

- add compile_rego_expr and helpers to translate every AST expression into RVM instructions,
- interop with binding plans, comprehensions, and membership checks.
- implement collection literal builders (ArrayCreate, SetCreate, ObjectCreate)
  - dedupe literal keys and handle mixed literal/dynamic fields via instruction data blocks.
- operations:
  - arithmetic/boolean/bin operators
  - membership
  - unary minus
  - set unions/intersections
  - etc
- user-defined and builtin function calls
- reference handling
  - analyse chained refs
  - distinguishe data/input/local roots
  - perform rule dispatch or virtual document lookups
  - emits optimized Index/ChainedIndex instructions.

# feat: Comprehensions & Loops

- shared comprehension emitter
 - wraps array/set/object comprehensions with ComprehensionBegin/End
 - context management
- loop lowering utilities
 - read hoisting metadata
 - emit LoopStart/LoopNext
 - some in lowering
 - every quantifiers
 - index iteration
 - propagate binding plans into stored registers so downstream statements see bound variables.

# feat: Destructuring Lowering

- destructuring planner integration
 - assignment/parameter/loop bindings use hoisted plans instead of re-walking ASTs.
- handle :=, =, wildcard matches, and equality
 - evaluate RHS
 - applying destructuring plans
 - emit assert condition as needed
- support nested array/object destructuring, dynamic keys, and some ... in forms

# test: Shared Testing + RVM Suites

- move YAML test helpers into test_utils.rs and re-export via common.rs for use by interpreter and vm test suites
- comprehensive compiler test suite
  - compiles policies with the new Rego→RVM compiler
  - runs them through RegoVM
  - compares against interpreter behavior
  - supports multiple entry points
  - provides assembly listings
  - filterable YAML suites.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-24 12:08:37 -06:00
Anand Krishnamoorthi
688e6128d4 feat: Detect incorrect multi-threaded use from c based ffi (#499)
Add runtime detection for shared handle misuse

wrap the FFI engine handle with parking_lot::RwLock when the new
contention_checks feature is enabled, surfacing a clear “handle is already
in use” error instead of allowing undefined behavior
keep the feature optional so no_std builds or environments that supply
their own synchronization can opt out
caution users that this guards the handle itself but does not make the
engine’s operations globally thread-safe on its own

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-17 14:21:13 -06:00
Anand Krishnamoorthi
ad8c543fb5 feat: Add Azure RBAC condition parser (#496)
* feat: Add Azure RBAC condition parser

- declare an `azure-rbac` feature and expose the Azure RBAC module with parser, AST, and YAML-driven tests
- extend the shared lexer with RBAC-specific tokens, single-quoted strings, and corrected raw-string spans
- verify the parser via comprehensive test cases covering every operator and complex chaining

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>



---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-17 14:15:35 -06:00
Anand Krishnamoorthi
49bd3c22f3 feat!: add Rego Virtual Machine (RVM) implementation (#495)
* feat!: add Rego Virtual Machine (RVM) implementation

This commit introduces a register-based virtual machine for executing Rego
policies with bytecode-style instructions. Unlike the existing tree-walking
interpreter, the RVM compiles policies into instruction sequences that operate
on virtual registers, offering better performance and optimization potential.

Core Components:

Instruction Set Architecture:
- Define instruction types for data operations, control flow, and builtins
- Implement instruction parameter encoding and display formatting
- Add instruction parser with comprehensive test coverage

Virtual Machine Engine:
- Register-based execution model with program counter management
- Loop execution supporting iterators, comprehensions, and quantifiers
- Function call handling with argument evaluation and context management
- Rule evaluation with default value resolution and virtual data support
- Arithmetic and comparison operation implementations

Program Representation:
- Program listing builder with instruction sequencing
- Rule tree construction for organizing policy rules
- Binary and JSON serialization for compiled programs
- Recompilation support for program modification

Testing Infrastructure:
- Extensive YAML test suites covering all VM features
- Rust unit tests for VM execution and instruction parsing
- Test suites for loops, comprehensions, builtins, and control flow

BREAKING CHANGE: Introduces new VM execution path alongside interpreter

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* docs: add detailed RVM architecture references

Introduce architecture.md explaining program artifacts, serialization, and runtime subsystems.
Document the full opcode catalog in instruction-set.md, including operands, parameter tables, and outcomes.
Walk through execution flow, stacks, and operational guidance in vm-runtime.md, tying the runtime to the new architecture docs.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-14 11:43:19 -06:00
Anand Krishnamoorthi
6dc505c88b build: Add xtask automation for binding version management (#491)
* build: Add xtask automation for binding version management

Introduces a dedicated xtask crate that keeps language binding versions
in sync with the core regorus crate, following the workflow pattern used
by rust-analyzer, gitoxide, and ripgrep.

Key features:
- Git-based change detection: compares binding source files against a
  base ref (merge-base with origin/main by default) plus unstaged/
  untracked files to identify which bindings have been modified
- SemVer-aware bumping: binding edits trigger a minor version increment
  (e.g. 0.5.1 → 0.6.0) under pre-1.0 semantics, signaling potential
  breaking changes; clean bindings simply align to the root version
- Multi-language support: updates Cargo manifests (Rust FFI, Java,
  Python, WASM, Ruby), Maven pom.xml (Java), Ruby version constants,
  and C# project files in a single pass
- CI integration: --check mode fails fast when manifests are out of
  sync, ensuring pre-commit and release-plz workflows catch stale
  versions before merge

Integration points:
- release-plz.toml: runs cargo xtask bindings --base-ref origin/main
  after bumping the root crate, so binding versions are updated
  atomically during the release process
- scripts/pre-commit: invokes cargo xtask bindings --check to block
  commits that would leave bindings out of sync
- .cargo/config.toml: defines cargo xtask alias for convenience

Documentation includes inline examples showing how version bumps behave
when bindings are ahead/behind the root, and notes that the minor
field acts as the major version under SemVer 0.y.z initial development
phase.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* build: refresh xtask tooling, workflows, and locks

- cargo xtask bindings: keep the binding version-sync pipeline intact
- cargo xtask update-deps: new helper to regenerate workspace/binding Cargo.lock files
- workflows: auto-detect the Java jar version in CI and temporarily disable the Ruby workflow
- lock files: refresh root + binding snapshots after the dependency sweep

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-28 16:10:54 -05:00
Anand Krishnamoorthi
091bbb2e5c feat: Optimize C# binding interop (#488)
- Introduce Utf8Marshaller helpers and SafeHandle wrappers so the managed API centralizes UTF-8 conversions and lifetime management for native pointers.
- Update Engine, Compiler, CompiledPolicy, SchemaRegistry, and TargetRegistry to rely on the new marshaller/safe handles, tightening disposal and reducing transient allocations during interop calls.
- Add allocation guard coverage in Regorus.Tests and report bytes/op in the compiled policy benchmark to surface future regressions.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-27 12:35:40 -05:00
Anand Krishnamoorthi
1e4ff952e6 feat!: Introduce structured destructuring plans for bindings (#485)
- add a dedicated `compiler/destructuring_planner` feature that precomputes binding plans for assignments, parameters, and `some in` expressions
- enrich `ScopeContext` with same-scope tracking, local scheduling hints, and module globals so the planner enforces := shadowing rules without blocking parent scopes
- wire the planner through compiler, hoist, interpreter, and engine paths while updating binding plan variants and adding query traversal helpers for dependency analysis
- document the new planner architecture and ship interpreter regressions that exercise nested destructuring, shadowing, and error reporting

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-21 15:57:49 -05:00
dependabot[bot]
25a7ddad0a build(deps): bump clap from 4.5.45 to 4.5.49 (#487)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.45 to 4.5.49.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.45...clap_complete-v4.5.49)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.49
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 09:26:05 -05:00
Anand Krishnamoorthi
5d8387f4d9 feat(hoist): pre-compute loop hoisting metadata at compilation time (#483)
Introduce a compiler pass that analyzes and pre-computes loop hoisting information
during policy compilation. This hoisted metadata is stored in lookup tables and made
available to downstream consumers:

- interpreter: use HoistedLoop entries during evaluation (replaces runtime scanning)
- type inference: can leverage pre-computed loop structure for type propagation
- RVM compiler: will consume hoisting metadata for optimized bytecode generation

Changes:
- populate loop hoisting tables during engine preparation and query snippet execution
- refactor eval_stmts_in_loop and eval_output_expr_in_loop to consume HoistedLoop directly
- add helper methods for accessing loop expressions, collections, and indices from HoistedLoop
- extend Lookup with get_checked and into_slots for safe query context access and merging

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-08 11:11:30 -05:00
Denis Komissarov
9604fe86f1 Bump the version of the C# bindings (#482) 2025-09-30 15:31:46 -05:00
Anand Krishnamoorthi
ac388684bc fix: CodeQL reported printf format specifier issues (#480)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-09-25 18:45:20 -05:00
Anand Krishnamoorthi
57f2e7703c ci: Add CodeQl workflow (#478)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-09-22 18:01:19 -05:00
Kirill Zabelin
4ec9e76440 Set input in with_document too when reuse engine (#474) 2025-09-08 07:37:46 -05:00
Tyler Schade
1b0c2d4072 feat: Implement net.cidr_contains builtin (#471)
Major changes:
- Implement the `net.cidr_contains` builtin
- Enable the v0 and v1 test for `net.cidr_contains`
- Add the `netip` crate to standardize CIDR searching and other
  operations

Key Concept:
- Allow users to leverage the `net.cidr_contains` builtin to check
  whether an IPv4 or IPv6 CIDR contains a specified IP address or
  subnet.

Testing:
- All tests passing.

Signed-off-by: tjons <tylerschade99@gmail.com>
2025-09-05 15:21:41 -05:00
Anand Krishnamoorthi
85753aaf37 feat: Implement efficient node lookup table using node indices (#463)
Major Changes:
- Add generic Lookup<T> structure for efficient O(1) module-level data access
- Combine separate scope and order lookups into unified QuerySchedule structure
- Add query_schedule field to Interpreter for dedicated user query scheduling
- Refactor loop hoising to separate module
- Use efficient lookup for loop vars
- Also added more tests for loops

Key Concept:
- Ensure module context and indexing stay synchronized during function calls

Testing:
- All scheduler and interpreter tests passing

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-26 15:01:45 -05:00
Anand Krishnamoorthi
c43c94559a feat: modernize GitHub Actions with security hardening and centralized Rust toolchain (#470)
Security Improvements:
- Pin all GitHub Actions to specific commit hashes instead of version tags
- Update actions/checkout from v4 to commit 08eba0b27e820071cde6df949e0beb9ba4906955
- Update actions/setup-python from v5 to commit a26af69be951a213d495a4c3e4e4022e16d87065 (v5.6.0)
- Update actions/setup-java from v4 to commit dded0888837ed1f317902acf8a20df0ad188d165 (v5.0.0)
- Update actions/setup-node from v4 to commit 1e60f620b9541d16bece96c5465dc8ee9832be0b (v4.4.0)
- Update actions/setup-go from v5 to commit 41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed (v5.1.0)
- Update actions/setup-dotnet from v4 to commit 3e891b0cb619bf60e2c25674b222b8940e2c1c25 (v4.1.0)
- Update actions/upload-artifact from v4 to commit ea165f8d65b6e75b540449e92b4886f43607fa02 (v4.6.2)
- Update actions/download-artifact from v4 to commit 634f93cb2916e3fdff6788551b99b062d0335ce0 (v5.0.0)
- Update github/codeql-action from v3 to commit 01fe2e8c43536ad5e1085bad5e7cd6fbc8a30988 (v3.29.11)

Rust Toolchain Consolidation:
- Create custom composite action .github/actions/toolchains/rust/action.yml
- Standardize on Rust 1.89.0 (latest stable) with clippy and rustfmt components
- Add optional targets parameter for cross-compilation support
- Replace dtolnay/rust-toolchain@stable across 16 workflows

This creates a more secure, maintainable, and consistent CI/CD pipeline
with centralized Rust toolchain management across all workflows.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-26 12:42:02 -05:00
Anand Krishnamoorthi
2a0b4ae6b5 feat! Mimalloc as the default allocator (#434)
This change integrates mimalloc as the default memory allocator for Regorus,
delivering significant performance improvements across all evaluation modes
and language bindings.

Technical Implementation:
- Build mimalloc in vendored mode from C sources (following QSharp approach)
- Implement GlobalAlloc trait for seamless Rust integration
- Add optional 'mimalloc' feature flag for conditional compilation
- Add comprehensive ACI benchmarks to measure evaluation performance

Performance Impact:

Rust Engine Evaluation:
- Single-threaded: ~29% improvement (423 vs 328 Kelem/s)
- Multi-threaded: Better scaling with reduced thread contention
- Fresh engines: ~24% improvement (56 vs 45 Kelem/s)

Rust Compiled Policy Evaluation:
- Single-threaded: ~41% improvement (426 vs 303 Kelem/s)
- Multi-threaded: Improved allocation efficiency under contention
- Fresh compilation: ~26% improvement (53 vs 42 Kelem/s)

C# FFI Bindings:
- Engine evaluation: ~27% improvement (279 vs 219 Kelem/s)
- Compiled policies: ~29% improvement (273 vs 211 Kelem/s)
- Better threading characteristics through improved underlying allocation

Key Benefits:
- Reduced allocation-related contention in multi-threaded scenarios
- More consistent performance across different thread counts
- Improved memory allocation efficiency for both native Rust and FFI workloads
- Better scaling characteristics for production deployments

The mimalloc integration provides substantial performance gains while
maintaining full compatibility with existing code through feature flags.

Reference: QSharp allocator implementation
(https://github.com/microsoft/qsharp/tree/main/source/allocator)

Fixes #297

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-25 15:01:38 -05:00
Burak Varlı
6c5338228b Update release-plz GitHub workflow (#466)
- Disable publishing for `ensure_no_std` test crate
- Add write content permissions to release-plz job expliclity

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-08-22 12:04:12 -05:00
Anand Krishnamoorthi
d561531613 feat: add multi-threaded evaluation benchmark suite with comprehensive C# implementation (#457)
This commit introduces a complete multi-threaded evaluation benchmark suite for both Rust and C# implementations of Regorus.

- Implemented engine evaluation benchmark with input and engine cloning strategies
- Implemented compiled policy evaluation benchmark with input cloning and shared compiled policy strategies.

- Created EngineEvaluationBenchmark.cs and CompiledPolicyEvaluationBenchmark.cs with time-based execution (3s warmup + 3s evaluation)
- Implemented configuration options matching Rust implementation (useClonedEngines, useSharedPolicies parameters)

- Created markdown analysis documentation with cross-platform performance analysis
- C# seems to achieve 58-89% of Rust performance on test machine.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-22 11:40:39 -05:00
Burak Varlı
a53c7c8192 fix: Use license instead of license-file in Cargo.toml (#464)
Since Regorus is MIT-licensed, it would be better to specify `license = "MIT"` rather than using `licene-file` to point to the MIT license file.
Some tools do not support parsing of `license-file`, for example crates.io classifies Regorus' license as "non-standard":

     $ curl -s https://crates.io/api/v1/crates/regorus/0.4.0 | jq .version.license
     "non-standard"

Using `license` would provide better compatability with various tools.

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-08-22 09:29:28 -05:00
dependabot[bot]
5c71debcb9 Bump the nuget group with 1 update (#462)
Bumps System.Text.Json from 8.0.0 to 8.0.5

---
updated-dependencies:
- dependency-name: System.Text.Json
  dependency-version: 8.0.5
  dependency-type: direct:production
  dependency-group: nuget
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 05:54:13 -05:00
Anand Krishnamoorthi
cc917ea75d feat: Complete target system with C# bindings and resource inference (#458)
* feat: Add Schema Registry and Validation Framework

This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects.

- Thread-safe, in-memory registry for schema storage and management
- Global registry patterns for effects and resources
- Concurrent access with proper error handling
- Unicode schema names support

- JSON Schema-compliant validation for all primitive types
- Advanced constraint validation (patterns, ranges, length limits)
- Discriminated union support with anyOf schemas
- Detailed error reporting with nested validation paths
- Discriminated subobject validation for polymorphic schemas

- **Registry Tests**: All registry operations
- **Effect Tests**: Policy effect validation
- **Resource Tests**: Resource validation
- **Validation Tests**: Core validation engine
- Thread-safety, error handling, integration scenarios, edge cases

- **Dependencies**: dashmap, once_cell, regex
- **Thread Safety**: Minimal locking with Rc<Schema> sharing
- **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc.

- Complete schema registry and validation subsystem
- Comprehensive test coverage
- Foundation for policy validation in Regorus

Benchmarks:

- Criterion benchmarks for basic types, effects and Azure resources
- Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation)
- String withs patterns validation: 30.2µs. Need to explore whether regex caching helps
  bring this down.
- Azure policy effects: 188ns-1.4µs

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* feat: Complete target system with C# bindings and resource inference

- Add comprehensive target system with TargetRegistry and target-aware compilation
- Implement resource type inference from policy equality expressions
- Create modular C# bindings with separate wrapper classes for each concept
- Add thread-safe CompiledPolicy with reference counting for safe disposal
- Enhance FFI with detailed error propagation and target functionality
- Create TargetExampleApp demonstrating Azure Policy integration
- Add CI/CD pipeline testing for all C# applications
- Support target definitions with schema validation and resource selectors
- Implement PolicyModule struct and target-aware compilation methods
- Add comprehensive test coverage for target functionality

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-19 20:23:43 -05:00
dependabot[bot]
3c33d31d08 build(deps): bump clap from 4.5.43 to 4.5.45 (#459)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.43 to 4.5.45.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.43...clap_complete-v4.5.45)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 06:40:36 -05:00
dependabot[bot]
965daa0a46 build(deps): bump dashmap from 5.5.3 to 6.1.0 (#460)
Bumps [dashmap](https://github.com/xacrimon/dashmap) from 5.5.3 to 6.1.0.
- [Release notes](https://github.com/xacrimon/dashmap/releases)
- [Commits](https://github.com/xacrimon/dashmap/compare/v.5.5.3...v6.1.0)

---
updated-dependencies:
- dependency-name: dashmap
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 05:03:37 -05:00
Anand Krishnamoorthi
db718654b5 feat: Add Schema Registry and Validation Framework (#456)
* feat: Add Schema Registry and Validation Framework

This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects.

- Thread-safe, in-memory registry for schema storage and management
- Global registry patterns for effects and resources
- Concurrent access with proper error handling
- Unicode schema names support

- JSON Schema-compliant validation for all primitive types
- Advanced constraint validation (patterns, ranges, length limits)
- Discriminated union support with anyOf schemas
- Detailed error reporting with nested validation paths
- Discriminated subobject validation for polymorphic schemas

- **Registry Tests**: All registry operations
- **Effect Tests**: Policy effect validation
- **Resource Tests**: Resource validation
- **Validation Tests**: Core validation engine
- Thread-safety, error handling, integration scenarios, edge cases

- **Dependencies**: dashmap, once_cell, regex
- **Thread Safety**: Minimal locking with Rc<Schema> sharing
- **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc.

- Complete schema registry and validation subsystem
- Comprehensive test coverage
- Foundation for policy validation in Regorus

Benchmarks:

- Criterion benchmarks for basic types, effects and Azure resources
- Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation)
- String withs patterns validation: 30.2µs. Need to explore whether regex caching helps
  bring this down.
- Azure policy effects: 188ns-1.4µs

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* Address PR feedback

- move error to a separate file
- use meaningful var names

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* Refactor

- Reusable Registry struct
- Split and simplify tests

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-14 15:59:30 -05:00
Anand Krishnamoorthi
77f8544868 feat: Type System (#452)
Details:

- Implement complete Type enum with 12 variants: Any, Integer, Number, Boolean,
  Null, String, Array, Set, Object, Enum, Const, AnyOf
- Add Schema wrapper struct with reference counting for efficient sharing
- Support JSON Schema-compatible deserialization with serde
- Implement discriminated subobjects for polymorphic type definitions
- Add comprehensive test suite covering all type variants
- Include Azure resource schema examples (Storage, VM, Key Vault, App Service)
- Create meta-schema validation system with lazy static validator
- Add extensive edge case and corner case test coverage
- Implement custom deserializers for complex schema patterns

This establishes the foundation for type checking and validation of Rego
policies, particularly useful for cloud resource schemas and policy validation.

Regorus's type system is a first of many features intended to
enable type checking and various other constraints on Rego policies.

The type system is inspired from:
   - JSON schema
   - Bicep

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-11 15:40:45 -05:00
dependabot[bot]
2fcd5e3eb9 build(deps): bump clap from 4.5.42 to 4.5.43 (#454)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.42 to 4.5.43.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.42...clap_complete-v4.5.43)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 07:07:53 -05:00
dependabot[bot]
30f0d4e781 build(deps): bump criterion from 0.6.0 to 0.7.0 (#455)
Bumps [criterion](https://github.com/bheisler/criterion.rs) from 0.6.0 to 0.7.0.
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.6.0...0.7.0)

---
updated-dependencies:
- dependency-name: criterion
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 06:34:50 -05:00
Anand Krishnamoorthi
de6aa2bcd1 fix: Fix broken build (#453)
The clone optimization PR didn't have the latest changes for "azure_policy".
Integration resulted in compile errors.

Also fix errors due to updated clippy lints.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-07 12:52:09 -05:00
Anand Krishnamoorthi
dbba57f499 perf: Optimize clone (#443)
Introduce the notion of CompiledPolicy to hold stuff that
remains immutable during evaluation - e.g. rules, function,
schedules etc

Cloning takes about 60 nano seconds for an engine loaded with
ACI policies. Earlier it used to take 40 microseconds.
Thus there is easily more than 100x speedup.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-06 20:50:47 -05:00
Tyler Schade
a8384da070 feat: implement net.cidr_is_valid builtin (#422)
Signed-off-by: tjons <tylerschade99@gmail.com>
2025-08-06 05:17:10 -05:00
Denis Komissarov
c5b2b0df97 build: Run format and clippy during C# binding build (#451) 2025-08-04 19:57:38 -05:00
Anand Krishnamoorthi
fc09802bfb deps: Update dependencies across all Cargo.toml files and ignore .sln files (#450)
Dependencies updated:
- cc: 1.2.29 -> 1.2.31
- chrono-tz: 0.10.3 -> 0.10.4
- clap: 4.5.40 -> 4.5.42
- clap_builder: 4.5.40 -> 4.5.42
- clap_derive: 4.5.40 -> 4.5.41
- phf: 0.11.3 -> 0.12.1
- phf_shared: 0.11.3 -> 0.12.1
- rand: 0.9.1 -> 0.9.2
- rb-sys: 0.9.116 -> 0.9.117
- rb-sys-build: 0.9.116 -> 0.9.117
- redox_syscall: 0.5.13 -> 0.5.17
- rustix: 1.0.7 -> 1.0.8
- serde_json: 1.0.140 -> 1.0.142
- windows-targets: 0.53.2 -> 0.53.3
- winnow: 0.7.11 -> 0.7.12

Removed obsolete build dependencies:
- chrono-tz-build, parse-zoneinfo, phf_codegen, phf_generator, rand_core

Updated in: main, ffi, java, python, ruby, and wasm bindings
Added *.sln to .gitignore to exclude Visual Studio solution files

Also fix python publishing pipeline by removing non-existent dependency.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-04 19:56:27 -05:00
Denis Komissarov
9fce2ccc00 feat: Implement methods to get package names and parameters (#425) 2025-08-04 15:02:22 -05:00
Milian Wolff
3b802c14cb fix: Fix install of regorus_ffi C++ bindings (#449)
When using a clean build dir the install failed as it referenced
a non-existing `regorus_ffiCorrosion.cmake` file. I believe I used
the shorter `regorus_ffi` as my `EXPORT` in the `corrosion_install`
at some point and then later didn't notice that I referenced a stale
generated file when I initially handed in this PR. We must make sure
that the same identifier is used here too. See also the documentation
from `corrosion_install`:

> * **EXPORT**: Creates an export that can be installed with `install(EXPORT)`. <export-name> must be globally unique.
>             Also creates a file at ${CMAKE_BINARY_DIR}/corrosion/<export-name>Corrosion.cmake that must be included in the installed config file.
2025-08-04 09:40:52 -05:00
Anand Krishnamoorthi
90b4ec6823 fix: Tweak release-plz config to handle dirty files. (#433)
bindings/ruby/bin/console and bindings/ruby/bin/setup show up
as dirty to release-plz and causes it to fail to update.

As a workaround, set allow_dirty to true to enable update.
However ensure that no dirty files are published by setting
publish_allow_dirty to false.

Also default to not publishing any packages except regorus.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-09 11:27:12 -05:00
Anand Krishnamoorthi
9487defa20 chore: Regorus v0.5.0 release (#432)
Also update binding versions and lock files.
Note:
- Ruby binding is not updated
- C# binding is v0.7.0. We will make it match Regorus version later.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 16:40:06 -05:00
Anand Krishnamoorthi
48d2064c14 ci: Update release-plz action to v0.5.108 (#431)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 14:22:49 -05:00
Kirill Zabelin
a29bfeeb4f Early return for 'some in' statement (#427)
* fix: use early exit in 'some in' statements

Update kata tests:
Since 'early return' now works with 'some in' statement, interpreter
does not do any evaluation after it found match for rule, therefore
we don't have other rule checks after interpreter found match
2025-07-08 13:47:06 -05:00
Anand Krishnamoorthi
0a9864f3ec fix: emit import warning to stderr (#430)
fixes #429

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 12:23:54 -05:00
Anand Krishnamoorthi
9cba07b778 fix: Fix mut_from_ref clippy warning (#428)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 11:48:13 -05:00
Denis Komissarov
168b2a9c88 build: Support manually generating C# bindings via Github action and add a README (#423) 2025-07-03 12:31:32 -05:00
Anand Krishnamoorthi
8ee1cf3298 fix: Clippy warnings (#424)
Also schedule works to be run at 8:00 AM everyday.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-02 20:04:27 -05:00
Anand Krishnamoorthi
444b2970a1 feat!: Indexes for nodes in the AST (#414)
Indexes allow associating extra data with nodes in the AST
using an array and then quickly looking up the array to fetch
the extra data.

- Index eidx for expressions
- Index sidx for statements
- Index qidx for queries.

AST nodes are not cloneable. Therefore once a module is created,
it is not possible to accidentally create two nodes with the same
index inadvertently via clone.

Also added IndexChecker in debug builds. When a module is parsed,
it will assert that indexes have been constructed correctly.

AST Cleanup
- Make literal expressions (null, val, number, string etc) also structs
  to match all other expressions
- Merge True and False nodes into a single Bool node.

Also update dependencies.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-06-20 15:09:07 -05:00
Milian Wolff
620f8a4547 Make the bindings/cpp CMake project installable (#416)
This leverages the still-undocumented `corrosion_install` to get
an installable ffi binding that can be consumed by other projects,
e.g. from yocto:

```
$ ninja install
[4/5] Install the project...
-- Install configuration: "Debug"
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/libregorus_ffi.so
-- Up-to-date: /home/milian/projects/compiled/regorus-test/include/regorus_ffi/regorus.hpp
-- Up-to-date: /home/milian/projects/compiled/regorus-test/include/regorus_ffi/regorus.ffi.hpp
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/cmake/regorus_ffi/regorus_ffi_targets.cmake
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/cmake/regorus_ffi/regorus_ffiConfig.cmake
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/cmake/regorus_ffi/regorus_ffiCorrosion.cmake
```

This can then be consumed as such:

```
find_package(regorus_ffi CONFIG REQUIRED)

add_executable(test test.cpp)
target_link_libraries(test PRIVATE regorus_ffi::regorus_ffi)
```
2025-06-02 11:45:22 -05:00
dependabot[bot]
c631d44154 build(deps): bump clap from 4.5.38 to 4.5.39 (#415)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.38 to 4.5.39.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.38...clap_complete-v4.5.39)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 08:59:41 -05:00
Anand Krishnamoorthi
39f10326cc build(deps): Update criterion and other deps (#412)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-05-23 14:00:14 -05:00
Burak Varlı
60ac4a7a7c Basic benchmarking setup with Criterion (#408)
* Basic benchmarking setup with Criterion
* Fix Clippy warnings

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-05-14 10:41:06 -05:00
Burak Varlı
5caac47b38 Default to Rego v1 in regorus parse (#407)
Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-05-14 09:28:18 -05:00
dependabot[bot]
2f6c39753c build(deps): bump clap from 4.5.37 to 4.5.38 (#406)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.37 to 4.5.38.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.37...clap_complete-v4.5.38)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 10:33:03 -05:00
Anand Krishnamoorthi
130f9685fd feat: Updates for Policy Framework (#405)
- Documentation
  - Regorus Engine is intended to be used from a single thread
  - Clone the engine after adding policies and data to use from another thread

- Builtin errors strictness:
  - default to less strict for OPA compatibility
  - Provide API to change strictness

- Expose GetAstAsJson to C#,
  This can allow writing policy validations in C#.

- Use spectre mitigated msvc crt libs (binskim compliance)

- Update dependencies

fixes #404

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-30 15:33:40 -05:00
Anand Krishnamoorthi
b11007a1be fix: Disallow else blocks for set rules (#403)
else blocks following contains and old-style sets will raise
a parse error. Consistent with OPA.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-29 11:14:37 -05:00
Anand Krishnamoorthi
6719456468 build: Update dependencies (#401)
Also bump up C# package version

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-29 11:06:05 -05:00
Anthony Martin
962c0cc459 Add C# test examples (#397) 2025-04-23 08:01:51 -05:00
Anand Krishnamoorthi
9e43bd9878 fix!: Remove cryptographic builtins (#396)
Cryptographic builtins are removed due to various reasons like FIPS
compliance. Users needing crypto builtins are encouraged to use
extensions.

Deprecated functions are also removed.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-16 12:24:19 -07:00
445 changed files with 82165 additions and 7259 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"

View File

@@ -4,11 +4,20 @@
"name": "Rust",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit"
]
}
},
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {},
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "8.0"
},
"ghcr.io/devcontainers/features/python:1": {}
}
// Use 'mounts' to make the cargo cache persistent in a Docker Volume.
// "mounts": [
// {

View File

@@ -0,0 +1,29 @@
name: rust-toolchain
description: Setup Rust toolchain with specified version and components
inputs:
toolchain:
description: 'Rust toolchain version'
required: false
default: '1.89.0'
components:
description: 'Additional components to install'
required: false
default: 'clippy rustfmt'
targets:
description: 'Target architectures to install'
required: false
default: ''
runs:
using: composite
steps:
- shell: bash
run: |
rustup override set ${{ inputs.toolchain }}
if [ -n "${{ inputs.components }}" ]; then
rustup component add ${{ inputs.components }}
fi
if [ -n "${{ inputs.targets }}" ]; then
rustup target add ${{ inputs.targets }}
fi
cargo --version
rustc --version

212
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,212 @@
name: "CodeQL Security Analysis"
on:
schedule:
# Run weekly on Wednesdays at 3:17 AM UTC
- cron: '17 3 * * 3'
workflow_dispatch:
# Allow manual triggering
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
# Rust analysis for main crate and Rust-based bindings
- language: rust
build-mode: none
working-directory: .
# C/C++ analysis for FFI bindings
- language: c-cpp
build-mode: manual
working-directory: bindings/ffi
# Python analysis for Python bindings
- language: python
build-mode: none
working-directory: bindings/python
# Java analysis for Java bindings
- language: java-kotlin
build-mode: manual
working-directory: bindings/java
# Go analysis for Go bindings
- language: go
build-mode: manual
working-directory: bindings/go
# C# analysis for C# bindings
- language: csharp
build-mode: manual
working-directory: bindings/csharp
# JavaScript analysis for WASM bindings
- language: javascript-typescript
build-mode: none
working-directory: bindings/wasm
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Setup language-specific dependencies BEFORE CodeQL init for proper tracing setup
- name: Setup Rust
if: matrix.language == 'rust' || matrix.language == 'c-cpp'
uses: ./.github/actions/toolchains/rust
- name: Setup Python
if: matrix.language == 'python'
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Setup Java
if: matrix.language == 'java-kotlin'
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '8'
- name: Setup Go
if: matrix.language == 'go'
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Setup .NET
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@v4
with:
global-json-file: ./bindings/csharp/global.json
- name: Setup Node.js
if: matrix.language == 'javascript-typescript'
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# Install additional build dependencies
- name: Install system dependencies
if: matrix.language == 'rust' || matrix.language == 'c-cpp'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake
- name: Install Python build dependencies
if: matrix.language == 'python'
working-directory: ${{ matrix.working-directory }}
run: |
python -m pip install --upgrade pip
pip install maturin[patchelf] pytest
- name: Setup Ruby
if: matrix.language == 'rust' && contains(matrix.working-directory, 'ruby')
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4.2'
bundler-cache: true
working-directory: bindings/ruby
- name: Install WASM build dependencies
if: matrix.language == 'javascript-typescript'
run: |
cargo install wasm-pack
# Manual build steps for different languages
- name: Build C/C++ FFI bindings
if: matrix.language == 'c-cpp'
working-directory: ${{ matrix.working-directory }}
run: |
# Build FFI library in no_std mode for embedded/constrained environments
cargo build --release --locked --features "ast,coverage,regorus/opa-no-std" --no-default-features
# Build the Rust FFI library that provides C-compatible interface
cargo build --release --locked
# Build C bindings using CMake
cd ../c
mkdir -p build
cd build
cmake ..
make
# Build C++ bindings using CMake
cd ../../cpp
mkdir -p build
cd build
cmake ..
make
- name: Build Java bindings
if: matrix.language == 'java-kotlin'
working-directory: ${{ matrix.working-directory }}
run: |
# Build the Rust JNI library that provides Java-compatible interface
cargo fetch
cargo build --release --locked
# Compile Java source and create JAR package with Maven
mvn package
- name: Build Go bindings
if: matrix.language == 'go'
working-directory: ${{ matrix.working-directory }}
run: |
# Build the FFI library that Go bindings depend on via CGO
cd ../ffi
cargo fetch
cargo build --release --locked
cd ../go
# Download Go dependencies
go mod tidy
# Set up environment for CGO linking to Rust FFI library
export CGO_ENABLED=1
export LD_LIBRARY_PATH="$(pwd)/../ffi/target/release:$LD_LIBRARY_PATH"
# Build Go packages with verbose output for CodeQL tracing
go build -v ./pkg/regorus
go build -v -o regorus_test .
- name: Build C# bindings
if: matrix.language == 'csharp'
working-directory: ${{ matrix.working-directory }}
run: |
# Build the FFI library that C# bindings access via P/Invoke
cd ../ffi
cargo fetch
cargo build --release --locked
cd ../csharp
# Restore NuGet packages and build .NET assemblies in release mode
# Build the main Regorus library project only (tests require packaged version)
dotnet restore Regorus/Regorus.csproj
dotnet build Regorus/Regorus.csproj --no-restore /p:Configuration=Release /p:IgnoreMissingArtifacts=true
- name: Build WASM bindings
if: matrix.language == 'javascript-typescript'
working-directory: ${{ matrix.working-directory }}
run: |
# Build WebAssembly module with wasm-pack for Node.js target
cargo fetch
wasm-pack build --target nodejs --release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,7 +18,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Build only std
run: cargo build -r --example regorus --no-default-features --features "std,rego-extensions"
- name: Doc Tests

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,7 +18,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Format Check
run: cargo fmt --check
- name: Fetch
@@ -41,3 +46,8 @@ jobs:
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
- name: Run tests (Azure Policy)
run: >-
cargo test --frozen --features azure_policy
- name: Run tests (Azure RBAC)
run: cargo test -r --frozen --features azure-rbac

View File

@@ -32,18 +32,18 @@ jobs:
os: windows-latest
extension: dll
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 8
distribution: "corretto"
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/toolchains/rust
with:
targets: ${{ matrix.target }}
- if: ${{ matrix.build_cmd == 'zigbuild' }}
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"
- if: ${{ matrix.build_cmd == 'zigbuild' }}
@@ -52,7 +52,7 @@ jobs:
- run: cargo ${{ matrix.build_cmd || 'build' }} --release --frozen --target ${{ matrix.target }}${{ matrix.glibc && format('.{0}', matrix.glibc) || '' }} --manifest-path ./bindings/java/Cargo.toml
- run: mkdir -p native/${{ matrix.target }}
- run: mv target/${{ matrix.target }}/release/*.${{ matrix.extension }} ./native/${{ matrix.target }}/
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: native-libraries-${{ matrix.target }}
path: native/
@@ -62,24 +62,24 @@ jobs:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 8
distribution: "corretto"
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: native-libraries-*
merge-multiple: true
path: ./bindings/java/native/
- run: mvn package
working-directory: ./bindings/java
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: built-jars
path: ./bindings/java/target/regorus-java-*.jar

View File

@@ -18,10 +18,11 @@ jobs:
matrix:
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
@@ -38,9 +39,9 @@ jobs:
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels
name: wheels-linux-${{ matrix.target }}
path: dist
windows:
@@ -49,11 +50,12 @@ jobs:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
architecture: ${{ matrix.target }}
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
@@ -69,9 +71,9 @@ jobs:
args: --release --out dist --manifest-path bindings/python/Cargo.toml --frozen --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels
name: wheels-windows-${{ matrix.target }}
path: dist
macos:
@@ -80,10 +82,11 @@ jobs:
matrix:
target: [x86_64, aarch64, universal2-apple-darwin]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
@@ -99,9 +102,9 @@ jobs:
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels
name: wheels-macos-${{ matrix.host.target }}
path: dist
release:
@@ -109,11 +112,13 @@ jobs:
runs-on: ubuntu-latest
# Commented out for initial release.
# if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
needs: [linux, windows, macos]
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: wheels
pattern: wheels-*
merge-multiple: true
path: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
env:

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'

View File

@@ -10,15 +10,17 @@ jobs:
release-plz:
name: Release-plz
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: ./.github/actions/toolchains/rust
- name: Run release-plz
uses: MarcoIeni/release-plz-action@98b2b45b090aadf18cb662caaf3de6222d98822a #v0.5.60
uses: MarcoIeni/release-plz-action@8724d33cd97b8295051102e2e19ca592962238f5 #v0.5.108
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@@ -16,6 +16,9 @@ on:
# The branches below must be a subset of the branches above
branches: [ "main" ]
workflow_dispatch:
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
rust-clippy-analyze:
@@ -27,15 +30,10 @@ jobs:
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1
with:
profile: minimal
toolchain: stable
components: clippy
override: true
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Install required cargo
run: cargo install clippy-sarif sarif-fmt
@@ -47,12 +45,13 @@ jobs:
run:
cargo clippy
--all-features
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
--frozen
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@c298edae2d512d807fe4bdc57c0ac5a036f61501 # v3.29.11
with:
sarif_file: rust-clippy-results.sarif
wait-for-processing: true

View File

@@ -5,16 +5,21 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Setup gcc, g++, cmake, ninja
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build

View File

@@ -1,10 +1,17 @@
name: bindings/csharp
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
VersionSuffix: ${{ github.event_name == 'workflow_dispatch' && 'manualtrigger' || null }}
jobs:
build-ffi:
@@ -31,16 +38,29 @@ jobs:
# **/release/libregorus_ffi.dylib
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Fetch crates
run: cargo fetch
working-directory: ./bindings/ffi
- name: Check Regorus binding formatting
run: cargo fmt --check
working-directory: ./bindings/ffi
- name: Check Clippy linting for Regorus binding
run: cargo clippy --frozen -- -D warnings
working-directory: ./bindings/ffi
- name: Build Regorus binding
run: cargo build -r --target ${{ matrix.runtime.target }} --locked
working-directory: ./bindings/ffi
- name: Upload regorus ffi shared library
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: regorus-ffi-artifacts-${{ matrix.runtime.target }}
# Note: The full path of each artifact relative to . is preserved.
@@ -54,17 +74,17 @@ jobs:
needs: build-ffi
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
global-json-file: ./bindings/csharp/global.json
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Download regorus ffi shared libraries
uses: actions/download-artifact@v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: regorus-ffi-artifacts-*
merge-multiple: true
@@ -83,7 +103,7 @@ jobs:
working-directory: ./bindings/csharp/Regorus
- name: Upload Regorus nuget
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: regorus-nuget
path: bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
@@ -107,31 +127,52 @@ jobs:
# target: aarch64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
global-json-file: ./bindings/csharp/global.json
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Download regorus nuget
uses: actions/download-artifact@v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: regorus-nuget
path: ./bindings/csharp/TestApp/regorus-nuget/
- name: Restore Test App
run: dotnet restore /p:RestoreSources=./regorus-nuget/
path: ./bindings/csharp/regorus-nuget/
- name: Restore Regorus.Tests
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
working-directory: ./bindings/csharp/Regorus.Tests
- name: Run Regorus.Tests
run: dotnet test --no-restore
working-directory: ./bindings/csharp/Regorus.Tests
- name: Restore TestApp
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
working-directory: ./bindings/csharp/TestApp
- name: Build Test App 8.0
run: dotnet build --framework net8.0
- name: Build TestApp
run: dotnet build --no-restore
working-directory: ./bindings/csharp/TestApp
- name: Run Test App 8.0
run: dotnet run --framework net8.0
- name: Run TestApp
run: dotnet run --no-build --framework net8.0
working-directory: ./bindings/csharp/TestApp
- name: Restore TargetExampleApp
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
working-directory: ./bindings/csharp/TargetExampleApp
- name: Build TargetExampleApp
run: dotnet build --no-restore
working-directory: ./bindings/csharp/TargetExampleApp
- name: Run TargetExampleApp
run: dotnet run --no-build --framework net8.0
working-directory: ./bindings/csharp/TargetExampleApp

View File

@@ -5,19 +5,24 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Test FFI
run: |
cargo fetch
cargo build -r --frozen
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo test --features contention_checks --frozen
working-directory: ./bindings/ffi

View File

@@ -5,18 +5,22 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-go@v5
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
with:
architecture: x64

View File

@@ -5,33 +5,43 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 8
distribution: "corretto"
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/toolchains/rust
- name: Building binding
run: |
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --manifest-path bindings/java/Cargo.toml --locked
- name: Capture binding version
run: |
version=$(cargo metadata --manifest-path bindings/java/Cargo.toml --format-version 1 \
| jq -r '.packages[] | select(.name == "regorus-java") | .version')
echo "REGORUS_JAVA_VERSION=$version" >> "$GITHUB_ENV"
- name: Build jar
run: mvn package
working-directory: ./bindings/java
- name: Test jar
run: |
javac -cp target/regorus-java-0.2.2.jar Test.java
java -Djava.library.path=target/release -cp target/regorus-java-0.2.2.jar:. Test
jar="regorus-java-${REGORUS_JAVA_VERSION}.jar"
javac -cp "target/${jar}" Test.java
java -Djava.library.path=target/release -cp "target/${jar}:." Test
working-directory: ./bindings/java

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,9 +18,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: ./.github/actions/toolchains/rust
with:
targets: x86_64-unknown-linux-musl
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- name: Fetch

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,9 +18,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add no_std target
run: rustup target add thumbv7m-none-eabi
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: ./.github/actions/toolchains/rust
with:
targets: thumbv7m-none-eabi
- name: Fetch
run: cargo fetch
- name: Build

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
PYTHON_VERSION: "3.10"
@@ -21,11 +24,12 @@ jobs:
runs-on: ${{ matrix.host.name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-python@v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -45,7 +49,7 @@ jobs:
sccache: 'true'
- name: Upload Wheel
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: regorus-wheel-${{ matrix.host.name }}
path: dist/regorus-*.whl
@@ -56,28 +60,28 @@ jobs:
python-version: ["3.10", "3.11", "3.12", "3.13"]
host:
- name: ubuntu-24.04
wheel: regorus-0.4.0-cp310-abi3-manylinux_2_34_x86_64.whl
wheel: regorus-0.5.0-cp310-abi3-manylinux_2_34_x86_64.whl
- name: ubuntu-22.04
wheel: regorus-0.4.0-cp310-abi3-manylinux_2_34_x86_64.whl
wheel: regorus-0.5.0-cp310-abi3-manylinux_2_34_x86_64.whl
- name: windows-latest
wheel: regorus-0.4.0-cp310-abi3-win_amd64.whl
wheel: regorus-0.5.0-cp310-abi3-win_amd64.whl
needs: build
runs-on: ${{ matrix.host.name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- name: Download Regorus wheel
uses: actions/download-artifact@v4
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: wheels
pattern: regorus-wheel-*
merge-multiple: true
- uses: actions/setup-python@v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
architecture: x64
@@ -86,4 +90,4 @@ jobs:
run: |
pip3 install ../../wheels/${{ matrix.host.wheel }}
python3 test.py
working-directory: bindings/python
working-directory: bindings/python

View File

@@ -8,10 +8,11 @@ on:
jobs:
test:
if: false # temporarily disabled
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0

View File

@@ -5,18 +5,24 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 18

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,7 +18,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Fetch
run: cargo fetch
- name: Build (all features)
@@ -37,3 +42,5 @@ jobs:
- name: Run tests (OPA Conformance)
run: >-
cargo test --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
- name: Run tests (Azure RBAC)
run: cargo test --frozen --features azure-rbac

7
.gitignore vendored
View File

@@ -28,4 +28,9 @@ bindings/*/target
# C# build folders
**bin
**obj
*.sln
# Visual Studio folders
**/*.vs
# Visual Studio solution files
*.sln

View File

@@ -6,6 +6,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.5.0](https://github.com/microsoft/regorus/compare/regorus-v0.4.0...regorus-v0.5.0) - 2025-07-08
### Added
- [**breaking**] Indexes for nodes in the AST ([#414](https://github.com/anakrish/regorus/pull/414))
- Updates for Policy Framework ([#405](https://github.com/anakrish/regorus/pull/405))
- Regorus nuget package ([#383](https://github.com/anakrish/regorus/pull/383))
### Fixed
- emit import warning to stderr ([#430](https://github.com/anakrish/regorus/pull/430))
- Clippy warnings ([#424](https://github.com/anakrish/regorus/pull/424))
- Disallow else blocks for set rules ([#403](https://github.com/anakrish/regorus/pull/403))
- [**breaking**] Remove cryptographic builtins ([#396](https://github.com/anakrish/regorus/pull/396))
- [**breaking**] Fix glob.match behavior in presence of : ([#390](https://github.com/anakrish/regorus/pull/390))
- C# EvalRule ([#387](https://github.com/anakrish/regorus/pull/387))
### Other
- Update release-plz action to v0.5.108 ([#431](https://github.com/anakrish/regorus/pull/431))
- Early return for 'some in' statement ([#427](https://github.com/anakrish/regorus/pull/427))
- Support manually generating C# bindings via Github action and add a README ([#423](https://github.com/anakrish/regorus/pull/423))
- Make the bindings/cpp CMake project installable ([#416](https://github.com/anakrish/regorus/pull/416))
- *(deps)* bump clap from 4.5.38 to 4.5.39 ([#415](https://github.com/anakrish/regorus/pull/415))
- *(deps)* Update criterion and other deps ([#412](https://github.com/anakrish/regorus/pull/412))
- Basic benchmarking setup with Criterion ([#408](https://github.com/anakrish/regorus/pull/408))
- Default to Rego v1 in `regorus parse` ([#407](https://github.com/anakrish/regorus/pull/407))
- *(deps)* bump clap from 4.5.37 to 4.5.38 ([#406](https://github.com/anakrish/regorus/pull/406))
- Update dependencies ([#401](https://github.com/anakrish/regorus/pull/401))
- Add C# test examples ([#397](https://github.com/anakrish/regorus/pull/397))
- *(deps)* bump clap from 4.5.35 to 4.5.36 ([#395](https://github.com/anakrish/regorus/pull/395))
- Python binding portability ([#388](https://github.com/anakrish/regorus/pull/388))
- *(deps)* bump clap from 4.5.34 to 4.5.35 ([#389](https://github.com/anakrish/regorus/pull/389))
- Use VersionPrefix and VersionSuffix ([#385](https://github.com/anakrish/regorus/pull/385))
- Check-in Cargo.lock files and lockdown .net ([#384](https://github.com/anakrish/regorus/pull/384))
## [0.4.0](https://github.com/microsoft/regorus/compare/regorus-v0.3.0...regorus-v0.4.0) - 2025-03-14
### Fixed

1271
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,15 @@
members = [
"tests/ensure_no_std",
"xtask",
]
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
license-file = "LICENSE"
license = "MIT"
repository = "https://github.com/microsoft/regorus"
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
@@ -19,25 +20,28 @@ keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
doctest = false
[features]
default = ["full-opa", "arc"]
default = ["full-opa", "arc", "rvm"]
arc = ["scientific/arc"]
ast = []
azure_policy = ["dep:jsonschema", "arc", "dashmap"]
azure-rbac = []
base64 = ["dep:data-encoding"]
base64url = ["dep:data-encoding"]
coverage = []
crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha2"]
deprecated = []
hex = ["dep:data-encoding"]
http = []
glob = ["dep:globset"]
graph = []
jsonschema = ["dep:jsonschema"]
mimalloc = ["dep:mimalloc"]
net = ["dep:ipnet"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
rvm = ["dep:bincode", "dep:indexmap"]
semver = ["dep:semver"]
std = ["rand/std", "rand/std_rng", "serde_json/std"]
std = ["rand/std", "rand/std_rng", "serde_json/std", "msvc_spectre_libs" ]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
@@ -46,13 +50,13 @@ full-opa = [
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"glob",
"graph",
"hex",
"http",
"jsonschema",
"mimalloc",
"net",
"opa-runtime",
"regex",
"semver",
@@ -72,8 +76,6 @@ opa-no-std = [
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"graph",
"hex",
"no_std",
@@ -93,41 +95,48 @@ rand = ["dep:rand"]
[dependencies]
anyhow = { version = "1.0.45", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
lazy_static = { version = "1.4.0", default-features = false }
# Crypto
constant_time_eq = {version = "0.4.0", optional = true, default-features = false }
hmac = {version = "0.12.1", optional = true, default-features = false}
sha2 = {version= "0.10.8", optional = true, default-features = false }
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
md-5 = {version = "0.10.6", optional = true, default-features = false }
thiserror = { version = "2.0", default-features = false }
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.3" }
scientific = { version = "0.5.3", default-features = false }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.11.1", optional = true, default-features = false }
semver = {version = "1.0.25", optional = true, default-features = false }
url = { version = "2.5.4", optional = true }
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.29.0", default-features = false, optional = true }
jsonschema = { version = "0.30.0", default-features = false, optional = true }
chrono = { version = "0.4.40", optional = true }
chrono-tz = { version = "0.10.1", optional = true }
ipnet = { version = "2.11.0", optional = true, default-features = false }
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
# Specify thread_rng for in order to use random_range
rand = { version = "0.9.0", default-features = false, features = ["thread_rng"], optional = true }
# Causes the project to link with the Spectre-mitigated CRT and libs.
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
dashmap = { version = "6.1", default-features = false, optional = true }
mimalloc = { path = "mimalloc", optional = true }
# rvm related deps
indexmap = { version = "2.12.1", default-features = false, features = ["serde"], optional = true }
bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"], optional = true }
[dev-dependencies]
anyhow = "1.0.45"
cfg-if = "1.0.0"
clap = { version = "4.5.36", features = ["derive"] }
prettydiff = { version = "0.8.0", default-features = false }
clap = { version = "4.5.53", features = ["derive"] }
prettydiff = { version = "0.9.0", default-features = false }
serde_yaml = "0.9.16"
test-generator = "0.3.1"
walkdir = "2.3.2"
criterion = { version = "0.7" }
num_cpus = "1.16"
[build-dependencies]
anyhow = "1.0"
@@ -153,6 +162,29 @@ name="kata"
harness=false
test=false
[[bench]]
name = "regorus_benchmark"
harness = false
[[bench]]
name = "schema_validation_benchmark"
harness = false
required-features = ["azure_policy"]
[[bench]]
name = "engine_evaluation_benchmark"
path = "benches/evaluation/engine_evaluation_benchmark.rs"
harness = false
[[bench]]
name = "compiled_policy_evaluation_benchmark"
path = "benches/evaluation/compiled_policy_evaluation_benchmark.rs"
harness = false
[[bench]]
name = "aci_benchmark"
harness = false
[[example]]
name="regorus"
harness=false

View File

@@ -145,6 +145,7 @@ $ regorus
Usage: regorus <COMMAND>
Commands:
ast Parse a Rego policy and dump AST
eval Evaluate a Rego Query
lex Tokenize a Rego policy
parse Parse a Rego policy
@@ -288,12 +289,6 @@ Currently, Regorus passes all the non-builtin specific tests.
See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
The following test suites don't pass fully due to missing builtins:
- `cryptoparsersaprivatekeys`
- `cryptox509parseandverifycertificates`
- `cryptox509parsecertificaterequest`
- `cryptox509parsecertificates`
- `cryptox509parsekeypair`
- `cryptox509parsersaprivatekey`
- `globsmatch`
- `graphql`
- `invalidkeyerror`
@@ -308,11 +303,9 @@ The following test suites don't pass fully due to missing builtins:
- `jwtverifyhs384`
- `jwtverifyhs512`
- `jwtverifyrsa`
- `netcidrcontains`
- `netcidrcontainsmatches`
- `netcidrexpand`
- `netcidrintersects`
- `netcidrisvalid`
- `netcidrmerge`
- `netcidroverlap`
- `netlookupipaddr`
@@ -324,7 +317,7 @@ The following test suites don't pass fully due to missing builtins:
They are captured in the following [github issues](https://github.com/microsoft/regorus/issues?q=is%3Aopen+is%3Aissue+label%3Alib).
Cryptographically insecure `sha1` related builtins are intentionally not supported to discourage their use.
Cryptographic builtins are not supported by design. Users that need cryptographic builtins are encouraged to use [extensions](https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_extension).
### Grammar

80
benches/aci_benchmark.rs Normal file
View File

@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use regorus::{Engine, Value};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use std::path::Path;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestCase {
note: String,
data: Value,
input: Value,
modules: Vec<String>,
query: String,
want_result: Value,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct YamlTest {
cases: Vec<TestCase>,
}
fn aci_policy_eval(c: &mut Criterion) {
let dir = Path::new("tests/aci");
for entry in WalkDir::new(dir)
.sort_by_file_name()
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if !path.to_string_lossy().ends_with(".yaml") {
continue;
}
let yaml = std::fs::read(path).expect("failed to read yaml test");
let yaml = String::from_utf8_lossy(&yaml);
let test: YamlTest = serde_yaml::from_str(&yaml).expect("failed to deserialize yaml test");
for case in &test.cases {
let rule = case.query.replace("=x", "");
c.bench_with_input(
BenchmarkId::new("case ", format!("{} {}", &case.note, &rule)),
&case,
|b, case| {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_data(case.data.clone())
.expect("failed to add data");
engine.set_input(case.input.clone());
for (idx, rego) in case.modules.iter().enumerate() {
if rego.ends_with(".rego") {
let path = dir.join(rego);
let path = path.to_str().expect("not a valid path");
engine
.add_policy_from_file(path)
.expect("failed to add policy");
} else {
engine
.add_policy(format!("rego{idx}.rego"), rego.clone())
.expect("failed to add policy");
}
}
b.iter(|| {
engine.eval_rule(rule.clone()).unwrap();
})
},
);
}
}
}
criterion_group!(aci_benches, aci_policy_eval);
criterion_main!(aci_benches);

View File

@@ -0,0 +1,157 @@
# Regorus Multi-Threaded Evaluation Benchmark
A benchmark suite for measuring the multi-threaded performance of the Regorus policy evaluation engine.
## Overview
This benchmark evaluates the throughput and scalability of Regorus policy evaluation across different thread counts and configuration strategies. It measures performance variations between fresh and cloned engine instances, as well as fresh and cloned input data.
## Features
- **Multi-threaded evaluation** testing from 1 to `num_cpus * 2` threads
- **Configurable engine strategies**: Fresh vs. cloned engine instances
- **Configurable input strategies**: Fresh parsing vs. cloned input data
- **Complex policy evaluation** using realistic RBAC and data sensitivity policies
- **Criterion-based benchmarking** with statistical analysis
- **Performance metrics** including throughput and timing
## Benchmark Structure
### Test Configurations
The benchmark tests four different configuration combinations:
1. **Cloned Engines + Cloned Inputs**: Pre-instantiated engines with pre-parsed input data
2. **Cloned Engines + Fresh Inputs**: Pre-instantiated engines with fresh JSON parsing
3. **Fresh Engines + Cloned Inputs**: New engine instances with pre-parsed input data
4. **Fresh Engines + Fresh Inputs**: New engine instances with fresh JSON parsing
### Thread Scaling
Tests are performed with thread counts: 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32 (up to `num_cpus * 2`)
Each thread performs 1000 policy evaluations to ensure statistically significant measurements.
## Running the Benchmark
### Prerequisites
- Rust 1.70+
- Cargo
### Execution
Run the complete benchmark suite:
```bash
cargo bench evaluation_benchmark
```
Run specific benchmarks:
```bash
# Run only cloned engines with cloned inputs
cargo bench "cloned_engines , cloned_inputs"
# Run only single-threaded tests
cargo bench "1 threads"
```
### Output
Results are generated in the `target/criterion/` directory and include:
- Detailed timing statistics
- Throughput measurements (Kelem/s)
- Performance comparison with previous runs
- HTML reports with graphs and analysis
## Test Policies
The benchmark uses complex Rego policies that simulate real-world scenarios:
### RBAC Policy
- Role-based access control with hierarchical permissions
- User-role-resource mapping
- Action-based authorization
### Data Sensitivity Policy
- Multi-level data classification (public, internal, confidential, secret)
- Access level validation
- Clearance-based filtering
### Time-based Access Policy
- Business hours validation
- Temporal access control
- Schedule-based permissions
### Azure Resource Policies
- **VM Deployment**: VM size restrictions, regional compliance, security configurations
- **Storage Account Security**: Encryption requirements, network ACLs, HTTPS enforcement
- **Key Vault Access**: Service principal validation, soft delete requirements, conditional access
- **Network Security Groups**: Port restrictions, CIDR validation, priority-based rules
### Policy Complexity Features
- **Multi-condition validation**: Complex nested object property checks
- **Network operations**: CIDR matching and IP range validation
- **Time-based constraints**: Timestamp comparisons and business hour logic
- **Security compliance**: Encryption, authentication, and access control patterns
- **Azure Resource Manager**: Real-world cloud governance scenarios
## Configuration
### Benchmark Parameters
- **Evaluations per thread**: 1000
- **Measurement iterations**: 100 samples per configuration
- **Warm-up time**: 3 seconds
- **Measurement time**: 10 seconds (extended for high thread counts)
### Customization
The benchmark can be customized by modifying `evaluation_benchmark.rs`:
```rust
// Adjust evaluations per thread
let evals_per_thread = 1000;
// Modify thread count calculation
let max_threads = num_cpus::get() * 2;
// Configure test scenarios
let scenarios = [
(true, true), // cloned_engines, cloned_inputs
(true, false), // cloned_engines, fresh_inputs
(false, true), // fresh_engines, cloned_inputs
(false, false), // fresh_engines, fresh_inputs
];
```
## Understanding Results
### Metrics
- **Total Evaluation Time**: Total execution time for all evaluations across all threads (ms)
- **Throughput**: Evaluations per second measured in Kelem/s
- **Kelem/s**: Thousands of elements (policy evaluations) per second
- Example: 98.71 Kelem/s = 98,710 policy evaluations per second
### Interpretation
- **Lower time** = better performance
- **Higher throughput** = better performance
- **Consistent results** across runs indicate stable performance
- **Outliers** may indicate system interference or measurement variance
### Tips
- Run on dedicated hardware for consistent results
- Disable other applications during benchmarking
- Use release builds for accurate performance measurements
- Consider CPU affinity for highly controlled testing
## Files
- `evaluation_benchmark.rs`: Main benchmark implementation
- Results are saved to `../../target/criterion/` directory

View File

@@ -0,0 +1,160 @@
# Compiled Policy Evaluation Benchmark Results
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **Rust Version**: 1.82.0
- **Allocator**: mimalloc (default allocator)
- **Benchmark Framework**: Criterion.rs
- **Test Data**: 20,000 inputs per evaluation (1000 per thread)
- **Policy**: Complex authorization policy with nested rules
## Benchmark Overview
The compiled policy evaluation benchmark tests Regorus compiled policy performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of compiled policy and input data reuse strategies.
## Configuration Combinations
1. **Compiled Shared Policies, Cloned Inputs**: Each thread uses shared compiled policies and clones of parsed input data - optimal for performance
2. **Compiled Shared Policies, Fresh Inputs**: Each thread uses shared compiled policies but parses new inputs each time
3. **Compiled Per Iteration, Cloned Inputs**: Each thread compiles the policy each iteration but reuses input data
4. **Compiled Per Iteration, Fresh Inputs**: Each thread compiles new policies and parses new inputs for each iteration
## Performance Results
### Compiled Shared Policies, Cloned Inputs (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2.35 | 426 |
| 2 | 5.36 | 373 |
| 4 | 11.70 | 342 |
| 6 | 20.33 | 295 |
| 8 | 43.26 | 185 |
| 10 | 61.93 | 162 |
| 12 | 79.30 | 151 |
| 14 | 94.45 | 148 |
| 16 | 113.39 | 141 |
| 18 | 154.41 | 117 |
| 20 | 184.37 | 108 |
| 22 | 204.00 | 108 |
| 24 | 220.45 | 109 |
| 26 | 237.07 | 110 |
| 28 | 252.58 | 111 |
| 30 | 273.57 | 110 |
| 32 | 292.69 | 109 |
### Compiled Shared Policies, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 3.34 | 299 |
| 2 | 7.29 | 274 |
| 4 | 15.19 | 263 |
| 6 | 24.90 | 241 |
| 8 | 49.22 | 163 |
| 10 | 68.45 | 146 |
| 12 | 86.55 | 139 |
| 14 | 104.77 | 134 |
| 16 | 136.07 | 118 |
| 18 | 169.05 | 106 |
| 20 | 198.25 | 101 |
| 22 | 217.05 | 101 |
| 24 | 234.75 | 102 |
| 26 | 254.53 | 102 |
| 28 | 276.06 | 101 |
| 30 | 296.12 | 101 |
| 32 | 318.81 | 100 |
### Compiled Per Iteration, Cloned Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 18.11 | 55 |
| 2 | 36.89 | 54 |
| 4 | 75.46 | 53 |
| 6 | 114.66 | 52 |
| 8 | 152.80 | 52 |
| 10 | 192.17 | 52 |
| 12 | 232.32 | 52 |
| 14 | 301.47 | 46 |
| 16 | 380.36 | 42 |
| 18 | 424.64 | 42 |
| 20 | 484.76 | 41 |
| 22 | 531.62 | 41 |
| 24 | 582.88 | 41 |
| 26 | 631.39 | 41 |
| 28 | 671.99 | 42 |
| 30 | 717.65 | 42 |
| 32 | 766.05 | 42 |
### Compiled Per Iteration, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 19.07 | 52 |
| 2 | 38.89 | 51 |
| 4 | 79.52 | 50 |
| 6 | 120.89 | 50 |
| 8 | 161.08 | 50 |
| 10 | 202.37 | 49 |
| 12 | 244.04 | 49 |
| 14 | 316.66 | 44 |
| 16 | 398.02 | 40 |
| 18 | 449.54 | 40 |
| 20 | 500.57 | 40 |
| 22 | 557.97 | 39 |
| 24 | 605.71 | 40 |
| 26 | 656.88 | 40 |
| 28 | 710.03 | 39 |
| 30 | 741.09 | 40 |
| 32 | 801.26 | 40 |
## Analysis
The compiled policy benchmark demonstrates the following performance characteristics with mimalloc as the default allocator:
1. **Best Performance**: Compiled shared policies with cloned inputs provide the highest throughput
2. **Compilation Impact**:
- Pre-compiled policies: Significantly faster than per-iteration compilation
- Per-iteration compilation: Major overhead (~7-8x slower than pre-compiled)
3. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for shared policy configurations
- mimalloc provides better thread scaling characteristics compared to the default allocator
- Higher thread counts show performance degradation due to contention, but less severe with mimalloc
- Per-iteration compilation shows poor scaling across all thread counts
4. **Input Processing**: Fresh inputs add ~30% overhead across all configurations
5. **Thread Performance with mimalloc**:
- Peak performance at 1 thread for most configurations
- Reasonable performance maintained up to 12-16 threads for shared policies
- Compiled policies show better thread scaling than per-iteration compilation
- mimalloc helps reduce allocation-related contention in multi-threaded scenarios
## Comparison with Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:---------------------|:-------------------|:--------------------|:--------------------|
| | CP / EE | CP / EE | CP / EE |
| Shared/Cloned | 426 / 423 | 342 / 406 | 185 / 341 |
| Shared/Fresh | 299 / 309 | 263 / 297 | 163 / 266 |
| Per-iteration/Cloned | 55 / 56 | 53 / 54 | 52 / 53 |
| Per-iteration/Fresh | 52 / 53 | 50 / 51 | 50 / 51 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:---------------------|:----------------------|:--------------------------|:-----------------------|
| | Avg CP / EE | Avg CP / EE | Avg CP / EE |
| Shared/Cloned | 384 / 414 | 203 / 329 | 123 / 250 |
| Shared/Fresh | 284 / 302 | 176 / 235 | 108 / 201 |
| Per-iteration/Cloned | 54 / 55 | 50 / 52 | 42 / 42 |
| Per-iteration/Fresh | 51 / 52 | 47 / 50 | 40 / 40 |
The compiled policy evaluation shows performance characteristics that are generally comparable to engine evaluation, though with some notable differences. While single-threaded performance is very close between the systems, there are observable impacts from the compilation approach that become more apparent under different threading scenarios.
**Key Observations:**
- **Single-threaded performance**: Very close parity between systems, though results may vary between runs
- **Threading behavior**: Engine evaluation demonstrates better scaling characteristics under higher thread contention (4+ threads)
- **Multi-threaded impact**: Compiled policies show more pronounced performance degradation under thread contention in shared policy configurations
- **Contention resistance**: Per-iteration compilation shows more consistent (though lower absolute) performance across thread counts
- **Optimal usage**: Both systems achieve best results with minimal threading (1-4 threads), though engine evaluation maintains better performance at higher thread counts for shared configurations

View File

@@ -0,0 +1,232 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use regorus::{compile_policy_with_entrypoint, CompiledPolicy, PolicyModule, Value};
use std::collections::HashMap;
use std::hint::black_box;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::time::Duration;
mod policy_data;
fn multi_threaded_compiled_eval(
num_threads: usize,
evals_per_thread: usize,
use_shared_policies: bool,
use_cloned_inputs: bool,
) -> (std::time::Duration, HashMap<String, usize>, usize) {
// Complex policies with multiple valid inputs for each
let policies_with_inputs = policy_data::policies_with_inputs();
// Policy names for tracking
let policy_names = policy_data::policy_names()
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
// Pre-compile all policies and share them between threads (only if using shared policies)
let compiled_policies: Option<Arc<Vec<CompiledPolicy>>> = if use_shared_policies {
Some(Arc::new(
policies_with_inputs
.iter()
.map(|(policy, _)| {
let module = PolicyModule {
id: "policy.rego".into(),
content: policy.as_str().into(),
};
compile_policy_with_entrypoint(
Value::new_object(),
&[module],
"data.bench.allow".into(),
)
.unwrap()
})
.collect(),
))
} else {
None
};
// Initialize policy evaluation counters
let policy_counters = Arc::new(Mutex::new(HashMap::new()));
for policy_name in &policy_names {
policy_counters
.lock()
.unwrap()
.insert(policy_name.to_string(), 0);
}
let total_evals = Arc::new(Mutex::new(0usize));
let barrier = Arc::new(Barrier::new(num_threads));
let mut handles = Vec::with_capacity(num_threads);
for thread_id in 0..num_threads {
let barrier = barrier.clone();
let policies_with_inputs = policies_with_inputs.clone();
let compiled_policies = compiled_policies.clone();
let policy_names = policy_names.clone();
let policy_counters = policy_counters.clone();
let total_evals = total_evals.clone();
handles.push(thread::spawn(move || {
let mut elapsed = std::time::Duration::ZERO;
// Pre-parse inputs if using cloned inputs
let parsed_inputs = if use_cloned_inputs {
Some(
policies_with_inputs
.iter()
.map(|(_, inputs)| {
inputs
.iter()
.map(|input_str| Value::from_json_str(input_str).unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
)
} else {
None
};
barrier.wait();
for i in 0..evals_per_thread {
// Use different policy for each iteration - thread_id ensures different threads
// start with different policies for better load distribution
let policy_idx = (thread_id + i) % policies_with_inputs.len();
let (_, inputs) = &policies_with_inputs[policy_idx];
// Use different input for the same policy based on iteration - thread_id ensures
// different threads start with different inputs for better load distribution
let input_idx = (thread_id + i) % inputs.len();
let input = &inputs[input_idx];
let start = std::time::Instant::now();
let input_value = if use_cloned_inputs {
parsed_inputs.as_ref().unwrap()[policy_idx][input_idx].clone()
} else {
Value::from_json_str(input).unwrap()
};
let result = if let Some(ref compiled_policies_vec) = compiled_policies {
// Use pre-compiled policy
let compiled_policy = &compiled_policies_vec[policy_idx];
compiled_policy.eval_with_input(input_value)
} else {
// Compile policy in each iteration
let (policy, _) = &policies_with_inputs[policy_idx];
let module = PolicyModule {
id: "policy.rego".into(),
content: policy.as_str().into(),
};
let compiled_policy = compile_policy_with_entrypoint(
Value::new_object(),
&[module],
"data.bench.allow".into(),
)
.unwrap();
compiled_policy.eval_with_input(input_value)
};
elapsed += start.elapsed();
// Track total and successful evaluations
{
let mut total = total_evals.lock().unwrap();
*total += 1;
}
if result.is_ok() {
if let Some(policy_name) = policy_names.get(policy_idx) {
let mut counters = policy_counters.lock().unwrap();
*counters.entry(policy_name.to_string()).or_insert(0) += 1;
}
}
}
elapsed
}));
}
let mut total = std::time::Duration::ZERO;
for handle in handles {
total += handle.join().unwrap();
}
let final_counters = policy_counters.lock().unwrap().clone();
let total_evals = *total_evals.lock().unwrap();
(total, final_counters, total_evals)
}
fn criterion_benchmark(c: &mut Criterion) {
let max_threads = num_cpus::get() * 2;
println!(
"Running compiled policy benchmark with max_threads: {}",
max_threads
);
let evals_per_thread = 1000;
// Benchmark all combinations of compilation strategy and input strategy
for use_shared_policies in [true, false] {
for use_cloned_inputs in [true, false] {
let group_name = match (use_shared_policies, use_cloned_inputs) {
(true, true) => "compiled_shared_policies, cloned_inputs ",
(true, false) => "compiled_shared_policies, fresh_inputs ",
(false, true) => "compiled_per_iteration , cloned_inputs ",
(false, false) => "compiled_per_iteration , fresh_inputs ",
};
let mut group = c.benchmark_group(group_name);
group.measurement_time(Duration::from_secs(5));
// Test specific thread counts: powers of 2 + some intermediate values
let thread_counts: Vec<usize> = (1..=max_threads)
.filter(|&n| {
n == 1 || // Always test single-threaded
n % 2 == 0 || // Always test even threads
n == max_threads // Maximum threads
})
.collect();
for threads in thread_counts {
let total_evals = threads * evals_per_thread;
group.throughput(Throughput::Elements(total_evals as u64));
group.bench_with_input(
BenchmarkId::new("compiled_eval", format!(" {threads} threads")),
&threads,
|b, &threads| {
b.iter_custom(|iters| {
let evals_per_thread = evals_per_thread * (iters as usize);
let (duration, policy_counters, total_evals_aggregated) = multi_threaded_compiled_eval(
black_box(threads),
black_box(evals_per_thread),
black_box(use_shared_policies),
black_box(use_cloned_inputs),
);
// Sanity check: Ensure the expected number of evaluations matches the actual number performed per iteration batch.
// total_evals is the expected number for this batch, total_evals_aggregated is the sum over all iters.
assert_eq!(total_evals, total_evals_aggregated/iters as usize);
// On one iteration, print policy evaluation statistics
if iters == 1 {
// println!("\nCompiled Policy Evaluation Statistics:");
for (policy_name, count) in &policy_counters {
// println!(" {}: {} evaluations", policy_name, count);
if *count == 0 {
println!("\x1b[31mERROR: Policy '{}' was never evaluated successfully!\x1b[0m", policy_name);
}
}
}
duration
});
},
);
}
group.finish();
}
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -0,0 +1,127 @@
# Engine Evaluation Benchmark Results
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **Rust Version**: 1.82.0
- **Allocator**: mimalloc (default allocator)
- **Benchmark Framework**: Criterion.rs
- **Test Data**: 20,000 inputs per evaluation (1000 per thread)
- **Policy**: Complex authorization policy with nested rules
## Benchmark Overview
The engine evaluation benchmark tests Regorus policy evaluation performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of engine and input data reuse strategies.
## Configuration Combinations
1. **Cloned Engines, Cloned Inputs**: Each thread uses its own engine and clones of parsed input data - optimal for performance
2. **Cloned Engines, Fresh Inputs**: Each thread uses its own engine but parses new inputs each time
3. **Fresh Engines, Cloned Inputs**: Each thread creates a new engine each iteration but reuses input data
4. **Fresh Engines, Fresh Inputs**: Each thread creates new engines and parses new inputs for each iteration
## Performance Results
### Cloned Engines, Cloned Inputs (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2.36 | 423 |
| 2 | 4.85 | 412 |
| 4 | 9.86 | 406 |
| 6 | 15.02 | 399 |
| 8 | 23.46 | 341 |
| 10 | 33.34 | 300 |
| 12 | 40.69 | 295 |
| 14 | 48.26 | 290 |
| 16 | 58.61 | 273 |
| 18 | 77.35 | 233 |
| 20 | 86.74 | 231 |
| 22 | 94.17 | 234 |
| 24 | 102.58 | 234 |
| 26 | 110.17 | 236 |
| 28 | 118.97 | 235 |
| 30 | 126.54 | 237 |
| 32 | 135.89 | 235 |
### Cloned Engines, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 3.24 | 309 |
| 2 | 6.57 | 304 |
| 4 | 13.47 | 297 |
| 6 | 20.42 | 294 |
| 8 | 30.01 | 266 |
| 10 | 40.99 | 244 |
| 12 | 49.99 | 240 |
| 14 | 60.09 | 233 |
| 16 | 73.95 | 216 |
| 18 | 95.94 | 188 |
| 20 | 105.24 | 190 |
| 22 | 114.30 | 192 |
| 24 | 124.67 | 193 |
| 26 | 134.76 | 193 |
| 28 | 145.16 | 193 |
| 30 | 155.23 | 193 |
| 32 | 165.42 | 193 |
### Fresh Engines, Cloned Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 17.88 | 56 |
| 2 | 36.32 | 55 |
| 4 | 74.45 | 54 |
| 6 | 112.95 | 53 |
| 8 | 150.24 | 53 |
| 10 | 189.61 | 53 |
| 12 | 228.25 | 53 |
| 14 | 297.37 | 47 |
| 16 | 373.61 | 43 |
| 18 | 426.46 | 42 |
| 20 | 477.80 | 42 |
| 22 | 523.00 | 42 |
| 24 | 570.74 | 42 |
| 26 | 619.92 | 42 |
| 28 | 670.24 | 42 |
| 30 | 717.47 | 42 |
| 32 | 748.25 | 43 |
### Fresh Engines, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 18.69 | 53 |
| 2 | 38.03 | 53 |
| 4 | 77.82 | 51 |
| 6 | 118.30 | 51 |
| 8 | 157.65 | 51 |
| 10 | 197.97 | 51 |
| 12 | 239.05 | 50 |
| 14 | 310.06 | 45 |
| 16 | 391.36 | 41 |
| 18 | 441.63 | 41 |
| 20 | 495.88 | 40 |
| 22 | 543.69 | 40 |
| 24 | 591.51 | 41 |
| 26 | 645.98 | 40 |
| 28 | 697.37 | 40 |
| 30 | 749.37 | 40 |
| 32 | 784.63 | 41 |
## Analysis
The benchmark results demonstrate the following performance characteristics with mimalloc as the default allocator:
1. **Best Performance**: Cloned engines with cloned inputs consistently deliver the highest throughput
2. **Configuration Performance Hierarchy**:
- Cloned engines, cloned inputs: Best performance (optimal configuration)
- Cloned engines, fresh inputs: ~27% reduction from optimal
- Fresh engines, cloned inputs: ~87% reduction from optimal
- Fresh engines, fresh inputs: ~87% reduction from optimal
3. **Scaling Patterns with mimalloc**:
- Performance degrades with increased thread count due to contention, but mimalloc provides better thread scaling characteristics
- Best throughput achieved at 1 thread for cloned engine configurations
- Fresh engine configurations show poor scaling across all thread counts
- The use of mimalloc as the default allocator has improved multi-threaded performance and reduced contention
4. **Engine Creation Overhead**: Fresh engine creation is a significant performance bottleneck (~7-8x slower than cloned engines)
5. **Input Processing**: Fresh input generation adds moderate overhead (~27% impact compared to cloned inputs)
6. **Thread Contention**: Performance degradation occurs with higher thread counts across all configurations, though mimalloc helps mitigate some allocation-related contention

View File

@@ -0,0 +1,228 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use regorus::{Engine, Value};
use std::collections::HashMap;
use std::hint::black_box;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::time::Duration;
mod policy_data;
fn multi_threaded_eval(
num_threads: usize,
evals_per_thread: usize,
use_cloned_engines: bool,
use_cloned_inputs: bool,
) -> (std::time::Duration, HashMap<String, usize>, usize) {
// Complex policies with multiple valid inputs for each
let policies_with_inputs = policy_data::policies_with_inputs();
// Policy names for tracking
let policy_names = policy_data::policy_names()
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
// Initialize policy evaluation counters
let policy_counters = Arc::new(Mutex::new(HashMap::new()));
for policy_name in &policy_names {
policy_counters
.lock()
.unwrap()
.insert(policy_name.to_string(), 0);
}
let barrier = Arc::new(Barrier::new(num_threads));
let mut handles = Vec::with_capacity(num_threads);
let total_evals = Arc::new(Mutex::new(0usize));
for thread_id in 0..num_threads {
let barrier = barrier.clone();
let policies_with_inputs = policies_with_inputs.clone();
let policy_names = policy_names.clone();
let policy_counters = policy_counters.clone();
let total_evals = total_evals.clone();
handles.push(thread::spawn(move || {
let mut elapsed = std::time::Duration::ZERO;
// Pre-create engines if using cloned engines
let engines = if use_cloned_engines {
Some(
policies_with_inputs
.iter()
.map(|(policy, _)| {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
{
// Warm up the engine to ensure it's fully prepared for evaluation.
// This prevents each cloned engine from repeating preparation work.
engine.set_input(Value::new_object());
let _ = engine.eval_rule("data.bench.allow".to_string());
}
engine
})
.collect::<Vec<_>>(),
)
} else {
None
};
// Pre-parse inputs if using cloned inputs
let parsed_inputs = if use_cloned_inputs {
Some(
policies_with_inputs
.iter()
.map(|(_, inputs)| {
inputs
.iter()
.map(|input_str| regorus::Value::from_json_str(input_str).unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
)
} else {
None
};
barrier.wait();
for i in 0..evals_per_thread {
// Use different policy for each iteration - thread_id ensures different threads
// start with different policies for better load distribution
let policy_idx = (thread_id + i) % policies_with_inputs.len();
let (policy, inputs) = &policies_with_inputs[policy_idx];
// Use different input for the same policy based on iteration - thread_id ensures
// different threads start with different inputs for better load distribution
let input_idx = (thread_id + i) % inputs.len();
let input = &inputs[input_idx];
let start = std::time::Instant::now();
let result = {
let mut engine = if use_cloned_engines {
engines.as_ref().unwrap()[policy_idx].clone()
} else {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
engine
};
let input_value = if use_cloned_inputs {
parsed_inputs.as_ref().unwrap()[policy_idx][input_idx].clone()
} else {
regorus::Value::from_json_str(input).unwrap()
};
engine.set_input(input_value);
engine.eval_rule("data.bench.allow".to_string())
// Engine cleanup/drop time is included in measurement to reflect
// real-world total cost of policy evaluation lifecycle
};
elapsed += start.elapsed();
// Track total and successful evaluations
{
let mut total = total_evals.lock().unwrap();
*total += 1;
}
if result.is_ok() {
if let Some(policy_name) = policy_names.get(policy_idx) {
let mut counters = policy_counters.lock().unwrap();
*counters.entry(policy_name.to_string()).or_insert(0) += 1;
}
}
}
elapsed
}));
}
let mut total = std::time::Duration::ZERO;
for handle in handles {
total += handle.join().unwrap();
}
let final_counters = policy_counters.lock().unwrap().clone();
let total_evals = *total_evals.lock().unwrap();
(total, final_counters, total_evals)
}
fn criterion_benchmark(c: &mut Criterion) {
let max_threads = num_cpus::get() * 2;
println!("Running benchmark with max_threads: {}", max_threads);
let evals_per_thread = 1000;
// Benchmark all combinations of cloned engines and inputs
for use_cloned_engines in [true, false] {
for use_cloned_inputs in [true, false] {
let group_name = match (use_cloned_engines, use_cloned_inputs) {
(true, true) => "cloned_engines , cloned_inputs ",
(true, false) => "cloned_engines , fresh_inputs ",
(false, true) => "fresh_engines , cloned_inputs ",
(false, false) => "fresh_engines , fresh_inputs ",
};
let mut group = c.benchmark_group(group_name);
group.measurement_time(Duration::from_secs(5));
// Test specific thread counts: powers of 2 + some intermediate values
let thread_counts: Vec<usize> = (1..=max_threads)
.filter(|&n| {
n == 1 || // Always test single-threaded
n % 2 == 0 || // Always test even threads
n == max_threads // Maximum threads
})
.collect();
for threads in thread_counts {
let total_evals = threads * evals_per_thread;
group.throughput(Throughput::Elements(total_evals as u64));
group.bench_with_input(
BenchmarkId::new("eval", format!(" {threads} threads")),
&threads,
|b, &threads| {
b.iter_custom(|iters| {
let evals_per_thread = evals_per_thread * (iters as usize);
let (duration, policy_counters, total_evals_aggregated) = multi_threaded_eval(
black_box(threads),
black_box(evals_per_thread),
black_box(use_cloned_engines),
black_box(use_cloned_inputs),
);
// Sanity check: Ensure the expected number of evaluations matches the actual number performed per iteration batch.
// total_evals is the expected number for this batch, total_evals_aggregated is the sum over all iters.
assert_eq!(total_evals, total_evals_aggregated/iters as usize);
// On one iteration, print policy evaluation statistics
if iters == 1 {
// println!("\nPolicy Evaluation Statistics:");
for (policy_name, count) in &policy_counters {
// println!(" {}: {} evaluations", policy_name, count);
if *count == 0 {
println!("\x1b[31mERROR: Policy '{}' was never evaluated successfully!\x1b[0m", policy_name);
}
}
}
duration
});
},
);
}
group.finish();
}
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -0,0 +1,117 @@
// This module provides the full set of policies, inputs, and policy names for evaluation benchmarks.
// Policies and inputs are now loaded from external files.
use std::fs;
use std::path::Path;
pub fn policies_with_inputs() -> Vec<(String, Vec<String>)> {
let policy_with_input_files = [
(
"rbac_policy.rego",
vec!["rbac_input.json", "rbac_input2.json", "rbac_input3.json"],
),
(
"api_access_policy.rego",
vec![
"api_access_input.json",
"api_access_input2.json",
"api_access_input3.json",
],
),
(
"data_sensitivity_policy.rego",
vec![
"data_sensitivity_input.json",
"data_sensitivity_input2.json",
"data_sensitivity_input3.json",
],
),
(
"time_based_policy.rego",
vec![
"time_based_input.json",
"time_based_input2.json",
"time_based_input3.json",
],
),
(
"data_processing_policy.rego",
vec![
"data_processing_input.json",
"data_processing_input2.json",
"data_processing_input3.json",
],
),
(
"azure_vm_policy.rego",
vec![
"azure_vm_input.json",
"azure_vm_input2.json",
"azure_vm_input3.json",
],
),
(
"azure_storage_policy.rego",
vec![
"azure_storage_input.json",
"azure_storage_input2.json",
"azure_storage_input3.json",
],
),
(
"azure_keyvault_policy.rego",
vec![
"azure_keyvault_input.json",
"azure_keyvault_input2.json",
"azure_keyvault_input3.json",
],
),
(
"azure_nsg_policy.rego",
vec![
"azure_nsg_input.json",
"azure_nsg_input2.json",
"azure_nsg_input3.json",
],
),
];
let mut policies_and_inputs = Vec::new();
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("benches")
.join("evaluation")
.join("test_data");
for (policy_file, input_files) in policy_with_input_files.iter() {
let policy_path = base_dir.join("policies").join(policy_file);
let policy_content = fs::read_to_string(&policy_path)
.unwrap_or_else(|e| panic!("Failed to read policy file {:?}: {}", policy_path, e));
let mut input_contents = Vec::new();
for input_file in input_files {
let input_path = base_dir.join("inputs").join(input_file);
let input_content = fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("Failed to read input file {:?}: {}", input_path, e));
input_contents.push(input_content);
}
policies_and_inputs.push((policy_content, input_contents));
}
policies_and_inputs
}
pub fn policy_names() -> Vec<&'static str> {
vec![
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy",
]
}

View File

@@ -0,0 +1,15 @@
{
"request": {
"method": "GET",
"path": "/api/v1/users/123"
},
"user": {
"id": "user123",
"scope": ["read:users", "write:users"],
"department": "engineering"
},
"resource": {
"owner": "user123",
"type": "user"
}
}

View File

@@ -0,0 +1,15 @@
{
"request": {
"method": "POST",
"path": "/api/v1/users"
},
"user": {
"id": "user456",
"scope": ["write:users", "admin:users"],
"department": "engineering"
},
"resource": {
"owner": "user456",
"type": "user"
}
}

View File

@@ -0,0 +1,15 @@
{
"request": {
"method": "DELETE",
"path": "/api/v1/users/789"
},
"user": {
"id": "admin123",
"scope": ["admin:users"],
"department": "security"
},
"resource": {
"owner": "user789",
"type": "user"
}
}

View File

@@ -0,0 +1,16 @@
{
"vault": {
"name": "mykeyvault",
"location": "eastus",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enablePurgeProtection": true,
"networkAcls": {
"defaultAction": "Deny",
"bypass": "AzureServices"
},
"tags": {
"environment": "production"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"vault": {
"name": "devkeyvault",
"location": "westus2",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 30,
"enablePurgeProtection": false,
"networkAcls": {
"defaultAction": "Allow",
"bypass": "AzureServices"
},
"tags": {
"environment": "development"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"vault": {
"name": "prodkeyvault",
"location": "eastus",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enablePurgeProtection": true,
"networkAcls": {
"defaultAction": "Deny",
"bypass": "AzureServices"
},
"tags": {
"environment": "production"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "10.0.0.0/24",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "80",
"priority": 1001
}
}

View File

@@ -0,0 +1,13 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "172.16.0.0/16",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "22",
"priority": 1200
}
}

View File

@@ -0,0 +1,13 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "203.0.113.0/24",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "443",
"priority": 300
}
}

View File

@@ -0,0 +1,15 @@
{
"account": {
"name": "mystorageaccount",
"tier": "Standard",
"replication": "LRS",
"location": "eastus",
"tags": {
"environment": "production"
}
},
"container": {
"name": "data",
"publicAccess": "None"
}
}

View File

@@ -0,0 +1,15 @@
{
"account": {
"name": "devstorageaccount",
"tier": "Premium",
"replication": "LRS",
"location": "westus2",
"tags": {
"environment": "production"
}
},
"container": {
"name": "logs",
"publicAccess": "None"
}
}

View File

@@ -0,0 +1,15 @@
{
"account": {
"name": "prodstorageaccount",
"tier": "Standard",
"replication": "GRS",
"location": "eastus",
"tags": {
"environment": "production"
}
},
"container": {
"name": "backups",
"publicAccess": "None"
}
}

View File

@@ -0,0 +1,14 @@
{
"vm": {
"size": "Standard_D2s_v3",
"os": "Linux",
"location": "eastus",
"tags": {
"environment": "production",
"department": "engineering"
}
},
"user": {
"department": "engineering"
}
}

View File

@@ -0,0 +1,14 @@
{
"vm": {
"size": "Standard_B1s",
"os": "Windows",
"location": "westus2",
"tags": {
"environment": "dev",
"department": "marketing"
}
},
"user": {
"department": "marketing"
}
}

View File

@@ -0,0 +1,14 @@
{
"vm": {
"size": "Standard_D4s_v3",
"os": "Linux",
"location": "eastus",
"tags": {
"environment": "production",
"department": "engineering"
}
},
"user": {
"department": "engineering"
}
}

View File

@@ -0,0 +1,16 @@
{
"operation": "collect",
"data": {
"type": "email",
"source": "user_input"
},
"consent": {
"given": true,
"purpose": "marketing",
"date": "2023-01-15"
},
"user": {
"age": 25,
"location": "US"
}
}

View File

@@ -0,0 +1,16 @@
{
"operation": "process",
"data": {
"type": "survey_response",
"source": "user_input"
},
"consent": {
"given": true,
"purpose": "analytics",
"date": "2023-06-15"
},
"user": {
"age": 30,
"location": "US"
}
}

View File

@@ -0,0 +1,16 @@
{
"operation": "delete",
"data": {
"type": "user_profile",
"source": "database"
},
"consent": {
"given": false,
"purpose": "none",
"date": "2022-01-01"
},
"user": {
"age": 16,
"location": "EU"
}
}

View File

@@ -0,0 +1,13 @@
{
"data": {
"type": "user_profile",
"classification": "personal",
"contains_pii": true,
"region": "EU"
},
"user": {
"clearance": "confidential",
"location": "EU"
},
"operation": "read"
}

View File

@@ -0,0 +1,13 @@
{
"data": {
"type": "financial_report",
"classification": "confidential",
"contains_pii": false,
"region": "US"
},
"user": {
"clearance": "secret",
"location": "US"
},
"operation": "read"
}

View File

@@ -0,0 +1,13 @@
{
"data": {
"type": "public_announcement",
"classification": "public",
"contains_pii": false,
"region": "GLOBAL"
},
"user": {
"clearance": "public",
"location": "EU"
},
"operation": "read"
}

View File

@@ -0,0 +1,12 @@
{
"user": {
"name": "alice",
"roles": ["viewer", "editor"]
},
"resource": {
"name": "document1",
"type": "document",
"owner": "alice"
},
"action": "read"
}

View File

@@ -0,0 +1,12 @@
{
"user": {
"name": "bob",
"roles": ["admin"]
},
"resource": {
"name": "document2",
"type": "document",
"owner": "bob"
},
"action": "write"
}

View File

@@ -0,0 +1,12 @@
{
"user": {
"name": "charlie",
"roles": ["viewer"]
},
"resource": {
"name": "document3",
"type": "document",
"owner": "alice"
},
"action": "read"
}

View File

@@ -0,0 +1,11 @@
{
"time": "09:30:00",
"day": "monday",
"user": {
"role": "employee",
"shift": "day"
},
"request": {
"urgent": false
}
}

View File

@@ -0,0 +1,11 @@
{
"time": "14:30:00",
"day": "wednesday",
"user": {
"role": "employee",
"shift": "day"
},
"request": {
"urgent": false
}
}

View File

@@ -0,0 +1,11 @@
{
"time": "22:00:00",
"day": "friday",
"user": {
"role": "admin",
"shift": "night"
},
"request": {
"urgent": true
}
}

View File

@@ -0,0 +1,13 @@
package bench
default allow := false
valid_api_paths := ["/api/v1/", "/api/v2/", "/api/v3/"]
allow if {
input.request.method == "GET"
some path in valid_api_paths
startswith(input.request.path, path)
input.user.authenticated == true
time.now_ns() - input.user.login_time < 86400000000000 # 24 hours in nanoseconds
}

View File

@@ -0,0 +1,28 @@
package bench
default allow := false
# Azure Key Vault access policy
valid_operations := [
"Microsoft.KeyVault/vaults/keys/read",
"Microsoft.KeyVault/vaults/secrets/read",
"Microsoft.KeyVault/vaults/certificates/read"
]
vault_admins := ["admin@company.com", "security@company.com"]
allow if {
input.operation in valid_operations
input.principal.type == "ServicePrincipal"
input.principal.appId != ""
input.resource.properties.enableSoftDelete == true
input.resource.properties.enablePurgeProtection == true
time.now_ns() - input.principal.createdTime < 31536000000000000 # Less than 1 year old
}
allow if {
input.operation in valid_operations
input.principal.type == "User"
input.principal.userPrincipalName in vault_admins
input.context.conditionalAccess.compliant == true
}

View File

@@ -0,0 +1,31 @@
package bench
default allow := false
# Azure Network Security Group rules policy
dangerous_ports := [22, 3389, 1433, 3306, 5432, 6379, 27017]
internal_networks := ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
is_internal_source if {
some network in internal_networks
net.cidr_contains(network, input.rule.sourceAddressPrefix)
}
allow if {
input.operation == "Microsoft.Network/networkSecurityGroups/securityRules/write"
input.rule.direction == "Inbound"
input.rule.access == "Allow"
input.rule.destinationPortRange != "*"
not input.rule.destinationPortRange in dangerous_ports
input.rule.sourceAddressPrefix != "*"
input.rule.sourceAddressPrefix != "Internet"
}
allow if {
input.operation == "Microsoft.Network/networkSecurityGroups/securityRules/write"
input.rule.direction == "Inbound"
input.rule.access == "Allow"
input.rule.destinationPortRange in dangerous_ports
is_internal_source
input.rule.priority >= 1000
}

View File

@@ -0,0 +1,17 @@
package bench
default allow := false
# Azure Storage Account security policy
required_encryption_algorithms := ["AES256", "RSA-OAEP"]
allow if {
input.operation == "Microsoft.Storage/storageAccounts/write"
input.resource.properties.supportsHttpsTrafficOnly == true
input.resource.properties.minimumTlsVersion == "TLS1_2"
input.resource.properties.encryption.services.blob.enabled == true
input.resource.properties.encryption.keySource == "Microsoft.Storage"
input.resource.properties.allowBlobPublicAccess == false
input.resource.properties.networkAcls.defaultAction == "Deny"
count(input.resource.properties.networkAcls.ipRules) > 0
}

View File

@@ -0,0 +1,20 @@
package bench
default allow := false
# Azure VM deployment policy
allowed_vm_sizes := [
"Standard_B1s", "Standard_B2s", "Standard_B4ms",
"Standard_D2s_v3", "Standard_D4s_v3", "Standard_F2s_v2"
]
allowed_regions := ["eastus", "westus2", "northeurope", "southeastasia"]
allow if {
input.operation == "Microsoft.Compute/virtualMachines/write"
input.resource.properties.hardwareProfile.vmSize in allowed_vm_sizes
input.resource.location in allowed_regions
input.resource.properties.osProfile.adminPassword == null # Require SSH keys
count(input.resource.tags) > 0 # Must have tags
input.resource.tags.environment in ["dev", "test", "prod"]
}

View File

@@ -0,0 +1,28 @@
package bench
default allow := false
# Complex data filtering and aggregation
sensitive_fields := ["ssn", "credit_card", "password"]
contains_sensitive_data if {
some field in sensitive_fields
object.get(input.data, field, null) != null
}
user_clearance_level := object.get(input.user.attributes, "clearance", 0)
required_clearance := 3 if contains_sensitive_data else := 1
allow if {
user_clearance_level >= required_clearance
input.operation in ["read", "export"]
count(input.data) > 0
count(input.data) <= 1000 # Limit data size
}
allow if {
input.user.role == "data_processor"
input.operation == "transform"
not contains_sensitive_data
}

View File

@@ -0,0 +1,25 @@
package bench
default allow := false
rbac_roles := {
"admin": ["read", "write", "delete", "admin"],
"manager": ["read", "write"],
"user": ["read"]
}
user_permissions contains perm if {
some role in input.user.roles
perm := rbac_roles[role][_]
}
allow if {
input.action in user_permissions
input.resource.owner == input.user.id
}
allow if {
input.action in user_permissions
input.resource.public == true
input.action == "read"
}

View File

@@ -0,0 +1,10 @@
package bench
default allow := false
allow if {
input.user.role == "admin"
input.action in ["read", "write", "delete"]
input.resource.classification in ["public", "internal"]
count(input.user.permissions) > 0
}

View File

@@ -0,0 +1,23 @@
package bench
default allow := false
# Time-based access control with complex conditions
business_hours if {
hour := time.clock([time.now_ns(), "America/New_York"])[0]
hour >= 9
hour < 17
}
allow if {
input.user.department in ["engineering", "product"]
input.action == "deploy"
business_hours
count([x | x := input.approvals[_]; x.status == "approved"]) >= 2
}
allow if {
input.user.emergency_access == true
input.action in ["read", "diagnose"]
input.justification != ""
}

View File

@@ -0,0 +1,186 @@
use std::hint::black_box;
use regorus::{Engine, Value};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use serde_json::json;
fn engine_with_policy(policy: &str) -> Engine {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
engine
}
fn eval_principal(engine: &mut Engine) {
engine.set_input(black_box(json!({"principal": "admin"}).into()));
let result = engine
.eval_rule(black_box("data.bench.allow".to_string()))
.unwrap();
assert_eq!(result, true.into());
}
fn allow_with_simple_equality(c: &mut Criterion) {
c.bench_function("simple equality check with constant", |b| {
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal == "admin"
"#,
);
b.iter(|| eval_principal(&mut engine))
});
c.bench_function("simple equality check with data", |b| {
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal == data.allowed_principal
"#,
);
engine
.add_data(json!({"allowed_principal": "admin"}).into())
.unwrap();
b.iter(|| eval_principal(&mut engine))
});
}
fn allow_with_simple_membership(c: &mut Criterion) {
let generate_principals = |n: usize| {
(0..n)
.map(|i| i.to_string())
.chain(std::iter::once("admin".to_string()))
.collect::<Vec<_>>()
};
let mut group = c.benchmark_group("allow with simple membership");
for size in [32, 64, 128, 512, 1024, 2048].iter() {
group.bench_with_input(BenchmarkId::new("with constant", size), size, |b, &size| {
let principals = generate_principals(size).join("\",\"");
let mut engine = engine_with_policy(&format!(
r#"
package bench
allowed_principals := {{
"{principals}"
}}
allow if input.principal in allowed_principals
"#
));
b.iter(|| eval_principal(&mut engine))
});
group.bench_with_input(BenchmarkId::new("with data", size), size, |b, &size| {
let principals = generate_principals(size);
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal in data.allowed_principals
"#,
);
engine
.add_data(json!({"allowed_principals": principals}).into())
.unwrap();
b.iter(|| eval_principal(&mut engine))
});
}
group.finish();
}
fn clone(c: &mut Criterion) {
// Use Arc<BtreeMap> as a reference. Clone will only increment
// the reference count.
let mut m = std::collections::BTreeMap::default();
m.insert(1, 2);
let m = std::sync::Arc::new(m);
c.bench_function("clone: Arc<BTreeMap>", |b| {
b.iter(|| {
let _ = m.clone();
})
});
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_policy_from_file("tests/aci/framework.rego")
.unwrap();
engine.add_policy_from_file("tests/aci/api.rego").unwrap();
engine
.add_policy_from_file("tests/aci/policy.rego")
.unwrap();
engine
.add_data(Value::from_json_file("tests/aci/data.json").expect("failed to load data.json"))
.expect("failed to add data");
engine.set_input(
Value::from_json_file("tests/aci/input.json").expect("failed to load input.json"),
);
// An engine without preparation will not have processed fields populated.
c.bench_function("clone: engine with aci policies", |b| {
b.iter(|| {
let _ = engine.clone();
})
});
// Trigger engine preparation.
let _ = engine.eval_query("data.framework.mount_overlay".to_string(), false);
// Prepared engine will have many more fields populated. But the fields are
// immutable after preparation and will be shared between clones.
c.bench_function("clone: prepared engine with aci policies", |b| {
b.iter(|| {
let _ = engine.clone();
})
});
}
fn aci_policy_eval(c: &mut Criterion) {
let mut group = c.benchmark_group("ACI Policy Eval");
let rules = ["data.policy.mount_overlay", "data.policy.mount_device"];
for rule in rules {
group.bench_with_input(BenchmarkId::new("rule", rule), &rule, |b, rule| {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_policy_from_file("tests/aci/api.rego")
.expect("failed to add api.rego");
engine
.add_policy_from_file("tests/aci/framework.rego")
.expect("failed to add framework.rego");
engine
.add_policy_from_file("tests/aci/policy.rego")
.expect("failed to add policy.rego");
engine
.add_data(
Value::from_json_file("tests/aci/data.json").expect("failed to load data.json"),
)
.expect("failed to add data");
let input =
Value::from_json_file("tests/aci/input.json").expect("failed to load input.json");
engine.set_input(input.clone());
engine.eval_rule(rule.to_string()).unwrap();
b.iter(|| {
engine.eval_rule(rule.to_string()).unwrap();
})
});
}
group.finish();
}
criterion_group!(
benches,
allow_with_simple_equality,
allow_with_simple_membership,
clone,
aci_policy_eval
);
criterion_main!(benches);

View File

@@ -0,0 +1,886 @@
use criterion::{criterion_group, criterion_main, Criterion};
use regorus::Value;
use regorus::{Schema, SchemaValidator};
use serde_json::json;
// Observed: validate_string - 3.19 ns/iter
fn bench_string_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "string",
"minLength": 3,
"maxLength": 10
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("hello");
c.bench_function("validate_string", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_number - 146.5 ns/iter
fn bench_number_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "number",
"minimum": 0.0,
"maximum": 100.0
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(42.5);
c.bench_function("validate_number", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_array - 95.0 ns/iter
fn bench_array_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "integer" },
"minItems": 2,
"maxItems": 5
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!([1, 2, 3]));
c.bench_function("validate_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_object - 126.9 ns/iter
fn bench_object_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name", "age"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({"name": "Alice", "age": 30}));
c.bench_function("validate_object", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_complex_nested - 710.5 ns/iter
fn bench_complex_nested_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"id": { "type": "string" },
"profile": {
"type": "object",
"properties": {
"email": { "type": "string" },
"roles": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["email", "roles"]
}
},
"required": ["id", "profile"]
},
"active": { "type": "boolean" }
},
"required": ["user", "active"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"user": {
"id": "u123",
"profile": {
"email": "alice@example.com",
"roles": ["admin", "user"]
}
},
"active": true
}));
c.bench_function("validate_complex_nested", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_string_pattern - 29.99 µs/iter
fn bench_string_pattern_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "string",
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("user@example.com");
c.bench_function("validate_string_pattern", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_enum - 7.26 ns/iter
fn bench_enum_validation(c: &mut Criterion) {
let schema_json = json!({
"enum": ["pending", "approved", "rejected", "cancelled"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("approved");
c.bench_function("validate_enum", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_boolean - 3.22 ns/iter
fn bench_boolean_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "boolean"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(true);
c.bench_function("validate_boolean", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_null - 3.22 ns/iter
fn bench_null_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "null"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::Null;
c.bench_function("validate_null", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_large_array - 17.30 µs/iter
fn bench_large_array_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "number" },
"minItems": 50,
"maxItems": 200
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let large_array: Vec<_> = (0..100).map(|i| json!(i as f64)).collect();
let value = Value::from(json!(large_array));
c.bench_function("validate_large_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_deeply_nested - 468.2 ns/iter
fn bench_deeply_nested_object(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"level1": {
"type": "object",
"properties": {
"level2": {
"type": "object",
"properties": {
"level3": {
"type": "object",
"properties": {
"level4": {
"type": "object",
"properties": {
"level5": {
"type": "string"
}
},
"required": ["level5"]
}
},
"required": ["level4"]
}
},
"required": ["level3"]
}
},
"required": ["level2"]
}
},
"required": ["level1"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"level1": {
"level2": {
"level3": {
"level4": {
"level5": "deep value"
}
}
}
}
}));
c.bench_function("validate_deeply_nested", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_mixed_type_array - 1.36 µs/iter
fn bench_mixed_type_array(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": {
"anyOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" }
]
}
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!(["hello", 42, true, "world", 99.5, false]));
c.bench_function("validate_mixed_type_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_additional_properties - 366.4 ns/iter
fn bench_additional_properties(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"additionalProperties": { "type": "string" },
"required": ["name"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"name": "Alice",
"age": 30,
"city": "New York",
"country": "USA",
"occupation": "Engineer"
}));
c.bench_function("validate_additional_properties", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_array_constraints - 146.2 ns/iter
fn bench_array_constraints(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "string" },
"minItems": 2,
"maxItems": 10
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!(["apple", "banana", "cherry", "date", "elderberry"]));
c.bench_function("validate_array_constraints", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_multi_level - 915.8 ns/iter
fn bench_multi_level_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"profile": {
"type": "object",
"properties": {
"settings": {
"type": "object",
"properties": {
"theme": {
"enum": ["light", "dark", "auto"]
},
"notifications": {
"type": "boolean"
}
},
"required": ["theme"],
"additionalProperties": { "type": "string" }
}
},
"required": ["settings"],
"additionalProperties": { "type": "any" }
}
},
"required": ["profile"],
"additionalProperties": { "type": "any" }
}
},
"required": ["user"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"user": {
"profile": {
"settings": {
"theme": "dark",
"notifications": true,
"language": "en"
},
"avatar": "default.png"
},
"lastLogin": "2024-01-01"
},
"metadata": "extra info"
}));
c.bench_function("validate_multi_level", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Azure Resource Validation Benchmarks
// Observed: validate_azure_vm_resource - 34.74 µs/iter
fn bench_azure_vm_resource_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"type": {
"const": "Microsoft.Compute/virtualMachines"
},
"apiVersion": {
"enum": ["2021-03-01", "2021-07-01", "2022-03-01"]
},
"name": {
"type": "string",
"pattern": "^[a-zA-Z0-9-._]{1,64}$"
},
"location": {
"type": "string",
"description": "Azure region where the VM will be deployed"
},
"properties": {
"type": "object",
"properties": {
"hardwareProfile": {
"type": "object",
"properties": {
"vmSize": {
"enum": ["Standard_B1s", "Standard_B2s", "Standard_D2s_v3", "Standard_D4s_v3"]
}
},
"required": ["vmSize"]
},
"osProfile": {
"type": "object",
"properties": {
"computerName": {
"type": "string"
},
"adminUsername": {
"type": "string"
}
},
"required": ["computerName", "adminUsername"]
}
},
"required": ["hardwareProfile", "osProfile"]
}
},
"required": ["type", "apiVersion", "name", "location", "properties"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2021-03-01",
"name": "my-vm-01",
"location": "eastus",
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B2s"
},
"osProfile": {
"computerName": "my-computer",
"adminUsername": "azureuser"
}
}
}));
c.bench_function("validate_azure_vm_resource", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_storage_resource - 22.12 µs/iter
fn bench_azure_storage_resource_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"type": {
"const": "Microsoft.Storage/storageAccounts"
},
"apiVersion": {
"enum": ["2021-04-01", "2021-06-01", "2022-05-01"]
},
"name": {
"type": "string",
"pattern": "^[a-z0-9]{3,24}$"
},
"location": {
"type": "string"
},
"sku": {
"type": "object",
"properties": {
"name": {
"enum": ["Standard_LRS", "Standard_GRS", "Standard_RAGRS", "Premium_LRS"]
}
},
"required": ["name"]
},
"kind": {
"enum": ["Storage", "StorageV2", "BlobStorage", "FileStorage", "BlockBlobStorage"]
},
"properties": {
"type": "object",
"properties": {
"accessTier": {
"enum": ["Hot", "Cool", "Archive"]
},
"encryption": {
"type": "object",
"properties": {
"services": {
"type": "object"
}
}
}
},
"additionalProperties": { "type": "any" }
}
},
"required": ["type", "apiVersion", "name", "location", "sku", "kind"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "mystorageaccount001",
"location": "westus2",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot",
"encryption": {
"services": {}
}
}
}));
c.bench_function("validate_azure_storage_resource", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_arm_template - 1.99 µs/iter
fn bench_azure_arm_template_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"contentVersion": {
"type": "string"
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"variables": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"resources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"apiVersion": {
"type": "string"
},
"name": {
"type": "string"
},
"location": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"tags": {
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["type", "apiVersion", "name"],
"additionalProperties": { "type": "any" }
}
},
"outputs": {
"type": "object",
"additionalProperties": { "type": "any" }
}
},
"required": ["resources"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string",
"defaultValue": "myVM"
}
},
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2021-03-01",
"name": "[parameters('vmName')]",
"location": "[resourceGroup().location]",
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B1s"
}
},
"tags": {
"environment": "dev",
"project": "test"
}
}
],
"outputs": {
"vmId": {
"type": "string",
"value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]"
}
}
}));
c.bench_function("validate_azure_arm_template", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Azure Policy Effect Validation Benchmarks
// Observed: validate_azure_policy_deny_effect - 188.6 ns/iter
fn bench_azure_policy_deny_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "deny"
},
"description": {
"type": "string"
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "deny",
"description": "Deny resources that don't meet security requirements"
}));
c.bench_function("validate_azure_policy_deny_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_audit_effect - 516.7 ns/iter
fn bench_azure_policy_audit_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "audit"
},
"description": {
"type": "string"
},
"auditDetails": {
"type": "object",
"properties": {
"category": {
"enum": ["security", "compliance", "cost", "operational"]
},
"severity": {
"enum": ["low", "medium", "high", "critical"]
}
},
"additionalProperties": { "type": "any" }
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "audit",
"description": "Audit non-compliant resources",
"auditDetails": {
"category": "security",
"severity": "high"
}
}));
c.bench_function("validate_azure_policy_audit_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_modify_effect - 1.17 µs/iter
fn bench_azure_policy_modify_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "modify"
},
"description": {
"type": "string"
},
"modifyDetails": {
"type": "object",
"properties": {
"roleDefinitionIds": {
"type": "array",
"items": { "type": "string" }
},
"operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"operation": {
"enum": ["add", "replace", "remove"]
},
"field": {
"type": "string"
},
"value": {
"type": "any"
}
},
"required": ["operation", "field"],
"additionalProperties": { "type": "any" }
}
}
},
"required": ["roleDefinitionIds", "operations"],
"additionalProperties": { "type": "any" }
}
},
"required": ["effect", "modifyDetails"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "modify",
"description": "Modify resources to ensure compliance",
"modifyDetails": {
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
],
"operations": [
{
"operation": "add",
"field": "tags.environment",
"value": "production"
}
]
}
}));
c.bench_function("validate_azure_policy_modify_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_complex_effect - 1.40 µs/iter
fn bench_azure_policy_complex_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"enum": ["auditIfNotExists", "deployIfNotExists"]
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"existenceCondition": {
"type": "object",
"properties": {
"field": { "type": "string" },
"equals": { "type": "string" }
},
"required": ["field"],
"additionalProperties": { "type": "any" }
},
"deployment": {
"type": "object",
"properties": {
"properties": {
"type": "object",
"properties": {
"mode": {
"enum": ["incremental", "complete"]
},
"template": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
}
},
"required": ["mode", "template"],
"additionalProperties": { "type": "any" }
}
},
"required": ["properties"],
"additionalProperties": { "type": "any" }
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "deployIfNotExists",
"parameters": {},
"existenceCondition": {
"field": "Microsoft.Security/complianceResults/resourceStatus",
"equals": "OffByPolicy"
},
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": []
},
"parameters": {}
}
}
}));
c.bench_function("validate_azure_policy_complex_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
criterion_group!(
schema_validation_benches,
bench_string_validation,
bench_number_validation,
bench_array_validation,
bench_object_validation,
bench_complex_nested_validation,
bench_string_pattern_validation,
bench_enum_validation,
bench_boolean_validation,
bench_null_validation,
bench_large_array_validation,
bench_deeply_nested_object,
bench_mixed_type_array,
bench_additional_properties,
bench_array_constraints,
bench_multi_level_validation,
bench_azure_vm_resource_validation,
bench_azure_storage_resource_validation,
bench_azure_arm_template_validation,
bench_azure_policy_deny_effect_validation,
bench_azure_policy_audit_effect_validation,
bench_azure_policy_modify_effect_validation,
bench_azure_policy_complex_effect_validation
);
criterion_main!(schema_validation_benches);

View File

@@ -1,97 +1,126 @@
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32)
#include <malloc.h>
#endif
#include "regorus.h"
// Regorus has been built for no_std and cannot access files.
char* file_to_string(const char* file) {
char * buffer = 0;
char *file_to_string(const char *file)
{
char *buffer = 0;
long length;
FILE * f = fopen (file, "rb");
FILE *f = fopen(file, "rb");
if (f)
{
fseek (f, 0, SEEK_END);
length = ftell (f);
fseek (f, 0, SEEK_SET);
buffer = malloc (length + 1);
buffer[length] = '\0';
if (buffer)
{
fread (buffer, 1, length, f);
}
fclose (f);
fseek(f, 0, SEEK_END);
length = ftell(f);
fseek(f, 0, SEEK_SET);
buffer = malloc(length + 1);
buffer[length] = '\0';
if (buffer)
{
fread(buffer, 1, length, f);
}
fclose(f);
}
return buffer;
}
// If regorus is built with custom-allocator, then provide implementation.
uint8_t* regorus_aligned_alloc(size_t alignment, size_t size) {
return (uint8_t*) aligned_alloc(alignment, size);
uint8_t *regorus_aligned_alloc(size_t alignment, size_t size)
{
// Aligned allocations must respect platform quirks: Windows offers
// _aligned_malloc/_aligned_free, while macOS/Linux reject aligned_alloc
// calls when size is not a multiple of alignment, so we rely on
// posix_memalign for the no_std build.
#if defined(_WIN32)
return (uint8_t *)_aligned_malloc(size, alignment);
#else
void *ptr = NULL;
// posix_memalign requires alignment to be at least sizeof(void*)
// and a power of two; normalize here so small requests succeed.
if (alignment < sizeof(void *))
{
alignment = sizeof(void *);
}
if (posix_memalign(&ptr, alignment, size) != 0)
{
return NULL;
}
return (uint8_t *)ptr;
#endif
}
void regorus_free(uint8_t* ptr) {
void regorus_free(uint8_t *ptr)
{
#if defined(_WIN32)
_aligned_free(ptr);
#else
free(ptr);
#endif
}
int main() {
int main()
{
// Create engine.
RegorusEngine* engine = regorus_engine_new();
RegorusEngine *engine = regorus_engine_new();
RegorusResult r;
char* buffer = NULL;
char *buffer = NULL;
// Turn on rego v0 since policy uses v0.
r = regorus_engine_set_rego_v0(engine, true);
if (r.status != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
// Load policies.
r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "api.rego", (buffer = file_to_string("../../../tests/aci/api.rego")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "policy.rego", (buffer = file_to_string("../../../tests/aci/policy.rego")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
r = regorus_engine_add_data_json(engine, (buffer = file_to_string("../../../tests/aci/data.json")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Set input
r = regorus_engine_set_input_json(engine, (buffer = file_to_string("../../../tests/aci/input.json")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Eval rule.
r = regorus_engine_eval_rule(engine, "data.framework.mount_overlay");
if (r.status != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
// Print output
printf("%s", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);

View File

@@ -8,43 +8,43 @@ int main() {
// Turn on rego v0 since policy uses v0.
r = regorus_engine_set_rego_v0(engine, true);
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
// Load policies.
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/api.rego");
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/policy.rego");
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
r = regorus_engine_add_data_from_json_file(engine, "../../../tests/aci/data.json");
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Set input
r = regorus_engine_set_input_from_json_file(engine, "../../../tests/aci/input.json");
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Eval rule.
r = regorus_engine_eval_query(engine, "data.framework.mount_overlay");
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
// Print output
@@ -66,14 +66,14 @@ int main() {
);
// Evaluate rule.
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
r = regorus_engine_set_enable_coverage(engine, true);
regorus_result_drop(r);
r = regorus_engine_eval_query(engine, "data.test.message");
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
// Print output
@@ -82,7 +82,7 @@ int main() {
// Print pretty coverage report.
r = regorus_engine_get_coverage_report_pretty(engine);
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
printf("%s\n", r.output);

View File

@@ -15,6 +15,8 @@ FetchContent_MakeAvailable(Corrosion)
project("regorus-test")
set(CMAKE_CXX_STANDARD 17)
# installable ffi target
corrosion_import_crate(
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
MANIFEST_PATH "../ffi/Cargo.toml"
@@ -31,7 +33,53 @@ corrosion_import_crate(
# Link statically
CRATE_TYPES "cdylib")
include(GNUInstallDirs)
set(regorus_ffi_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}/regorus_ffi)
set(regorus_ffi_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/regorus_ffi)
set(regorus_ffi_LIBDIR ${CMAKE_INSTALL_LIBDIR})
set(regorus_ffi_BINDIR ${CMAKE_INSTALL_BINDIR})
add_library(regorus_ffi::regorus_ffi ALIAS regorus_ffi)
corrosion_install(TARGETS regorus_ffi EXPORT regorus_ffi_targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
target_include_directories(regorus_ffi
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../ffi>
$<INSTALL_INTERFACE:${regorus_ffi_INCLUDEDIR}>
)
set(regorus_ffi_HEADER_FILES
regorus.hpp
../ffi/regorus.ffi.hpp
)
install(FILES ${regorus_ffi_HEADER_FILES}
DESTINATION ${regorus_ffi_INCLUDEDIR}
COMPONENT Devel
)
install(EXPORT regorus_ffi_targets
FILE regorus_ffi_targets.cmake
NAMESPACE regorus_ffi::
DESTINATION ${regorus_ffi_CONFIGDIR}
)
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/regorus_ffiConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/regorus_ffiConfig.cmake
INSTALL_DESTINATION ${regorus_ffi_CONFIGDIR}
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/regorus_ffiConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/corrosion/regorus_ffi_targetsCorrosion.cmake
DESTINATION ${regorus_ffi_CONFIGDIR}
)
# test binary
add_executable(regorus_test main.cpp)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus_ffi)
target_link_libraries(regorus_test regorus_ffi::regorus_ffi)

View File

@@ -11,8 +11,8 @@ namespace regorus {
class Result {
public:
operator bool() const { return result.status == RegorusStatus::RegorusStatusOk; }
bool operator !() const { return result.status != RegorusStatus::RegorusStatusOk; }
operator bool() const { return result.status == RegorusStatus::Ok; }
bool operator !() const { return result.status != RegorusStatus::Ok; }
const char* output() const {
if (*this && result.output) {

View File

@@ -0,0 +1,3 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/regorus_ffi_targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/regorus_ffi_targetsCorrosion.cmake")

421
bindings/csharp/API.md Normal file
View File

@@ -0,0 +1,421 @@
# Regorus C# API Documentation
This document describes the C# API for Regorus, focusing on the compiled policy approach for high-performance policy evaluation.
## Overview
The Regorus C# bindings provide a modern, thread-safe API for compiling and evaluating Open Policy Agent (OPA) Rego policies. The API is designed around pre-compiled policies that can be evaluated efficiently multiple times with different inputs.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ CompiledPolicy Workflow │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Policy Modules │ │ Target/Schema │ │ Static Data │
│ (.rego files) │ │ Registries │ │ (JSON) │
└─────────┬───────┘ └────────┬─────────┘ └─────────┬───────┘
│ │ │
└─────────────────────┼────────────────────────┘
┌─────────────────────────┐
│ Compile │
│ ┌─────────────────────┐│
│ │ Parse & Analyze ││
│ │ Infer Resource Types││
│ │ Build AST & Rules ││
│ │ Target Integration ││
│ └─────────────────────┘│
└─────────────┬───────────┘
┌─────────────────────────┐
│ CompiledPolicy │
│ ┌─────────────────────┐ │
│ │ AST & Rules │ │
│ │ Target Info │ │
│ │ Resource Types │ │
│ │ Function Table │ │
│ │ Compiled Modules │ │
│ └─────────────────────┘ │
└─────────────┬───────────┘
┌─────────────────────┐
│ Service Cache │
│ (Policy Framework, │
│ MS Graph, etc.) │
│ ┌─────────────────┐ │
│ │ CompiledPolicy │ │ ◄─── Same LOCK-FREE policy
│ │ (cached) │ │ instance shared across
│ └─────────────────┘ │ all threads
└─────────┬───────────┘
┌───────┼───────┬───────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Thread 1 │ │ Thread 2 │ │ Thread N │
│ │ │ │ │ │
│ input1 ────▶│ │ input2 ────▶│ │ inputN ────▶│
│ ◄─── result │ │ ◄─── result │ │ ◄─── result │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Key Benefits │
├─────────────────────────────────────────────────────────────────┤
│ ✓ Compile Once, Evaluate Many ✓ Lock-Free Concurrent Eval │
│ ✓ No Re-parsing Overhead ✓ Reference Counting Safety │
│ ✓ Reduced GC Pressure ✓ Proper Resource Management │
│ ✓ Cache-Friendly Design ✓ Target System Integration │
└─────────────────────────────────────────────────────────────────┘
```
## Key Features
- **Pre-compiled Policies**: Compile once, evaluate many times for optimal performance
- **Target System Support**: Built-in support for Azure Policy targets with resource type inference
- **Thread Safety**: All operations are thread-safe without external synchronization
- **Registry Management**: Centralized management of targets and schemas
- **Policy Introspection**: Rich metadata about compiled policies
## Core Classes
### CompiledPolicy
The `CompiledPolicy` class represents a pre-compiled Rego policy that can be evaluated efficiently.
```csharp
public sealed class CompiledPolicy : IDisposable
{
// Evaluate the policy with input data
public string? EvalWithInput(string inputJson);
// Get comprehensive policy metadata
public PolicyInfo GetPolicyInfo();
// Dispose of unmanaged resources
public void Dispose();
}
```
**Thread Safety**: All methods are thread-safe. Multiple threads can call `EvalWithInput()` concurrently, and `Dispose()` will safely wait for active evaluations to complete.
### Compiler
The `Compiler` class provides static methods for compiling policies.
```csharp
public static class Compiler
{
// Compile a policy with a specific entrypoint rule
public static CompiledPolicy CompilePolicyWithEntrypoint(
string dataJson,
IEnumerable<PolicyModule> modules,
string entryPointRule);
// Compile a target-aware policy (requires azure_policy feature)
public static CompiledPolicy CompilePolicyForTarget(
string dataJson,
IEnumerable<PolicyModule> modules);
}
```
### PolicyModule
Represents a single policy module to be compiled. Each PolicyModule corresponds to a Rego file (.rego), and each Rego file defines a Rego package using the `package` declaration at the top of the file.
```csharp
public struct PolicyModule
{
public string Id { get; set; }
public string Content { get; set; }
public PolicyModule(string id, string content);
}
```
**Properties:**
- `Id`: A unique identifier for the module, typically the filename (e.g., "policy.rego", "rules/storage.rego")
- `Content`: The complete Rego policy content, including the `package` declaration and all rules
**Example:**
```csharp
var module = new PolicyModule("storage-policy.rego", @"
package azure.storage
import rego.v1
default allow := false
allow if input.type == ""Microsoft.Storage/storageAccounts""
");
```
### PolicyInfo
Provides comprehensive metadata about a compiled policy.
```csharp
public class PolicyInfo
{
// List of module identifiers
public List<string> ModuleIds { get; set; }
// Target name (for target-aware policies)
public string? TargetName { get; set; }
// Resource types this policy can evaluate
public List<string> ApplicableResourceTypes { get; set; }
// Primary rule/entrypoint
public string EntrypointRule { get; set; }
// Effect rule (for target-aware policies)
public string? EffectRule { get; set; }
// Policy parameters
public List<PolicyParameters> Parameters { get; set; }
}
```
## Registry Classes
### TargetRegistry
Manages target definitions for Azure Policy-style evaluations.
```csharp
public static class TargetRegistry
{
// Register a target from JSON
public static void RegisterFromJson(string targetJson);
// Check if a target exists
public static bool Contains(string name);
// List all registered targets
public static string ListNames();
// Remove a target
public static bool Remove(string name);
// Clear all targets
public static void Clear();
// Get count of registered targets
public static int Count { get; }
// Check if registry is empty
public static bool IsEmpty { get; }
}
```
### SchemaRegistry
Manages schema definitions for validation.
```csharp
public static class SchemaRegistry
{
// Register resource schemas
public static void RegisterResourceSchema(string name, string schemaJson);
public static bool ContainsResourceSchema(string name);
public static string ListResourceSchemas();
// Register effect schemas
public static void RegisterEffectSchema(string name, string schemaJson);
public static bool ContainsEffectSchema(string name);
public static string ListEffectSchemas();
// Clear methods
public static void ClearResourceSchemas();
public static void ClearEffectSchemas();
}
```
## Usage Examples
### Basic Policy Compilation and Evaluation
```csharp
// Define policy modules
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package example
import rego.v1
default allow := false
allow if input.user == ""admin""
")
};
// Compile the policy
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.example.allow");
// Evaluate with different inputs
var result1 = policy.EvalWithInput(@"{""user"": ""admin""}"); // true
var result2 = policy.EvalWithInput(@"{""user"": ""guest""}"); // false
```
### Target-Aware Policy (Azure Policy Style)
```csharp
// Register target definition
TargetRegistry.RegisterFromJson(@"{
""name"": ""azure.storage"",
""resource_schema_selector"": ""type"",
""resource_types"": {
""Microsoft.Storage/storageAccounts"": {
""schema"": { /* JSON Schema */ }
}
}
}");
// Define policy with target
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package policy
import rego.v1
__target__ := ""azure.storage""
default effect := ""deny""
effect := ""allow"" if {
input.type == ""Microsoft.Storage/storageAccounts""
input.properties.supportsHttpsTrafficOnly == true
}
")
};
// Compile for target
using var policy = Compiler.CompilePolicyForTarget("{}", modules);
// Evaluate Azure resource
var resource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": {
""supportsHttpsTrafficOnly"": true
}
}";
var result = policy.EvalWithInput(resource); // "allow"
```
### Policy Introspection
```csharp
// Get policy metadata
var info = policy.GetPolicyInfo();
Console.WriteLine($"Target: {info.TargetName}");
Console.WriteLine($"Effect Rule: {info.EffectRule}");
Console.WriteLine($"Modules: {string.Join(", ", info.ModuleIds)}");
Console.WriteLine($"Resource Types: {string.Join(", ", info.ApplicableResourceTypes)}");
// Access parameters
if (info.Parameters != null && info.Parameters.Count > 0)
{
foreach (var parameterSet in info.Parameters)
{
Console.WriteLine($"Module: {parameterSet.SourceFile}");
foreach (var param in parameterSet.Parameters)
{
Console.WriteLine($"Parameter: {param.Name} ({param.Type})");
if (param.Default != null)
Console.WriteLine($" Default: {param.Default}");
}
}
}
```
### Concurrent Evaluation
```csharp
// CompiledPolicy is thread-safe
var tasks = Enumerable.Range(0, 100).Select(i =>
Task.Run(() => policy.EvalWithInput($@"{{""id"": {i}}}"))
).ToArray();
var results = await Task.WhenAll(tasks);
```
## Performance Considerations
### Compilation Overhead
- Policy compilation has significant overhead due to parsing and analysis
- **Best Practice**: Compile once, reuse many times
- Consider caching compiled policies for repeated use
### Memory Management
- `CompiledPolicy` manages unmanaged resources
- **Always** dispose of compiled policies using `using` statements or explicit `Dispose()`
- Disposal is thread-safe and waits for active evaluations
### Thread Safety
- All classes are thread-safe for concurrent reads/evaluations
- Registry modifications should be done during initialization
- No external synchronization required
## Error Handling
All methods throw `Exception` on errors with descriptive messages:
```csharp
try
{
var policy = Compiler.CompilePolicyWithEntrypoint(data, modules, rule);
var result = policy.EvalWithInput(input);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
```
## Feature Flags
Some functionality requires specific Rust feature flags:
- **azure_policy**: Required for target-aware compilation and policy parameters
- Without this feature, target-related methods will not be available
## Version Compatibility
- Requires .NET Standard 2.0 or later
- Compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+
- Uses System.Text.Json for JSON serialization (added as dependency)
## Best Practices
1. **Compile Once, Evaluate Many**: Pre-compile policies for repeated evaluation
2. **Use Disposable Pattern**: Always dispose of CompiledPolicy instances
3. **Thread-Safe Design**: Take advantage of built-in thread safety
4. **Registry Setup**: Configure targets and schemas during application startup
5. **Error Handling**: Wrap operations in try-catch blocks for robust error handling
6. **Performance Monitoring**: Monitor evaluation times for performance optimization
## Migration from Engine-Based API
If migrating from an engine-based approach:
```csharp
// Old approach (if it existed)
// var engine = new Engine();
// engine.AddPolicy("policy.rego", policyContent);
// engine.SetInputJson(inputJson);
// var result = engine.EvalRule("data.policy.allow");
// New compiled approach
var modules = new[] { new PolicyModule("policy.rego", policyContent) };
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.policy.allow");
var result = policy.EvalWithInput(inputJson);
```
The compiled approach provides better performance for repeated evaluations and clearer resource management.

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>Enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<PropertyGroup>
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
</ItemGroup>
<ItemGroup>
<None Include="../../ffi/target/release/libregorus_ffi.dylib" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Regorus;
namespace Benchmarks
{
public class CompiledPolicyEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFiles = new[]
{
("rbac_policy.rego", new[] { "rbac_input.json", "rbac_input2.json", "rbac_input3.json" }),
("api_access_policy.rego", new[] { "api_access_input.json", "api_access_input2.json", "api_access_input3.json" }),
("data_sensitivity_policy.rego", new[] { "data_sensitivity_input.json", "data_sensitivity_input2.json", "data_sensitivity_input3.json" }),
("time_based_policy.rego", new[] { "time_based_input.json", "time_based_input2.json", "time_based_input3.json" }),
("data_processing_policy.rego", new[] { "data_processing_input.json", "data_processing_input2.json", "data_processing_input3.json" }),
("azure_vm_policy.rego", new[] { "azure_vm_input.json", "azure_vm_input2.json", "azure_vm_input3.json" }),
("azure_storage_policy.rego", new[] { "azure_storage_input.json", "azure_storage_input2.json", "azure_storage_input3.json" }),
("azure_keyvault_policy.rego", new[] { "azure_keyvault_input.json", "azure_keyvault_input2.json", "azure_keyvault_input3.json" }),
("azure_nsg_policy.rego", new[] { "azure_nsg_input.json", "azure_nsg_input2.json", "azure_nsg_input3.json" })
};
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy"
};
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
{
var result = new List<(string Policy, string[] Inputs)>();
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
{
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
var policy = File.ReadAllText(policyPath);
var inputs = inputFiles.Select(inputFile =>
{
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
return File.ReadAllText(inputPath);
}).ToArray();
result.Add((policy, inputs));
}
return result;
}
private static List<CompiledPolicy> PrepareSharedCompiledPolicies()
{
var policiesWithInputs = LoadPoliciesWithInputs();
var compiledPolicies = new List<CompiledPolicy>();
foreach (var (policy, _) in policiesWithInputs)
{
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
compiledPolicies.Add(compiled);
}
return compiledPolicies;
}
public static void RunCompiledPolicyEvaluationBenchmark()
{
var cpuCount = Environment.ProcessorCount;
var maxThreads = cpuCount * 2;
var threadCounts = new List<int> { 1, 2 };
// Add even numbers from 4 to maxThreads
for (int i = 4; i <= maxThreads; i += 2)
{
threadCounts.Add(i);
}
Console.WriteLine($"Running compiled policy benchmark with max_threads: {maxThreads}");
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
Console.WriteLine();
// Benchmark both shared policies and per-iteration compilation
var configurations = new[]
{
(true, "compiled_shared_policies"),
(false, "compiled_per_iteration")
};
foreach (var (useSharedPolicies, groupName) in configurations)
{
Console.WriteLine($"=== {groupName} ===");
foreach (var threads in threadCounts)
{
RunCompiledPolicyBenchmark(threads, useSharedPolicies, groupName);
}
Console.WriteLine();
}
}
public static void RunCompiledPolicyBenchmark(int threads, bool useSharedPolicies, string groupName)
{
const int warmupSeconds = 3;
const int durationSeconds = 3;
var policiesWithInputs = LoadPoliciesWithInputs();
List<CompiledPolicy>? compiledPolicies = null;
if (useSharedPolicies)
{
compiledPolicies = PrepareSharedCompiledPolicies();
}
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
// Warmup phase
var (_, _, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: true);
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
// Actual benchmark phase
var (totalEvaluations, evaluationTime, policyCounters, allocatedBytes) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: false);
// Calculate throughput based on pure evaluation time (consistent with Rust benchmark)
var evalsPerSecond = totalEvaluations / evaluationTime.TotalSeconds;
var kelemsPerSecond = evalsPerSecond / 1000.0;
Console.WriteLine($"{groupName}/eval/{threads} threads");
Console.WriteLine($" time: [{evaluationTime.TotalMilliseconds:F2} ms]");
Console.WriteLine($" thrpt: [{kelemsPerSecond:F2} Kelem/s]");
if (totalEvaluations > 0)
{
var bytesPerEval = allocatedBytes / (double)totalEvaluations;
Console.WriteLine($" alloc: [{bytesPerEval:F2} B/op] (total {allocatedBytes} B)");
}
// Clean up compiled policies if we created them
if (compiledPolicies != null)
{
foreach (var policy in compiledPolicies)
{
policy.Dispose();
}
}
// Verify that all policies were evaluated
var allEvaluated = policyCounters.Values.All(count => count > 0);
if (allEvaluated)
{
Console.WriteLine("✓ All policies were evaluated successfully");
}
else
{
Console.WriteLine("ERROR: Some policies were never evaluated successfully!");
}
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters, long allocatedBytes) RunBenchmarkPhase(
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
List<CompiledPolicy>? compiledPolicies,
bool useSharedPolicies,
bool isWarmup)
{
var barrier = new Barrier(threads);
var tasks = new Task[threads];
var policyCounters = new Dictionary<string, int>();
var evaluationTimes = new Dictionary<int, TimeSpan>();
var lockObject = new object();
var stopExecution = false;
long allocatedBytes = 0;
// Initialize counters
foreach (var policyName in PolicyNames)
{
policyCounters[policyName] = 0;
}
var stopwatch = Stopwatch.StartNew();
for (int threadId = 0; threadId < threads; threadId++)
{
int tid = threadId;
tasks[threadId] = Task.Run(() =>
{
long allocationStart = 0;
if (!isWarmup)
{
allocationStart = GC.GetAllocatedBytesForCurrentThread();
}
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
int policyIdx = (tid + evaluationCount) % policiesWithInputs.Count;
var (policy, inputs) = policiesWithInputs[policyIdx];
// Use different input for the same policy based on iteration
int inputIdx = evaluationCount % inputs.Length;
var input = inputs[inputIdx];
try
{
// Measure only the evaluation call
var evalStopwatch = Stopwatch.StartNew();
if (useSharedPolicies)
{
var result = compiledPolicies![policyIdx].EvalWithInput(input);
}
else
{
// Compile policy in each iteration
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
var result = compiled.EvalWithInput(input);
compiled.Dispose();
}
evalStopwatch.Stop();
localEvaluationTime += evalStopwatch.Elapsed;
// Track successful evaluations (only during actual benchmark, not warmup)
if (!isWarmup)
{
lock (lockObject)
{
policyCounters[PolicyNames[policyIdx]]++;
}
}
}
catch (Exception)
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
lock (lockObject)
{
if (!evaluationTimes.ContainsKey(tid))
evaluationTimes[tid] = TimeSpan.Zero;
evaluationTimes[tid] = localEvaluationTime;
}
var allocationEnd = GC.GetAllocatedBytesForCurrentThread();
System.Threading.Interlocked.Add(ref allocatedBytes, allocationEnd - allocationStart);
}
});
}
// Stop execution after the specified duration
Task.Delay(TimeSpan.FromSeconds(durationSeconds)).ContinueWith(_ => stopExecution = true);
Task.WaitAll(tasks);
stopwatch.Stop();
var totalEvaluations = policyCounters.Values.Sum();
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
// Use pure evaluation time (consistent with Rust benchmark)
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
return (totalEvaluations, evaluationTime, policyCounters, allocatedBytes);
}
}
}

View File

@@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Regorus;
namespace Benchmarks
{
public class EngineEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFiles = new[]
{
("rbac_policy.rego", new[] { "rbac_input.json", "rbac_input2.json", "rbac_input3.json" }),
("api_access_policy.rego", new[] { "api_access_input.json", "api_access_input2.json", "api_access_input3.json" }),
("data_sensitivity_policy.rego", new[] { "data_sensitivity_input.json", "data_sensitivity_input2.json", "data_sensitivity_input3.json" }),
("time_based_policy.rego", new[] { "time_based_input.json", "time_based_input2.json", "time_based_input3.json" }),
("data_processing_policy.rego", new[] { "data_processing_input.json", "data_processing_input2.json", "data_processing_input3.json" }),
("azure_vm_policy.rego", new[] { "azure_vm_input.json", "azure_vm_input2.json", "azure_vm_input3.json" }),
("azure_storage_policy.rego", new[] { "azure_storage_input.json", "azure_storage_input2.json", "azure_storage_input3.json" }),
("azure_keyvault_policy.rego", new[] { "azure_keyvault_input.json", "azure_keyvault_input2.json", "azure_keyvault_input3.json" }),
("azure_nsg_policy.rego", new[] { "azure_nsg_input.json", "azure_nsg_input2.json", "azure_nsg_input3.json" })
};
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy"
};
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
{
var result = new List<(string Policy, string[] Inputs)>();
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
{
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
var policy = File.ReadAllText(policyPath);
var inputs = inputFiles.Select(inputFile =>
{
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
return File.ReadAllText(inputPath);
}).ToArray();
result.Add((policy, inputs));
}
return result;
}
private static List<Engine> PrepareClonedEngines()
{
var policiesWithInputs = LoadPoliciesWithInputs();
var engines = new List<Engine>();
foreach (var (policy, _) in policiesWithInputs)
{
var engine = new Engine();
engine.AddPolicy("policy.rego", policy);
// Warm up the engine to ensure it's fully prepared for evaluation
// This prevents each cloned engine from repeating preparation work
engine.SetInputJson("{}");
try
{
engine.EvalRule("data.bench.allow");
}
catch
{
// Ignore warmup errors
}
engines.Add(engine);
}
return engines;
}
public static void RunEngineEvaluationBenchmark()
{
var cpuCount = Environment.ProcessorCount;
var maxThreads = cpuCount * 2;
var threadCounts = new List<int> { 1, 2 };
// Add even numbers from 4 to maxThreads
for (int i = 4; i <= maxThreads; i += 2)
{
threadCounts.Add(i);
}
Console.WriteLine($"Running engine benchmark with max_threads: {maxThreads}");
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
Console.WriteLine();
// Benchmark both cloned engines and fresh engines
var configurations = new[]
{
(true, "cloned_engines"),
(false, "fresh_engines")
};
foreach (var (useClonedEngines, groupName) in configurations)
{
Console.WriteLine($"=== {groupName} ===");
foreach (var threads in threadCounts)
{
RunEngineEvaluationBenchmark(threads, useClonedEngines, groupName);
}
Console.WriteLine();
}
}
public static void RunEngineEvaluationBenchmark(int threads, bool useClonedEngines, string groupName)
{
const int warmupSeconds = 3;
const int durationSeconds = 3;
var policiesWithInputs = LoadPoliciesWithInputs();
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
// Warmup phase
var (_, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, useClonedEngines, isWarmup: true);
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
// Actual benchmark phase
var (totalEvaluations, evaluationTime, policyCounters) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, useClonedEngines, isWarmup: false);
// Calculate throughput based on pure evaluation time (consistent with Rust benchmark)
var evalsPerSecond = totalEvaluations / evaluationTime.TotalSeconds;
var kelemsPerSecond = evalsPerSecond / 1000.0;
Console.WriteLine($"{groupName}/eval/{threads} threads");
Console.WriteLine($" time: [{evaluationTime.TotalMilliseconds:F2} ms]");
Console.WriteLine($" thrpt: [{kelemsPerSecond:F2} Kelem/s]");
// Verify that all policies were evaluated
var allEvaluated = policyCounters.Values.All(count => count > 0);
if (allEvaluated)
{
Console.WriteLine("✓ All policies were evaluated successfully");
}
else
{
Console.WriteLine("ERROR: Some policies were never evaluated successfully!");
}
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters) RunBenchmarkPhase(
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
bool useClonedEngines,
bool isWarmup)
{
var barrier = new Barrier(threads);
var tasks = new Task[threads];
var policyCounters = new Dictionary<string, int>();
var evaluationTimes = new Dictionary<int, TimeSpan>();
var lockObject = new object();
var stopExecution = false;
// Initialize counters
foreach (var policyName in PolicyNames)
{
policyCounters[policyName] = 0;
}
// Pre-create engines if using cloned engines
List<Engine>? clonedEngines = null;
if (useClonedEngines)
{
clonedEngines = PrepareClonedEngines();
}
var stopwatch = Stopwatch.StartNew();
for (int threadId = 0; threadId < threads; threadId++)
{
int tid = threadId;
tasks[threadId] = Task.Run(() =>
{
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
int policyIdx = (tid + evaluationCount) % policiesWithInputs.Count;
var (policy, inputs) = policiesWithInputs[policyIdx];
// Use different input for the same policy based on iteration
int inputIdx = evaluationCount % inputs.Length;
var input = inputs[inputIdx];
try
{
// Measure only the engine operations
var evalStopwatch = Stopwatch.StartNew();
Engine engine;
if (useClonedEngines)
{
engine = clonedEngines![policyIdx].Clone();
}
else
{
engine = new Engine();
engine.AddPolicy("policy.rego", policy);
}
engine.SetInputJson(input);
var result = engine.EvalRule("data.bench.allow");
engine.Dispose();
evalStopwatch.Stop();
localEvaluationTime += evalStopwatch.Elapsed;
// Track successful evaluations (only during actual benchmark, not warmup)
if (!isWarmup)
{
lock (lockObject)
{
policyCounters[PolicyNames[policyIdx]]++;
}
}
}
catch (Exception)
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
lock (lockObject)
{
if (!evaluationTimes.ContainsKey(tid))
evaluationTimes[tid] = TimeSpan.Zero;
evaluationTimes[tid] = localEvaluationTime;
}
}
});
}
// Stop execution after the specified duration
Task.Delay(TimeSpan.FromSeconds(durationSeconds)).ContinueWith(_ => stopExecution = true);
Task.WaitAll(tasks);
stopwatch.Stop();
// Clean up cloned engines if we created them
if (clonedEngines != null)
{
foreach (var engine in clonedEngines)
{
engine.Dispose();
}
}
var totalEvaluations = policyCounters.Values.Sum();
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
// Use pure evaluation time (consistent with Rust benchmark)
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
return (totalEvaluations, evaluationTime, policyCounters);
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
namespace Benchmarks
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== Regorus C# Benchmarks ===\n");
try
{
Console.WriteLine("Running Engine Evaluation Benchmark...");
EngineEvaluationBenchmark.RunEngineEvaluationBenchmark();
}
catch (Exception ex)
{
Console.WriteLine($"Engine benchmark failed: {ex.Message}");
}
Console.WriteLine("\n" + new string('=', 80) + "\n");
try
{
Console.WriteLine("Running Compiled Policy Evaluation Benchmark...");
CompiledPolicyEvaluationBenchmark.RunCompiledPolicyEvaluationBenchmark();
}
catch (Exception ex)
{
Console.WriteLine($"Compiled policy benchmark failed: {ex.Message}");
}
Console.WriteLine("\n=== Benchmarks Complete ===");
}
}
}

View File

@@ -0,0 +1,140 @@
# Compiled Policy Evaluation Benchmark Results (C#/.NET)
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **.NET Version**: 8.0
- **Allocator**: mimalloc (default allocator for Rust FFI)
- **Benchmark Framework**: Custom time-based benchmarking
- **Test Data**: 20,000 inputs per evaluation (distributed across threads)
- **Policy**: Complex authorization policy with nested rules
- **Warmup Duration**: 3 seconds per configuration
- **Evaluation Duration**: 3 seconds per configuration
## Benchmark Overview
The C# compiled policy evaluation benchmark tests Regorus compiled policy performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of compiled policy compilation strategies.
## Configuration Combinations
1. **Compiled Shared Policies**: All threads share pre-compiled policy instances - optimal for performance
2. **Compiled Per Iteration**: Each thread compiles the policy for each evaluation iteration
*Note: The C# implementation uses a simpler configuration model compared to Rust, which also varies input data handling (cloned vs fresh inputs). The C# benchmarks focus on compilation strategies with consistent input handling.*
## Performance Results
### Compiled Shared Policies (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2905.41 | 273 |
| 2 | 5808.07 | 240 |
| 4 | 11631.23 | 227 |
| 6 | 17431.95 | 216 |
| 8 | 23183.42 | 126 |
| 10 | 28886.11 | 118 |
| 12 | 34659.87 | 108 |
| 14 | 40564.07 | 84 |
| 16 | 46446.38 | 72 |
| 18 | 52047.06 | 63 |
| 20 | 56983.45 | 58 |
| 22 | 404931.47 | 55 |
| 24 | 61673.71 | 55 |
| 26 | 64370.41 | 51 |
| 28 | 56897.04 | 59 |
| 30 | 406850.06 | 52 |
| 32 | 56786.24 | 58 |
### Compiled Per Iteration
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2978.06 | 49 |
| 2 | 5965.09 | 47 |
| 4 | 11928.23 | 46 |
| 6 | 17892.58 | 45 |
| 8 | 23773.82 | 43 |
| 10 | 29705.61 | 42 |
| 12 | 35631.97 | 40 |
| 14 | 41563.35 | 34 |
| 16 | 47452.93 | 31 |
| 18 | 53505.42 | 27 |
| 20 | 59393.86 | 25 |
| 22 | 436115.28 | 23 |
| 24 | 71088.08 | 21 |
| 26 | 76928.70 | 19 |
| 28 | 82759.27 | 18 |
| 30 | 560658.97 | 17 |
| 32 | 93949.39 | 16 |
## Analysis
The C# compiled policy benchmark demonstrates important performance characteristics with mimalloc as the default allocator:
1. **Compilation Strategy Impact**: Shared compiled policies significantly outperform per-iteration compilation (~5.6x at 1 thread)
2. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for shared policies
- Performance generally degrades with increased thread count, but mimalloc provides better allocation efficiency
3. **Performance Hierarchy**:
- Shared compiled policies: Best performance (optimal configuration)
- Per-iteration compilation: ~82% reduction from optimal
4. **Compilation Overhead**: Per-iteration compilation creates substantial overhead, similar to fresh engine creation
5. **Thread Contention**: Significant performance degradation beyond 8 threads for both configurations, though mimalloc helps mitigate some allocation-related issues
## Comparison with Rust Compiled Policy Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:-----------------|:-------------------|:--------------------|:--------------------|
| | C# / Rust | C# / Rust | C# / Rust |
| Shared Policies | 273 / 426 | 227 / 342 | 126 / 185 |
| Per-iteration | 49 / 55 | 46 / 50 | 43 / 50 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:-----------------|:----------------------|:--------------------------|:-----------------------|
| | Avg C# / Rust | Avg C# / Rust | Avg C# / Rust |
| Shared Policies | 249 / 384 | 150 / 203 | 58 / 123 |
| Per-iteration | 47 / 54 | 40 / 50 | 22 / 42 |
**Key Observations:**
- **Single-threaded performance**: C# achieves 64% of Rust performance for shared policies, 89% for per-iteration
- **Threading scaling**: Both platforms show similar degradation patterns, but Rust maintains better absolute performance
- **Contention resistance**: Per-iteration compilation shows more consistent relative performance across thread counts
- **Platform differences**: C# shows more pronounced performance drops at higher thread counts, particularly for shared policies
*Note: Rust benchmarks include additional input data variations (cloned vs fresh inputs) that are not present in the C# implementation.*
## Comparison with C# Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:----------------|:-------------------|:--------------------|:--------------------|
| | CP / EE | CP / EE | CP / EE |
| Shared Policies | 273 / 279 | 227 / 217 | 126 / 114 |
| Per-iteration | 49 / 50 | 46 / 47 | 43 / 45 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:----------------|:----------------------|:--------------------------|:-----------------------|
| | Avg CP / EE | Avg CP / EE | Avg CP / EE |
| Shared Policies | 249 / 248 | 150 / 128 | 58 / 54 |
| Per-iteration | 47 / 48 | 40 / 39 | 22 / 27 |
**Key Observations:**
- **Single-threaded parity**: Both systems perform nearly identically at 1 thread
- **Threading behavior**: Compiled policies slightly outperform engine evaluation at higher thread counts for shared policies
- **Contention resistance**: Per-iteration configurations show very similar performance characteristics across all thread counts
- **Platform consistency**: Both C# implementations show similar scaling patterns and contention behavior
## Performance Insights
1. **C# vs Rust Performance**: C# compiled policies achieve 65% average performance of Rust for shared policies, 87% average for per-iteration across low contention scenarios
2. **Engine vs Compiled**: In C#, engine and compiled policy evaluation show very similar average performance (compiled policies achieve 100% of engine performance for shared policies, 98% for per-iteration)
3. **mimalloc Impact**: The use of mimalloc as the default allocator in the underlying Rust FFI provides better memory allocation efficiency and improved threading characteristics
4. **Threading Scaling**: Both C# configurations demonstrate similar contention patterns, with shared policies showing more pronounced degradation under high thread contention compared to per-iteration compilation

View File

@@ -0,0 +1,118 @@
# Engine Evaluation Benchmark Results (C#/.NET)
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **.NET Version**: 8.0
- **Allocator**: mimalloc (default allocator for Rust FFI)
- **Benchmark Framework**: Custom time-based benchmarking
- **Test Data**: 20,000 inputs per evaluation (distributed across threads)
- **Policy**: Complex authorization policy with nested rules
- **Warmup Duration**: 3 seconds per configuration
- **Evaluation Duration**: 3 seconds per configuration
## Benchmark Overview
The C# engine evaluation benchmark tests Regorus policy evaluation performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of engine reuse strategies.
## Configuration Combinations
1. **Cloned Engines**: Each thread uses its own cloned engine instance - optimal for performance
2. **Fresh Engines**: Each thread creates a new engine for each evaluation iteration
*Note: The C# implementation uses a simpler configuration model compared to Rust, which also varies input data handling (cloned vs fresh inputs). The C# benchmarks focus on engine reuse strategies with consistent input handling.*
## Performance Results
### Cloned Engines (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2903.43 | 279 |
| 2 | 5808.35 | 227 |
| 4 | 11645.08 | 217 |
| 6 | 17469.69 | 207 |
| 8 | 23268.07 | 114 |
| 10 | 28996.14 | 104 |
| 12 | 34808.60 | 98 |
| 14 | 40703.21 | 72 |
| 16 | 46488.23 | 63 |
| 18 | 52078.52 | 56 |
| 20 | 57014.31 | 51 |
| 22 | 60482.22 | 47 |
| 24 | 62445.67 | 46 |
| 26 | 65128.74 | 45 |
| 28 | 58001.92 | 50 |
| 30 | 66154.78 | 42 |
| 32 | 64999.03 | 45 |
### Fresh Engines
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2982.28 | 50 |
| 2 | 5962.62 | 48 |
| 4 | 11917.94 | 47 |
| 6 | 17874.77 | 46 |
| 8 | 23729.94 | 45 |
| 10 | 29635.17 | 42 |
| 12 | 35574.71 | 38 |
| 14 | 41482.61 | 34 |
| 16 | 47425.16 | 32 |
| 18 | 53248.87 | 29 |
| 20 | 58424.34 | 27 |
| 22 | 61302.24 | 26 |
| 24 | 67430.08 | 23 |
| 26 | 65226.79 | 24 |
| 28 | 73118.48 | 22 |
| 30 | 326472.94 | 23 |
| 32 | 63805.03 | 24 |
## Analysis
The C# benchmark results demonstrate important performance characteristics with mimalloc as the default allocator:
1. **Engine Reuse Impact**: Cloned engines significantly outperform fresh engines (~5.6x at 1 thread)
2. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for both configurations
- Performance degrades with increased thread count due to contention, but mimalloc provides better allocation efficiency
- Cloned engines show better relative scaling characteristics
3. **Performance Hierarchy**:
- Cloned engines: Best performance (optimal configuration)
- Fresh engines: ~82% reduction from optimal
4. **Thread Contention**: Significant performance drop beyond 8 threads, especially for fresh engines, though mimalloc helps mitigate some allocation-related issues
5. **C# vs Rust Performance**: C# shows ~66% of Rust performance for equivalent cloned engine configuration
## Comparison with Rust Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:---------------|:-------------------|:--------------------|:--------------------|
| | C# / Rust | C# / Rust | C# / Rust |
| Cloned Engines | 279 / 423 | 217 / 406 | 114 / 341 |
| Fresh Engines | 50 / 56 | 47 / 54 | 45 / 53 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:---------------|:----------------------|:--------------------------|:-----------------------|
| | Avg C# / Rust | Avg C# / Rust | Avg C# / Rust |
| Cloned Engines | 253 / 414 | 128 / 329 | 54 / 250 |
| Fresh Engines | 48 / 55 | 39 / 52 | 27 / 42 |
**Key Observations:**
- **Single-threaded performance**: C# achieves 66% of Rust performance for cloned engines, 89% for fresh engines
- **Threading scaling**: Both platforms show similar degradation patterns, but Rust maintains better absolute performance
- **Contention resistance**: Fresh engines show more consistent relative performance across thread counts
- **Platform differences**: C# shows more pronounced performance drops at higher thread counts, particularly for cloned engines
*Note: Rust benchmarks include additional input data variations (cloned vs fresh inputs) that are not present in the C# implementation.*
## Performance Insights
1. **Engine Creation Overhead**: Fresh engine creation has significant performance impact in C# (~5.6x slower than cloned engines)
2. **Thread Scaling**: C# shows moderate thread contention with better characteristics when using mimalloc
3. **Memory Management**: .NET garbage collection patterns combined with mimalloc allocation efficiency
4. **Interop Performance**: C# bindings achieve 66% of Rust performance for cloned engines, demonstrating effective FFI implementation
5. **mimalloc Benefits**: The use of mimalloc as the default allocator in the underlying Rust FFI provides improved memory allocation efficiency and better threading characteristics

32
bindings/csharp/README.md Normal file
View File

@@ -0,0 +1,32 @@
# Regorus CSharp
**Regorus** is
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
interpreter written in Rust.
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
See main [Regorus page](https://github.com/microsoft/regorus) for more details about the project.
# Building
## Github Actions
The simplest way to build a Nuget for Regorus' C# bindings is to use Github Actions. The action to do so is named `bindings/csharp` and is defined in `.github/workflows/test-csharp.yml`.
There are two ways to trigger a Nuget build.
1. Runs are triggered automatically whenever a push or pull request is made to the `main` branch.
2. A run can be triggered manually by navigating to the action in the Github UI and clicking `Run workflow`. This option allows you to generate a Nuget for any branch, which is useful when testing the integration of in-progress changes to Regorus with other projects. Nuget files that are generated via this flow will have a `manualtrigger` suffix appended to their version number, making it easy to distinguish them from Nugets generated using the `main` branch.
![Image displaying the run workflow button](docs/images/readme/manuallytriggeringrun.png)
Once the workflow run completes, the generated Nuget can be downloaded by following these steps:
1. Open the run.
2. Click on `Build Regorus nuget` on the left.
3. Expand the `Upload Regorus nuget` step.
4. Click the `Artifact download URL` link at the bottom.
5. Save and extract the downloaded zip file to find the `.nupkg` file.
![Image displaying the download URL link](docs/images/readme/downloadnuget.png)
## Local
TODO

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>Enable</Nullable>
<TargetFramework>net8.0</TargetFramework>
<EnableMSTestRunner>true</EnableMSTestRunner>
<!-- More info about dotnet test integration https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test -->
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
</PropertyGroup>
<PropertyGroup>
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<ItemGroup>
<None Include="../../../tests/**/*.*" Link="tests/%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest" Version="3.8.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,256 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text.Json.Nodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
[TestClass]
public class RegorusTests
{
[TestMethod]
public void Basic_evaluation_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\nx = 1\nmessage = `Hello`");
var result = engine.EvalRule("data.test.message");
Assert.AreEqual("\"Hello\"", result);
}
[TestMethod]
public void Evaluation_using_file_policies_succeeds()
{
using var engine = new Engine();
engine.SetRegoV0(true);
// Load policies and data.
engine.AddPolicyFromFile("tests/aci/framework.rego");
engine.AddPolicyFromFile("tests/aci/api.rego");
engine.AddPolicyFromFile("tests/aci/policy.rego");
engine.AddDataFromJsonFile("tests/aci/data.json");
// Set input and eval rule.
engine.SetInputFromJsonFile("tests/aci/input.json");
var result = engine.EvalRule("data.framework.mount_overlay");
var expected = """
{
"allowed": true,
"metadata": [
{
"action": "add",
"key": "container0",
"name": "matches",
"value": [
{
"allow_elevated": true,
"allow_stdio_access": false,
"capabilities": {
"ambient": [
"CAP_SYS_ADMIN"
],
"bounding": [
"CAP_SYS_ADMIN"
],
"effective": [
"CAP_SYS_ADMIN"
],
"inheritable": [
"CAP_SYS_ADMIN"
],
"permitted": [
"CAP_SYS_ADMIN"
]
},
"command": [
"rustc",
"--help"
],
"env_rules": [
{
"pattern": "PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"required": true,
"strategy": "string"
},
{
"pattern": "RUSTUP_HOME=/usr/local/rustup",
"required": true,
"strategy": "string"
},
{
"pattern": "CARGO_HOME=/usr/local/cargo",
"required": true,
"strategy": "string"
},
{
"pattern": "RUST_VERSION=1.52.1",
"required": true,
"strategy": "string"
},
{
"pattern": "TERM=xterm",
"required": false,
"strategy": "string"
},
{
"pattern": "PREFIX_.+=.+",
"required": false,
"strategy": "re2"
}
],
"exec_processes": [
{
"command": [
"top"
],
"signals": []
}
],
"layers": [
"fe84c9d5bfddd07a2624d00333cf13c1a9c941f3a261f13ead44fc6a93bc0e7a",
"4dedae42847c704da891a28c25d32201a1ae440bce2aecccfa8e6f03b97a6a6c",
"41d64cdeb347bf236b4c13b7403b633ff11f1cf94dbc7cf881a44d6da88c5156",
"eb36921e1f82af46dfe248ef8f1b3afb6a5230a64181d960d10237a08cd73c79",
"e769d7487cc314d3ee748a4440805317c19262c7acd2fdbdb0d47d2e4613a15c",
"1b80f120dbd88e4355d6241b519c3e25290215c469516b49dece9cf07175a766"
],
"mounts": [
{
"destination": "/container/path/one",
"options": [
"rbind",
"rshared",
"rw"
],
"source": "sandbox:///host/path/one",
"type": "bind"
},
{
"destination": "/container/path/two",
"options": [
"rbind",
"rshared",
"ro"
],
"source": "sandbox:///host/path/two",
"type": "bind"
}
],
"no_new_privileges": true,
"seccomp_profile_sha256": "",
"signals": [],
"user": {
"group_idnames": [
{
"pattern": "",
"strategy": "any"
}
],
"umask": "0022",
"user_idname": {
"pattern": "",
"strategy": "any"
}
},
"working_dir": "/home/user"
}
]
},
{
"action": "add",
"key": "/run/gcs/c/container0/rootfs",
"name": "overlayTargets",
"value": true
}
]
}
""";
Assert.IsTrue(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result!)), $"Actual: {result}");
}
[TestMethod]
public void GetPolicyPackageNames_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\nx = 1\nmessage = `Hello`");
engine.AddPolicy(
"test.rego",
"package test.nested.name\nx = 1\nmessage = `Hello`");
var result = engine.GetPolicyPackageNames();
var packageNames = JsonNode.Parse(result!);
Assert.AreEqual("test", packageNames![0]["package_name"].ToString());
Assert.AreEqual("test.nested.name", packageNames![1]["package_name"].ToString());
}
[TestMethod]
public void GetPolicyParameters_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\n default parameters.a = 5\nparameters.b = 10\nx = 1\nmessage = `Hello`");
var result = engine.GetPolicyParameters();
var parameters = JsonNode.Parse(result!);
Assert.AreEqual(1, parameters![0]["parameters"].AsArray().Count);
Assert.AreEqual(1, parameters![0]["modifiers"].AsArray().Count);
Assert.AreEqual("a", parameters![0]["parameters"][0]["name"].ToString());
Assert.AreEqual("b", parameters![0]["modifiers"][0]["name"].ToString());
}
[TestMethod]
public void SetInputJson_has_negligible_allocations_after_warmup()
{
using var engine = new Engine();
const string payload = "{}";
// Warm up the engine and JIT to ensure subsequent measurements are representative.
for (int i = 0; i < 16; i++)
{
engine.SetInputJson(payload);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
const int iterations = 256;
var before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < iterations; i++)
{
engine.SetInputJson(payload);
}
var after = GC.GetAllocatedBytesForCurrentThread();
var allocated = Math.Max(0, after - before);
var bytesPerOp = allocated / (double)iterations;
// Runtime bookkeeping (delegate caches, GC write barriers) differs across platforms, so
// we measure bytes per call rather than absolute totals and allow a small budget.
// CI will flag regressions where marshalling starts allocating per invocation.
// Allow a small budget for delegates and runtime bookkeeping while still flagging regressions.
Assert.IsTrue(
bytesPerOp <= 512,
$"Expected ≤512 B/op after warmup, but observed {bytesPerOp:F2} B/op (total {allocated} bytes)."
);
}
}

View File

@@ -0,0 +1,220 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text;
using System.Text.Json;
using System.Threading;
#nullable enable
namespace Regorus
{
/// <summary>
/// Represents a compiled Regorus policy that can be evaluated efficiently.
/// This class wraps a pre-compiled policy that can be evaluated multiple times
/// with different inputs without recompilation overhead.
///
/// This class manages unmanaged resources and should not be copied or cloned.
/// Each instance represents a unique native policy object.
///
/// Thread Safety: This class is thread-safe for all operations. Multiple threads
/// can safely call EvalWithInput() concurrently, and Dispose() will safely wait
/// for all active evaluations to complete before freeing resources. No external
/// synchronization is required.
/// </summary>
public unsafe sealed class CompiledPolicy : IDisposable
{
private RegorusCompiledPolicyHandle? _handle;
private readonly ManualResetEventSlim _idleEvent = new(initialState: true);
private int _isDisposed;
private int _activeEvaluations;
internal CompiledPolicy(RegorusCompiledPolicyHandle handle)
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
/// <summary>
/// Evaluates the compiled policy with the given input.
/// For target policies, evaluates the target's effect rule.
/// For regular policies, evaluates the originally compiled rule.
/// </summary>
/// <param name="inputJson">JSON encoded input data (resource) to validate against the policy</param>
/// <returns>The evaluation result as JSON string</returns>
/// <exception cref="Exception">Thrown when policy evaluation fails</exception>
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
public string? EvalWithInput(string inputJson)
{
// Increment active evaluations count
var active = System.Threading.Interlocked.Increment(ref _activeEvaluations);
if (active == 1)
{
_idleEvent.Reset();
}
try
{
ThrowIfDisposed();
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
{
return UseHandle(policyPtr =>
{
unsafe
{
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
}
});
});
}
finally
{
// Decrement active evaluations count
var remaining = System.Threading.Interlocked.Decrement(ref _activeEvaluations);
if (remaining == 0)
{
_idleEvent.Set();
}
}
}
/// <summary>
/// Gets information about the compiled policy including metadata about modules,
/// target configuration, and resource types.
/// </summary>
/// <returns>Policy information containing module IDs, target name, applicable resource types, entry point rule, and parameters</returns>
/// <exception cref="Exception">Thrown when getting policy info fails</exception>
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
public PolicyInfo GetPolicyInfo()
{
ThrowIfDisposed();
var jsonResult = UseHandle(policyPtr =>
{
unsafe
{
return CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info((Internal.RegorusCompiledPolicy*)policyPtr));
}
});
if (string.IsNullOrEmpty(jsonResult))
{
throw new Exception("Failed to get policy info: empty response");
}
try
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
?? throw new Exception("Failed to deserialize policy info");
}
catch (JsonException ex)
{
throw new Exception($"Failed to parse policy info JSON: {ex.Message}", ex);
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
var handle = _handle;
if (handle != null)
{
_idleEvent.Wait();
handle.Dispose();
_handle = null;
}
_idleEvent.Dispose();
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
private string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type switch
{
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => StringFromUTF8((IntPtr)result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private RegorusCompiledPolicyHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
return handle;
}
private T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
}
}

View File

@@ -0,0 +1,197 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Represents a policy module with an ID and content.
/// </summary>
public struct PolicyModule
{
/// <summary>
/// Gets or sets the unique identifier for this policy module.
/// </summary>
public string Id { get; set; }
/// <summary>
/// Gets or sets the Rego policy content.
/// </summary>
public string Content { get; set; }
/// <summary>
/// Initializes a new instance of the PolicyModule struct.
/// </summary>
/// <param name="id">The unique identifier for this policy module</param>
/// <param name="content">The Rego policy content</param>
public PolicyModule(string id, string content)
{
Id = id;
Content = content;
}
}
/// <summary>
/// Provides static methods for compiling policies into efficient compiled representations.
/// These are convenience methods that create an engine internally and perform compilation.
/// </summary>
public static unsafe class Compiler
{
/// <summary>
/// Compiles a policy from data and modules with a specific entry point rule.
/// This is a convenience function that sets up an Engine internally and calls the appropriate compilation method.
/// </summary>
/// <param name="dataJson">JSON string containing static data for policy evaluation</param>
/// <param name="modules">List of policy modules to compile</param>
/// <param name="entryPointRule">The specific rule path to evaluate (e.g., "data.policy.allow")</param>
/// <returns>A compiled policy that can be evaluated efficiently</returns>
/// <exception cref="Exception">Thrown when compilation fails</exception>
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IEnumerable<PolicyModule> modules, string entryPointRule)
{
var modulesArray = modules.ToArray();
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
try
{
for (int i = 0; i < modulesArray.Length; i++)
{
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
pinnedStrings.Add(idPinned);
pinnedStrings.Add(contentPinned);
nativeModules[i] = new Internal.RegorusPolicyModule
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
{
unsafe
{
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
{
var result = Internal.API.regorus_compile_policy_with_entrypoint(
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length, (byte*)entryPointPtr);
var policy = GetCompiledPolicyResult(result);
return policy;
}
}
}));
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
}
/// <summary>
/// Compiles a target-aware policy from data and modules.
/// This is a convenience function that sets up an Engine internally and calls target-aware compilation.
/// At least one module must contain a `__target__` declaration.
/// </summary>
/// <param name="dataJson">JSON string containing static data for policy evaluation</param>
/// <param name="modules">List of policy modules to compile</param>
/// <returns>A compiled policy that can be evaluated efficiently</returns>
/// <exception cref="Exception">Thrown when compilation fails</exception>
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IEnumerable<PolicyModule> modules)
{
var modulesArray = modules.ToArray();
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
try
{
for (int i = 0; i < modulesArray.Length; i++)
{
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
pinnedStrings.Add(idPinned);
pinnedStrings.Add(contentPinned);
nativeModules[i] = new Internal.RegorusPolicyModule
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
unsafe
{
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
{
var result = Internal.API.regorus_compile_policy_for_target(
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length);
var policy = GetCompiledPolicyResult(result);
return policy;
}
}
});
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
}
private static string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private static CompiledPolicy GetCompiledPolicyResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown compilation error occurred");
}
if (result.data_type != Internal.RegorusDataType.Pointer || result.pointer_value == null)
{
throw new Exception("Expected compiled policy pointer but got different data type");
}
var handle = RegorusCompiledPolicyHandle.FromPointer((IntPtr)result.pointer_value);
return new CompiledPolicy(handle);
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
}
}

View File

@@ -0,0 +1,445 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// C# Wrapper for the Regorus engine.
/// This class is not thread-safe. For multithreaded use, prefer cloning after adding policies and data to an instance.
/// Cloning is cheap and involves only incrementing reference counts for shared immutable objects like parsed policies,
/// data etc. Mutable state is deep copied as needed.
/// </summary>
public unsafe sealed class Engine : IDisposable
{
private RegorusEngineHandle? _handle;
private int _isDisposed;
public Engine()
{
_handle = RegorusEngineHandle.Create();
}
public void Dispose()
{
Dispose(disposing: true);
// This object will be cleaned up by the Dispose method.
// Therefore, call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
_handle?.Dispose();
_handle = null;
}
}
private Engine(RegorusEngineHandle handle)
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
public Engine Clone()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
var clonePtr = Regorus.Internal.API.regorus_engine_clone((Regorus.Internal.RegorusEngine*)enginePtr);
if (clonePtr is null)
{
throw new InvalidOperationException("Failed to clone Regorus engine.");
}
var handle = RegorusEngineHandle.FromPointer((IntPtr)clonePtr);
return new Engine(handle);
}
});
}
public void SetStrictBuiltinErrors(bool strict)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
}
});
}
public string? AddPolicy(string path, string rego)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(path, pathPtr =>
Utf8Marshaller.WithUtf8(rego, regoPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr));
}
});
}
}));
}
public void SetRegoV0(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
});
}
public string? AddPolicyFromFile(string path)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
});
}
public void AddDataJson(string data)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(data, dataPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
}
});
}
});
}
public void AddDataFromJsonFile(string path)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
});
}
public void SetInputJson(string input)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(input, inputPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
}
});
}
});
}
public void SetInputFromJsonFile(string path)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
});
}
public string? EvalQuery(string query)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(query, queryPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr));
}
});
}
});
}
public string? EvalRule(string rule)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(rule, rulePtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr));
}
});
}
});
}
public void SetEnableCoverage(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
});
}
public void ClearCoverageData()
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetCoverageReport()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetCoverageReportPretty()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public void SetGatherPrints(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
});
}
public string? TakePrints()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetAstAsJson()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetPolicyPackageNames()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetPolicyParameters()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
{
if (result.status != Regorus.Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
Regorus.Internal.API.regorus_result_drop(result);
throw ex;
}
var resultString = "";
if (result.output is not null)
{
resultString = StringFromUTF8((IntPtr)result.output);
}
Regorus.Internal.API.regorus_result_drop(result);
return resultString;
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
{
throw new ObjectDisposedException(nameof(Engine));
}
}
private RegorusEngineHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(Engine));
}
return handle;
}
private void UseHandle(Action<IntPtr> action)
{
UseHandle<object?>(handlePtr =>
{
action(handlePtr);
return null;
});
}
private T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(Engine));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
}
}

View File

@@ -0,0 +1,546 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
#pragma warning disable CS8500
#pragma warning disable CS8981
namespace Regorus.Internal
{
/// <summary>
/// Native FFI method declarations for Regorus.
/// This file contains all P/Invoke declarations for the Regorus native library.
/// </summary>
internal static unsafe partial class API
{
private const string LibraryName = "regorus_ffi";
#region Common Methods
/// <summary>
/// Drop a RegorusResult.
/// output and error_message strings are not valid after drop.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_result_drop(RegorusResult result);
#endregion
#region Engine Methods
/// <summary>
/// Construct a new Engine.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusEngine* regorus_engine_new();
/// <summary>
/// Clone a RegorusEngine.
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
/// <summary>
/// Drop a RegorusEngine.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_engine_drop(RegorusEngine* engine);
/// <summary>
/// Add a policy.
/// The policy is parsed into AST.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_policy(RegorusEngine* engine, byte* path, byte* rego);
/// <summary>
/// Add a policy from file.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_policy_from_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_policy_from_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Add policy data.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_data_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_data_json(RegorusEngine* engine, byte* data);
/// <summary>
/// Get list of loaded Rego packages as JSON.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_packages", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_packages(RegorusEngine* engine);
/// <summary>
/// Get list of policies as JSON.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_policies", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policies(RegorusEngine* engine);
/// <summary>
/// Add data from JSON file.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_data_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Clear policy data.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_data(RegorusEngine* engine);
/// <summary>
/// Set input.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_input_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_input_json(RegorusEngine* engine, byte* input);
/// <summary>
/// Set input from JSON file.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_input_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Evaluate query.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_eval_query", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_eval_query(RegorusEngine* engine, byte* query);
/// <summary>
/// Evaluate specified rule.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_eval_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_eval_rule(RegorusEngine* engine, byte* rule);
/// <summary>
/// Enable/disable coverage.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_enable_coverage", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_enable_coverage(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Get coverage report.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_coverage_report", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_coverage_report(RegorusEngine* engine);
/// <summary>
/// Enable/disable strict builtin errors.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_strict_builtin_errors
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_strict_builtin_errors", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_strict_builtin_errors(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool strict);
/// <summary>
/// Get pretty printed coverage report.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_coverage_report_pretty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_coverage_report_pretty(RegorusEngine* engine);
/// <summary>
/// Clear coverage data.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_coverage_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_coverage_data(RegorusEngine* engine);
/// <summary>
/// Whether to gather output of print statements.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_gather_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_gather_prints(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Take all the gathered print statements.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_take_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_take_prints(RegorusEngine* engine);
/// <summary>
/// Get AST of policies.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_ast_as_json
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_ast_as_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_ast_as_json(RegorusEngine* engine);
/// <summary>
/// Gets the package names defined in each policy added to the engine.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_package_names
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_policy_package_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policy_package_names(RegorusEngine* engine);
/// <summary>
/// Gets the parameters defined in each policy added to the engine.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_parameters
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_policy_parameters", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policy_parameters(RegorusEngine* engine);
/// <summary>
/// Enable/disable rego v1.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_rego_v0", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_rego_v0(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Compile a target-aware policy from the current engine state.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.compile_for_target
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_compile_for_target(RegorusEngine* engine);
/// <summary>
/// Compile a policy with a specific entry point rule.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.compile_with_entrypoint
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_with_entrypoint", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_compile_with_entrypoint(RegorusEngine* engine, byte* rule);
#endregion
#region Compilation Methods
/// <summary>
/// Compiles a policy from data and modules with a specific entry point rule.
/// This is a convenience function that wraps regorus::compile_policy_with_entrypoint.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_with_entrypoint", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_policy_with_entrypoint(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len, byte* entry_point_rule);
/// <summary>
/// Compiles a target-aware policy from data and modules.
/// This is a convenience function that wraps regorus::compile_policy_for_target.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_policy_for_target(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len);
#endregion
#region Compiled Policy Methods
/// <summary>
/// Drop a RegorusCompiledPolicy.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compiled_policy_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_compiled_policy_drop(RegorusCompiledPolicy* compiled_policy);
/// <summary>
/// Evaluate the compiled policy with the given input.
/// For target policies, evaluates the target's effect rule.
/// For regular policies, evaluates the originally compiled rule.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compiled_policy_eval_with_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compiled_policy_eval_with_input(RegorusCompiledPolicy* compiled_policy, byte* input);
/// <summary>
/// Get information about the compiled policy including metadata about modules,
/// target configuration, and resource types.
/// Returns a JSON-encoded PolicyInfo struct containing comprehensive
/// information about the compiled policy such as module IDs, target name,
/// applicable resource types, entry point rule, and parameters.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compiled_policy_get_policy_info", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compiled_policy_get_policy_info(RegorusCompiledPolicy* compiled_policy);
#endregion
#region Target Registry Methods
/// <summary>
/// Register a target from JSON definition.
/// The target JSON should follow the target schema format.
/// Once registered, the target can be referenced in Rego policies using __target__ rules.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_register_target_from_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_register_target_from_json(byte* target_json);
/// <summary>
/// Check if a target is registered.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_contains", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_contains(byte* name);
/// <summary>
/// Get a list of all registered target names as JSON array.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_list_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_list_names();
/// <summary>
/// Remove a target from the registry by name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_remove", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_remove(byte* name);
/// <summary>
/// Clear all targets from the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_clear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_clear();
/// <summary>
/// Get the number of registered targets.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_len();
/// <summary>
/// Check if the target registry is empty.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_is_empty();
#endregion
#region Resource Schema Registry Methods
/// <summary>
/// Register a resource schema from JSON with a given name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_register", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_register(byte* name, byte* schema_json);
/// <summary>
/// Check if a resource schema with the given name exists.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_contains", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_contains(byte* name);
/// <summary>
/// Get the number of registered resource schemas.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_len();
/// <summary>
/// Check if the resource schema registry is empty.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_is_empty();
/// <summary>
/// List all registered resource schema names as a JSON array.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_list_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_list_names();
/// <summary>
/// Remove a resource schema by name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_remove", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_remove(byte* name);
/// <summary>
/// Clear all resource schemas from the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_clear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_clear();
#endregion
#region Effect Schema Registry Methods
/// <summary>
/// Register an effect schema from JSON with a given name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_register", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_register(byte* name, byte* schema_json);
/// <summary>
/// Check if an effect schema with the given name exists.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_contains", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_contains(byte* name);
/// <summary>
/// Get the number of registered effect schemas.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_len();
/// <summary>
/// Check if the effect schema registry is empty.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_is_empty();
/// <summary>
/// List all registered effect schema names as a JSON array.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_list_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_list_names();
/// <summary>
/// Remove an effect schema by name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_remove", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_remove(byte* name);
/// <summary>
/// Clear all effect schemas from the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_clear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_clear();
#endregion
}
#region Native Structures
/// <summary>
/// Type of data contained in RegorusResult.
/// </summary>
internal enum RegorusDataType : uint
{
/// <summary>
/// No data / void.
/// </summary>
None,
/// <summary>
/// String data (output field is valid).
/// </summary>
String,
/// <summary>
/// Boolean data (bool_value field is valid).
/// </summary>
Boolean,
/// <summary>
/// Integer data (int_value field is valid).
/// </summary>
Integer,
/// <summary>
/// Pointer data (pointer_value field is valid).
/// </summary>
Pointer,
}
/// <summary>
/// Status of a call on RegorusEngine.
/// </summary>
internal enum RegorusStatus : uint
{
/// <summary>
/// The operation was successful.
/// </summary>
Ok,
/// <summary>
/// The operation was unsuccessful.
/// </summary>
Error,
/// <summary>
/// Invalid data format provided.
/// </summary>
InvalidDataFormat,
/// <summary>
/// Invalid entrypoint rule specified.
/// </summary>
InvalidEntrypoint,
/// <summary>
/// Compilation failed.
/// </summary>
CompilationFailed,
/// <summary>
/// Invalid argument provided.
/// </summary>
InvalidArgument,
/// <summary>
/// Invalid module ID.
/// </summary>
InvalidModuleId,
/// <summary>
/// Invalid policy content.
/// </summary>
InvalidPolicy,
}
/// <summary>
/// Result of a call on RegorusEngine.
/// Must be freed using regorus_result_drop.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusResult
{
/// <summary>
/// Status.
/// </summary>
public RegorusStatus status;
/// <summary>
/// Type of data contained in this result.
/// </summary>
public RegorusDataType data_type;
/// <summary>
/// String output produced by the call.
/// Valid when data_type is String. Owned by Rust.
/// </summary>
public byte* output;
/// <summary>
/// Boolean value.
/// Valid when data_type is Boolean.
/// </summary>
public bool bool_value;
/// <summary>
/// Integer value.
/// Valid when data_type is Integer.
/// </summary>
public long int_value;
/// <summary>
/// Pointer value.
/// Valid when data_type is Pointer.
/// </summary>
public void* pointer_value;
/// <summary>
/// Errors produced by the call.
/// Owned by Rust.
/// </summary>
public byte* error_message;
}
/// <summary>
/// Wrapper for regorus::Engine.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusEngine
{
}
/// <summary>
/// Wrapper for regorus::CompiledPolicy.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusCompiledPolicy
{
}
/// <summary>
/// FFI wrapper for PolicyModule struct.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusPolicyModule
{
public byte* id;
public byte* content;
}
#endregion
}

View File

@@ -0,0 +1,141 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Text.Json.Serialization;
#nullable enable
namespace Regorus
{
/// <summary>
/// Information about a compiled policy, including metadata about modules,
/// target configuration, and resource types that the policy can evaluate.
/// </summary>
public class PolicyInfo
{
/// <summary>
/// List of module identifiers that were compiled into this policy.
/// Each module ID represents a unique policy module that contributes
/// rules, functions, or data to the compiled policy.
/// </summary>
[JsonPropertyName("module_ids")]
public List<string> ModuleIds { get; set; } = new List<string>();
/// <summary>
/// Name of the target configuration used during compilation, if any.
/// This indicates which target schema and validation rules were applied.
/// </summary>
[JsonPropertyName("target_name")]
public string? TargetName { get; set; }
/// <summary>
/// List of resource types that this policy can evaluate.
/// For target-aware policies, this contains the inferred or configured
/// resource types. For general policies, this may be empty.
/// </summary>
[JsonPropertyName("applicable_resource_types")]
public List<string> ApplicableResourceTypes { get; set; } = new List<string>();
/// <summary>
/// The primary rule or entrypoint that this policy evaluates.
/// This is the rule path that will be executed when the policy runs.
/// </summary>
[JsonPropertyName("entrypoint_rule")]
public string EntrypointRule { get; set; } = string.Empty;
/// <summary>
/// The effect rule name for target-aware policies, if applicable.
/// This is the specific effect rule (e.g., "effect", "allow", "deny")
/// that determines the policy decision for target evaluation.
/// </summary>
[JsonPropertyName("effect_rule")]
public string? EffectRule { get; set; }
/// <summary>
/// Parameters that can be configured for this policy.
/// Contains parameter names and their expected types or default values.
/// Used for parameterized policies that accept configuration at evaluation time.
/// Each element represents parameters from a different module.
/// </summary>
[JsonPropertyName("parameters")]
public List<PolicyParameters> Parameters { get; set; } = new List<PolicyParameters>();
}
/// <summary>
/// Parameters that can be configured for a policy.
/// </summary>
public class PolicyParameters
{
/// <summary>
/// Source file where the parameters are defined.
/// </summary>
[JsonPropertyName("source_file")]
public string SourceFile { get; set; } = string.Empty;
/// <summary>
/// List of parameter definitions.
/// </summary>
[JsonPropertyName("parameters")]
public List<PolicyParameter> Parameters { get; set; } = new List<PolicyParameter>();
/// <summary>
/// List of parameter modifiers.
/// </summary>
[JsonPropertyName("modifiers")]
public List<PolicyParameterModifier> Modifiers { get; set; } = new List<PolicyParameterModifier>();
}
/// <summary>
/// A single parameter definition.
/// </summary>
public class PolicyParameter
{
/// <summary>
/// Name of the parameter.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Type of the parameter.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// Default value of the parameter, if any.
/// </summary>
[JsonPropertyName("default")]
public object? Default { get; set; }
/// <summary>
/// Description of the parameter.
/// </summary>
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>
/// Allowed values for the parameter, if constrained.
/// </summary>
[JsonPropertyName("allowed_values")]
public List<object>? AllowedValues { get; set; }
}
/// <summary>
/// A parameter modifier that affects parameter behavior.
/// </summary>
public class PolicyParameterModifier
{
/// <summary>
/// Name of the modifier.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Value of the modifier.
/// </summary>
[JsonPropertyName("value")]
public object? Value { get; set; }
}
}

View File

@@ -1,240 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using System.Text;
#nullable enable
namespace Regorus
{
public unsafe sealed class Engine : System.IDisposable
{
private Regorus.Internal.RegorusEngine* E;
// Detect redundant Dispose() calls in a thread-safe manner.
// _isDisposed == 0 means Dispose(bool) has not been called yet.
// _isDisposed == 1 means Dispose(bool) has been already called.
private int isDisposed;
public Engine()
{
E = Regorus.Internal.API.regorus_engine_new();
}
public void Dispose()
{
Dispose(disposing: true);
// This object will be cleaned up by the Dispose method.
// Therefore, call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
void Dispose(bool disposing)
{
// In case _isDisposed is 0, atomically set it to 1.
// Enter the branch only if the original value is 0.
if (System.Threading.Interlocked.CompareExchange(ref isDisposed, 1, 0) == 0)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// No managed resource to dispose.
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
if (E != null)
{
Regorus.Internal.API.regorus_engine_drop(E);
E = null;
}
}
}
// Use C# finalizer syntax for finalization code.
// This finalizer will run only if the Dispose method
// does not get called.
~Engine() => Dispose(disposing: false);
// Helper for implementing Clone
private Engine(Internal.RegorusEngine* engine)
{
this.E = engine;
}
public Engine Clone() => new(Internal.API.regorus_engine_clone(E));
byte[] NullTerminatedUTF8Bytes(string s)
{
return Encoding.UTF8.GetBytes(s + char.MinValue);
}
public string? AddPolicy(string path, string rego)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
var regoBytes = NullTerminatedUTF8Bytes(rego);
fixed (byte* pathPtr = pathBytes)
{
fixed (byte* regoPtr = regoBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
public void SetRegoV0(bool enable)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0(E, enable));
}
public string? AddPolicyFromFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
fixed (byte* pathPtr = pathBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
public void AddDataJson(string data)
{
var dataBytes = NullTerminatedUTF8Bytes(data);
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json(E, dataPtr));
}
}
public void AddDataFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
public void SetInputJson(string input)
{
var inputBytes = NullTerminatedUTF8Bytes(input);
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json(E, inputPtr));
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file(E, pathPtr));
}
}
public string? EvalQuery(string query)
{
var queryBytes = NullTerminatedUTF8Bytes(query);
fixed (byte* queryPtr = queryBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query(E, queryPtr));
}
}
public string? EvalRule(string rule)
{
var ruleBytes = NullTerminatedUTF8Bytes(rule);
fixed (byte* rulePtr = ruleBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule(E, rulePtr));
}
}
public void SetEnableCoverage(bool enable)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage(E, enable));
}
public void ClearCoverageData()
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data(E));
}
public string? GetCoverageReport()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report(E));
}
public string? GetCoverageReportPretty()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty(E));
}
public void SetGatherPrints(bool enable)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints(E, enable));
}
public string? TakePrints()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints(E));
}
string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
{
if (result.status != Regorus.Internal.RegorusStatus.RegorusStatusOk)
{
var message = StringFromUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
Regorus.Internal.API.regorus_result_drop(result);
throw ex;
}
var resultString = "";
if (result.output is not null)
{
resultString = StringFromUTF8((IntPtr)result.output);
}
Regorus.Internal.API.regorus_result_drop(result);
return resultString;
}
}
}

View File

@@ -8,31 +8,37 @@
<LangVersion>10.0</LangVersion>
<!-- See https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-pack -->
<VersionPrefix>0.5.0</VersionPrefix>
<VersionPrefix>0.8.0</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
<!--
$(RegorusFFIArtifactsDir) is the location where regorus shared libraries have been
$(RegorusFFIArtifactsDir) is the location where regorus shared libraries have been
built for various platforms and copied to. RegorusFFIArtifactsDir is passed in
by the publishing pipeline.
by the publishing pipeline.
For each target triple, `Pack` expects the regorus ffi shared library
to be found in $(RegorusFFIArtifactsDir)/<target-triple>/release.
If $(IgnoreMissingArtifacts) is not set, ensure that the binaries for officially supported platforms exists.
-->
<Target Name="ChecksRegorusFFIArtifactsDir" BeforeTargets="Pack">
<Target Name="ChecksRegorusFFIArtifactsDir" BeforeTargets="Pack" Condition="'$(IgnoreMissingArtifacts)' == ''">
<Error Text="RegorusFFIArtifactsDir must be supplied." Condition="$(RegorusFFIArtifactsDir) == ''" />
<!-- Ensure that the binaries for officially supported platforms exists. -->
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.dll missing."
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.dll missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.dll')" />
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.pdb missing."
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.pdb missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.pdb')" />
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/libregorus_ffi.so missing."
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/libregorus_ffi.so missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/libregorus_ffi.so')" />
</Target>
</Target>
<ItemGroup>
<None Include="docs/README.md" Pack="true" PackagePath="/" />

View File

@@ -1,219 +0,0 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace Regorus.Internal
{
internal static unsafe partial class API
{
const string __DllName = "regorus_ffi";
/// <summary>
/// Drop a `RegorusResult`.
///
/// `output` and `error_message` strings are not valid after drop.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_result_drop(RegorusResult r);
/// <summary>
/// Construct a new Engine
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusEngine* regorus_engine_new();
/// <summary>
/// Clone a [`RegorusEngine`]
///
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
///
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
[DllImport(__DllName, EntryPoint = "regorus_engine_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_engine_drop(RegorusEngine* engine);
/// <summary>
/// Add a policy
///
/// The policy is parsed into AST.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: Rego policy.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_add_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_policy(RegorusEngine* engine, byte* path, byte* rego);
[DllImport(__DllName, EntryPoint = "regorus_engine_add_policy_from_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_policy_from_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Add policy data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
/// * `data`: JSON encoded value to be used as policy data.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_add_data_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_data_json(RegorusEngine* engine, byte* data);
/// <summary>
/// Get list of loaded Rego packages as JSON.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_get_packages", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_packages(RegorusEngine* engine);
/// <summary>
/// Get list of policies as JSON.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_get_policies", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policies(RegorusEngine* engine);
[DllImport(__DllName, EntryPoint = "regorus_engine_add_data_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Clear policy data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_clear_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_data(RegorusEngine* engine);
/// <summary>
/// Set input.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input
/// * `input`: JSON encoded value to be used as input to query.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_set_input_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_input_json(RegorusEngine* engine, byte* input);
[DllImport(__DllName, EntryPoint = "regorus_engine_set_input_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Evaluate query.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query
/// * `query`: Rego expression to be evaluate.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_eval_query", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_eval_query(RegorusEngine* engine, byte* query);
/// <summary>
/// Evaluate specified rule.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule
/// * `rule`: Path to the rule.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_eval_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_eval_rule(RegorusEngine* engine, byte* rule);
/// <summary>
/// Enable/disable coverage.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
/// * `enable`: Whether to enable or disable coverage.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_set_enable_coverage", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_enable_coverage(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Get coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_get_coverage_report", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_coverage_report(RegorusEngine* engine);
/// <summary>
/// Get pretty printed coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_get_coverage_report_pretty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_coverage_report_pretty(RegorusEngine* engine);
/// <summary>
/// Clear coverage data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_clear_coverage_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_coverage_data(RegorusEngine* engine);
/// <summary>
/// Whether to gather output of print statements.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints
/// * `enable`: Whether to enable or disable gathering print statements.
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_set_gather_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_gather_prints(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Take all the gathered print statements.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_take_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_take_prints(RegorusEngine* engine);
/// <summary>
/// Get AST of policies.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_ast_as_json
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_get_ast_as_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_ast_as_json(RegorusEngine* engine);
/// <summary>
/// Enable/disable rego v1.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0
/// </summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_set_rego_v0", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_rego_v0(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusResult
{
public RegorusStatus status;
public byte* output;
public byte* error_message;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusEngine
{
}
internal enum RegorusStatus : uint
{
RegorusStatusOk,
RegorusStatusError,
}
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
#nullable enable
namespace Regorus
{
internal sealed class RegorusEngineHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusEngineHandle() : base(ownsHandle: true)
{
}
internal static RegorusEngineHandle Create()
{
unsafe
{
var raw = Internal.API.regorus_engine_new();
if (raw is null)
{
throw new InvalidOperationException("Failed to create Regorus engine.");
}
var handle = new RegorusEngineHandle();
handle.SetHandle((IntPtr)raw);
return handle;
}
}
internal static RegorusEngineHandle FromPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
{
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
}
var handle = new RegorusEngineHandle();
handle.SetHandle(pointer);
return handle;
}
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
{
unsafe
{
Internal.API.regorus_engine_drop((Internal.RegorusEngine*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
internal sealed class RegorusCompiledPolicyHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusCompiledPolicyHandle() : base(ownsHandle: true)
{
}
internal static RegorusCompiledPolicyHandle FromPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
{
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
}
var handle = new RegorusCompiledPolicyHandle();
handle.SetHandle(pointer);
return handle;
}
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
{
unsafe
{
Internal.API.regorus_compiled_policy_drop((Internal.RegorusCompiledPolicy*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
}

View File

@@ -0,0 +1,297 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Provides static methods for managing the global resource schema registry.
/// Resource schemas define the structure and validation rules for Azure Policy resources.
/// </summary>
public static unsafe class SchemaRegistry
{
/// <summary>
/// Register a resource schema from JSON with a given name.
/// </summary>
/// <param name="name">Name to register the schema under</param>
/// <param name="schemaJson">JSON string representing the schema</param>
/// <exception cref="Exception">Thrown when schema registration fails</exception>
public static void RegisterResource(string name, string schemaJson)
{
Utf8Marshaller.WithUtf8(name, namePtr =>
{
Utf8Marshaller.WithUtf8(schemaJson, schemaPtr =>
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
}
});
});
}
/// <summary>
/// Check if a resource schema with the given name exists.
/// </summary>
/// <param name="name">Name of the schema to check</param>
/// <returns>True if the schema exists, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool ContainsResource(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_resource_schema_contains((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Get the number of registered resource schemas.
/// </summary>
/// <returns>The number of registered resource schemas</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static long ResourceCount
{
get
{
var result = Internal.API.regorus_resource_schema_len();
return GetIntResult(result);
}
}
/// <summary>
/// Check if the resource schema registry is empty.
/// </summary>
/// <returns>True if the registry is empty, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool IsResourceRegistryEmpty
{
get
{
var result = Internal.API.regorus_resource_schema_is_empty();
return GetBoolResult(result);
}
}
/// <summary>
/// List all registered resource schema names.
/// </summary>
/// <returns>JSON array of schema names</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListResourceNames()
{
return CheckAndDropResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
}
/// <summary>
/// Remove a resource schema by name.
/// </summary>
/// <param name="name">Name of the schema to remove</param>
/// <returns>True if the schema was removed, false if it wasn't found</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool RemoveResource(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_resource_schema_remove((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Clear all resource schemas from the registry.
/// </summary>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void ClearResources()
{
CheckAndDropResult(Internal.API.regorus_resource_schema_clear());
}
/// <summary>
/// Register an effect schema from JSON with a given name.
/// </summary>
/// <param name="name">Name to register the schema under</param>
/// <param name="schemaJson">JSON string representing the schema</param>
/// <exception cref="Exception">Thrown when schema registration fails</exception>
public static void RegisterEffect(string name, string schemaJson)
{
Utf8Marshaller.WithUtf8(name, namePtr =>
{
Utf8Marshaller.WithUtf8(schemaJson, schemaPtr =>
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
}
});
});
}
/// <summary>
/// Check if an effect schema with the given name exists.
/// </summary>
/// <param name="name">Name of the schema to check</param>
/// <returns>True if the schema exists, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool ContainsEffect(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_effect_schema_contains((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Get the number of registered effect schemas.
/// </summary>
/// <returns>The number of registered effect schemas</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static long EffectCount
{
get
{
var result = Internal.API.regorus_effect_schema_len();
return GetIntResult(result);
}
}
/// <summary>
/// Check if the effect schema registry is empty.
/// </summary>
/// <returns>True if the registry is empty, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool IsEffectRegistryEmpty
{
get
{
var result = Internal.API.regorus_effect_schema_is_empty();
return GetBoolResult(result);
}
}
/// <summary>
/// List all registered effect schema names.
/// </summary>
/// <returns>JSON array of schema names</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListEffectNames()
{
return CheckAndDropResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
}
/// <summary>
/// Remove an effect schema by name.
/// </summary>
/// <param name="name">Name of the schema to remove</param>
/// <returns>True if the schema was removed, false if it wasn't found</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool RemoveEffect(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_effect_schema_remove((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Clear all effect schemas from the registry.
/// </summary>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void ClearEffects()
{
CheckAndDropResult(Internal.API.regorus_effect_schema_clear());
}
private static string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private static string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type switch
{
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => StringFromUTF8((IntPtr)result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static bool GetBoolResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static long GetIntResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
}
}

View File

@@ -0,0 +1,192 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Provides static methods for managing the global target registry.
/// Targets define resource types and their associated schemas for Azure Policy evaluation.
/// </summary>
public static unsafe class TargetRegistry
{
/// <summary>
/// Register a target from JSON definition.
/// The target JSON should follow the target schema format.
/// Once registered, the target can be referenced in Rego policies using `__target__` rules.
/// </summary>
/// <param name="targetJson">JSON encoded target definition</param>
/// <exception cref="Exception">Thrown when target registration fails</exception>
public static void RegisterFromJson(string targetJson)
{
Utf8Marshaller.WithUtf8(targetJson, targetPtr =>
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
}
});
}
/// <summary>
/// Check if a target is registered.
/// </summary>
/// <param name="name">Name of the target to check</param>
/// <returns>True if the target is registered, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool Contains(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_target_registry_contains((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Get a list of all registered target names.
/// </summary>
/// <returns>JSON array of target names</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListNames()
{
return CheckAndDropResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
}
/// <summary>
/// Remove a target from the registry by name.
/// </summary>
/// <param name="name">The target name to remove</param>
/// <returns>True if the target was removed, false if it wasn't found</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool Remove(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_target_registry_remove((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Clear all targets from the registry.
/// </summary>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void Clear()
{
CheckAndDropResult(Internal.API.regorus_target_registry_clear());
}
/// <summary>
/// Get the number of registered targets.
/// </summary>
/// <returns>The number of registered targets</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static long Count
{
get
{
var result = Internal.API.regorus_target_registry_len();
return GetIntResult(result);
}
}
/// <summary>
/// Check if the target registry is empty.
/// </summary>
/// <returns>True if the registry is empty, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool IsEmpty
{
get
{
var result = Internal.API.regorus_target_registry_is_empty();
return GetBoolResult(result);
}
}
private static string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private static string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type switch
{
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => StringFromUTF8((IntPtr)result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static bool GetBoolResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static long GetIntResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
}
}

View File

@@ -0,0 +1,154 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
#nullable enable
namespace Regorus.Internal
{
/// <summary>
/// Helpers for marshaling managed strings to null-terminated UTF-8 buffers.
/// Provides stack-based storage for short lived conversions and pooled backing
/// for longer lived pinned buffers.
/// </summary>
internal static class Utf8Marshaller
{
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
// up to 512 bytes to cover common short strings while keeping the stack usage well
// below typical per-frame limits; larger payloads fall back to pooled buffers.
private const int StackAllocThreshold = 512;
/// <summary>
/// Represents a pooled and pinned UTF-8 buffer suitable for scenarios where
/// the pointer must remain stable beyond the immediate call site (for example,
/// when referenced by another buffer passed to native code).
/// </summary>
internal sealed class PinnedUtf8 : IDisposable
{
private GCHandle _handle;
private byte[]? _buffer;
private bool _disposed;
internal unsafe PinnedUtf8(string value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
var byteCount = Encoding.UTF8.GetByteCount(value);
_buffer = ArrayPool<byte>.Shared.Rent(byteCount + 1);
try
{
var written = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, 0);
_buffer[written] = 0;
_handle = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
Pointer = (byte*)_handle.AddrOfPinnedObject();
Length = written + 1;
}
catch
{
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = null;
throw;
}
}
internal unsafe byte* Pointer { get; }
internal int Length { get; }
public void Dispose()
{
if (_disposed)
{
return;
}
if (_handle.IsAllocated)
{
_handle.Free();
}
if (_buffer != null)
{
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = null;
}
_disposed = true;
}
}
internal unsafe delegate void Utf8PointerAction(byte* pointer);
internal static unsafe void WithUtf8(string value, Utf8PointerAction action)
{
if (action is null)
{
throw new ArgumentNullException(nameof(action));
}
WithUtf8<object?>(value, ptr =>
{
action((byte*)ptr);
return null;
});
}
internal static T WithUtf8<T>(string value, Func<IntPtr, T> func)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
if (func is null)
{
throw new ArgumentNullException(nameof(func));
}
var byteCount = Encoding.UTF8.GetByteCount(value);
var required = byteCount + 1;
if (required <= StackAllocThreshold)
{
Span<byte> buffer = stackalloc byte[required];
return Invoke(value, func, buffer, byteCount);
}
var rented = ArrayPool<byte>.Shared.Rent(required);
try
{
Span<byte> buffer = rented;
return Invoke(value, func, buffer, byteCount);
}
finally
{
ArrayPool<byte>.Shared.Return(rented);
}
}
private static unsafe T Invoke<T>(string value, Func<IntPtr, T> func, Span<byte> buffer, int byteCount)
{
fixed (char* charPtr = value)
fixed (byte* bytePtr = buffer)
{
var written = Encoding.UTF8.GetBytes(charPtr, value.Length, bytePtr, byteCount);
bytePtr[written] = 0;
return func((IntPtr)bytePtr);
}
}
internal static PinnedUtf8 Pin(string value)
{
return new PinnedUtf8(value);
}
}
}

View File

@@ -0,0 +1,291 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Text.Json;
namespace TargetExampleApp;
class Program
{
// Policy definition constants
private const string AZURE_STORAGE_POLICY_DEFINITION = @"
package policy
import rego.v1
# Target declaration for Azure Policy
__target__ := ""target.tests.azure_policy""
default parameters.requiredTLSVersion = """"
default parameters.allowedPorts = []
# Policy rules for storage accounts
default allow := false
# Allow storage accounts with HTTPS-only traffic and proper encryption
allow if {
input.type == ""Microsoft.Storage/storageAccounts""
input.properties.supportsHttpsTrafficOnly == true
input.properties.encryption.services.blob.enabled == true
input.properties.minimumTlsVersion in [parameters.requiredTLSVersion]
}
# Allow network security groups with proper inbound rules
allow if {
input.type == ""Microsoft.Network/networkSecurityGroups""
count([rule |
rule := input.properties.securityRules[_]
rule.properties.direction == ""Inbound""
rule.properties.access == ""Allow""
rule.properties.sourceAddressPrefix == ""*""
rule.properties.destinationPortRange in [parameters.allowedPorts]
]) == 0
}";
private const string AZURE_STORAGE_POLICY_ASSIGNMENT = @"
package policy
import rego.v1
parameters.requiredTLSVersion = ""TLS1_2""
parameters.allowedPorts = [""22"", ""3389""]";
// Test data constants
private const string COMPLIANT_STORAGE_ACCOUNT = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""compliantstorageacct"",
""location"": ""eastus"",
""kind"": ""StorageV2"",
""properties"": {
""supportsHttpsTrafficOnly"": true,
""minimumTlsVersion"": ""TLS1_2"",
""allowBlobPublicAccess"": false,
""encryption"": {
""services"": {
""blob"": { ""enabled"": true },
""file"": { ""enabled"": true }
}
}
},
""tags"": {
""environment"": ""production""
}
}";
private const string NON_COMPLIANT_STORAGE_ACCOUNT = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""insecurestorageacct"",
""location"": ""westus"",
""kind"": ""Storage"",
""properties"": {
""supportsHttpsTrafficOnly"": false,
""minimumTlsVersion"": ""TLS1_0"",
""allowBlobPublicAccess"": true,
""encryption"": {
""services"": {
""blob"": { ""enabled"": false },
""file"": { ""enabled"": false }
}
}
}
}";
static void Main(string[] args)
{
Console.WriteLine("=== Regorus Target Example Application ===\n");
try
{
DemonstrateTargetFunctionality();
Console.WriteLine("\n=== Target demonstration completed successfully! ===");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Environment.Exit(1);
}
}
static void DemonstrateTargetFunctionality()
{
Console.WriteLine("REGORUS TARGET FUNCTIONALITY DEMONSTRATION");
Console.WriteLine("==========================================");
// 1. Register target using JSON from file
var targetJsonPath = Path.Combine(AppContext.BaseDirectory, "azure_policy.target.json");
var targetJson = File.ReadAllText(targetJsonPath);
Console.WriteLine("1. Registering target from JSON file:");
Console.WriteLine(targetJson);
Regorus.TargetRegistry.RegisterFromJson(targetJson);
Console.WriteLine($"Target registered. Registry contains {Regorus.TargetRegistry.Count} target(s)");
Console.WriteLine($"Registered targets: {Regorus.TargetRegistry.ListNames()}");
// 2. Compile policy for target
var policyModules = new List<Regorus.PolicyModule>
{
new Regorus.PolicyModule($"definition-{Guid.NewGuid():N}", AZURE_STORAGE_POLICY_DEFINITION),
new Regorus.PolicyModule($"assignment-{Guid.NewGuid():N}", AZURE_STORAGE_POLICY_ASSIGNMENT)
};
var policyDataJson = "{}";
Console.WriteLine("\n2. Compiling policy for target...");
using var compiledPolicy = Regorus.Compiler.CompilePolicyForTarget(policyDataJson, policyModules);
Console.WriteLine("Policy compiled successfully!");
// 2.5. Demonstrate policy information retrieval
Console.WriteLine("\n2.5. Retrieving policy information:");
DemonstratePolicyInfo(compiledPolicy);
// 3. Evaluate with different inputs
Console.WriteLine("\n3. Testing policy evaluation:");
Console.WriteLine("Compliant storage account:");
Console.WriteLine(COMPLIANT_STORAGE_ACCOUNT);
var compliantResult = compiledPolicy.EvalWithInput(COMPLIANT_STORAGE_ACCOUNT);
Console.WriteLine($"Result: {compliantResult}");
Console.WriteLine("\nNon-compliant storage account:");
Console.WriteLine(NON_COMPLIANT_STORAGE_ACCOUNT);
var nonCompliantResult = compiledPolicy.EvalWithInput(NON_COMPLIANT_STORAGE_ACCOUNT);
Console.WriteLine($"Result: {nonCompliantResult}");
// 4. Demonstrate thread-safe concurrent evaluation
Console.WriteLine("\n4. Testing concurrent evaluation from multiple threads:");
DemonstrateConcurrentEvaluation(compiledPolicy);
}
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
{
var testInputs = new[]
{
("Thread-1-Compliant", COMPLIANT_STORAGE_ACCOUNT),
("Thread-2-NonCompliant", NON_COMPLIANT_STORAGE_ACCOUNT),
("Thread-3-Compliant", COMPLIANT_STORAGE_ACCOUNT.Replace("compliantstorageacct", "thread3storage")),
("Thread-4-NonCompliant", NON_COMPLIANT_STORAGE_ACCOUNT.Replace("insecurestorageacct", "thread4storage")),
("Thread-5-Compliant", COMPLIANT_STORAGE_ACCOUNT.Replace("compliantstorageacct", "thread5storage"))
};
Console.WriteLine($"Starting {testInputs.Length} concurrent evaluations...");
var tasks = testInputs.Select(input =>
Task.Run(() => {
var (threadName, json) = input;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// Multiple evaluations per thread to stress test
var results = new List<string>();
for (int i = 0; i < 1000; i++)
{
var result = compiledPolicy.EvalWithInput(json);
results.Add(result);
}
stopwatch.Stop();
var microseconds = stopwatch.ElapsedTicks * 1000000 / System.Diagnostics.Stopwatch.Frequency;
// Verify all results are identical (thread safety)
var firstResult = results[0];
var allIdentical = results.All(r => r == firstResult);
Console.WriteLine($"✓ {threadName}: {results.Count} evaluations in {microseconds}μs, " +
$"Results consistent: {allIdentical}");
return (threadName, results.Count, microseconds, allIdentical);
})
).ToArray();
// Wait for all threads to complete
var results = Task.WhenAll(tasks).Result;
Console.WriteLine("\nConcurrency test results:");
var totalEvaluations = results.Sum(r => r.Item2);
var maxTime = results.Max(r => r.Item3);
var allConsistent = results.All(r => r.allIdentical);
Console.WriteLine($"✓ Total evaluations: {totalEvaluations}");
Console.WriteLine($"✓ Max thread time: {maxTime}μs");
Console.WriteLine($"✓ All threads consistent: {allConsistent}");
Console.WriteLine($"✓ Approximate throughput: {totalEvaluations * 1000000.0 / maxTime:F0} evaluations/second");
Console.WriteLine("✓ No locks required - CompiledPolicy is thread-safe!");
}
static void DemonstratePolicyInfo(Regorus.CompiledPolicy compiledPolicy)
{
Console.WriteLine("Getting policy metadata using GetPolicyInfo()...");
try
{
var policyInfo = compiledPolicy.GetPolicyInfo();
Console.WriteLine($"✓ Policy Information Retrieved:");
Console.WriteLine($" Target Name: {policyInfo.TargetName ?? "None"}");
Console.WriteLine($" Effect Rule: {policyInfo.EffectRule ?? "None"}");
Console.WriteLine($" Entrypoint Rule: {policyInfo.EntrypointRule}");
Console.WriteLine($" Module IDs ({policyInfo.ModuleIds.Count}):");
foreach (var moduleId in policyInfo.ModuleIds)
{
Console.WriteLine($" - {moduleId}");
}
Console.WriteLine($" Applicable Resource Types ({policyInfo.ApplicableResourceTypes.Count}):");
foreach (var resourceType in policyInfo.ApplicableResourceTypes)
{
Console.WriteLine($" - {resourceType}");
}
if (policyInfo.Parameters != null && policyInfo.Parameters.Count > 0)
{
Console.WriteLine($" Policy Parameters:");
foreach (var parameterSet in policyInfo.Parameters)
{
Console.WriteLine($" From '{parameterSet.SourceFile}':");
Console.WriteLine($" Parameters ({parameterSet.Parameters.Count}):");
foreach (var param in parameterSet.Parameters)
{
Console.WriteLine($" - {param.Name} ({param.Type})");
if (param.Default != null)
{
Console.WriteLine($" Default: {param.Default}");
}
if (!string.IsNullOrEmpty(param.Description))
{
Console.WriteLine($" Description: {param.Description}");
}
}
if (parameterSet.Modifiers.Count > 0)
{
Console.WriteLine($" Modifiers ({parameterSet.Modifiers.Count}):");
foreach (var modifier in parameterSet.Modifiers)
{
Console.WriteLine($" - {modifier.Name}: {modifier.Value}");
}
}
}
}
else
{
Console.WriteLine(" No parameter information available");
}
// Demonstrate JSON serialization of policy info
Console.WriteLine("\n✓ Policy Info as JSON:");
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var policyInfoJson = JsonSerializer.Serialize(policyInfo, jsonOptions);
Console.WriteLine(policyInfoJson);
}
catch (Exception ex)
{
Console.WriteLine($"✗ Failed to get policy info: {ex.Message}");
}
}
}

Some files were not shown because too many files have changed in this diff Show More