CKS — Constraints

8 minute read

Constraints

CKS Exam Domain: Minimize Microservice Vulnerabilities (20%)

A Constraint is an instance of a ConstraintTemplate. While the template defines the policy logic, the Constraint defines where and how to apply it — which resources, which namespaces, what parameter values, and what enforcement action.


Creating Constraints

A Constraint’s kind matches the spec.crd.spec.names.kind defined in the ConstraintTemplate. The API group is always constraints.gatekeeper.sh.

Basic Structure

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: <ConstraintKind>           # Matches the ConstraintTemplate's kind
metadata:
  name: <constraint-name>
spec:
  enforcementAction: deny        # deny | dryrun | warn
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    namespaces: ["default", "production"]
    excludedNamespaces: ["kube-system"]
    labelSelector:
      matchLabels:
        app: web
  parameters:                    # Values passed to Rego via input.parameters
    labels: ["app", "team"]

Match Expressions

Match expressions control which resources the Constraint applies to.

kinds

Specify the API group and kind of resources to match:

match:
  kinds:
  - apiGroups: [""]            # core API group (Pods, Services, etc.)
    kinds: ["Pod"]
  - apiGroups: ["apps"]        # apps API group
    kinds: ["Deployment"]

namespaces

Limit the Constraint to specific namespaces:

match:
  namespaces: ["default", "production", "staging"]

If omitted, the Constraint applies to all namespaces.

excludedNamespaces

Exclude specific namespaces (often system namespaces):

match:
  excludedNamespaces: ["kube-system", "gatekeeper-system", "kube-public"]

Exam tip: Always exclude gatekeeper-system to avoid breaking Gatekeeper itself. Excluding kube-system prevents blocking critical cluster components.

labelSelector

Match only resources with specific labels:

match:
  labelSelector:
    matchLabels:
      environment: production
    matchExpressions:
    - key: tier
      operator: In
      values: ["frontend", "backend"]

namespaceSelector

Match based on the namespace’s labels (not the resource’s labels):

match:
  namespaceSelector:
    matchLabels:
      policy-enforcement: enabled

This is useful for gradually rolling out policies — label namespaces as they are ready.

scope

Match cluster-scoped or namespace-scoped resources:

match:
  scope: Namespaced    # or Cluster, or *

Enforcement Actions

Action Behaviour Use Case
deny Rejects the admission request Production enforcement
dryrun Allows the request but records the violation in audit Testing before enforcement
warn Allows the request but returns a warning to the user Gradual rollout

deny (Default)

spec:
  enforcementAction: deny

The API server returns an error:

Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request:
[require-labels] Missing required labels: {"app"}

dryrun

spec:
  enforcementAction: dryrun

The resource is created but the violation appears in:

kubectl get constraint require-labels -o yaml
status:
  violations:
  - enforcementAction: dryrun
    kind: Pod
    name: test-pod
    namespace: default
    message: 'Missing required labels: {"app"}'

warn

spec:
  enforcementAction: warn

The resource is created but kubectl shows a warning:

Warning: [require-labels] Missing required labels: {"app"}
pod/test-pod created

Complete Constraint Examples

Constraint 1: Require Labels

Matches the K8sRequiredLabels ConstraintTemplate from the previous section:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-app-label
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    - apiGroups: ["apps"]
      kinds: ["Deployment", "StatefulSet", "DaemonSet"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
  parameters:
    labels: ["app"]

Test:

# This should be denied — no 'app' label
kubectl run test-pod --image=nginx -n default

# This should succeed
kubectl run test-pod --image=nginx -n default --labels="app=nginx"

Constraint 2: Block Privileged Containers

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockPrivilegedContainer
metadata:
  name: block-privileged
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces: ["kube-system"]

Test:

# bad-privileged-pod.yaml — should be denied
apiVersion: v1
kind: Pod
metadata:
  name: priv-pod
spec:
  containers:
  - name: priv
    image: nginx
    securityContext:
      privileged: true
kubectl apply -f bad-privileged-pod.yaml
# Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request:
# [block-privileged] Privileged container not allowed: priv

Constraint 3: Enforce Allowed Registries

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
  name: allowed-registries
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
  parameters:
    registries:
    - "gcr.io/my-project/"
    - "docker.io/library/"
    - "registry.internal.company.com/"

Test:

# Allowed — matches registry prefix
kubectl run good --image=gcr.io/my-project/app:v1

# Denied — not in allowed list
kubectl run bad --image=docker.io/randomuser/malicious:latest

Constraint 4: Require Resource Limits

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireResourceLimits
metadata:
  name: require-limits
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]

Constraint Status and Violations

Every Constraint resource has a status section populated by Gatekeeper:

kubectl get constraint require-app-label -o yaml
status:
  auditTimestamp: "2025-02-09T10:30:00Z"
  totalViolations: 3
  violations:
  - enforcementAction: deny
    group: ""
    kind: Pod
    name: legacy-pod-1
    namespace: default
    version: v1
    message: 'Missing required labels: {"app"}'
  - enforcementAction: deny
    group: ""
    kind: Pod
    name: legacy-pod-2
    namespace: staging
    version: v1
    message: 'Missing required labels: {"app"}'
  - enforcementAction: deny
    group: apps
    kind: Deployment
    name: old-deploy
    namespace: default
    version: v1
    message: 'Missing required labels: {"app"}'

Quick Commands

# List all constraints (all types)
kubectl get constraints

# Get violations count
kubectl get constraint require-app-label -o jsonpath='{.status.totalViolations}'

# List violation details
kubectl get constraint require-app-label -o jsonpath='{.status.violations[*].name}'

# Describe for human-readable output
kubectl describe K8sRequiredLabels require-app-label

Audit Mode — Scanning Existing Resources

Gatekeeper’s audit controller periodically scans all resources matching a Constraint and reports violations in status.violations — even if those resources were created before the Constraint existed.

This is different from admission-time enforcement:

Timing Mechanism Effect
Admission (real-time) ValidatingAdmissionWebhook Blocks new non-compliant resources
Audit (periodic) Audit controller scan Reports existing non-compliant resources

Using Audit Effectively

  1. Deploy with dryrun first to see what would be violated:
spec:
  enforcementAction: dryrun
  1. Check violations:
kubectl get constraint require-app-label -o yaml | grep -A 50 violations
  1. Fix existing violations (add labels, set limits, etc.)

  2. Switch to deny for enforcement:

spec:
  enforcementAction: deny

Audit Configuration

The audit controller’s scan interval and other settings can be configured via Gatekeeper flags:

# Check current audit settings
kubectl get deploy gatekeeper-audit -n gatekeeper-system -o yaml | grep -A 5 args

Default flags:

--audit-interval=60          # Scan every 60 seconds
--constraint-violations-limit=20  # Max violations reported per constraint
--audit-from-cache=false     # Read from API server (true = read from cache)

Passing Parameters to Constraints

Parameters bridge the ConstraintTemplate (generic logic) and the Constraint (specific values).

Template Side

# In ConstraintTemplate
spec:
  crd:
    spec:
      validation:
        openAPIV3Schema:
          type: object
          properties:
            maxReplicas:
              type: integer
            allowedImages:
              type: array
              items:
                type: string

Constraint Side

# In Constraint
spec:
  parameters:
    maxReplicas: 5
    allowedImages:
    - "nginx"
    - "redis"

Rego Side

# In the ConstraintTemplate's Rego
violation[{"msg": msg}] {
  input.review.object.spec.replicas > input.parameters.maxReplicas
  msg := sprintf("Replicas %v exceeds max %v", [input.review.object.spec.replicas, input.parameters.maxReplicas])
}

Debugging Constraints

Step 1: Check Constraint Status

kubectl describe <ConstraintKind> <constraint-name>

Look for:

  • status.byPod — which controller pods have ingested the constraint
  • status.violations — current audit violations
  • Events — any errors creating or applying the constraint

Step 2: Check Gatekeeper Logs

# Controller manager logs (admission decisions)
kubectl logs -n gatekeeper-system -l control-plane=controller-manager --tail=100

# Audit controller logs (periodic scan results)
kubectl logs -n gatekeeper-system -l control-plane=audit-controller --tail=100

Step 3: Test with a Known-Bad Resource

# Create a resource that should violate the constraint
kubectl run test-violation --image=nginx --dry-run=server -o yaml

Using --dry-run=server sends the request to the API server (triggering webhooks) without actually creating the resource.

Step 4: Check Webhook Configuration

kubectl get validatingwebhookconfiguration gatekeeper-validating-webhook-configuration -o yaml

Ensure failurePolicy and rules match expectations.

Common Issues

Symptom Cause Fix
Constraint created but not enforcing enforcementAction: dryrun Change to deny
Constraint not matching expected resources Wrong kinds or namespaces Check match criteria
Violations not appearing in status Audit controller not running Check gatekeeper-audit pod
“No matching constraint template” error Template not created or has Rego errors Create/fix the template

Multiple Constraints from One Template

You can create multiple Constraints from a single ConstraintTemplate with different parameters or match rules:

# Constraint 1: Require 'app' label on Pods
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-app-label-pods
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
  parameters:
    labels: ["app"]
---
# Constraint 2: Require 'team' and 'cost-center' labels on Namespaces
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-label-namespaces
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Namespace"]
  parameters:
    labels: ["team", "cost-center"]

This reusability is a key advantage of the ConstraintTemplate/Constraint model.


Practice Questions

Question 1

Create a Constraint that blocks privileged containers in all namespaces except kube-system and gatekeeper-system. Use enforcement action deny.

Show Answer
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockPrivilegedContainer
metadata:
  name: block-privileged-all
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces:
    - kube-system
    - gatekeeper-system

Verify:

kubectl get K8sBlockPrivilegedContainer block-privileged-all
kubectl describe K8sBlockPrivilegedContainer block-privileged-all

Question 2

You deployed a Constraint with enforcementAction: deny but existing non-compliant Pods are still running. Why?

Show Answer

Gatekeeper enforces policies at admission time only — it blocks new resources from being created or updated. It does not delete or modify existing resources.

The audit controller will report existing violations in status.violations, but it takes no remediation action.

To find existing violations:

kubectl get constraint <name> -o jsonpath='{.status.violations}'

To fix, you must manually update or recreate the non-compliant resources.


Question 3

How do you test a Constraint in dry-run mode before enforcing it?

Show Answer

Set enforcementAction: dryrun on the Constraint:

spec:
  enforcementAction: dryrun

Then:

  1. Apply the Constraint.
  2. Create resources normally — they will be allowed regardless of violations.
  3. Check the Constraint’s status.violations to see what would have been blocked.
kubectl get constraint <name> -o yaml | grep -A 30 violations
  1. Once satisfied, change to enforcementAction: deny and re-apply.

Question 4

Write a Constraint that restricts image registries to gcr.io/company/ and registry.k8s.io/ for all Pods in the production namespace only.

Show Answer
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
  name: production-registries-only
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    namespaces: ["production"]
  parameters:
    registries:
    - "gcr.io/company/"
    - "registry.k8s.io/"

Test:

# Allowed
kubectl run test --image=gcr.io/company/app:v1 -n production

# Denied
kubectl run test --image=docker.io/nginx -n production

# Allowed (different namespace — Constraint only matches production)
kubectl run test --image=docker.io/nginx -n default

Question 5

A Constraint shows totalViolations: 0 but you know there are non-compliant resources. What should you check?

Show Answer

Common causes for zero violations despite non-compliant resources:

  1. Match criteria too narrow — the Constraint’s kinds, namespaces, or labelSelector may not match the violating resources.
kubectl get constraint <name> -o yaml | grep -A 20 match
  1. Audit controller not syncing the resource type — check the Gatekeeper Config resource:
kubectl get config config -n gatekeeper-system -o yaml

Ensure the relevant resource type is listed under spec.sync.syncOnly.

  1. Audit controller not running:
kubectl get pods -n gatekeeper-system -l control-plane=audit-controller
  1. Audit hasn’t run yet — default interval is 60 seconds. Wait and re-check.

  2. excludedNamespaces too broad — verify the non-compliant resources aren’t in excluded namespaces.


Previous Next
← Constraint Templates Rego Policies →

Updated: