An opinionated framework designed to help build maintainable and well-structured Rego code. It acts as the entry point to all your rules and provides the following features:
- Translating rule results into well-structured output
- Output contains explanation of how it was calculated
- Mechanism for mixing allow and deny rules using strategies
- Grouping rules into rule sets that can be combined for a top-level result
- There should be a straight-forward way to group rules into larger sets and combine their results in a flexible way.
- Combining rule outcomes should be based on well-defined configurable strategies rather than custom code.
- Top level rules should be called
allowanddenywith results that can be combined intuitively. - Rule results should be easy to match to the rule that produced them.
- Rule results should be objects to make them extensible.
- Outputs should explain the final allow/deny result.
- Don't interfere with the user's ability to structure code into packages.
- Don't make the user do more work / write more code than without using the framework.
- Provide mechanism for custom outputs for compatibility with external enforcement points like Envoy or Kubernetes.
Here is a Rego rule in Rune format. The name of the rule (allow / deny) and it's result type (set of objects)
is predefined. Each result object has a minimum of 2 properties (id and msg).
Your code is expected to be a series of such allow and deny rules one or more Rule Sets.
allow[result] {
"movie-readers" in input.subject.groups
regex.match("movies/\\d+", input.resource)
input.action == "read"
result := {
"id": "A-MR1",
"msg": sprintf("%s is allowed to read %s because part of movie-readers group",
[input.subject.username, input.resource])
}
}Rule Sets provide a clear way to structure your Rego code. They are Rego packages with some metadata and a rule naming scheme.
Note: Rune currently only supports Rule Sets that are sub-packages of the `policy` package.
Each Rule Set produces it's own allow or deny result. These can be combined into an overall result (see Rule result combining).
bundle -> deny
\_ rule_set[policy.movies] -> allow
\_ rule_set[policy.actors] -> deny
To provide the Rule Set's metadata Rune expects an object called rule_set to be defined in the package.
This provides configurable strategy for evaluating the sets of allow and deny results.
rule_set := {
"name": "Access to Movies",
"resolution_strategy": "default-deny"
}The resolution strategy configuration in the Rule Set's metadata controls how the results of allow and deny rules combine into a final result. Currently, Rune supports the following strategies:
default-allow: The final result isallowunless at least onedenyrule evaluates to truedefault-deny: The final result isdenyunless at least oneallowrule evaluates to truedefault-allow-overrule: The final result isallowif a nodenyrule is triggered or if anallowrule is triggered as well.default-deny-overrule: The final result isdenyif a noallowrule is triggered or if andenyrule is triggered as well.
The first two strategies a pretty straight-forward, but the third and fourth need some explanation: They can be used in cases where additionally to your "normal" rules you have some that are so important they override the final decision.
For example: No user can access the /admin endpoint unless they are part of the administrator group. However,
a user with a suspicious_activity=true attribute can't access the endpoint even if they are part of the group.
This can be implemented using a default-deny-overrule strategy, an allow rule for the group check
and a deny rule for the suspicious activity check. The user will be denied if the allow rule doesn't
fire or if the deny rule does.
Rune provides the entrypoint rule, so it can take over the evaluation process. rune.rego must be packaged into
the bundle that get's deployed in OPA. Clients should invoke the rune.results rule:
Note: In this repo you can run the example movies/actors rule set using the eval.sh script and providing it with an
input file path (e.g. example/input-add-actor-to-movie.json). eval.sh will copy rune.rego into the example/bundle
folder
opa eval data.rune.results --bundle ./example/bundle -i example/input-add-actor-to-movie.jsonThe results from Rune look like this:
{
"resolution_strategy": "default-deny",
"result": "allow",
"rule_sets": {
"actors": {
"name": "Access to Actors",
"reason": {
"enforced_allows": [
{
"id": "A-ACT1",
"msg": "adam.sandor can add and remove actors from movies"
}
],
"enforced_denies": [],
"resolution_strategy": "default-deny"
},
"result": "allow",
"result_validation_errors": []
},
"movies": {
"name": "Access to Movies",
"reason": {
"enforced_allows": [
{
"id": "A-E-MR3",
"msg": "adam.sandor is allowed to edit movies/224333 because part of movie-editors group"
}
],
"enforced_denies": [],
"resolution_strategy": "default-deny-overrule"
},
"result": "allow",
"result_validation_errors": []
}
}
}The example folder contains a rule bundle that showcases Rune's functionality. There are two rule sets: actors and movies. These will combine into an overall rule result that will produce output similar to what's showin in the previous section.
Try running: ./eval.sh example/input-add-actor-to-movie.json to see the results, and play around the input and the sample
rules!
To make Rune rule sets work with enforcement points like the Kubernetes API server or Envoy the outputs have to be modified to fit the expected format. These adapters can be provided as part of the framework + an extension point in user code.
Easy way to bundle a specific version of Rune with user code. Opens the opportunity of pre-processing, for example to create a json file with a list of packages containing rule sets.
rune --version=0.1.1 bundle ./Provide an extension point for users to define their own resolution strategies in the the form of a function.