Enforcing Policies in Kubernetes with OPA
As you scale your K8s cluster, maintaining security and compliance becomes harder and harder. Using Open Policy Agent (OPA), and writing policies in the Rego language allows you to define and enforce policies across your K8s cluster.
In this article, we will explore how we can set up policies in our K8s cluster.
What is Rego?
Rego is a declarative language designed to implement policies over complex structured data. Taking advantage of it, lets you enforce security, compliance, and operational policies across K8s.
TL;DR? Check out the video:
Setting up OPA in K8s
You can use any type of K8s cluster, but in this example, I want to keep things simple, so I will use a kind cluster.
Create a kind K8s cluster
First, let’s install kind. I’m using a Mac so I’ll run:
brew install kind
For other operating systems, you can check out the kind documentation on how to install it.
After kind is installed and you have docker running, to create the cluster you will simply need to use the following command:
kind create cluster -name opa-cluster
Next, let’s install helm:
brew install helm
Now, we will use OPA gatekeeper, which is a project specifically designed for policy enforcement in Kubernetes using OPA. Let’s install it using helm:
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace
For this example, I want to restrict my K8s pods to be able to pull images with latest tags, as this can cause breakable changes.
To do this, we will first need to create a New ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snoimagelatest
spec:
crd:
spec:
names:
kind: K8sNoImageLatest
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snoimagelatest
violation[{"msg": msg, "details": {"container": container_name}}] {
container := input.review.object.spec.containers[_]
endswith(container.image, ":latest")
container_name := container.name
msg := sprintf("container %s is using the latest tag", [container_name])
}
As you can see, in the targets, we have set up a rego policy. This rego policy checks to see if our container images are using the latest tag.
Now, based on this template, we can create a constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoImageLatest
metadata:
name: no-latest-image-tag
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
We are applying this to all of our pod resources, but if you are trying to create a deployment, or a statefulset, replica set or even a daemon set, based on this constraint, we won’t have any restrictions.
Let’s see how would this look like for a pod:
apiVersion: v1
kind: Pod
metadata:
name: nginx-latest
spec:
containers:
- name: nginx
image: nginx:latest
kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [no-latest-image-tag] container nginx is using the latest tag
But if you try to create an nginx pod from with a deployment, you won’t have any restrictions:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
kubectl apply -f deployment.yaml
deployment.apps/nginx-deployment created
If we want to apply the constraint to a deployment as well, we have to update the constraint template to add another violation:
violation[{"msg": msg, "details": {"container": container_name}}] {
container := input.review.object.spec.template.spec.containers[_]
endswith(container.image, ":latest")
container_name := container.name
msg := sprintf("container %s is using the latest tag", [container_name])
}
Let’s also update the constraint and retry to create the deployment:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoImageLatest
metadata:
name: no-latest-image-tag
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- apiGroups: ["apps"]
kinds: ["Deployment"]
kubectl apply -f deployment.yaml
Error from server (Forbidden): error when creating "deployment.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [no-latest-image-tag] container nginx is using the latest tag
Now, if we provide an image tag to the pod for example apart from latest, we can easily see that we can create the resource:
apiVersion: v1
kind: Pod
metadata:
name: nginx-latest
spec:
containers:
- name: nginx
image: nginx:1.26.0
kubectl apply -f nginx.yaml
pod/nginx-latest created
Key points
In this article, we have walked through how you can install OPA Gatekeeper in your K8s cluster, and also saw how to build a constraint and a constraint template and observed them in action.
Is there a better way to do this? Yes, and I will show you how in another post, so stay tuned.