mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
59 Commits
ant/cs_tes
...
copilot/av
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
209a534b7c | ||
|
|
ed360879a6 | ||
|
|
4988bda647 | ||
|
|
92b9ec8fa8 | ||
|
|
a3a20a1235 | ||
|
|
688e6128d4 | ||
|
|
ad8c543fb5 | ||
|
|
49bd3c22f3 | ||
|
|
6dc505c88b | ||
|
|
091bbb2e5c | ||
|
|
1e4ff952e6 | ||
|
|
25a7ddad0a | ||
|
|
5d8387f4d9 | ||
|
|
9604fe86f1 | ||
|
|
ac388684bc | ||
|
|
57f2e7703c | ||
|
|
4ec9e76440 | ||
|
|
1b0c2d4072 | ||
|
|
85753aaf37 | ||
|
|
c43c94559a | ||
|
|
2a0b4ae6b5 | ||
|
|
6c5338228b | ||
|
|
d561531613 | ||
|
|
a53c7c8192 | ||
|
|
5c71debcb9 | ||
|
|
cc917ea75d | ||
|
|
3c33d31d08 | ||
|
|
965daa0a46 | ||
|
|
db718654b5 | ||
|
|
77f8544868 | ||
|
|
2fcd5e3eb9 | ||
|
|
30f0d4e781 | ||
|
|
de6aa2bcd1 | ||
|
|
dbba57f499 | ||
|
|
a8384da070 | ||
|
|
c5b2b0df97 | ||
|
|
fc09802bfb | ||
|
|
9fce2ccc00 | ||
|
|
3b802c14cb | ||
|
|
90b4ec6823 | ||
|
|
9487defa20 | ||
|
|
48d2064c14 | ||
|
|
a29bfeeb4f | ||
|
|
0a9864f3ec | ||
|
|
9cba07b778 | ||
|
|
168b2a9c88 | ||
|
|
8ee1cf3298 | ||
|
|
444b2970a1 | ||
|
|
620f8a4547 | ||
|
|
c631d44154 | ||
|
|
39f10326cc | ||
|
|
60ac4a7a7c | ||
|
|
5caac47b38 | ||
|
|
2f6c39753c | ||
|
|
130f9685fd | ||
|
|
b11007a1be | ||
|
|
6719456468 | ||
|
|
962c0cc459 | ||
|
|
9e43bd9878 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
@@ -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": [
|
||||
// {
|
||||
|
||||
29
.github/actions/toolchains/rust/action.yml
vendored
Normal file
29
.github/actions/toolchains/rust/action.yml
vendored
Normal 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
212
.github/workflows/codeql.yml
vendored
Normal 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}}"
|
||||
7
.github/workflows/pr-extensions.yml
vendored
7
.github/workflows/pr-extensions.yml
vendored
@@ -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
|
||||
|
||||
12
.github/workflows/pr.yml
vendored
12
.github/workflows/pr.yml
vendored
@@ -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
|
||||
|
||||
18
.github/workflows/publish-java.yml
vendored
18
.github/workflows/publish-java.yml
vendored
@@ -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
|
||||
|
||||
35
.github/workflows/publish-python.yml
vendored
35
.github/workflows/publish-python.yml
vendored
@@ -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:
|
||||
|
||||
4
.github/workflows/publish-wasm.yml
vendored
4
.github/workflows/publish-wasm.yml
vendored
@@ -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'
|
||||
|
||||
8
.github/workflows/release-plz.yml
vendored
8
.github/workflows/release-plz.yml
vendored
@@ -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 }}
|
||||
|
||||
19
.github/workflows/rust-clippy.yml
vendored
19
.github/workflows/rust-clippy.yml
vendored
@@ -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
|
||||
|
||||
7
.github/workflows/test-c-cpp.yml
vendored
7
.github/workflows/test-c-cpp.yml
vendored
@@ -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
|
||||
|
||||
|
||||
77
.github/workflows/test-csharp.yml
vendored
77
.github/workflows/test-csharp.yml
vendored
@@ -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
|
||||
|
||||
|
||||
|
||||
7
.github/workflows/test-ffi.yml
vendored
7
.github/workflows/test-ffi.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/test-go.yml
vendored
8
.github/workflows/test-go.yml
vendored
@@ -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
|
||||
|
||||
|
||||
20
.github/workflows/test-java.yml
vendored
20
.github/workflows/test-java.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/test-musl.yml
vendored
10
.github/workflows/test-musl.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/test-no-std.yml
vendored
10
.github/workflows/test-no-std.yml
vendored
@@ -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
|
||||
|
||||
24
.github/workflows/test-python.yml
vendored
24
.github/workflows/test-python.yml
vendored
@@ -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
|
||||
|
||||
3
.github/workflows/test-ruby.yml
vendored
3
.github/workflows/test-ruby.yml
vendored
@@ -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
|
||||
|
||||
|
||||
10
.github/workflows/test-wasm.yml
vendored
10
.github/workflows/test-wasm.yml
vendored
@@ -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
|
||||
|
||||
|
||||
9
.github/workflows/tests-debug.yml
vendored
9
.github/workflows/tests-debug.yml
vendored
@@ -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
7
.gitignore
vendored
@@ -28,4 +28,9 @@ bindings/*/target
|
||||
# C# build folders
|
||||
**bin
|
||||
**obj
|
||||
*.sln
|
||||
|
||||
# Visual Studio folders
|
||||
**/*.vs
|
||||
|
||||
# Visual Studio solution files
|
||||
*.sln
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -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
1271
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
76
Cargo.toml
76
Cargo.toml
@@ -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
|
||||
|
||||
11
README.md
11
README.md
@@ -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
80
benches/aci_benchmark.rs
Normal 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);
|
||||
157
benches/evaluation/README.md
Normal file
157
benches/evaluation/README.md
Normal 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
|
||||
160
benches/evaluation/compiled_policy_evaluation_benchmark.md
Normal file
160
benches/evaluation/compiled_policy_evaluation_benchmark.md
Normal 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
|
||||
|
||||
232
benches/evaluation/compiled_policy_evaluation_benchmark.rs
Normal file
232
benches/evaluation/compiled_policy_evaluation_benchmark.rs
Normal 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);
|
||||
127
benches/evaluation/engine_evaluation_benchmark.md
Normal file
127
benches/evaluation/engine_evaluation_benchmark.md
Normal 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
|
||||
228
benches/evaluation/engine_evaluation_benchmark.rs
Normal file
228
benches/evaluation/engine_evaluation_benchmark.rs
Normal 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);
|
||||
117
benches/evaluation/policy_data.rs
Normal file
117
benches/evaluation/policy_data.rs
Normal 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",
|
||||
]
|
||||
}
|
||||
15
benches/evaluation/test_data/inputs/api_access_input.json
Normal file
15
benches/evaluation/test_data/inputs/api_access_input.json
Normal 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"
|
||||
}
|
||||
}
|
||||
15
benches/evaluation/test_data/inputs/api_access_input2.json
Normal file
15
benches/evaluation/test_data/inputs/api_access_input2.json
Normal 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"
|
||||
}
|
||||
}
|
||||
15
benches/evaluation/test_data/inputs/api_access_input3.json
Normal file
15
benches/evaluation/test_data/inputs/api_access_input3.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"vault": {
|
||||
"name": "mykeyvault",
|
||||
"location": "eastus",
|
||||
"enableSoftDelete": true,
|
||||
"softDeleteRetentionInDays": 90,
|
||||
"enablePurgeProtection": true,
|
||||
"networkAcls": {
|
||||
"defaultAction": "Deny",
|
||||
"bypass": "AzureServices"
|
||||
},
|
||||
"tags": {
|
||||
"environment": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"vault": {
|
||||
"name": "devkeyvault",
|
||||
"location": "westus2",
|
||||
"enableSoftDelete": true,
|
||||
"softDeleteRetentionInDays": 30,
|
||||
"enablePurgeProtection": false,
|
||||
"networkAcls": {
|
||||
"defaultAction": "Allow",
|
||||
"bypass": "AzureServices"
|
||||
},
|
||||
"tags": {
|
||||
"environment": "development"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"vault": {
|
||||
"name": "prodkeyvault",
|
||||
"location": "eastus",
|
||||
"enableSoftDelete": true,
|
||||
"softDeleteRetentionInDays": 90,
|
||||
"enablePurgeProtection": true,
|
||||
"networkAcls": {
|
||||
"defaultAction": "Deny",
|
||||
"bypass": "AzureServices"
|
||||
},
|
||||
"tags": {
|
||||
"environment": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
benches/evaluation/test_data/inputs/azure_nsg_input.json
Normal file
13
benches/evaluation/test_data/inputs/azure_nsg_input.json
Normal 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
|
||||
}
|
||||
}
|
||||
13
benches/evaluation/test_data/inputs/azure_nsg_input2.json
Normal file
13
benches/evaluation/test_data/inputs/azure_nsg_input2.json
Normal 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
|
||||
}
|
||||
}
|
||||
13
benches/evaluation/test_data/inputs/azure_nsg_input3.json
Normal file
13
benches/evaluation/test_data/inputs/azure_nsg_input3.json
Normal 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
|
||||
}
|
||||
}
|
||||
15
benches/evaluation/test_data/inputs/azure_storage_input.json
Normal file
15
benches/evaluation/test_data/inputs/azure_storage_input.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"account": {
|
||||
"name": "mystorageaccount",
|
||||
"tier": "Standard",
|
||||
"replication": "LRS",
|
||||
"location": "eastus",
|
||||
"tags": {
|
||||
"environment": "production"
|
||||
}
|
||||
},
|
||||
"container": {
|
||||
"name": "data",
|
||||
"publicAccess": "None"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"account": {
|
||||
"name": "devstorageaccount",
|
||||
"tier": "Premium",
|
||||
"replication": "LRS",
|
||||
"location": "westus2",
|
||||
"tags": {
|
||||
"environment": "production"
|
||||
}
|
||||
},
|
||||
"container": {
|
||||
"name": "logs",
|
||||
"publicAccess": "None"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"account": {
|
||||
"name": "prodstorageaccount",
|
||||
"tier": "Standard",
|
||||
"replication": "GRS",
|
||||
"location": "eastus",
|
||||
"tags": {
|
||||
"environment": "production"
|
||||
}
|
||||
},
|
||||
"container": {
|
||||
"name": "backups",
|
||||
"publicAccess": "None"
|
||||
}
|
||||
}
|
||||
14
benches/evaluation/test_data/inputs/azure_vm_input.json
Normal file
14
benches/evaluation/test_data/inputs/azure_vm_input.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"vm": {
|
||||
"size": "Standard_D2s_v3",
|
||||
"os": "Linux",
|
||||
"location": "eastus",
|
||||
"tags": {
|
||||
"environment": "production",
|
||||
"department": "engineering"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"department": "engineering"
|
||||
}
|
||||
}
|
||||
14
benches/evaluation/test_data/inputs/azure_vm_input2.json
Normal file
14
benches/evaluation/test_data/inputs/azure_vm_input2.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"vm": {
|
||||
"size": "Standard_B1s",
|
||||
"os": "Windows",
|
||||
"location": "westus2",
|
||||
"tags": {
|
||||
"environment": "dev",
|
||||
"department": "marketing"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"department": "marketing"
|
||||
}
|
||||
}
|
||||
14
benches/evaluation/test_data/inputs/azure_vm_input3.json
Normal file
14
benches/evaluation/test_data/inputs/azure_vm_input3.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"vm": {
|
||||
"size": "Standard_D4s_v3",
|
||||
"os": "Linux",
|
||||
"location": "eastus",
|
||||
"tags": {
|
||||
"environment": "production",
|
||||
"department": "engineering"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"department": "engineering"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"data": {
|
||||
"type": "user_profile",
|
||||
"classification": "personal",
|
||||
"contains_pii": true,
|
||||
"region": "EU"
|
||||
},
|
||||
"user": {
|
||||
"clearance": "confidential",
|
||||
"location": "EU"
|
||||
},
|
||||
"operation": "read"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"data": {
|
||||
"type": "financial_report",
|
||||
"classification": "confidential",
|
||||
"contains_pii": false,
|
||||
"region": "US"
|
||||
},
|
||||
"user": {
|
||||
"clearance": "secret",
|
||||
"location": "US"
|
||||
},
|
||||
"operation": "read"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"data": {
|
||||
"type": "public_announcement",
|
||||
"classification": "public",
|
||||
"contains_pii": false,
|
||||
"region": "GLOBAL"
|
||||
},
|
||||
"user": {
|
||||
"clearance": "public",
|
||||
"location": "EU"
|
||||
},
|
||||
"operation": "read"
|
||||
}
|
||||
12
benches/evaluation/test_data/inputs/rbac_input.json
Normal file
12
benches/evaluation/test_data/inputs/rbac_input.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"user": {
|
||||
"name": "alice",
|
||||
"roles": ["viewer", "editor"]
|
||||
},
|
||||
"resource": {
|
||||
"name": "document1",
|
||||
"type": "document",
|
||||
"owner": "alice"
|
||||
},
|
||||
"action": "read"
|
||||
}
|
||||
12
benches/evaluation/test_data/inputs/rbac_input2.json
Normal file
12
benches/evaluation/test_data/inputs/rbac_input2.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"user": {
|
||||
"name": "bob",
|
||||
"roles": ["admin"]
|
||||
},
|
||||
"resource": {
|
||||
"name": "document2",
|
||||
"type": "document",
|
||||
"owner": "bob"
|
||||
},
|
||||
"action": "write"
|
||||
}
|
||||
12
benches/evaluation/test_data/inputs/rbac_input3.json
Normal file
12
benches/evaluation/test_data/inputs/rbac_input3.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"user": {
|
||||
"name": "charlie",
|
||||
"roles": ["viewer"]
|
||||
},
|
||||
"resource": {
|
||||
"name": "document3",
|
||||
"type": "document",
|
||||
"owner": "alice"
|
||||
},
|
||||
"action": "read"
|
||||
}
|
||||
11
benches/evaluation/test_data/inputs/time_based_input.json
Normal file
11
benches/evaluation/test_data/inputs/time_based_input.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"time": "09:30:00",
|
||||
"day": "monday",
|
||||
"user": {
|
||||
"role": "employee",
|
||||
"shift": "day"
|
||||
},
|
||||
"request": {
|
||||
"urgent": false
|
||||
}
|
||||
}
|
||||
11
benches/evaluation/test_data/inputs/time_based_input2.json
Normal file
11
benches/evaluation/test_data/inputs/time_based_input2.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"time": "14:30:00",
|
||||
"day": "wednesday",
|
||||
"user": {
|
||||
"role": "employee",
|
||||
"shift": "day"
|
||||
},
|
||||
"request": {
|
||||
"urgent": false
|
||||
}
|
||||
}
|
||||
11
benches/evaluation/test_data/inputs/time_based_input3.json
Normal file
11
benches/evaluation/test_data/inputs/time_based_input3.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"time": "22:00:00",
|
||||
"day": "friday",
|
||||
"user": {
|
||||
"role": "admin",
|
||||
"shift": "night"
|
||||
},
|
||||
"request": {
|
||||
"urgent": true
|
||||
}
|
||||
}
|
||||
13
benches/evaluation/test_data/policies/api_access_policy.rego
Normal file
13
benches/evaluation/test_data/policies/api_access_policy.rego
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
31
benches/evaluation/test_data/policies/azure_nsg_policy.rego
Normal file
31
benches/evaluation/test_data/policies/azure_nsg_policy.rego
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
20
benches/evaluation/test_data/policies/azure_vm_policy.rego
Normal file
20
benches/evaluation/test_data/policies/azure_vm_policy.rego
Normal 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"]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
10
benches/evaluation/test_data/policies/rbac_policy.rego
Normal file
10
benches/evaluation/test_data/policies/rbac_policy.rego
Normal 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
|
||||
}
|
||||
23
benches/evaluation/test_data/policies/time_based_policy.rego
Normal file
23
benches/evaluation/test_data/policies/time_based_policy.rego
Normal 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 != ""
|
||||
}
|
||||
186
benches/regorus_benchmark.rs
Normal file
186
benches/regorus_benchmark.rs
Normal 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);
|
||||
886
benches/schema_validation_benchmark.rs
Normal file
886
benches/schema_validation_benchmark.rs
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
3
bindings/cpp/regorus_ffiConfig.cmake.in
Normal file
3
bindings/cpp/regorus_ffiConfig.cmake.in
Normal 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
421
bindings/csharp/API.md
Normal 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.
|
||||
25
bindings/csharp/Benchmarks/Benchmarks.csproj
Normal file
25
bindings/csharp/Benchmarks/Benchmarks.csproj
Normal 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>
|
||||
294
bindings/csharp/Benchmarks/CompiledPolicyEvaluationBenchmark.cs
Normal file
294
bindings/csharp/Benchmarks/CompiledPolicyEvaluationBenchmark.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
293
bindings/csharp/Benchmarks/EngineEvaluationBenchmark.cs
Normal file
293
bindings/csharp/Benchmarks/EngineEvaluationBenchmark.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
bindings/csharp/Benchmarks/Program.cs
Normal file
36
bindings/csharp/Benchmarks/Program.cs
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
118
bindings/csharp/Benchmarks/engine_evaluation_benchmark.md
Normal file
118
bindings/csharp/Benchmarks/engine_evaluation_benchmark.md
Normal 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
32
bindings/csharp/README.md
Normal 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.
|
||||

|
||||
|
||||
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.
|
||||

|
||||
|
||||
## Local
|
||||
|
||||
TODO
|
||||
27
bindings/csharp/Regorus.Tests/Regorus.Tests.csproj
Normal file
27
bindings/csharp/Regorus.Tests/Regorus.Tests.csproj
Normal 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>
|
||||
256
bindings/csharp/Regorus.Tests/RegorusTests.cs
Normal file
256
bindings/csharp/Regorus.Tests/RegorusTests.cs
Normal 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)."
|
||||
);
|
||||
}
|
||||
}
|
||||
220
bindings/csharp/Regorus/CompiledPolicy.cs
Normal file
220
bindings/csharp/Regorus/CompiledPolicy.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
197
bindings/csharp/Regorus/Compiler.cs
Normal file
197
bindings/csharp/Regorus/Compiler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
445
bindings/csharp/Regorus/Engine.cs
Normal file
445
bindings/csharp/Regorus/Engine.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
546
bindings/csharp/Regorus/NativeMethods.cs
Normal file
546
bindings/csharp/Regorus/NativeMethods.cs
Normal 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
|
||||
}
|
||||
141
bindings/csharp/Regorus/PolicyInfo.cs
Normal file
141
bindings/csharp/Regorus/PolicyInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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="/" />
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
90
bindings/csharp/Regorus/SafeHandles.cs
Normal file
90
bindings/csharp/Regorus/SafeHandles.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
297
bindings/csharp/Regorus/SchemaRegistry.cs
Normal file
297
bindings/csharp/Regorus/SchemaRegistry.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
bindings/csharp/Regorus/TargetRegistry.cs
Normal file
192
bindings/csharp/Regorus/TargetRegistry.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
154
bindings/csharp/Regorus/Utf8Marshaller.cs
Normal file
154
bindings/csharp/Regorus/Utf8Marshaller.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
291
bindings/csharp/TargetExampleApp/Program.cs
Normal file
291
bindings/csharp/TargetExampleApp/Program.cs
Normal 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
Reference in New Issue
Block a user