Gatekeeper is a Kubernetes admission controller that checks, audits, and enforces your clusters’ compliance with policies related to security, regulations, or business rules. In this lab, you will deploy Gatekeeper and rollout and test some sample policies.
To begin, create a dedicated directory for this lab and switch into it:
cd ~
mkdir gatekeeper && cd gatekeeper
Deploying Gatekeeper #
You can deploy Gatekeeper to your cluster with the following command:
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
Once deployed, validate that all Gatekeeper pods are successfully in a Running state.
You can also validate that the ValidatingWebhookConfiguration was also created with the following command:
kubectl get validatingwebhookconfigurations -l gatekeeper.sh/system=yes
You will see an output similar to below:
NAME WEBHOOKS AGE
gatekeeper-validating-webhook-configuration 2 2m26s
This webhook configuration will be used to validate any resources that are deployed on to your cluster against a set of constraints that are applied. Examples of constraints include:
- Ensuring that deployments contain required labels (e.g. billingcode, owner)
- Validating that resource limits and requests are set
- Ensuring that deployed pods outside of system namespaces are non-privileged
Next, validate that the MutatingWebhookConfiguration was created in the Gatekeeper namespace:
kubectl get mutatingwebhookconfigurations -l gatekeeper.sh/system=yes
You will see an output similar to below:
NAME WEBHOOKS AGE
gatekeeper-mutating-webhook-configuration 1 4m14s
This mutating webhook configuration is used to mutate resources in the cluster against a mutating policy. This allows a cluster operator to modify workloads, such as:
- Add annonations automatically to any pod
- Set specific pods to be non-privileged
- Apply a DNS configuration to all pods
In the next sections we will deploy both validation and mutating policies and demonstrate them in action.
Validation Policy #
To demonstrate validation policies, we will deploy a policy that restricts allowed image repositories for Kubernetes pods. Specifically, we will disallow images from the cgr.dev repository.
Create a namespace called gatekeeper with the label lab=gatekeeper. In that namespace create a Pod with the following manifest:
apiVersion: v1
kind: Pod
metadata:
name: cgr-nginx
namespace: gatekeeper
labels:
lab: gatekeeper
spec:
containers:
- name: nginx
image: cgr.dev/chainguard/nginx:latest
ports:
- containerPort: 80
Validate the pod is running.
Next, let’s create our initial ConstraintTemplate manifest with the following contents:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: disallowimagerepos
annotations:
metadata.gatekeeper.sh/title: "Disallow Image Repositories"
metadata.gatekeeper.sh/version: 1.0.0
description: >-
Disallowed container repositories that begin with a string from the specified list.
spec:
crd:
spec:
names:
kind: DisallowImageRepos
validation:
# Schema for the `parameters` field
openAPIV3Schema:
type: object
properties:
repos:
description: The list of prefixes a container image is not allowed to have.
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package disallowimagerepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
image := container.image
startswith(image, input.parameters.repos[_])
msg := sprintf("container <%v> has an invalid image repo <%v>, disallowed repos are %v", [container.name, container.image, input.parameters.repos])
}
Apply it to your GKE cluster.
This template defines a new constraint that rejects an image if the prefix of a container image appears in a defined list. It includes the following major parts:
- .spec.crd.spec.validation.openAPIV3Schema.properties.repos: a list of prefixes that should be disallowed (e.g. [“docker.io”, “gcr.io”])
- .spec.targets.rego: logic that enforces the constraint. In this case, we specify the package name to match the k8s object name, and define a violation that is triggered if the container image comes from a disallowed repo
Create a new manifest with the following contents to enforce the constraint in your cluster. This will prevent any Pods from being deployed if they reference an image from the cgr.dev repository.
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: DisallowImageRepos
metadata:
name: repo-must-not-be-from-chainguard
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
repos:
- "cgr.dev/"
Apply it to your cluster and use the kubectl get disallowimagerepos command to view if there are any violations. You may need to wait a few minutes for gatekeeper to scan all currently running pods before results show. Continue checking until you see an output similar to below:
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
repo-must-not-be-from-chainguard 1
You will see that we have 1 violation.
Use the kubectl describe command on your constraint to view that violation. This will show you the violations name, the namespace it lives in, and an explanation as to why it violates the policy. This demonstrates the auditing capabilities of Gatekeeper.
You will see an output similar to below:
Note: Most of the output is redacted to simplify the output.
...
Violations:
Enforcement Action: deny
Group:
Kind: Pod
Message: container <nginx> has an invalid image repo <cgr.dev/chainguard/nginx:1.25.0>, disallowed repos are ["cgr.dev/"]
Name: cgr-nginx
Namespace: gatekeeper
Version: v1
...
Delete that pod and check your Constraint again with the kubectl get command to make sure your violations are at 0. You should see an output similar to below:
Note: It may take a few minutes for Gatekeeper to reconcile.
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
repo-must-not-be-from-chainguard 0
With the Constraint in place, try to redeploy that Pod to your cluster.
You will get back the following message:
Error from server (Forbidden): error when creating "pod.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [repo-must-not-be-from-chainguard] container <nginx> has an invalid image repo <cgr.dev/chainguard/nginx:1.25.0>, disallowed repos are ["cgr.dev/"
As you can see, Gatekeeper enforced the constraint and rejected the Pod from being deployed on to the cluster.
Before moving onto the next lab, delete your Constraint:
kubectl delete DisallowImageRepos repo-must-not-be-from-chainguard
Mutation policy #
To demonstrate valid Mutation policies, we will deploy a policy that sets the ImagePullPolicy of all containers in all namespaces to Always. This can be beneficial to ensure that Kubernetes always pulls the latest image available vs. deploying an older version that is cached.
Create a manifest with the following contents:
apiVersion: mutations.gatekeeper.sh/v1
kind: Assign
metadata:
name: image-pull-policy-always
spec:
applyTo:
- groups: [""]
kinds: ["Pod"]
versions: ["v1"]
match:
scope: Namespaced
kinds:
- apiGroups: ["*"]
kinds: ["Pod"]
excludedNamespaces: ["kube-system"]
location: "spec.containers[name:*].imagePullPolicy"
parameters:
assign:
value: Always
This manifest contains three major parts:
- applyTo: define the Kind’s schema to apply this mutation
- match: a filter to select which resources to apply this mutation
- location and paramters: the path to be modified, and what value should be used in the modification
Apply that mutation to your cluster.
Now to test that mutation, use the following manifest to deploy a Pod with ImagePullPolicy set to Never.
apiVersion: v1
kind: Pod
metadata:
name: nginx-never-pull
namespace: gatekeeper
labels:
lab: gatekeeper
spec:
containers:
- name: nginx
image: nginx:1.24.0-bullseye
imagePullPolicy: Never
ports:
- containerPort: 80
Apply it to your cluster and check if the Pod is successfully in a Running state.
You will see an output similar to the following:
NAME READY STATUS RESTARTS AGE
nginx-never-pull 1/1 Running 0 42s
We can see that the Pod is successfully running. This is because Gatekeeper intervened when the Pod got scheduled and changed the imagePullPolicy to always.
We can validate that by inspecting the Pod further with the help of jq:
kubectl get pods --namespace gatekeeper --output json | jq .items[0].spec.containers[0].imagePullPolicy
You will see an output similar to the following:
"Always"
Bonus #
Want to further explore Gatekeeper? Try the two bonus activities:
- Create and test a new mutation that assigns the label
hello=worldto every Pod that gets deployed. - Create and test a Constraint that blocks new Namespaces from being created if they do not have a label key
lab.
Once done, delete each from your cluster so that it does not interfere with future labs. Save the manifests in your Cloud Shell so you can always apply them later.
References #
If you are interested in learning more about Gatekeeper, have a look at the following resources: