mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: add multi-threaded evaluation benchmark suite with comprehensive C# implementation (#457)
This commit introduces a complete multi-threaded evaluation benchmark suite for both Rust and C# implementations of Regorus. - Implemented engine evaluation benchmark with input and engine cloning strategies - Implemented compiled policy evaluation benchmark with input cloning and shared compiled policy strategies. - Created EngineEvaluationBenchmark.cs and CompiledPolicyEvaluationBenchmark.cs with time-based execution (3s warmup + 3s evaluation) - Implemented configuration options matching Rust implementation (useClonedEngines, useSharedPolicies parameters) - Created markdown analysis documentation with cross-platform performance analysis - C# seems to achieve 58-89% of Rust performance on test machine. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
a53c7c8192
commit
d561531613
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
|
||||
135
benches/evaluation/compiled_policy_evaluation_benchmark.md
Normal file
135
benches/evaluation/compiled_policy_evaluation_benchmark.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 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
|
||||
- **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 | 3.30 | 303 |
|
||||
| 2 | 8.53 | 234 |
|
||||
| 4 | 18.78 | 213 |
|
||||
| 6 | 32.35 | 186 |
|
||||
| 8 | 73.12 | 109 |
|
||||
| 10 | 108.97 | 92 |
|
||||
| 12 | 145.56 | 82 |
|
||||
| 14 | 196.14 | 71 |
|
||||
| 16 | 248.77 | 64 |
|
||||
| 18 | 290.01 | 62 |
|
||||
| 20 | 317.16 | 63 |
|
||||
| 22 | 348.83 | 63 |
|
||||
| 24 | 361.05 | 66 |
|
||||
| 26 | 389.70 | 67 |
|
||||
| 28 | 418.66 | 67 |
|
||||
| 30 | 444.40 | 68 |
|
||||
| 32 | 476.53 | 67 |
|
||||
|
||||
### Compiled Shared Policies, Fresh Inputs
|
||||
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|
||||
|--------:|---------------------------:|---------------------:|
|
||||
| 1 | 4.51 | 222 |
|
||||
| 2 | 9.77 | 205 |
|
||||
| 4 | 23.36 | 171 |
|
||||
| 6 | 38.12 | 157 |
|
||||
| 8 | 85.02 | 94 |
|
||||
| 10 | 133.66 | 75 |
|
||||
| 12 | 180.46 | 66 |
|
||||
| 14 | 238.23 | 59 |
|
||||
| 16 | 318.78 | 50 |
|
||||
| 18 | 353.15 | 51 |
|
||||
| 20 | 389.29 | 51 |
|
||||
| 22 | 459.61 | 48 |
|
||||
| 24 | 507.62 | 47 |
|
||||
| 26 | 539.43 | 48 |
|
||||
| 28 | 554.99 | 50 |
|
||||
| 30 | 625.57 | 48 |
|
||||
| 32 | 690.55 | 46 |
|
||||
|
||||
### Compiled Per Iteration, Cloned Inputs
|
||||
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|
||||
|--------:|---------------------------:|---------------------:|
|
||||
| 1 | 22.68 | 44 |
|
||||
| 2 | 47.99 | 42 |
|
||||
| 4 | 108.09 | 37 |
|
||||
| 6 | 167.62 | 36 |
|
||||
| 8 | 283.17 | 28 |
|
||||
| 10 | 418.25 | 24 |
|
||||
| 12 | 546.24 | 22 |
|
||||
| 14 | 688.79 | 20 |
|
||||
| 16 | 951.72 | 17 |
|
||||
| 18 | 1060.20 | 17 |
|
||||
| 20 | 1223.60 | 16 |
|
||||
| 22 | 1342.50 | 16 |
|
||||
| 24 | 1445.70 | 17 |
|
||||
| 26 | 1676.50 | 15 |
|
||||
| 28 | 1765.20 | 16 |
|
||||
| 30 | 1939.00 | 15 |
|
||||
| 32 | 2197.30 | 15 |
|
||||
|
||||
### Compiled Per Iteration, Fresh Inputs
|
||||
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|
||||
|--------:|---------------------------:|---------------------:|
|
||||
| 1 | 23.95 | 42 |
|
||||
| 2 | 49.53 | 40 |
|
||||
| 4 | 116.42 | 34 |
|
||||
| 6 | 197.35 | 30 |
|
||||
| 8 | 293.04 | 27 |
|
||||
| 10 | 385.90 | 26 |
|
||||
| 12 | 508.82 | 24 |
|
||||
| 14 | 679.23 | 21 |
|
||||
| 16 | 913.02 | 18 |
|
||||
| 18 | 1075.90 | 17 |
|
||||
| 20 | 1209.80 | 17 |
|
||||
| 22 | 1358.90 | 16 |
|
||||
| 24 | 1523.90 | 16 |
|
||||
| 26 | 1700.20 | 15 |
|
||||
| 28 | 1966.90 | 14 |
|
||||
| 30 | 2179.30 | 14 |
|
||||
| 32 | 2327.70 | 14 |
|
||||
|
||||
## Analysis
|
||||
|
||||
The compiled policy benchmark demonstrates the following performance characteristics:
|
||||
|
||||
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 (~7x slower than pre-compiled)
|
||||
3. **Scaling Patterns**:
|
||||
- Best throughput achieved at 1 thread for shared policy configurations
|
||||
- Higher thread counts show performance degradation due to contention
|
||||
- Per-iteration compilation shows poor scaling across all thread counts
|
||||
4. **Input Processing**: Fresh inputs add ~25-30% overhead across all configurations
|
||||
5. **Thread Performance**:
|
||||
- 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
|
||||
|
||||
## Comparison with Engine Evaluation
|
||||
|
||||
| Configuration | Compiled Policy (1 thread) | Engine Evaluation (1 thread) | Performance Ratio |
|
||||
|:---------------------|:--------------------------------|:--------------------------------|------------------:|
|
||||
| Shared/Cloned | Best performance | Higher throughput | 0.67x-0.92x |
|
||||
| Shared/Fresh | ~27% reduction from optimal | ~30% reduction from optimal | 0.62x-0.97x |
|
||||
| Per-iteration/Cloned | ~85% reduction from optimal | ~86% reduction from optimal | 0.80x-0.98x |
|
||||
| Per-iteration/Fresh | ~86% reduction from optimal | ~87% reduction from optimal | 0.78x-1.00x |
|
||||
|
||||
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);
|
||||
125
benches/evaluation/engine_evaluation_benchmark.md
Normal file
125
benches/evaluation/engine_evaluation_benchmark.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Engine Evaluation Benchmark Results
|
||||
|
||||
## Test Environment
|
||||
- **Platform**: Apple Silicon (M-Series)
|
||||
- **CPU**: 16 cores
|
||||
- **Architecture**: ARM64 (aarch64-apple-darwin)
|
||||
- **Rust Version**: 1.82.0
|
||||
- **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 | 3.05 | 328 |
|
||||
| 2 | 7.46 | 268 |
|
||||
| 4 | 16.10 | 248 |
|
||||
| 6 | 25.94 | 231 |
|
||||
| 8 | 50.18 | 159 |
|
||||
| 10 | 80.27 | 125 |
|
||||
| 12 | 106.31 | 113 |
|
||||
| 14 | 137.31 | 102 |
|
||||
| 16 | 163.91 | 98 |
|
||||
| 18 | 182.06 | 99 |
|
||||
| 20 | 191.36 | 105 |
|
||||
| 22 | 201.51 | 109 |
|
||||
| 24 | 217.65 | 110 |
|
||||
| 26 | 228.11 | 114 |
|
||||
| 28 | 248.17 | 113 |
|
||||
| 30 | 264.15 | 114 |
|
||||
| 32 | 314.27 | 102 |
|
||||
|
||||
### Cloned Engines, Fresh Inputs
|
||||
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|
||||
|--------:|---------------------------:|---------------------:|
|
||||
| 1 | 4.36 | 229 |
|
||||
| 2 | 10.34 | 194 |
|
||||
| 4 | 21.98 | 182 |
|
||||
| 6 | 34.05 | 176 |
|
||||
| 8 | 66.47 | 120 |
|
||||
| 10 | 100.78 | 99 |
|
||||
| 12 | 141.69 | 85 |
|
||||
| 14 | 188.53 | 74 |
|
||||
| 16 | 261.27 | 61 |
|
||||
| 18 | 285.29 | 63 |
|
||||
| 20 | 312.14 | 64 |
|
||||
| 22 | 329.42 | 67 |
|
||||
| 24 | 347.97 | 69 |
|
||||
| 26 | 370.24 | 70 |
|
||||
| 28 | 394.75 | 71 |
|
||||
| 30 | 419.30 | 72 |
|
||||
| 32 | 433.58 | 74 |
|
||||
|
||||
### Fresh Engines, Cloned Inputs
|
||||
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|
||||
|--------:|---------------------------:|---------------------:|
|
||||
| 1 | 22.39 | 45 |
|
||||
| 2 | 49.22 | 41 |
|
||||
| 4 | 98.09 | 41 |
|
||||
| 6 | 160.21 | 37 |
|
||||
| 8 | 281.26 | 28 |
|
||||
| 10 | 413.61 | 24 |
|
||||
| 12 | 578.15 | 21 |
|
||||
| 14 | 746.34 | 19 |
|
||||
| 16 | 961.44 | 17 |
|
||||
| 18 | 1127.70 | 16 |
|
||||
| 20 | 1248.40 | 16 |
|
||||
| 22 | 1386.90 | 16 |
|
||||
| 24 | 1559.70 | 15 |
|
||||
| 26 | 1736.30 | 15 |
|
||||
| 28 | 1891.80 | 15 |
|
||||
| 30 | 2077.00 | 14 |
|
||||
| 32 | 2289.30 | 14 |
|
||||
|
||||
### Fresh Engines, Fresh Inputs
|
||||
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|
||||
|--------:|---------------------------:|---------------------:|
|
||||
| 1 | 23.63 | 42 |
|
||||
| 2 | 48.82 | 41 |
|
||||
| 4 | 102.32 | 39 |
|
||||
| 6 | 160.09 | 37 |
|
||||
| 8 | 271.21 | 29 |
|
||||
| 10 | 397.39 | 25 |
|
||||
| 12 | 489.09 | 25 |
|
||||
| 14 | 670.33 | 21 |
|
||||
| 16 | 884.83 | 18 |
|
||||
| 18 | 1044.00 | 17 |
|
||||
| 20 | 1174.20 | 17 |
|
||||
| 22 | 1330.40 | 17 |
|
||||
| 24 | 1480.90 | 16 |
|
||||
| 26 | 1679.50 | 15 |
|
||||
| 28 | 1873.90 | 15 |
|
||||
| 30 | 2070.90 | 14 |
|
||||
| 32 | 2325.40 | 14 |
|
||||
|
||||
## Analysis
|
||||
|
||||
The benchmark results demonstrate the following performance characteristics:
|
||||
|
||||
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: ~30% reduction from optimal
|
||||
- Fresh engines, cloned inputs: ~86% reduction from optimal
|
||||
- Fresh engines, fresh inputs: ~87% reduction from optimal
|
||||
3. **Scaling Patterns**:
|
||||
- Performance degrades with increased thread count due to contention
|
||||
- Best throughput achieved at 1 thread for cloned engine configurations
|
||||
- Fresh engine configurations show poor scaling across all thread counts
|
||||
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 (~30% impact compared to cloned inputs)
|
||||
6. **Thread Contention**: Performance degradation occurs with higher thread counts across all configurations
|
||||
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 != ""
|
||||
}
|
||||
Reference in New Issue
Block a user