mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: OPA v0.68.0. Engine::set_rego_v1 (#305)
Provide ability in the engine to treat subsequently loaded policies as rego.v1. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
edd0ccca5b
commit
af5071446b
@@ -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.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.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.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.67.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
Regorus passes the [OPA v0.68.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.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0)
|
||||
Regorus has been verified to be compliant with [OPA v0.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.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:
|
||||
|
||||
@@ -33,6 +33,7 @@ fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<St
|
||||
engine.add_policy(path.clone(), read_file(&path)?)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn rego_eval(
|
||||
bundles: &[String],
|
||||
files: &[String],
|
||||
@@ -41,6 +42,7 @@ fn rego_eval(
|
||||
enable_tracing: bool,
|
||||
non_strict: bool,
|
||||
#[cfg(feature = "coverage")] coverage: bool,
|
||||
v1: bool,
|
||||
) -> Result<()> {
|
||||
// Create engine.
|
||||
let mut engine = regorus::Engine::new();
|
||||
@@ -50,6 +52,8 @@ fn rego_eval(
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(coverage);
|
||||
|
||||
engine.set_rego_v1(v1);
|
||||
|
||||
// Load files from given bundles.
|
||||
for dir in bundles.iter() {
|
||||
let entries =
|
||||
@@ -233,6 +237,10 @@ enum RegorusCommand {
|
||||
#[cfg(feature = "coverage")]
|
||||
#[arg(long, short)]
|
||||
coverage: bool,
|
||||
|
||||
/// Turn on rego.v1
|
||||
#[arg(long)]
|
||||
v1: bool,
|
||||
},
|
||||
|
||||
/// Tokenize a Rego policy.
|
||||
@@ -274,6 +282,7 @@ fn main() -> Result<()> {
|
||||
non_strict,
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
v1,
|
||||
} => rego_eval(
|
||||
&bundles,
|
||||
&data,
|
||||
@@ -283,6 +292,7 @@ fn main() -> Result<()> {
|
||||
non_strict,
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
v1,
|
||||
),
|
||||
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
|
||||
RegorusCommand::Parse { file } => rego_parse(file),
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct Engine {
|
||||
modules: Vec<Ref<Module>>,
|
||||
interpreter: Interpreter,
|
||||
prepared: bool,
|
||||
rego_v1: bool,
|
||||
}
|
||||
|
||||
/// Create a default engine.
|
||||
@@ -36,9 +37,35 @@ impl Engine {
|
||||
modules: vec![],
|
||||
interpreter: Interpreter::new(),
|
||||
prepared: false,
|
||||
rego_v1: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn rego.v1 on/off for subsequently added policies.
|
||||
///
|
||||
/// Explicit import rego.v1 is not needed if set.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.set_rego_v1(true);
|
||||
/// engine.add_policy(
|
||||
/// "test.rego".to_string(),
|
||||
/// r#"
|
||||
/// package test
|
||||
/// allow if true # if keyword is automatically imported
|
||||
/// "#.to_string())?;
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
pub fn set_rego_v1(&mut self, rego_v1: bool) {
|
||||
self.rego_v1 = rego_v1;
|
||||
}
|
||||
|
||||
/// Add a policy.
|
||||
///
|
||||
/// The policy file will be parsed and converted to AST representation.
|
||||
@@ -67,7 +94,7 @@ impl Engine {
|
||||
///
|
||||
pub fn add_policy(&mut self, path: String, rego: String) -> Result<String> {
|
||||
let source = Source::from_contents(path, rego)?;
|
||||
let mut parser = Parser::new(&source)?;
|
||||
let mut parser = self.make_parser(&source)?;
|
||||
let module = Ref::new(parser.parse()?);
|
||||
self.modules.push(module.clone());
|
||||
// if policies change, interpreter needs to be prepared again
|
||||
@@ -98,7 +125,7 @@ impl Engine {
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
pub fn add_policy_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<String> {
|
||||
let source = Source::from_file(path)?;
|
||||
let mut parser = Parser::new(&source)?;
|
||||
let mut parser = self.make_parser(&source)?;
|
||||
let module = Ref::new(parser.parse()?);
|
||||
self.modules.push(module.clone());
|
||||
// if policies change, interpreter needs to be prepared again
|
||||
@@ -428,7 +455,7 @@ impl Engine {
|
||||
|
||||
// Parse the query.
|
||||
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
|
||||
let mut parser = Parser::new(&query_source)?;
|
||||
let mut parser = self.make_parser(&query_source)?;
|
||||
let query_node = parser.parse_user_query()?;
|
||||
if query_node.span.text() == "data" {
|
||||
self.eval_modules(enable_tracing)?;
|
||||
@@ -545,7 +572,7 @@ impl Engine {
|
||||
|
||||
// Parse the query.
|
||||
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
|
||||
let mut parser = Parser::new(&query_source)?;
|
||||
let mut parser = self.make_parser(&query_source)?;
|
||||
let query_node = parser.parse_user_query()?;
|
||||
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
|
||||
self.interpreter.eval_user_query(
|
||||
@@ -870,4 +897,12 @@ impl Engine {
|
||||
|
||||
serde_json::to_string_pretty(&ast).map_err(anyhow::Error::msg)
|
||||
}
|
||||
|
||||
fn make_parser<'a>(&self, source: &'a Source) -> Result<Parser<'a>> {
|
||||
let mut parser = Parser::new(source)?;
|
||||
if self.rego_v1 {
|
||||
parser.enable_rego_v1()?;
|
||||
}
|
||||
Ok(parser)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,18 @@ impl<'source> Parser<'source> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enable_rego_v1(&mut self) -> Result<()> {
|
||||
self.turn_on_rego_v1(self.tok.1.clone())
|
||||
}
|
||||
|
||||
fn turn_on_rego_v1(&mut self, span: Span) -> Result<()> {
|
||||
self.rego_v1 = true;
|
||||
for kw in FUTURE_KEYWORDS {
|
||||
self.set_future_keyword(kw, &span)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn token_text(&self) -> &str {
|
||||
match self.tok.0 {
|
||||
TokenKind::Symbol | TokenKind::Number | TokenKind::Ident | TokenKind::Eof => {
|
||||
@@ -1648,10 +1660,7 @@ impl<'source> Parser<'source> {
|
||||
|
||||
let is_future_kw =
|
||||
if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" {
|
||||
self.rego_v1 = true;
|
||||
for kw in FUTURE_KEYWORDS {
|
||||
self.set_future_keyword(kw, &span)?;
|
||||
}
|
||||
self.turn_on_rego_v1(span.clone())?;
|
||||
true
|
||||
} else {
|
||||
self.handle_import_future_keywords(&comps)?
|
||||
|
||||
@@ -1,115 +1,229 @@
|
||||
aggregates
|
||||
all
|
||||
any
|
||||
arithmetic
|
||||
array
|
||||
assignments
|
||||
base64builtins
|
||||
base64urlbuiltins
|
||||
baseandvirtualdocs
|
||||
bitsand
|
||||
bitsnegate
|
||||
bitsor
|
||||
bitsshiftleft
|
||||
bitsshiftright
|
||||
bitsxor
|
||||
casts
|
||||
comparisonexpr
|
||||
completedoc
|
||||
compositebasedereference
|
||||
compositereferences
|
||||
comprehensions
|
||||
containskeyword
|
||||
cryptohmacequal
|
||||
cryptohmacmd5
|
||||
cryptohmacsha1
|
||||
cryptohmacsha256
|
||||
cryptohmacsha512
|
||||
cryptomd5
|
||||
cryptosha1
|
||||
cryptosha256
|
||||
dataderef
|
||||
defaultkeyword
|
||||
disjunction
|
||||
elsekeyword
|
||||
embeddedvirtualdoc
|
||||
eqexpr
|
||||
evaltermexpr
|
||||
every
|
||||
example
|
||||
fix1863
|
||||
functionerrors
|
||||
functions
|
||||
globmatch
|
||||
globquotemeta
|
||||
helloworld
|
||||
hexbuiltins
|
||||
indexing
|
||||
indirectreferences
|
||||
inputvalues
|
||||
intersection
|
||||
jsonbuiltins
|
||||
jsonfilter
|
||||
jsonfilteridempotent
|
||||
jsonremove
|
||||
jsonremoveidempotent
|
||||
jsonschema
|
||||
jwtbuiltins
|
||||
negation
|
||||
nestedreferences
|
||||
numbersrange
|
||||
numbersrangestep
|
||||
objectfilter
|
||||
objectfilteridempotent
|
||||
objectfilternonstringkey
|
||||
objectget
|
||||
objectkeys
|
||||
objectremove
|
||||
objectremoveidempotent
|
||||
objectremovenonstringkey
|
||||
objectunion
|
||||
objectunionn
|
||||
partialdocconstants
|
||||
partialiter
|
||||
partialobjectdoc
|
||||
partialsetdoc
|
||||
planner-ir
|
||||
rand
|
||||
reachable
|
||||
refheads
|
||||
regexfind
|
||||
regexfindallstringsubmatch
|
||||
regexisvalid
|
||||
regexmatch
|
||||
regexmatchtemplate
|
||||
regexreplace
|
||||
regexsplit
|
||||
replacen
|
||||
semvercompare
|
||||
semverisvalid
|
||||
sets
|
||||
sprintf
|
||||
strings
|
||||
subset
|
||||
toarray
|
||||
topdowndynamicdispatch
|
||||
toset
|
||||
time
|
||||
trim
|
||||
trimleft
|
||||
trimprefix
|
||||
trimright
|
||||
trimspace
|
||||
trimsuffix
|
||||
type
|
||||
typebuiltin
|
||||
typenamebuiltin
|
||||
undos
|
||||
union
|
||||
units
|
||||
urlbuiltins
|
||||
uuid
|
||||
varreferences
|
||||
virtualdocs
|
||||
walkbuiltin
|
||||
withkeyword
|
||||
v0/aggregates
|
||||
v0/all
|
||||
v0/any
|
||||
v0/arithmetic
|
||||
v0/array
|
||||
v0/assignments
|
||||
v0/base64builtins
|
||||
v0/base64urlbuiltins
|
||||
v0/baseandvirtualdocs
|
||||
v0/bitsand
|
||||
v0/bitsnegate
|
||||
v0/bitsor
|
||||
v0/bitsshiftleft
|
||||
v0/bitsshiftright
|
||||
v0/bitsxor
|
||||
v0/casts
|
||||
v0/comparisonexpr
|
||||
v0/completedoc
|
||||
v0/compositebasedereference
|
||||
v0/compositereferences
|
||||
v0/comprehensions
|
||||
v0/containskeyword
|
||||
v0/cryptohmacequal
|
||||
v0/cryptohmacmd5
|
||||
v0/cryptohmacsha1
|
||||
v0/cryptohmacsha256
|
||||
v0/cryptohmacsha512
|
||||
v0/cryptomd5
|
||||
v0/cryptosha1
|
||||
v0/cryptosha256
|
||||
v0/dataderef
|
||||
v0/defaultkeyword
|
||||
v0/disjunction
|
||||
v0/elsekeyword
|
||||
v0/embeddedvirtualdoc
|
||||
v0/eqexpr
|
||||
v0/evaltermexpr
|
||||
v0/every
|
||||
v0/example
|
||||
v0/fix1863
|
||||
v0/functionerrors
|
||||
v0/functions
|
||||
v0/globmatch
|
||||
v0/globquotemeta
|
||||
v0/helloworld
|
||||
v0/hexbuiltins
|
||||
v0/indexing
|
||||
v0/indirectreferences
|
||||
v0/inputvalues
|
||||
v0/intersection
|
||||
v0/jsonbuiltins
|
||||
v0/jsonfilter
|
||||
v0/jsonfilteridempotent
|
||||
v0/jsonremove
|
||||
v0/jsonremoveidempotent
|
||||
v0/jsonschema
|
||||
v0/jwtbuiltins
|
||||
v0/negation
|
||||
v0/nestedreferences
|
||||
v0/numbersrange
|
||||
v0/numbersrangestep
|
||||
v0/objectfilter
|
||||
v0/objectfilteridempotent
|
||||
v0/objectfilternonstringkey
|
||||
v0/objectget
|
||||
v0/objectkeys
|
||||
v0/objectremove
|
||||
v0/objectremoveidempotent
|
||||
v0/objectremovenonstringkey
|
||||
v0/objectunion
|
||||
v0/objectunionn
|
||||
v0/partialdocconstants
|
||||
v0/partialiter
|
||||
v0/partialobjectdoc
|
||||
v0/partialsetdoc
|
||||
v0/planner-ir
|
||||
v0/rand
|
||||
v0/reachable
|
||||
v0/refheads
|
||||
v0/regexfind
|
||||
v0/regexfindallstringsubmatch
|
||||
v0/regexisvalid
|
||||
v0/regexmatch
|
||||
v0/regexmatchtemplate
|
||||
v0/regexreplace
|
||||
v0/regexsplit
|
||||
v0/replacen
|
||||
v0/semvercompare
|
||||
v0/semverisvalid
|
||||
v0/sets
|
||||
v0/sprintf
|
||||
v0/strings
|
||||
v0/subset
|
||||
v0/toarray
|
||||
v0/topdowndynamicdispatch
|
||||
v0/toset
|
||||
v0/time
|
||||
v0/trim
|
||||
v0/trimleft
|
||||
v0/trimprefix
|
||||
v0/trimright
|
||||
v0/trimspace
|
||||
v0/trimsuffix
|
||||
v0/type
|
||||
v0/typebuiltin
|
||||
v0/typenamebuiltin
|
||||
v0/undos
|
||||
v0/union
|
||||
v0/units
|
||||
v0/urlbuiltins
|
||||
v0/uuid
|
||||
v0/varreferences
|
||||
v0/virtualdocs
|
||||
v0/walkbuiltin
|
||||
v0/withkeyword
|
||||
v1/aggregates
|
||||
v1/all
|
||||
v1/any
|
||||
v1/arithmetic
|
||||
v1/array
|
||||
v1/assignments
|
||||
v1/base64builtins
|
||||
v1/base64urlbuiltins
|
||||
v1/baseandvirtualdocs
|
||||
v1/bitsand
|
||||
v1/bitsnegate
|
||||
v1/bitsor
|
||||
v1/bitsshiftleft
|
||||
v1/bitsshiftright
|
||||
v1/bitsxor
|
||||
v1/casts
|
||||
v1/comparisonexpr
|
||||
v1/completedoc
|
||||
v1/compositebasedereference
|
||||
v1/compositereferences
|
||||
v1/comprehensions
|
||||
v1/containskeyword
|
||||
v1/cryptohmacequal
|
||||
v1/cryptohmacmd5
|
||||
v1/cryptohmacsha1
|
||||
v1/cryptohmacsha256
|
||||
v1/cryptohmacsha512
|
||||
v1/cryptomd5
|
||||
v1/cryptosha1
|
||||
v1/cryptosha256
|
||||
v1/dataderef
|
||||
v1/defaultkeyword
|
||||
v1/disjunction
|
||||
v1/elsekeyword
|
||||
v1/embeddedvirtualdoc
|
||||
v1/eqexpr
|
||||
v1/evaltermexpr
|
||||
v1/every
|
||||
v1/example
|
||||
v1/fix1863
|
||||
v1/functionerrors
|
||||
v1/functions
|
||||
v1/globmatch
|
||||
v1/globquotemeta
|
||||
v1/helloworld
|
||||
v1/hexbuiltins
|
||||
v1/indexing
|
||||
v1/indirectreferences
|
||||
v1/inputvalues
|
||||
v1/intersection
|
||||
v1/jsonbuiltins
|
||||
v1/jsonfilter
|
||||
v1/jsonfilteridempotent
|
||||
v1/jsonremove
|
||||
v1/jsonremoveidempotent
|
||||
v1/jsonschema
|
||||
v1/jwtbuiltins
|
||||
v1/negation
|
||||
v1/nestedreferences
|
||||
v1/numbersrange
|
||||
v1/numbersrangestep
|
||||
v1/objectfilter
|
||||
v1/objectfilteridempotent
|
||||
v1/objectfilternonstringkey
|
||||
v1/objectget
|
||||
v1/objectkeys
|
||||
v1/objectremove
|
||||
v1/objectremoveidempotent
|
||||
v1/objectremovenonstringkey
|
||||
v1/objectunion
|
||||
v1/objectunionn
|
||||
v1/partialdocconstants
|
||||
v1/partialiter
|
||||
v1/partialobjectdoc
|
||||
v1/partialsetdoc
|
||||
v1/planner-ir
|
||||
v1/rand
|
||||
v1/reachable
|
||||
v1/refheads
|
||||
v1/regexfind
|
||||
v1/regexfindallstringsubmatch
|
||||
v1/regexisvalid
|
||||
v1/regexmatch
|
||||
v1/regexmatchtemplate
|
||||
v1/regexreplace
|
||||
v1/regexsplit
|
||||
v1/replacen
|
||||
v1/semvercompare
|
||||
v1/semverisvalid
|
||||
v1/sets
|
||||
v1/sprintf
|
||||
v1/strings
|
||||
v1/subset
|
||||
v1/toarray
|
||||
v1/topdowndynamicdispatch
|
||||
v1/toset
|
||||
v1/time
|
||||
v1/trim
|
||||
v1/trimleft
|
||||
v1/trimprefix
|
||||
v1/trimright
|
||||
v1/trimspace
|
||||
v1/trimsuffix
|
||||
v1/type
|
||||
v1/typebuiltin
|
||||
v1/typenamebuiltin
|
||||
v1/undos
|
||||
v1/union
|
||||
v1/units
|
||||
v1/urlbuiltins
|
||||
v1/uuid
|
||||
v1/varreferences
|
||||
v1/virtualdocs
|
||||
v1/walkbuiltin
|
||||
|
||||
@@ -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.67.0";
|
||||
const OPA_BRANCH: &str = "v0.68.0";
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -51,12 +51,14 @@ struct YamlTest {
|
||||
cases: Vec<TestCase>,
|
||||
}
|
||||
|
||||
fn eval_test_case(case: &TestCase) -> Result<Value> {
|
||||
fn eval_test_case(case: &TestCase, is_rego_v1_test: bool) -> Result<Value> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
engine.set_rego_v1(is_rego_v1_test);
|
||||
|
||||
if let Some(data) = &case.data {
|
||||
engine.add_data(data.clone())?;
|
||||
}
|
||||
@@ -172,6 +174,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_rego_v1_test = path_dir_str.starts_with("v1/");
|
||||
let entry = status.entry(path_dir_str).or_insert((0, 0, 0));
|
||||
|
||||
let yaml_str = std::fs::read_to_string(&path_str)?;
|
||||
@@ -216,7 +219,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
|
||||
print!("{:4}: {:90}", entry.2, case.note);
|
||||
entry.2 += 1;
|
||||
match (eval_test_case(&case), &case.want_result) {
|
||||
match (eval_test_case(&case, is_rego_v1_test), &case.want_result) {
|
||||
(Ok(actual), Some(expected))
|
||||
if is_json_schema_test && json_schema_tests_check(&actual, &expected) =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user