CKS — Constraints
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-systemto avoid breaking Gatekeeper itself. Excludingkube-systemprevents 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
- Deploy with
dryrunfirst to see what would be violated:
spec:
enforcementAction: dryrun
- Check violations:
kubectl get constraint require-app-label -o yaml | grep -A 50 violations
-
Fix existing violations (add labels, set limits, etc.)
-
Switch to
denyfor 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 constraintstatus.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:
- Apply the Constraint.
- Create resources normally — they will be allowed regardless of violations.
- Check the Constraint’s
status.violationsto see what would have been blocked.
kubectl get constraint <name> -o yaml | grep -A 30 violations
- Once satisfied, change to
enforcementAction: denyand 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:
- Match criteria too narrow — the Constraint’s
kinds,namespaces, orlabelSelectormay not match the violating resources.
kubectl get constraint <name> -o yaml | grep -A 20 match
- Audit controller not syncing the resource type — check the Gatekeeper
Configresource:
kubectl get config config -n gatekeeper-system -o yaml
Ensure the relevant resource type is listed under spec.sync.syncOnly.
- Audit controller not running:
kubectl get pods -n gatekeeper-system -l control-plane=audit-controller
-
Audit hasn’t run yet — default interval is 60 seconds. Wait and re-check.
-
excludedNamespacestoo broad — verify the non-compliant resources aren’t in excluded namespaces.
| Previous | Next |
|---|---|
| ← Constraint Templates | Rego Policies → |