Guest post originally published on the Magalix blog by Mohamed Ahmed
In this article, we’re going to demonstrate how you can enforce the most fine-grained security policies using OPA. Notice that this article is part of a series in which we’re building upon the knowledge that you gained in the introducing OPA as a code & integrating OPA with Kubernetes. If you haven’t already done so, please go through the published articles in this series.
You may be already familiar with Pod Security Policy where you can apply very specific security controls over pods. For example, using Linux kernel capabilities, using host namespace, network, port, or filesystem, in addition to many others. Using OPA you can also impose similar controls on pods and in this lab, we’ll create an OPA policy that disallows creating privileged containers in pods. A privileged container has a higher access level to the host than non-privileged containers.
Why Use OPA And Not The Native Pod Security Policy?
There’s nothing wrong with using Pod Security Policy to enforce our security policy. However, PSP – by definition – can only be applied to pods. They cannot handle other Kubernetes resources like Ingresses, Deployments, Services, etc. The power of OPA comes from its ability to be applied to any Kubernetes resource. OPA is deployed to Kubernetes as an admission controller, which intercepts the API calls sent to the API server and validates, and/or mutates them. Accordingly, you can have one unified OPA policy that applies to different components of the system, and not just pods. For example, a policy that forces users to use the company’s domain in their ingresses and also ensures that they only pull images from the company’s image repository. Please note that we’re using OPA that’s deployed using kube-mgmt and not the OPA Gatekeeper.
The Policy Code In Rego
In this article, we assume that you’re already familiar with OPA and the Rego language. We also assume that you have a running Kubernetes cluster that has OPA and the kube-mgmt container deployed. Please refer to our previous article for installation instructions. Our no-priv-pod.rego file should look as follows:
package kubernetes.admission
deny[msg] {
c := input_containers[_]
c.securityContext.privileged
msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
}
input_containers[c] {
c := input.request.object.spec.containers[_]
}
input_containers[c] {
c := input.request.object.spec.initContainers[_]
}
Let’s briefly go through this file:
- Line 1: Contains the package. Notice that you must use kubernetes.admission for the policy to work.
- Line 2: Deny is the default object that will contain the policy that we need to execute. If the enclosed code evaluates to true, the policy will be violated.
- Line 3: We define a variable that will hold all the containers in the pod and receive values from the input_containers[c] defined later.
- Line 4: If the pod contains the “privileged” property, the statement will be true.
- Line 5: The message that will be displayed to the users when they try to run privileged containers. It includes the container name and the offending security context.
- Lines 7-9: The input_containers[c] function extracts the containers from the request object. Notice the use of the _ character to iterate over all the containers in the array. In Rego, you don’t need to define loops – the underscore character will automatically do this for you.
- Lines 10-12: We define the function again for init containers. Notice that in Rego it’s legal to define the same function more than once. We do this to overcome a limitation in Rego functions in which you cannot return more than one output. When the function name is called, both functions are executed, and the output is combined using the AND operator. So, in our case, the presence of one privileged container in one or more locations will violate the policy.
Deploying The Policy
OPA expects to find its policies in ConfigMaps in the opa namespace. To apply our code to a ConfigMap, we run the following command:
kubectl create configmap no-priv-pods --from-file=no-priv-pod.rego
The kube-mgmt sidecar container is continuously watching the API server for ConfigMaps in the opa namespace so that you can deploy policies by just creating the ConfigMap.
Exercising The Policy
Let’s ensure that our policy is working by attempting to deploy a privileged container:
kubectl -n default apply -f - <<EOT
apiVersion: v1
kind: Pod
metadata:
name: nginx-privileged
labels:
app: nginx-privileged
spec:
containers:
- name: nginx
image: nginx
securityContext:
privileged: true #false
EOT
Error from server (Privileged container is not allowed: nginx, securityContext: {"privileged": true}): error when creating "STDIN": admission webhook "validating-webhook.openpolicyagent.org" denied the request: Privileged container is not allowed: nginx, securityContext: {"privileged": true}
Notice that we intentionally deployed the pod to the default namespace because our admission webhook will ignore any resources created in the opa namespace or the kube-system.
TL;DR
- OPA is a general-purpose, platform-agnostic policy enforcement tool that can be integrated with Kubernetes in multiple ways.
- You can use OPA policies to mimic Pod Security Policy to prevent privileged containers from being scheduled on the cluster.
- Since OPA can work with other Kubernetes resources and not only Pods, it’s recommended that you use it to create cluster-level policy documents that span all concerned resources.