I'd like to whitelist a container in the K8sPSPCapabilities constraint template but am having some difficulty with the rego language. I'd like to disallow the NET_RAW capability for all containers except a specific container. Would appreciate it if someone could point me in the right direction.
1 Answers
I believe you're referring to this template from the Gatekeeper ConstraintTemplate library, which is daunting. Unfortunately as written that ConstraintTemplate doesn't allow deny listing specific allowedCapabilities
. Let's construct it.
If you're just looking for the Rego, here's it in the Rego Playground.
Validating a Container
We'll start from the inside out. First, let's assume we have a Container and want to ensure it does not have NET_RAW
in .securityContext.capabilities.add
. First, we'll collect the values of this list into a set.
capabilities := {c | c := container.securityContext.capabilities.add[_]}
This is called a set comprehension.
Since it's quite possible that you'll want to deny multiple capabilities, we'll assume you'll want to set this as a list parameter rather than having it hard-coded in your ConstraintTemplate. We need to convert this list from input.parameters
into a set as well.
denied := {c | c := input.parameters.deniedCapabilities[_]}
What we want to do now is construct the set intersection between the capabilities
in the Container and those in denied
.
count(capabilities & denied) > 0
This returns true
if there are any capabilities in both the container and in the list of denied capabilities.
We must consider the case where the Container specifies '*'
, which would implicitly include NET_RAW
but not be matched by our logic above. We check this case with:
capabilities['*']
Note that every truthy statement in a Rego function is implicitly AND-ed together. So the following would check both conditions, but we want to match either:
count(capabilities & denied) > 0
capabilities['*']
We can do this by providing two definitions for has_disallowed_capabilities
:
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
denied := {c | c := input.parameters.deniedCapabilities[_]}
count(capabilities & denied) > 0
}
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
capabilities["*"]
}
If we call has_disallowed_capabilities
on a Container, Rego will automatically check both definitions and return true if at least one returns true.
Validating a Pod
Now we'll actually write the violation
functions idiomatic to Gatekeeper.
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
has_disallowed_capabilities(container)
msg := sprintf("container <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
This violation first creates an iterator, container
, which will iterate over all Containers in the Pod. By calling a function on the iterator, we implicitly call that function on every Container in the iterator.
We must also check initContainers
as those may also have the NET_RAW
capability. It looks near-identical to the above:
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
has_disallowed_capabilities(container)
msg := sprintf("initContainer <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
Whitelisting a Container
Almost done. As you said above, we want to whitelist a specific container. Recall that all statements in a Rego function are AND-ed together, and the violation
function must return true
in order for the Container to fail validation. So if we create a statement that evaluates to false
if it matches the Container we want, we're done!
Let's say we want to ignore Containers with the name: abc
. We can match all other containers with:
container.name != "abc"
This just needs to go in both of our violation
functions, and we'll be all set!
Putting everything together
Finally, we put all that we've created above into a ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8spspdeniedcapabilities
annotations:
description: Denies Pod capabilities.
spec:
crd:
spec:
names:
kind: K8sPSPDeniedCapabilities
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
deniedCapabilities:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package capabilities
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.name != "abc"
has_disallowed_capabilities(container)
msg := sprintf("container <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.name != "abc"
has_disallowed_capabilities(container)
msg := sprintf("initContainer <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
denied := {c | c := input.parameters.deniedCapabilities[_]}
count(capabilities & denied) > 0
}
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
capabilities["*"]
}
And a Constraint to instantiate it:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPDeniedCapabilities
metadata:
name: denied-capabilities
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
deniedCapabilities: ["NET_RAW"]
If instead you want the whitelisted containers to be configurable, you would follow a similar process to the one we used to make deniedCapabilities
.

- 3,417
- 2
- 28
- 46