CKS — Constraint Templates
Constraint Templates
CKS Exam Domain: Minimize Microservice Vulnerabilities (20%)
A ConstraintTemplate defines the policy logic (Rego) and the parameter schema. It is the reusable blueprint from which you create one or more Constraints. Think of ConstraintTemplates as the class definition and Constraints as the instances.
What Are ConstraintTemplates?
Every Gatekeeper policy has two parts:
- ConstraintTemplate — defines what to check (Rego logic) and what parameters the policy accepts.
- Constraint — defines where and how to apply the template (which resources, which namespaces, what parameter values).
ConstraintTemplate (Rego + Schema)
│
▼
Creates a new CRD
│
▼
Constraint (instances of that CRD)
When you create a ConstraintTemplate, Gatekeeper dynamically registers a new Custom Resource Definition. You then create Constraint resources of that new kind.
ConstraintTemplate CRD Structure
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: <template-name> # lowercase, e.g. k8srequiredlabels
spec:
crd:
spec:
names:
kind: <ConstraintKind> # PascalCase, e.g. K8sRequiredLabels
validation:
openAPIV3Schema: # Parameter schema (optional)
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
# Rego logic here
}
Key Fields
| Field | Purpose |
|---|---|
metadata.name |
Template name (lowercase, usually matches the kind in lowercase) |
spec.crd.spec.names.kind |
The CRD kind that Constraints will use |
spec.crd.spec.validation.openAPIV3Schema |
JSON Schema for parameters passed from Constraints |
spec.targets[].target |
Always admission.k8s.gatekeeper.sh for admission control |
spec.targets[].rego |
The Rego policy logic |
Example 1: Require Labels on All Resources
This template checks that specified labels exist on any resource.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
description: "List of required label keys"
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}
How the Rego Works
provided— set of label keys that exist on the incoming object.required— set of label keys from the Constraint parameters.missing— set difference: labels that are required but not present.- If
count(missing) > 0, a violation is raised with a descriptive message.
Example 2: Block Privileged Containers
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sblockprivilegedcontainer
spec:
crd:
spec:
names:
kind: K8sBlockPrivilegedContainer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sblockprivilegedcontainer
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged container not allowed: %v", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged init container not allowed: %v", [container.name])
}
Security Impact
Privileged containers have:
- Full access to all host devices
- Ability to modify the host kernel
- Access to all host namespaces (PID, network, IPC)
- Equivalent to root on the host node
This template checks both containers and initContainers.
Example 3: Enforce Allowed Image Registries
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
type: object
properties:
registries:
type: array
description: "List of allowed image registry prefixes"
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not registry_allowed(container.image)
msg := sprintf("Image '%v' is from a disallowed registry. Allowed: %v", [container.image, input.parameters.registries])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not registry_allowed(container.image)
msg := sprintf("Init container image '%v' is from a disallowed registry. Allowed: %v", [container.image, input.parameters.registries])
}
registry_allowed(image) {
registry := input.parameters.registries[_]
startswith(image, registry)
}
Why This Matters
Restricting image registries prevents:
- Pulling from untrusted public registries
- Supply-chain attacks via malicious images
- Shadow IT images with unknown vulnerabilities
Example 4: Require Resource Limits
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequireresourcelimits
spec:
crd:
spec:
names:
kind: K8sRequireResourceLimits
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequireresourcelimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("Container '%v' must set cpu limits", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("Container '%v' must set memory limits", [container.name])
}
Without resource limits, a single container can starve node resources, causing cascading failures across all Pods on the node.
Template Parameters — openAPIV3Schema
Parameters let you make templates reusable. The schema is defined using OpenAPI v3:
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
maxReplicas:
type: integer
minimum: 1
allowedPorts:
type: array
items:
type: integer
These parameters are then passed from the Constraint and accessed in Rego via input.parameters.
Rego Access Pattern
# Access a parameter array
label := input.parameters.labels[_]
# Access a parameter integer
input.parameters.maxReplicas
# Access a parameter string
input.parameters.registries[_]
Gatekeeper Library — Built-In Templates
The Gatekeeper Library provides production-ready ConstraintTemplates for common policies:
| Template | Purpose |
|---|---|
K8sRequiredLabels |
Require specific labels |
K8sBlockPrivilegedContainer |
Block privileged containers |
K8sAllowedRepos |
Restrict image registries |
K8sContainerLimits |
Enforce resource limits |
K8sBlockNodePort |
Block NodePort services |
K8sDisallowedTags |
Block latest tag |
K8sRequiredProbes |
Require readiness/liveness probes |
K8sPSPHostFilesystem |
Block hostPath volumes |
K8sPSPCapabilities |
Restrict container capabilities |
K8sExternalIPs |
Restrict externalIPs on Services |
Install from Gatekeeper Library
# Clone the library
git clone https://github.com/open-policy-agent/gatekeeper-library.git
# Apply a specific template
kubectl apply -f gatekeeper-library/library/general/requiredlabels/template.yaml
Exam tip: In the CKS exam, you may be given a ConstraintTemplate to fix or complete. You don’t need to memorise Rego — understand the structure and the input.review.object path.
ConstraintTemplate Lifecycle
# Create a ConstraintTemplate
kubectl apply -f template.yaml
# Verify the template is created
kubectl get constrainttemplates
# Check that the new CRD was registered
kubectl get crd | grep <kind-lowercase>
# Describe for status
kubectl describe constrainttemplate <name>
# Delete a ConstraintTemplate (also deletes all its Constraints)
kubectl delete constrainttemplate <name>
Status Check
kubectl get constrainttemplate k8srequiredlabels -o yaml
Look for:
status:
created: true
byPod:
- id: gatekeeper-controller-manager-...
observedGeneration: 1
If created: false or errors appear, check the Rego syntax.
Common Mistakes
| Mistake | Fix |
|---|---|
| Package name doesn’t match template name | Ensure package <name> in Rego matches metadata.name |
Missing violation rule name |
The rule must be named violation |
| Wrong input path | Always start with input.review.object for the incoming resource |
| Forgetting init containers | Check both spec.containers[_] and spec.initContainers[_] |
| Schema mismatch | Parameter types in schema must match how you use them in Rego |
Debugging ConstraintTemplates
# Check template status
kubectl get constrainttemplate <name> -o jsonpath='{.status}'
# Gatekeeper controller logs
kubectl logs -n gatekeeper-system -l control-plane=controller-manager --tail=100
# Look for Rego compilation errors
kubectl logs -n gatekeeper-system -l control-plane=controller-manager | grep -i "error\|rego"
If a template fails to create the CRD, the Rego has a syntax error. Fix the Rego and re-apply.
Practice Questions
Question 1
Write a ConstraintTemplate that blocks containers running as root (runAsUser: 0).
Show Answer
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sblockrunasroot
spec:
crd:
spec:
names:
kind: K8sBlockRunAsRoot
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sblockrunasroot
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.runAsUser == 0
msg := sprintf("Container '%v' must not run as root (UID 0)", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.securityContext.runAsUser == 0
msg := sprintf("Init container '%v' must not run as root (UID 0)", [container.name])
}
Question 2
What happens when you delete a ConstraintTemplate that has active Constraints?
Show Answer
Deleting a ConstraintTemplate also deletes all Constraints of that type, because the underlying CRD is removed. Kubernetes garbage collection cleans up all resources of a CRD when the CRD itself is deleted.
# This deletes the template AND all K8sRequiredLabels constraints
kubectl delete constrainttemplate k8srequiredlabels
# Verify
kubectl get K8sRequiredLabels
# error: the server doesn't have a resource type "K8sRequiredLabels"
Question 3
Explain the purpose of spec.crd.spec.validation.openAPIV3Schema in a ConstraintTemplate.
Show Answer
The openAPIV3Schema field defines the parameter schema for the template. It specifies what parameters Constraints can pass to the Rego policy, including their types and validation rules.
For example:
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
This schema:
- Enables Kubernetes to validate Constraint parameter values at creation time
- Documents the expected inputs for the policy
- Parameters are accessed in Rego via
input.parameters.labels
Without this schema, Constraints cannot pass parameters to the Rego policy.
Question 4
A ConstraintTemplate was applied but no CRD was created. What is the most likely cause?
Show Answer
The most likely cause is a Rego syntax error in the template. When Gatekeeper cannot compile the Rego policy, it fails to create the associated CRD.
Debugging steps:
# Check the template status
kubectl describe constrainttemplate <name>
# Look in the status section for error messages
# Check controller logs for Rego compilation errors
kubectl logs -n gatekeeper-system -l control-plane=controller-manager | grep -i error
Common Rego syntax issues:
- Mismatched brackets
- Package name mismatch
- Invalid Rego functions
- Missing
violationrule
Question 5
Modify the K8sAllowedRegistries template to also check ephemeral containers.
Show Answer
Add a third violation rule for ephemeral containers:
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not registry_allowed(container.image)
msg := sprintf("Image '%v' is from a disallowed registry", [container.image])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not registry_allowed(container.image)
msg := sprintf("Init container image '%v' is from a disallowed registry", [container.image])
}
violation[{"msg": msg}] {
container := input.review.object.spec.ephemeralContainers[_]
not registry_allowed(container.image)
msg := sprintf("Ephemeral container image '%v' is from a disallowed registry", [container.image])
}
registry_allowed(image) {
registry := input.parameters.registries[_]
startswith(image, registry)
}
Ephemeral containers are used for debugging (kubectl debug) and can be a vector for pulling untrusted images if not checked.
| Previous | Next |
|---|---|
| ← OPA Introduction | Constraints → |