OPA Gatekeeper: a policy that survives review
The two objects
- ConstraintTemplate — defines a new kind and the Rego that evaluates it.
- Constraint — an instance of that kind: parameters, and what it matches.
Install (if the cluster does not have it):
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.15/deploy/gatekeeper.yaml
kubectl -n gatekeeper-system get pods
Template: require a label on every Pod
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata: {name: k8srequiredlabels}
spec:
crd:
spec:
names: {kind: K8sRequiredLabels}
validation:
openAPIV3Schema:
type: object
properties:
labels: {type: array, items: {type: string}}
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
Constraint: apply it to Pods, require owner
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata: {name: pods-must-have-owner}
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["prod"]
parameters:
labels: ["owner"]
kubectl apply -f template.yaml && kubectl apply -f constraint.yaml
kubectl get constraint && kubectl get k8srequiredlabels
kubectl run bad --image=nginx -n prod # rejected
Template: only these registries
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
satisfied := [good | repo = input.parameters.repos[_]; good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("image '%v' comes from an untrusted registry", [container.image])
}
Template: replica limits
violation[{"msg": msg}] {
input.review.object.kind == "Deployment"
replicas := input.review.object.spec.replicas
limit := input.parameters.replicas
replicas > limit
msg := sprintf("replicas %v exceeds the limit of %v", [replicas, limit])
}
Reading a denial
Error from server ([pods-must-have-owner] you must provide labels: {"owner"}): ...
The bracketed part is the Constraint name — that is how you prove which policy
blocked you when the task asks "which policy rejected it".
Traps
openAPIV3Schemamust describe every parameter you pass, or the Constraint is rejected.- Rego is a real language:
violation[{"msg": ...}]is a set rule; forget the braces and nothing validates. - ConstraintTemplates take a few seconds to register the new kind —
kubectl get crd | grep constraintsuntil it appears. match.kindswith the wrongapiGroupssilently matches nothing.""for core,appsfor Deployments.- A Gatekeeper that is not running looks exactly like a policy that does not match: check
kubectl -n gatekeeper-system get podsfirst.