3

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 Answers1

2

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.

Will Beason
  • 3,417
  • 2
  • 28
  • 46