mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
16 Commits
regorus-v0
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dff65f0329 | ||
|
|
6bf40c7394 | ||
|
|
a488a84969 | ||
|
|
e4a58ad1dc | ||
|
|
430a453fde | ||
|
|
ef549a6528 | ||
|
|
52afcbe5c5 | ||
|
|
f0576cef77 | ||
|
|
20eece58ed | ||
|
|
6599ce6001 | ||
|
|
7095e269b7 | ||
|
|
6e1f8cdb36 | ||
|
|
fb5151e0e4 | ||
|
|
37d283cb38 | ||
|
|
25dbd27d82 | ||
|
|
292948a694 |
4
.github/workflows/test-csharp.yml
vendored
4
.github/workflows/test-csharp.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
working-directory: ./bindings/csharp
|
||||
working-directory: ./bindings/csharp/net8.0
|
||||
|
||||
- name: Run
|
||||
run: LD_LIBRARY_PATH=. dotnet run
|
||||
working-directory: ./bindings/csharp
|
||||
working-directory: ./bindings/csharp/net8.0
|
||||
|
||||
28
.github/workflows/test-csharp40.yml
vendored
Normal file
28
.github/workflows/test-csharp40.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: bindings/csharp40
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: "5.0.x"
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
working-directory: ./bindings/csharp/net40
|
||||
|
||||
- name: Run
|
||||
run: dotnet run
|
||||
working-directory: ./bindings/csharp/net40
|
||||
4
.github/workflows/test-java.yml
vendored
4
.github/workflows/test-java.yml
vendored
@@ -32,6 +32,6 @@ jobs:
|
||||
|
||||
- name: Test jar
|
||||
run: |
|
||||
javac -cp target/regorus-java-0.1.5.jar Test.java
|
||||
java -Djava.library.path=../../target/release -cp target/regorus-java-0.1.5.jar:. Test
|
||||
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
|
||||
working-directory: ./bindings/java
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
**/wheels/
|
||||
**/__pycache__/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -6,6 +6,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.3](https://github.com/microsoft/regorus/compare/regorus-v0.2.2...regorus-v0.2.3) - 2024-08-16
|
||||
|
||||
### Fixed
|
||||
- Match OPA behavior for split ([#295](https://github.com/microsoft/regorus/pull/295))
|
||||
- Merge data to init document ([#293](https://github.com/microsoft/regorus/pull/293))
|
||||
|
||||
### Other
|
||||
- Update cbindgen requirement from 0.26.0 to 0.27.0 ([#296](https://github.com/microsoft/regorus/pull/296))
|
||||
- Bump rexml in /bindings/ruby in the bundler group across 1 directory ([#294](https://github.com/microsoft/regorus/pull/294))
|
||||
- Update csbindgen requirement from =1.9.0 to =1.9.3 ([#292](https://github.com/microsoft/regorus/pull/292))
|
||||
|
||||
## [0.2.2](https://github.com/microsoft/regorus/compare/regorus-v0.2.1...regorus-v0.2.2) - 2024-07-28
|
||||
|
||||
### Added
|
||||
- Update to opa v0.67.0 ([#286](https://github.com/microsoft/regorus/pull/286))
|
||||
|
||||
### Fixed
|
||||
- Handle aliases in scheduler ([#285](https://github.com/microsoft/regorus/pull/285))
|
||||
|
||||
### Other
|
||||
- Update readme ([#288](https://github.com/microsoft/regorus/pull/288))
|
||||
- Update binding versions ([#287](https://github.com/microsoft/regorus/pull/287))
|
||||
- build.rs create hooks dir if not exists ([#283](https://github.com/microsoft/regorus/pull/283))
|
||||
- add extension_list example ([#281](https://github.com/microsoft/regorus/pull/281))
|
||||
- Fix build break ([#278](https://github.com/microsoft/regorus/pull/278))
|
||||
- Update pyo3 requirement from 0.21.0 to 0.22.0 ([#275](https://github.com/microsoft/regorus/pull/275))
|
||||
- Update to OPA v0.66.0 ([#274](https://github.com/microsoft/regorus/pull/274))
|
||||
|
||||
## [0.2.1](https://github.com/microsoft/regorus/compare/regorus-v0.2.0...regorus-v0.2.1) - 2024-06-19
|
||||
|
||||
### Added
|
||||
@@ -354,3 +382,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- LICENSE committed
|
||||
- CODE_OF_CONDUCT.md committed
|
||||
- Initial commit
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ members = [
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
edition = "2021"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
|
||||
@@ -10,7 +10,7 @@ Regorus is also
|
||||
- *cross-platform* - Written in platform-agnostic Rust.
|
||||
- *no_std compatible* - Regorus can be used in `no_std` environments too. Most of the builtins are supported.
|
||||
- *current* - We strive to keep Regorus up to date with latest OPA release. Regorus supports `import rego.v1`.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust.
|
||||
See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
|
||||
Support for extensibility using other languages coming soon.
|
||||
@@ -99,7 +99,7 @@ $ cargo build -r --example regorus --no-default-features; strip target/release/e
|
||||
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
|
||||
```
|
||||
|
||||
Regorus passes the [OPA v0.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
Regorus passes the [OPA v0.67.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
builtins. See [OPA Conformance](#opa-conformance) below.
|
||||
|
||||
## Bindings
|
||||
@@ -276,7 +276,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
|
||||
```
|
||||
## OPA Conformance
|
||||
|
||||
Regorus has been verified to be compliant with [OPA v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0)
|
||||
Regorus has been verified to be compliant with [OPA v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0)
|
||||
using a [test driver](https://github.com/microsoft/regorus/blob/main/tests/opa.rs) that loads and runs the OPA testsuite using Regorus, and verifies that expected outputs are produced.
|
||||
|
||||
The test driver can be invoked by running:
|
||||
|
||||
65
bindings/csharp/net40/Program.cs
Normal file
65
bindings/csharp/net40/Program.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Program.cs" company="Microsoft">
|
||||
// Copyright (c)2012 Microsoft. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Contains code to test the Regorus Policy Engine base class for C#
|
||||
// and .NET4.0 bindings. It can be built and tested in Windows only.
|
||||
// </summary>
|
||||
//-----------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.WindowsAzure.Regorus.IaaS;
|
||||
|
||||
namespace regoregorus_test
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
long nanosecPerTick = (1000L * 1000L * 1000L) / Stopwatch.Frequency;
|
||||
var w = new Stopwatch();
|
||||
w.Restart();
|
||||
|
||||
var engine = new RegorusPolicyEngine();
|
||||
|
||||
w.Stop();
|
||||
var newEngineTicks = w.ElapsedTicks;
|
||||
|
||||
|
||||
w.Restart();
|
||||
|
||||
// Load policies and data.
|
||||
engine.AddPolicyFromFile("../../../examples/extension_list/agent_extension_policy.rego");
|
||||
engine.AddDataFromJsonFile("../../../examples/extension_list/agent-extension-data-allow-only.json");
|
||||
|
||||
|
||||
w.Stop();
|
||||
var loadPoliciesTicks = w.ElapsedTicks;
|
||||
|
||||
|
||||
w.Restart();
|
||||
|
||||
// Set input and eval query.
|
||||
engine.SetInputFromJsonFile("../../../examples/extension_list/agent-extension-input.json");
|
||||
var results = engine.EvalQuery("data.agent_extension_policy.extensions_to_download=x");
|
||||
Console.WriteLine("Download query test: \n {0}", results);
|
||||
|
||||
results = engine.EvalQuery("data.agent_extension_policy.extensions_validated");
|
||||
|
||||
Console.WriteLine("Signing validation test: \n {0}", results);
|
||||
|
||||
engine.Dispose();
|
||||
|
||||
w.Stop();
|
||||
var evalTicks = w.ElapsedTicks;
|
||||
|
||||
Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks * nanosecPerTick) / (1000.0 * 1000.0));
|
||||
Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks * nanosecPerTick) / (1000.0 * 1000.0));
|
||||
Console.WriteLine("EvalQuery and print results took {0} msecs", (evalTicks * nanosecPerTick) / (1000.0 * 1000.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
bindings/csharp/net40/README
Normal file
4
bindings/csharp/net40/README
Normal file
@@ -0,0 +1,4 @@
|
||||
The Regorus C# binding library can be built via command "dotnet build". We can use the Regorus C# binding library built from this
|
||||
directory to create a Nuget. This Nuget will contain the Regorus C# binding library with definitions that
|
||||
work for .NET framework 4.0 (net40) and above. Note the Nuget can only be created after the binding library has been built.
|
||||
RegorusCsharp-Lib-x64.nuspec is built for x64 architecture.
|
||||
203
bindings/csharp/net40/Regorus.cs
Normal file
203
bindings/csharp/net40/Regorus.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Regorus.cs" company="Microsoft">
|
||||
// Copyright (c)2012 Microsoft. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Contains code for the Regorus Policy Engine base class for C# and
|
||||
// .NET4.0 bindings. Currently this base class is not thread-safe. Make
|
||||
// sure we use it in a signle-threaded environment or add additional
|
||||
// protection when using it.
|
||||
// </summary>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.WindowsAzure.Regorus.IaaS
|
||||
{
|
||||
|
||||
public class RegorusPolicyEngine : ICloneable, IDisposable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
|
||||
public RegorusPolicyEngine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (E != null)
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
// to avoid Dispose() being called multiple times by mistake.
|
||||
E = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (RegorusPolicyEngine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
public void AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
var regoBytes = Encoding.UTF8.GetBytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed(byte* regoPtr = regoBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicyFromPath(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
|
||||
foreach (string file in regoFiles)
|
||||
{
|
||||
AddPolicyFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = Encoding.UTF8.GetBytes(query);
|
||||
|
||||
var resultJson = "";
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
{
|
||||
var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr);
|
||||
if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk) {
|
||||
if (result.output != null) {
|
||||
resultJson = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.output);
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
} else {
|
||||
CheckAndDropResult(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (resultJson != null) {
|
||||
return resultJson;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void CheckAndDropResult(RegorusFFI.RegorusResult result)
|
||||
{
|
||||
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
|
||||
unsafe {
|
||||
var message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
22
bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec
Normal file
22
bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>RegorusCsharp-Lib-x64</id>
|
||||
<version>0.2.1</version>
|
||||
<title>RegorusCsharp-Lib-x64</title>
|
||||
<authors>yangjie@microsoft.com</authors>
|
||||
<owners>yangjie@microsoft.com</owners>
|
||||
<projectUrl>https://www.microsoft.com</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Regorus C# library for x64</description>
|
||||
<releaseNotes>remove Regorus.cs from Nuget</releaseNotes>
|
||||
<copyright>Copyright (C) Microsoft Corp</copyright>
|
||||
<summary></summary>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="RegorusFFI.g.cs" target="RegorusFFI.g.cs"/>
|
||||
<file src="regorus_ffi.dll" target="lib\regorusc.dll" />
|
||||
<file src="README" target="README" />
|
||||
<file src="..\..\..\LICENSE" target="LICENSE" />
|
||||
</files>
|
||||
</package>
|
||||
24
bindings/csharp/net40/regorus-test.csproj
Normal file
24
bindings/csharp/net40/regorus-test.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI">
|
||||
|
||||
<Target Name="BuildRegorusFFI">
|
||||
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
|
||||
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
|
||||
<ItemGroup>
|
||||
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
|
||||
</Target>
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net40</TargetFramework>
|
||||
<RootNamespace>regorus_test</RootNamespace>
|
||||
<StartupObject>regoregorus_test.Program</StartupObject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +1,14 @@
|
||||
using System.Diagnostics;
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Program.cs" company="Microsoft">
|
||||
// Copyright (c)2012 Microsoft. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Contains code to test the Regorus class for C#
|
||||
// and .NET 8.0 bindings.
|
||||
// </summary>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
long nanosecPerTick = (1000L*1000L*1000L) / Stopwatch.Frequency;
|
||||
var w = new Stopwatch();
|
||||
@@ -21,10 +31,10 @@ var newEngineTicks = w.ElapsedTicks;
|
||||
w.Restart();
|
||||
|
||||
// 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");
|
||||
engine.AddPolicyFromFile("../../../tests/aci/framework.rego");
|
||||
engine.AddPolicyFromFile("../../../tests/aci/api.rego");
|
||||
engine.AddPolicyFromFile("../../../tests/aci/policy.rego");
|
||||
engine.AddDataFromJsonFile("../../../tests/aci/data.json");
|
||||
|
||||
|
||||
w.Stop();
|
||||
@@ -34,7 +44,7 @@ var loadPoliciesTicks = w.ElapsedTicks;
|
||||
w.Restart();
|
||||
|
||||
// Set input and eval rule.
|
||||
engine.SetInputFromJsonFile("../../tests/aci/input.json");
|
||||
engine.SetInputFromJsonFile("../../../tests/aci/input.json");
|
||||
var value = engine.EvalQuery("data.framework.mount_overlay");
|
||||
var valueDoc = System.Text.Json.JsonDocument.Parse(value);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI">
|
||||
|
||||
<Target Name="BuildRegorusFFI">
|
||||
<Exec Command="cargo build -r --manifest-path ../ffi/Cargo.toml" />
|
||||
<Copy SourceFiles="../ffi/RegorusFFI.g.cs" DestinationFolder="." />
|
||||
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
|
||||
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
|
||||
<ItemGroup>
|
||||
<RegorusDylib Include="..\..\target\release\*regorus_ffi*" />
|
||||
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
|
||||
</Target>
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorus-ffi"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -20,5 +20,5 @@ coverage = ["regorus/coverage"]
|
||||
custom_allocator = []
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.26.0"
|
||||
csbindgen = "1.9.0"
|
||||
cbindgen = "0.27.0"
|
||||
csbindgen = "=1.9.3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorus-java"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/java"
|
||||
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>com.microsoft.regorus</groupId>
|
||||
<artifactId>regorus-java</artifactId>
|
||||
<version>0.1.5</version>
|
||||
<version>0.2.2</version>
|
||||
|
||||
<name>Regorus Java</name>
|
||||
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regoruspy"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/python"
|
||||
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
@@ -19,7 +19,7 @@ coverage = ["regorus/coverage"]
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
ordered-float = "4.2.0"
|
||||
pyo3 = {version = "0.21.0", features = ["anyhow", "extension-module"] }
|
||||
pyo3 = {version = "0.22.0", features = ["anyhow", "extension-module"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
serde_json = "1.0.112"
|
||||
|
||||
|
||||
214
bindings/python/test_extensions.py
Normal file
214
bindings/python/test_extensions.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
import json
|
||||
import pytest
|
||||
import regorus
|
||||
|
||||
TEST_EXT_NAME = "Microsoft.Azure.ActiveDirectory.AADSSHLoginForLinux"
|
||||
|
||||
|
||||
@pytest.fixture(name="engine", scope="function")
|
||||
def engine_fixture():
|
||||
"""
|
||||
Fixture to handle creation and cleanup of a default policy engine.
|
||||
New engine is created for each test case.
|
||||
"""
|
||||
engine = regorus.Engine()
|
||||
engine.add_policy_from_file('../../examples/extension_list/agent_extension_policy.rego')
|
||||
yield engine
|
||||
|
||||
|
||||
@pytest.fixture(name="input_data")
|
||||
def input_data_fixture():
|
||||
"""
|
||||
Fixture to handle creation and cleanup of a default input data.
|
||||
New input data is created for each test case.
|
||||
"""
|
||||
input_data = {
|
||||
"extensions": {
|
||||
TEST_EXT_NAME: {
|
||||
"signingInfo": {
|
||||
"extensionSigned": False
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
input_json = json.dumps(input_data)
|
||||
yield input_json
|
||||
|
||||
|
||||
@pytest.fixture(name="default_data")
|
||||
def default_data_fixture():
|
||||
"""Fixture for default data"""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
yield data_json
|
||||
|
||||
|
||||
def test_default_data_json(engine, input_data):
|
||||
"""Test the default data in json format for extension policy."""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
def test_default_data_file(engine, input_data):
|
||||
"""Test the default data in file format for extension policy."""
|
||||
data_default_path = "../../examples/extension_list/agent-extension-default-data.json"
|
||||
engine.add_data_from_json_file(data_default_path)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
def test_allow_all(engine, input_data):
|
||||
"""Test the policy engine with allow all policy."""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
def test_name_only_input(engine, default_data):
|
||||
"""Test input with only the extension name."""
|
||||
input_data = {
|
||||
"extensions": {
|
||||
TEST_EXT_NAME: {
|
||||
}
|
||||
}
|
||||
}
|
||||
input_json = json.dumps(input_data)
|
||||
engine.add_data_json(default_data)
|
||||
engine.set_input_json(input_json)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input_signed, extension_signed", [
|
||||
(True, True),
|
||||
(True, False),
|
||||
(False, True),
|
||||
(False, False)
|
||||
])
|
||||
def test_extension_signed_rule(engine, input_signed, extension_signed):
|
||||
"""
|
||||
Test extension signing rule. Engine should be able to handle
|
||||
both signed and unsigned extensions, with extensionSigned rule set
|
||||
to either true or false.
|
||||
"""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": extension_signed
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
input_data = {
|
||||
"extensions": {
|
||||
TEST_EXT_NAME: {
|
||||
"signingInfo": {
|
||||
"extensionSigned": input_signed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
input_data = json.dumps(input_data)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
|
||||
# assert results
|
||||
if extension_signed:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_validated'][TEST_EXT_NAME]['signingValidated'] == input_signed
|
||||
else:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_validated'][TEST_EXT_NAME]['signingValidated']
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ext_allowed, allow_rule", [
|
||||
(True, True),
|
||||
(True, False),
|
||||
(False, True),
|
||||
(False, False)
|
||||
])
|
||||
def test_allowlist_rule(engine, ext_allowed, allow_rule):
|
||||
"""
|
||||
Test allowListOnly rule. Engine should be able to handle
|
||||
both allowed and disallowed extensions, with allowListOnly rule
|
||||
set to either true or false.
|
||||
"""
|
||||
if ext_allowed:
|
||||
ext_name = TEST_EXT_NAME
|
||||
else:
|
||||
ext_name = "random_disallowed_extension"
|
||||
|
||||
input_json = {
|
||||
"extensions": {
|
||||
ext_name: {
|
||||
"signingInfo": {
|
||||
"extensionSigned": False
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": allow_rule
|
||||
},
|
||||
"azureGuestExtensionsPolicy": {
|
||||
"Microsoft.CPlat.Core.RunCommandLinux": {
|
||||
},
|
||||
TEST_EXT_NAME: {
|
||||
}
|
||||
}
|
||||
}
|
||||
input_json = json.dumps(input_json)
|
||||
data_json = json.dumps(data_json)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_json)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
if allow_rule:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][ext_name]['downloadAllowed'] == ext_allowed
|
||||
else:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][ext_name]['downloadAllowed']
|
||||
@@ -10,9 +10,9 @@ GEM
|
||||
ast (2.4.2)
|
||||
json (2.7.2)
|
||||
language_server-protocol (3.17.0.3)
|
||||
minitest (5.24.0)
|
||||
minitest (5.24.1)
|
||||
parallel (1.25.1)
|
||||
parser (3.3.3.0)
|
||||
parser (3.3.4.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
racc (1.8.0)
|
||||
@@ -21,24 +21,24 @@ GEM
|
||||
rake-compiler (1.2.7)
|
||||
rake
|
||||
rake-compiler-dock (1.5.1)
|
||||
rb_sys (0.9.97)
|
||||
rb_sys (0.9.99)
|
||||
regexp_parser (2.9.2)
|
||||
rexml (3.3.0)
|
||||
rexml (3.3.3)
|
||||
strscan
|
||||
rubocop (1.64.1)
|
||||
rubocop (1.65.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
regexp_parser (>= 2.4, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.31.3)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-minitest (0.35.0)
|
||||
rubocop-minitest (0.35.1)
|
||||
rubocop (>= 1.61, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rake (0.6.0)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorusrb"
|
||||
version = "0.12.0"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
publish = false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorusjs"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/wasm"
|
||||
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
|
||||
3
build.rs
3
build.rs
@@ -8,6 +8,9 @@ fn main() -> Result<()> {
|
||||
// Copy hooks to appropriate location so that git will run them.
|
||||
// In git worktrees, .git is a symlink and the following commands fail.
|
||||
if Path::new(".git").is_dir() {
|
||||
if !Path::new("./.git/hooks").exists() {
|
||||
std::fs::create_dir_all("./.git/hooks")?;
|
||||
}
|
||||
std::fs::copy("./scripts/pre-commit", "./.git/hooks/pre-commit")?;
|
||||
std::fs::copy("./scripts/pre-push", "./.git/hooks/pre-push")?;
|
||||
}
|
||||
|
||||
19
examples/extension_list/agent-extension-data-allow-only.json
Normal file
19
examples/extension_list/agent-extension-data-allow-only.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"azureGuestAgentPolicy": {
|
||||
"signingRules": {
|
||||
"extensionSigned": true
|
||||
},
|
||||
"allowListOnly": true
|
||||
},
|
||||
"azureGuestExtensionsPolicy": {
|
||||
"test3": {
|
||||
"runtimeRules": {}
|
||||
},
|
||||
"test2": {
|
||||
"signingRules": {
|
||||
"extensionSigned": false
|
||||
},
|
||||
"runtimeRules": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": false
|
||||
},
|
||||
"allowListOnly": false
|
||||
}
|
||||
}
|
||||
28
examples/extension_list/agent-extension-input.json
Normal file
28
examples/extension_list/agent-extension-input.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"extensions": {
|
||||
"Microsoft.Azure.ActiveDirectory.AADSSHLoginForLinux": {
|
||||
"signingInfo": {
|
||||
|
||||
"extensionSigned": false
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
"test2": {
|
||||
"signingInfo": {
|
||||
"extensionSigned": true
|
||||
}
|
||||
},
|
||||
"test3": {
|
||||
"signingInfo": {
|
||||
"extensionSigned": false
|
||||
}
|
||||
},
|
||||
"test1": {
|
||||
"signingInfo": {
|
||||
"extensionSigned": false
|
||||
}
|
||||
},
|
||||
"test4": {}
|
||||
}
|
||||
}
|
||||
125
examples/extension_list/agent_extension_policy.rego
Normal file
125
examples/extension_list/agent_extension_policy.rego
Normal file
@@ -0,0 +1,125 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
package agent_extension_policy
|
||||
|
||||
import rego.v1
|
||||
|
||||
policy_version := "0.1.0"
|
||||
|
||||
default default_global_rules := {
|
||||
"allowListOnly": false,
|
||||
"signingRules": {
|
||||
"extensionSigned": false,
|
||||
"signingDetails": {},
|
||||
},
|
||||
"updateAllowed": true,
|
||||
"uninstallAllowed": true,
|
||||
}
|
||||
|
||||
default global_rules := {
|
||||
"allowListOnly": false,
|
||||
"signingRules": {
|
||||
"extensionSigned": false,
|
||||
"signingDetails": {},
|
||||
},
|
||||
"updateAllowed": true,
|
||||
"uninstallAllowed": true,
|
||||
}
|
||||
|
||||
global_rules := object.union(default_global_rules, data.azureGuestAgentPolicy) if {
|
||||
data.azureGuestAgentPolicy
|
||||
}
|
||||
|
||||
default any_extension_allowed := true
|
||||
|
||||
any_extension_allowed := false if {
|
||||
global_rules.allowListOnly
|
||||
}
|
||||
|
||||
default default_signing_info := {"signingInfo": {}}
|
||||
|
||||
# Download rule 1: if the extension is in the list and download rule satisfied: download allowed
|
||||
extensions_to_download[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
download_rule_validated(input_extension, data.azureGuestExtensionsPolicy[name])
|
||||
extension := object.union(input_extension, {"downloadAllowed": true})
|
||||
}
|
||||
|
||||
# Download rule 2: if the extension is in the list and download rule not satisfied: download denied
|
||||
extensions_to_download[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
not download_rule_validated(input_extension, data.azureGuestExtensionsPolicy[name])
|
||||
extension := object.union(input_extension, {"downloadAllowed": false})
|
||||
}
|
||||
|
||||
# Download rule 3: if the extension is not in the list: depending on allowListOnly on or off
|
||||
extensions_to_download[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
not data.azureGuestExtensionsPolicy[name]
|
||||
extension := object.union(input_extension, {"downloadAllowed": any_extension_allowed})
|
||||
}
|
||||
|
||||
# Validate rule 1: if individual signing rule exists, signing rule validated according to the rules
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
|
||||
extension_global_rules := object.union(global_rules, data.azureGuestExtensionsPolicy[name])
|
||||
extension_signing_info := object.union(extension_global_rules, default_signing_info)
|
||||
output := object.union(input_extension, extension_signing_info)
|
||||
signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": true})
|
||||
}
|
||||
|
||||
# Validate rule 2: if indivual signing rule exists, signing rule not validated according to the rules
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
|
||||
extension_global_rules := object.union(global_rules, data.azureGuestExtensionsPolicy[name])
|
||||
extension_signing_info := object.union(extension_global_rules, default_signing_info)
|
||||
output := object.union(input_extension, extension_signing_info)
|
||||
not signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": false})
|
||||
}
|
||||
|
||||
# Validate rule 3: if individual signing rule doesn't exist, signing rule validated according to global signing rule
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
not data.azureGuestExtensionsPolicy[name]
|
||||
extension_global_rules := object.union(input_extension, global_rules)
|
||||
output := object.union(extension_global_rules, default_signing_info)
|
||||
signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": true})
|
||||
}
|
||||
|
||||
# Validate rule 4: if individual signing rule doesn't exist, signing rule not validated according to the global rules
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
not data.azureGuestExtensionsPolicy[name]
|
||||
extension_global_rules := object.union(input_extension, global_rules)
|
||||
output := object.union(extension_global_rules, default_signing_info)
|
||||
not signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": false})
|
||||
}
|
||||
|
||||
# Currently if download rules doesn't exist, allow the extension because its name is in the list.
|
||||
# In the future additional rules can be checked with downloadRules present.
|
||||
download_rule_validated(_, rules) if {
|
||||
not rules.downloadRules
|
||||
}
|
||||
|
||||
# Signing is validated if input comes with extension signed, or the input of signing information is matching the
|
||||
# rules in data.
|
||||
signing_validated(signingInfo, signingRules) if {
|
||||
signingInfo
|
||||
signingRules
|
||||
signingInfo.extensionSigned
|
||||
} else if {
|
||||
signingInfo
|
||||
signingRules
|
||||
signingInfo.extensionSigned == signingRules.extensionSigned
|
||||
}
|
||||
@@ -28,6 +28,7 @@ pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn
|
||||
m.insert("startswith", (startswith, 2));
|
||||
m.insert("strings.any_prefix_match", (any_prefix_match, 2));
|
||||
m.insert("strings.any_suffix_match", (any_suffix_match, 2));
|
||||
m.insert("strings.count", (strings_count, 2));
|
||||
m.insert("strings.replace_n", (replace_n, 2));
|
||||
m.insert("strings.reverse", (reverse, 1));
|
||||
m.insert("substring", (substring, 3));
|
||||
@@ -145,11 +146,18 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
||||
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let delimiter = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
Ok(Value::from_array(
|
||||
// Handle https://github.com/microsoft/regorus/issues/291
|
||||
let parts: Vec<Value> = if delimiter.as_ref() == "" {
|
||||
// If delimiter is "", str::split returns a leading and trailing "" whereas Golang's split doesn't.
|
||||
// Therefore avoid str::split and instead return each char as a Value::String.
|
||||
s.chars().map(|c| Value::from(c.to_string())).collect()
|
||||
} else {
|
||||
s.split(delimiter.as_ref())
|
||||
.map(|s| Value::String(s.into()))
|
||||
.collect(),
|
||||
))
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(Value::from(parts))
|
||||
}
|
||||
|
||||
fn to_string(v: &Value, unescape: bool) -> String {
|
||||
@@ -512,6 +520,27 @@ fn any_suffix_match(
|
||||
))
|
||||
}
|
||||
|
||||
fn strings_count(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
_strict: bool,
|
||||
) -> Result<Value> {
|
||||
let name = "strings.count";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
let search = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let substring = ensure_string(name, ¶ms[0], &args[1])?;
|
||||
|
||||
Ok(Value::from(
|
||||
search
|
||||
.as_bytes()
|
||||
.windows(substring.len())
|
||||
.filter(|&w| w == substring.as_bytes())
|
||||
.count(),
|
||||
))
|
||||
}
|
||||
|
||||
fn startswith(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "startswith";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
@@ -121,6 +121,7 @@ fn timestamp(uuid: &Uuid) -> Option<Timestamp> {
|
||||
// https://github.com/uuid-rs/uuid/blob/94ecea893fadac93248f1bd6f47673c09cec5912/src/lib.rs#L900-L904
|
||||
if uuid.get_version_num() == 2 {
|
||||
let (ticks, counter) = decode_rfc4122_timestamp(uuid);
|
||||
#[allow(deprecated)]
|
||||
return Some(Timestamp::from_rfc4122(ticks, counter));
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ impl Engine {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn clear_data(&mut self) {
|
||||
self.interpreter.set_data(Value::new_object());
|
||||
self.interpreter.set_init_data(Value::new_object());
|
||||
self.prepared = false;
|
||||
}
|
||||
|
||||
@@ -276,7 +276,40 @@ impl Engine {
|
||||
bail!("data must be object");
|
||||
}
|
||||
self.prepared = false;
|
||||
self.interpreter.get_data_mut().merge(data)
|
||||
self.interpreter.get_init_data_mut().merge(data)
|
||||
}
|
||||
|
||||
/// Get the data document.
|
||||
///
|
||||
/// The returned value is the data document that has been constructed using
|
||||
/// one or more calls to [`Engine::add_data`]. The values of policy rules are
|
||||
/// not included in the returned document.
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // If not set, data document is empty.
|
||||
/// assert_eq!(engine.get_data(), Value::new_object());
|
||||
///
|
||||
/// // Merge { "x" : 1, "y" : {} }
|
||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?).is_ok());
|
||||
///
|
||||
/// // Merge { "z" : 2 }
|
||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?).is_ok());
|
||||
///
|
||||
/// let data = engine.get_data();
|
||||
/// assert_eq!(data["x"], Value::from(1));
|
||||
/// assert_eq!(data["y"], Value::new_object());
|
||||
/// assert_eq!(data["z"], Value::from(2));
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_data(&self) -> Value {
|
||||
self.interpreter.get_init_data().clone()
|
||||
}
|
||||
|
||||
pub fn add_data_json(&mut self, data_json: &str) -> Result<()> {
|
||||
@@ -537,11 +570,7 @@ impl Engine {
|
||||
self.interpreter.set_modules(&self.modules);
|
||||
|
||||
self.interpreter.clear_builtins_cache();
|
||||
// when the interpreter is prepared the initial data is saved
|
||||
// the data will be reset to init_data each time clean_internal_evaluation_state is called
|
||||
let init_data = self.interpreter.get_data_mut().clone();
|
||||
self.interpreter.set_init_data(init_data);
|
||||
|
||||
// clean_internal_evaluation_state will set data to an efficient clont of use supplied init_data
|
||||
// Initialize the with-document with initial data values.
|
||||
// with-modifiers will be applied to this document.
|
||||
self.interpreter.init_with_document()?;
|
||||
|
||||
@@ -216,18 +216,22 @@ impl Interpreter {
|
||||
self.modules = modules.to_vec();
|
||||
}
|
||||
|
||||
pub fn set_init_data(&mut self, init_data: Value) {
|
||||
self.init_data = init_data;
|
||||
}
|
||||
|
||||
pub fn set_data(&mut self, data: Value) {
|
||||
self.data = data;
|
||||
}
|
||||
|
||||
pub fn get_data_mut(&mut self) -> &mut Value {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
pub fn set_init_data(&mut self, data: Value) {
|
||||
self.init_data = data;
|
||||
}
|
||||
|
||||
pub fn get_init_data(&self) -> &Value {
|
||||
&self.init_data
|
||||
}
|
||||
|
||||
pub fn get_init_data_mut(&mut self) -> &mut Value {
|
||||
&mut self.init_data
|
||||
}
|
||||
|
||||
pub fn set_traces(&mut self, enable_tracing: bool) {
|
||||
self.traces = match enable_tracing {
|
||||
true => Some(vec![]),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::doc_lazy_continuation)]
|
||||
// Use README.md as crate documentation.
|
||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||
// We'll default to building for no_std - use core, alloc instead of std.
|
||||
|
||||
@@ -386,6 +386,7 @@ pub struct Analyzer {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Schedule {
|
||||
#[allow(unused)]
|
||||
pub scopes: BTreeMap<Ref<Query>, Scope>,
|
||||
@@ -411,7 +412,7 @@ impl Analyzer {
|
||||
}
|
||||
|
||||
pub fn analyze(mut self, modules: &[Ref<Module>]) -> Result<Schedule> {
|
||||
self.add_rules(modules)?;
|
||||
self.add_rules_and_aliases(modules)?;
|
||||
self.functions = gather_functions(modules)?;
|
||||
|
||||
for m in modules {
|
||||
@@ -429,7 +430,7 @@ impl Analyzer {
|
||||
modules: &[Ref<Module>],
|
||||
query: &Ref<Query>,
|
||||
) -> Result<Schedule> {
|
||||
self.add_rules(modules)?;
|
||||
self.add_rules_and_aliases(modules)?;
|
||||
self.analyze_query(None, None, query, Scope::default())?;
|
||||
|
||||
Ok(Schedule {
|
||||
@@ -438,7 +439,7 @@ impl Analyzer {
|
||||
})
|
||||
}
|
||||
|
||||
fn add_rules(&mut self, modules: &[Ref<Module>]) -> Result<()> {
|
||||
fn add_rules_and_aliases(&mut self, modules: &[Ref<Module>]) -> Result<()> {
|
||||
for m in modules {
|
||||
let path = get_path_string(&m.package.refr, Some("data"))?;
|
||||
let scope: &mut Scope = self.packages.entry(path).or_default();
|
||||
@@ -455,6 +456,12 @@ impl Analyzer {
|
||||
};
|
||||
scope.unscoped.insert(var);
|
||||
}
|
||||
|
||||
for import in &m.imports {
|
||||
if let Some(var) = &import.r#as {
|
||||
scope.unscoped.insert(var.source_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -422,3 +422,32 @@ fn one_yaml() -> Result<()> {
|
||||
fn run(path: &str) {
|
||||
yaml_test(path).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_data() -> Result<()> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Merge { "x" : 1, "y" : {} }
|
||||
engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?)?;
|
||||
|
||||
// Merge { "z" : 2 }
|
||||
engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?)?;
|
||||
|
||||
// Add a policy
|
||||
engine.add_policy("policy.rego".to_string(), "package a".to_string())?;
|
||||
|
||||
// Evaluate virtual data document. The virtual document includes all rules as well.
|
||||
let v_data = engine.eval_query("data".to_string(), false)?.result[0].expressions[0]
|
||||
.value
|
||||
.clone();
|
||||
// There must be an empty package.
|
||||
assert_eq!(v_data["a"], Value::new_object());
|
||||
|
||||
// Get the data document.
|
||||
let data = engine.get_data();
|
||||
|
||||
// There must NOT be any value of `a`.
|
||||
assert_eq!(data["a"], Value::Undefined);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
17
tests/interpreter/cases/builtins/strings/split.yaml
Normal file
17
tests/interpreter/cases/builtins/strings/split.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: empty separator
|
||||
data: {}
|
||||
modules: []
|
||||
query: "x := split(\"test\", \"\")"
|
||||
want_result:
|
||||
x: ["t", "e", "s", "t"]
|
||||
|
||||
- note: empty separator, empty string
|
||||
data: {}
|
||||
modules: []
|
||||
query: "x := split(\"\", \"\")"
|
||||
want_result:
|
||||
x: []
|
||||
@@ -34,6 +34,7 @@ cases:
|
||||
|
||||
- |
|
||||
package b
|
||||
import rego.v1
|
||||
# Both the following imports are overridden by rules
|
||||
#import data.a.b as a
|
||||
#import data.a.b
|
||||
@@ -43,6 +44,10 @@ cases:
|
||||
|
||||
a = 10
|
||||
c = C + b
|
||||
|
||||
r if {
|
||||
some v in [C]
|
||||
}
|
||||
query: data
|
||||
want_result:
|
||||
a:
|
||||
@@ -50,6 +55,7 @@ cases:
|
||||
b:
|
||||
a: 10
|
||||
c: 22
|
||||
r: true
|
||||
|
||||
- note: import overridden by rule
|
||||
modules:
|
||||
|
||||
@@ -172,6 +172,64 @@ struct Cli {
|
||||
generate: bool,
|
||||
}
|
||||
|
||||
fn stateful_policy_test() -> Result<()> {
|
||||
// Create an engine for evaluating Rego policies.
|
||||
let mut engine = regorus::Engine::new();
|
||||
|
||||
let policy = String::from(
|
||||
r#"
|
||||
package example
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
print("data.allowed_actions = ", data.allowed_actions)
|
||||
input.action in data.allowed_actions["user1"]
|
||||
print("This rule should be allowed")
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
// Add policy to the engine.
|
||||
engine.add_policy(String::from("policy.rego"), policy)?;
|
||||
|
||||
// Evaluate first input. Expect to evaluate to false, since state is not set
|
||||
engine.set_input(regorus::Value::from_json_str(
|
||||
r#"{
|
||||
"action": "write"
|
||||
}"#,
|
||||
)?);
|
||||
|
||||
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
|
||||
println!("Received result: {:?}", r);
|
||||
assert_eq!(r, false);
|
||||
|
||||
// Add data to engine. Set state
|
||||
engine.add_data(regorus::Value::from_json_str(
|
||||
r#"{
|
||||
"allowed_actions": {
|
||||
"user1" : ["read", "write"]
|
||||
}}"#,
|
||||
)?)?;
|
||||
|
||||
// Evaluate second input. Expect to evaluate to true, since state has been set now
|
||||
engine.set_input(regorus::Value::from_json_str(
|
||||
r#"{
|
||||
"action": "write"
|
||||
}"#,
|
||||
)?);
|
||||
|
||||
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
|
||||
println!("Received result: {:?}", r);
|
||||
assert_eq!(
|
||||
r, true,
|
||||
"expect result to be true since rule evaluates to true after state has been updated, per rego logs"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
run_kata_tests(
|
||||
@@ -179,5 +237,6 @@ fn main() -> Result<()> {
|
||||
&cli.name,
|
||||
cli.coverage,
|
||||
cli.generate,
|
||||
)
|
||||
)?;
|
||||
stateful_policy_test()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
|
||||
const OPA_BRANCH: &str = "v0.65.0";
|
||||
const OPA_BRANCH: &str = "v0.67.0";
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
|
||||
Reference in New Issue
Block a user