CKS — Constraint Templates

7 minute read

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:

  1. ConstraintTemplate — defines what to check (Rego logic) and what parameters the policy accepts.
  2. 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

  1. provided — set of label keys that exist on the incoming object.
  2. required — set of label keys from the Constraint parameters.
  3. missing — set difference: labels that are required but not present.
  4. 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 violation rule

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 →

Updated: