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

ActionBehaviorUse Case
denyReject the requestProduction enforcement
dryrunAllow but log the violation (audit)Testing before enforcement
warnAllow but return a warning to the userDeveloper 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\"}"
Gatekeeper audits existing resources, not just new ones. Unlike admission webhooks (which only see new requests), Gatekeeper's audit controller periodically scans all resources and reports which ones violate constraints. This lets you see the full compliance picture — not just future violations.

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-----
Kyverno's killer feature: mutation + generation. Gatekeeper only validates (accept/reject). Kyverno can validate AND mutate (fix things automatically) AND generate new resources (create NetworkPolicies, Secrets, RBAC). This makes it a full policy-as-code automation platform, not just a gatekeeper.

3. Gatekeeper vs Kyverno — When to Use Which

AspectOPA/GatekeeperKyverno
Policy languageRego (dedicated logic language)YAML (K8s-native, no new language)
Learning curveSteep (Rego is unfamiliar)Low (YAML patterns familiar to K8s users)
Validate
Mutate❌ (validate only)✅ (patch resources)
Generate✅ (create resources automatically)
Image verificationVia external data✅ Built-in (cosign, Notary)
Audit existing✅ (built-in audit controller)✅ (policy reports)
Complexity of rulesHandles very complex logic (Rego is Turing-complete)Good for pattern-based rules; complex logic is verbose
EcosystemCNCF graduated, broad non-K8s useCNCF 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

PolicyGatekeeper ApproachKyverno Approach
Require labelsRego: check input.review.object.metadata.labelsPattern: metadata.labels.team: "?*"
Block privileged containersRego: deny if securityContext.privileged == truePattern: =(securityContext.privileged): false
Restrict image registriesRego: deny if image not in allowed listvalidate.deny with image pattern match
Require resource limitsRego: check containers[].resources.limitsPattern: containers[].resources.limits.memory: "?*"
Auto-add network policy❌ Can't generateGenerate rule on Namespace creation
Verify image signaturesExternal data + RegoBuilt-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
In production, start with 5-10 critical policies (require labels, restrict registries, block privileged, require resource limits, require probes) in 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

ConceptKey Point
Policy enginesCustom admission logic beyond Pod Security Standards
GatekeeperOPA + Rego, validate-only, ConstraintTemplate + Constraint CRDs
KyvernoYAML policies, validate + mutate + generate, ClusterPolicy CRD
ConstraintTemplateReusable policy logic (Rego) — parameterized
ConstraintInstance of a template — applies to specific resources/namespaces
enforcementActiondeny (block), dryrun (log), warn (feedback)
AuditBoth scan existing resources for violations (not just new requests)
Kyverno generateAuto-create resources (NetworkPolicy, RBAC) when triggers match
Policy librariesDon'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?

ConstraintTemplate defines the reusable policy logic (in Rego) and creates a new CRD. Constraint is an instance of that CRD — it applies the template to specific resources with specific parameters. Like class vs instance: Template = "check for required labels (parameterized)," Constraint = "require 'team' label on Deployments in production."

Q2: A Gatekeeper Constraint has enforcementAction: dryrun. A non-compliant Deployment is created. What happens?

The Deployment is allowed (created successfully). The violation is logged in the Constraint's status (audit results). The user doesn't see any error or warning. Use dryrun to assess impact before switching to deny. Check violations with: kubectl get constraint require-team-label -o yaml | grep -A20 violations

Q3: What can Kyverno do that Gatekeeper cannot?

Three things: (1) Mutate — automatically modify resources (add labels, set defaults). (2) Generate — create new resources when triggers fire (e.g., NetworkPolicy per Namespace). (3) Built-in image verification — verify cosign/Notary signatures natively. Gatekeeper is validate-only (though Gatekeeper v3.14+ has experimental mutation).

Q4: You want every new namespace to automatically get a default-deny NetworkPolicy. Which tool and what policy type?

Kyverno with a generate rule. Create a ClusterPolicy that matches Namespace creation and generates a NetworkPolicy in the new namespace. Gatekeeper can't do this (no generation capability). The Kyverno policy watches for new Namespaces and auto-creates the NetworkPolicy — no manual step needed.

Q5: Both Gatekeeper and Kyverno can audit existing resources. Why is this important beyond just admission?

Admission webhooks only see new requests. Resources created before the policy was installed are never checked by admission. Audit mode scans all existing resources and reports violations — showing you the full compliance gap. This is essential for: (1) Seeing the impact before enabling enforcement. (2) Tracking compliance over time. (3) Finding resources that predate the policy.

Q6: You're on the CKS exam. The question says "ensure containers only use images from registry.company.com." Which approach is fastest?

If Gatekeeper is installed: create a ConstraintTemplate (Rego that checks 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.