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

Compile Constraint Templates to Separate OPA Environments #135

Closed
maxsmythe opened this issue Aug 19, 2021 · 2 comments
Closed

Compile Constraint Templates to Separate OPA Environments #135

maxsmythe opened this issue Aug 19, 2021 · 2 comments

Comments

@maxsmythe
Copy link
Contributor

maxsmythe commented Aug 19, 2021

Currently we are compiling all constraint templates into the same OPA artifact (d.compiler in the following code):

c := ast.NewCompiler().WithPathConflictsCheck(storage.NonEmpty(ctx, d.storage, txn)).
WithCapabilities(d.capabilities)
if c.Compile(updatedModules); c.Failed() {
d.storage.Abort(ctx, txn)
return 0, c.Errors
}
for name, mod := range insert {
if err := d.storage.UpsertPolicy(ctx, txn, name, []byte(mod.text)); err != nil {
d.storage.Abort(ctx, txn)
return 0, err
}
}
if err := d.storage.Commit(ctx, txn); err != nil {
return 0, err
}
d.compiler = c
d.modules = updatedModules

Which is then used to evaluate constraints here:

func (d *driver) eval(ctx context.Context, path string, input interface{}, cfg *drivers.QueryCfg) (rego.ResultSet, *string, error) {
d.modulesMux.RLock()
defer d.modulesMux.RUnlock()
args := []func(*rego.Rego){
rego.Compiler(d.compiler),
rego.Store(d.storage),
rego.Input(input),
rego.Query(path),
}
if d.traceEnabled || cfg.TracingEnabled {
buf := topdown.NewBufferTracer()
args = append(args, rego.Tracer(buf))
rego := rego.New(args...)
res, err := rego.Eval(ctx)
b := &bytes.Buffer{}
topdown.PrettyTrace(b, *buf)
t := b.String()
return res, &t, err
}
rego := rego.New(args...)
res, err := rego.Eval(ctx)
return res, nil, err
}

With the coordination of executing constraint templates done in Rego here:

package hooks["{{.Target}}"]
violation[response] {
data.hooks["{{.Target}}"].library.autoreject_review[rejection]
review := get_default(input, "review", {})
constraint := get_default(rejection, "constraint", {})
spec := get_default(constraint, "spec", {})
enforcementAction := get_default(spec, "enforcementAction", "deny")
response = {
"msg": get_default(rejection, "msg", ""),
"metadata": {"details": get_default(rejection, "details", {})},
"constraint": constraint,
"review": review,
"enforcementAction": enforcementAction,
}
}
# Finds all violations for a given target
violation[response] {
data.hooks["{{.Target}}"].library.matching_constraints[constraint]
review := get_default(input, "review", {})
inp := {
"review": review,
"parameters": get_default(get_default(constraint, "spec", {}), "parameters", {}),
}
inventory[inv]
data.templates["{{.Target}}"][constraint.kind].violation[r] with input as inp with data.inventory as inv
spec := get_default(constraint, "spec", {})
enforcementAction := get_default(spec, "enforcementAction", "deny")
response = {
"msg": r.msg,
"metadata": {"details": get_default(r, "details", {})},
"constraint": constraint,
"review": review,
"enforcementAction": enforcementAction,
}
}
# Finds all violations in the cached state of a given target
audit[response] {
data.hooks["{{.Target}}"].library.matching_reviews_and_constraints[[review, constraint]]
inp := {
"review": review,
"parameters": get_default(get_default(constraint, "spec", {}), "parameters", {}),
}
inventory[inv]
data.templates["{{.Target}}"][constraint.kind].violation[r] with input as inp with data.inventory as inv
spec := get_default(constraint, "spec", {})
enforcementAction := get_default(spec, "enforcementAction", "deny")
response = {
"msg": r.msg,
"metadata": {"details": get_default(r, "details", {})},
"constraint": constraint,
"review": review,
"enforcementAction": enforcementAction,
}
}
# get_default(data, "external", {}) seems to cause this error:
# "rego_type_error: undefined function data.hooks.<target>.get_default"
inventory[inv] {
inv = data.external["{{.Target}}"]
}
inventory[{}] {
not data.external["{{.Target}}"]
}
# get_default returns the value of an object's field or the provided default value.
# It avoids creating an undefined state when trying to access an object attribute that does
# not exist
get_default(object, field, _default) = object[field]
get_default(object, field, _default) = _default {
not has_field(object, field)
}
has_field(object, field) {
_ = object[field]
}

We should change the compile/execution flow so that each constraint template is compiled and evaluated as separate Rego artifacts, with the execution being coordinated by Golang. This will have a number of benefits:

@maxsmythe
Copy link
Contributor Author

It would also help with limiting the impact of runtime errors, per: open-policy-agent/gatekeeper#1562

@willbeason
Copy link
Member

Done in #202

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

No branches or pull requests

2 participants