-
Notifications
You must be signed in to change notification settings - Fork 326
Description
This is a proposal for implementing fine-grained excludes.
This proposal adds a new excludes type to the conftest tool which allows for more fine-grained exclusion of policies. I have also attached a sample implementation (linked here) that can already be played with & serve as a reference.
Issue
Currently conftest is limited to only being able to 'disable' a policy rule over a whole file. This means that it's not possible to have a policy be applied to a subset of elements within a file, it's all or nothing.
This is naturally an issue, especially for projects which are wanting to adopt conftest incrementally into an already existing project. In this case it is difficult to apply a fine-grained policy enforcement which blocks newly added policy-violating definitions into an already existing file.
While there are workarounds, it is difficult to use them and requires modifying the deny rules directly (meaning they won't show up as being explicit exceptions).
Proposal
The linked Proposal includes an extension to conftest with a new keyword exclude. This can be used to attach specific exclusions to a policy. Below is a quick sample:
package main
deny_root[result] {
input.kind == "Deployment"
c = input.spec.template.spec.containers[_]
not c.securityContext.runAsNonRoot
# "msg" is used to set the message that conftest will output
# all other keys are used to match against the exclusion
#
# "result" can still be a plain string for backwards compatibility
result := {
"container": c.name,
"deployment": input.metadata.name,
"msg": sprintf("container %s in deployment %s doesn't set runAsNonRoot", [c.name, input.metadata.name]),
}
}
# here we are adding specific exceptions.
root_exceptions = [{"deployment": "mydep", "containers": ["host-agent"]}]
# The exception we want to be able to express is "mydep can run container 'host-agent' as root", but no other containers (even in the same pod) are allowed to run as root.
exclude_root[attrs] {
deployment := input.metadata.name
container := input.spec.template.spec.containers[_].name
exception := root_exceptions[_]
deployment == exception.deployment
container == exception.containers[_]
here we can return a list of attributes which we would like to match & exclude on.
attrs = [{"container": container, "deployment": deployment}]
}
First off, the current behaviour is preserved, so no policies will be affected by this change.
There are a few changes to how we need to write our rego.
- When returning from the deny/warn policy, an object can be returned instead (as specified in the above example)
- The exclusion & the deny rules must have a matching suffix (ie.
deny_root&exclude_root). - the return of the exclude is matched against the attributes of the deny rule (rather than on the name of the rule)
With these changes, we are able to write more flexible exclusion rules with as much flexibility in our exclusion logic as we would like.
Actions
- Are there any blocking issues that should be addressed? If not, then I would appreciate an ok & I will proceed with documentation & flesh out the testing in preparation for merging.