Pod Security Standards give you three fixed levels. But real organizations need custom policies: "images must come from our registry," "all Deployments must have a team label," "no containers can run as UID 0." Policy engines — OPA/Gatekeeper and Kyverno — provide this flexibility using admission webhooks under the hood.
1. OPA/Gatekeeper
OPA (Open Policy Agent) is a general-purpose policy engine. Gatekeeper is the Kubernetes-specific integration — it runs OPA policies as a validating admission webhook via CRDs.
Architecture
# Two CRD types: # 1. ConstraintTemplate — defines the POLICY LOGIC (reusable template in Rego) # 2. Constraint — APPLIES the template with specific parameters # Flow: API request → Gatekeeper webhook → evaluates Rego policy → allow/deny
ConstraintTemplate — The Policy Template
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels # Creates this CRD for Constraints
validation:
openAPIV3Schema:
type: object
properties:
labels: # Parameters the template accepts
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: | # Policy logic in Rego language
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])
}
Constraint — Apply the Template
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels # Matches the ConstraintTemplate's CRD name
metadata:
name: require-team-label
spec:
enforcementAction: deny # deny | dryrun | warn
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
namespaces: ["production"] # Only in these namespaces
excludedNamespaces: ["kube-system"]
parameters:
labels: ["team", "cost-center"] # Parameters passed to Rego
# Result: any Deployment in "production" without "team" AND "cost-center" labels → REJECTED
# Error: "Missing required labels: {"cost-center", "team"}"
Enforcement Actions
| Action | Behavior | Use Case |
|---|---|---|
deny | Reject the request | Production enforcement |
dryrun | Allow but log the violation (audit) | Testing before enforcement |
warn | Allow but return a warning to the user | Developer feedback |
Audit Mode — Find Existing Violations
# Gatekeeper periodically scans existing resources against constraints:
kubectl get k8srequiredlabels require-team-label -o yaml
# status:
# totalViolations: 7
# violations:
# - enforcementAction: deny
# kind: Deployment
# name: legacy-app
# namespace: production
# message: "Missing required labels: {\"team\"}"
2. Kyverno — Kubernetes-Native Policies
Kyverno takes a different approach: policies are written in YAML (no Rego), using K8s-native constructs. It can validate, mutate, generate resources, and clean up — making it more than just a policy engine.
Validate Policy — Block Non-Compliant Resources
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-team-label
spec:
validationFailureAction: Enforce # Enforce | Audit
rules:
- name: check-team-label
match:
any:
- resources:
kinds: ["Deployment"]
namespaces: ["production"]
validate:
message: "Deployment must have a 'team' label"
pattern:
metadata:
labels:
team: "?*" # ?* means "must exist, any value"
Mutate Policy — Auto-Fix Resources
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-labels
spec:
rules:
- name: add-managed-by
match:
any:
- resources:
kinds: ["Deployment", "StatefulSet"]
mutate:
patchStrategicMerge:
metadata:
labels:
managed-by: kyverno # Auto-add this label if missing
Generate Policy — Create Resources Automatically
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-network-policy
spec:
rules:
- name: default-deny
match:
any:
- resources:
kinds: ["Namespace"]
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny
namespace: "{{request.object.metadata.name}}"
data:
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
# Result: every new namespace automatically gets a default-deny NetworkPolicy!
Image Verification Policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["registry.company.com/*"]
attestors:
- entries:
- keys:
publicKeys: |
-----BEGIN PUBLIC KEY-----
MFkwEwYH...
-----END PUBLIC KEY-----
3. Gatekeeper vs Kyverno — When to Use Which
| Aspect | OPA/Gatekeeper | Kyverno |
|---|---|---|
| Policy language | Rego (dedicated logic language) | YAML (K8s-native, no new language) |
| Learning curve | Steep (Rego is unfamiliar) | Low (YAML patterns familiar to K8s users) |
| Validate | ✅ | ✅ |
| Mutate | ❌ (validate only) | ✅ (patch resources) |
| Generate | ❌ | ✅ (create resources automatically) |
| Image verification | Via external data | ✅ Built-in (cosign, Notary) |
| Audit existing | ✅ (built-in audit controller) | ✅ (policy reports) |
| Complexity of rules | Handles very complex logic (Rego is Turing-complete) | Good for pattern-based rules; complex logic is verbose |
| Ecosystem | CNCF graduated, broad non-K8s use | CNCF incubating, K8s-only |
| CKS exam | ✅ Tested (know the CRD model) | May appear (newer) |
Decision Guide
# Choose Gatekeeper when: # - You need complex logic (cross-resource validation, external data) # - Your team already knows Rego (or uses OPA elsewhere: Envoy, Terraform) # - You're in a large enterprise with a policy-as-code platform # Choose Kyverno when: # - Your team prefers YAML over learning a new language # - You need mutation (auto-fix resources) or generation (auto-create resources) # - You need built-in image verification (supply chain security) # - You want simpler pattern-based validation # Both work well. Pick one and standardize.
4. Common Policy Examples
| Policy | Gatekeeper Approach | Kyverno Approach |
|---|---|---|
| Require labels | Rego: check input.review.object.metadata.labels | Pattern: metadata.labels.team: "?*" |
| Block privileged containers | Rego: deny if securityContext.privileged == true | Pattern: =(securityContext.privileged): false |
| Restrict image registries | Rego: deny if image not in allowed list | validate.deny with image pattern match |
| Require resource limits | Rego: check containers[].resources.limits | Pattern: containers[].resources.limits.memory: "?*" |
| Auto-add network policy | ❌ Can't generate | Generate rule on Namespace creation |
| Verify image signatures | External data + Rego | Built-in verifyImages |
Policy Library
# Both have pre-built policy libraries: # Gatekeeper: https://open-policy-agent.github.io/gatekeeper-library/ # Kyverno: https://kyverno.io/policies/ # Don't write from scratch — start with library policies and customize
Audit mode. Review violations for 2 weeks, fix the worst offenders, then switch to Enforce. Don't enable enforcement on day one — you'll break existing workloads.
Summary
| Concept | Key Point |
|---|---|
| Policy engines | Custom admission logic beyond Pod Security Standards |
| Gatekeeper | OPA + Rego, validate-only, ConstraintTemplate + Constraint CRDs |
| Kyverno | YAML policies, validate + mutate + generate, ClusterPolicy CRD |
| ConstraintTemplate | Reusable policy logic (Rego) — parameterized |
| Constraint | Instance of a template — applies to specific resources/namespaces |
| enforcementAction | deny (block), dryrun (log), warn (feedback) |
| Audit | Both scan existing resources for violations (not just new requests) |
| Kyverno generate | Auto-create resources (NetworkPolicy, RBAC) when triggers match |
| Policy libraries | Don't write from scratch — use pre-built policies from official repos |
📝 Quiz: OPA/Gatekeeper & Kyverno
Q1: What's the relationship between a ConstraintTemplate and a Constraint in Gatekeeper?
Q2: A Gatekeeper Constraint has enforcementAction: dryrun. A non-compliant Deployment is created. What happens?
kubectl get constraint require-team-label -o yaml | grep -A20 violationsQ3: What can Kyverno do that Gatekeeper cannot?
Q4: You want every new namespace to automatically get a default-deny NetworkPolicy. Which tool and what policy type?
Q5: Both Gatekeeper and Kyverno can audit existing resources. Why is this important beyond just admission?
Q6: You're on the CKS exam. The question says "ensure containers only use images from registry.company.com." Which approach is fastest?
input.review.object.spec.containers[_].image starts with "registry.company.com") + a Constraint applying it. If Kyverno is installed: create a ClusterPolicy with a validate.deny rule matching images that don't start with the allowed prefix. If neither is installed and it's K8s 1.28+: use a ValidatingAdmissionPolicy with CEL — no external tool needed.