Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

engine: Add rego evaluation engine #784

Merged
merged 4 commits into from
Aug 30, 2023
Merged

engine: Add rego evaluation engine #784

merged 4 commits into from
Aug 30, 2023

Conversation

JAORMX
Copy link
Contributor

@JAORMX JAORMX commented Aug 29, 2023

This adds rego as an engine for rules, it takes whatever input
from the ingester and is able to evaluate it against OPA/rego policies.
We also pass in the policy from mediator so we could use that as input
if needed.

There are two modes of operations:

  • deny-by-default: Which is more secure and expects policy writers to only set an allow variable to true if the policy matches expectations
  • constraints: will deny the request if a violation is found. It can return a message with further explanations for the denial.

the reason why I didn't just use the deny-by-default as the only engine is that it's non-trivial to return
meaningful messages to the user with such a model in rego.

evankanderson
evankanderson previously approved these changes Aug 29, 2023
Copy link
Member

@evankanderson evankanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks good. I had a few small suggestions here and there, but none are blocking.

internal/engine/eval/rego/config.go Outdated Show resolved Hide resolved
Comment on lines 1644 to +1651
// jq is only used if the `jq` type is selected.
// It defines the comparisons that are made between
// the ingested data and the policy rule.
repeated JQComparison jq = 2;

// rego is only used if the `rego` type is selected.
optional Rego rego = 3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we want to make these a oneof to ensure that only one type is set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to switch out parsing to using protojson for this to work. I asked a question here https://stacklok.slack.com/archives/C057KR4DT0W/p1693378177378369 so I can look at that in a separate PR

internal/engine/eval/rego/config.go Show resolved Hide resolved
}

// NewRegoEvaluator creates a new rego evaluator
func NewRegoEvaluator(cfg *pb.RuleType_Definition_Eval_Rego) (*Evaluator, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we used the enclosing pb.RuleType_Definition_Eval message here, we could define a common "create me one" interface for evaluators. I don't know if that's important, but it would make it slightly easier to plug in additional evaluation engines if we want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true! Let me write myself an action point to look into this.

Comment on lines +79 to +85
r := e.newRegoFromOptions(
rego.Dump(&buf),
)
pq, err := r.PrepareForEval(ctx)
if err != nil {
return fmt.Errorf("could not prepare Rego: %w", err)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a future caching opportunity (i.e. run this in NewRegoEvaluator)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're evaluating the same data, yeah, we could probably cache the data and/or the result.

internal/engine/eval/rego/eval.go Outdated Show resolved Hide resolved
return fmt.Errorf("missing details in result")
}

detmap, ok := det.(map[string]interface{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't det a rego.Vars, which is an alias of map[string]interface{}? i.e. do we need this line at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we do, we can't access the contents of the rego.Vars otherwise. Seems to me like it was kind of an unnecessary aliasing 😕

This adds rego as an engine for rules, it takes whatever input
from the ingester and is able to evaluate it against OPA/rego policies.
We also pass in the policy from mediator so we could use that as input
if needed.

There are two modes of operations:
- `deny-by-default`: Which is more secure and expects policy writers to only set an `allow` variable to true if the policy matches expectations
- `constraints`: will deny the request if a violation is found. It can return a message with further explanations for the denial.

the reason why I didn't just use the `deny-by-default` as the only engine is that it's non-trivial to return
meaningful messages to the user with such a model in rego.
Copy link
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This iteration looks good to me!

@JAORMX JAORMX merged commit a6ca94a into main Aug 30, 2023
14 checks passed
@JAORMX JAORMX deleted the rego branch August 30, 2023 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants