Skip to content

18.3 Admission Control and Policy Enforcement

Scanning container images, signing artifacts, and maintaining secure configurations provide security visibility, but visibility alone does not prevent insecure workloads from reaching production. The gap between knowing about a vulnerability and preventing its deployment requires enforcement mechanisms that automatically block non-compliant resources before they can run. In Kubernetes environments, admission control serves as this enforcement layer—a gatekeeper that evaluates every resource creation or modification request against organizational policies.

Without admission control, security becomes advisory. Developers might receive alerts about unsigned images or critical vulnerabilities, but nothing stops them from deploying anyway under deadline pressure. Admission control transforms security requirements from suggestions into guardrails, ensuring that policy violations are caught automatically rather than depending on human vigilance.

Kubernetes Admission Controller Architecture

When you run kubectl apply or a CI/CD pipeline deploys a resource, the request passes through a series of stages before the resource is persisted to etcd and acted upon by controllers. Admission controllers intercept requests after authentication and authorization but before persistence:

Request → Authentication → Authorization → Admission Control → Persistence → Controllers
                                    ┌──────────────────────┐
                                    │  Mutating Webhooks   │
                                    │  (modify resources)  │
                                    └──────────┬───────────┘
                                    ┌──────────────────────┐
                                    │ Validating Webhooks  │
                                    │ (accept/reject)      │
                                    └──────────────────────┘

Kubernetes provides two types of admission webhooks:

Mutating admission webhooks can modify incoming resources. They might inject sidecar containers, add default labels, or modify security contexts. Mutating webhooks run first, allowing subsequent validating webhooks to evaluate the final, mutated resource.

Validating admission webhooks evaluate resources against policies and return accept or reject decisions. They cannot modify resources—only approve or deny them. Multiple validating webhooks can evaluate the same resource, and all must approve for the request to proceed.

This architecture enables powerful policy enforcement without modifying Kubernetes core components. Organizations deploy webhook servers that receive admission review requests, evaluate them against policies, and return decisions. The challenge lies in making policy authoring, testing, and management practical at scale.

Open Policy Agent and Gatekeeper

Open Policy Agent (OPA) is a general-purpose policy engine that evaluates policies written in Rego, a declarative query language designed for policy decisions. OPA can enforce policies across many systems—Kubernetes, API gateways, service meshes, and more—using a consistent policy language.

Gatekeeper is the Kubernetes-native implementation of OPA, providing Custom Resource Definitions (CRDs) for defining and managing policies within Kubernetes itself. Gatekeeper operates as a validating admission webhook, intercepting resource requests and evaluating them against OPA policies.

Installing Gatekeeper:

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml

Note: Version numbers shown are examples. Always check the official documentation for the current stable release before deployment.

Gatekeeper uses a two-resource model: ConstraintTemplates define the policy logic in Rego, while Constraints apply that template to specific resources with parameters.

A ConstraintTemplate requiring container images from approved registries:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sallowedrepos
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRepos
      validation:
        openAPIV3Schema:
          type: object
          properties:
            repos:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sallowedrepos

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not strings.any_prefix_match(container.image, input.parameters.repos)
          msg := sprintf("Container '%s' uses image '%s' from disallowed registry", 
                         [container.name, container.image])
        }

A Constraint applying the template:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: require-approved-registries
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    repos:
      - "myregistry.io/"
      - "gcr.io/my-project/"

Gatekeeper's strengths include its policy language expressiveness (Rego can implement complex logic), cross-platform applicability (the same policies work outside Kubernetes), and mature ecosystem. Its primary challenge is Rego's learning curve—the language, while powerful, requires investment to master.

Kyverno: Kubernetes-Native Policy Engine

Kyverno takes a different approach, using Kubernetes-native YAML for policy definitions rather than a specialized language. Policies look like other Kubernetes resources, reducing the learning curve for platform engineers already fluent in Kubernetes.

Installing Kyverno:

kubectl create -f https://github.com/kyverno/kyverno/releases/download/v1.11.0/install.yaml

Note: Version numbers shown are examples. Always check the official documentation for the current stable release before deployment.

Kyverno policies combine matching rules with validation, mutation, or generation logic:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-approved-registries
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-registry
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Images must come from approved registries"
      pattern:
        spec:
          containers:
          - image: "myregistry.io/* | gcr.io/my-project/*"

Kyverno also excels at image verification, integrating directly with Sigstore:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
  - name: verify-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "myregistry.io/*"
      attestors:
      - entries:
        - keyless:
            issuer: "https://token.actions.githubusercontent.com"
            subject: "https://github.com/myorg/*"

Comparing the two engines:

Aspect Gatekeeper/OPA Kyverno
Policy language Rego (learning curve) Kubernetes YAML (familiar)
Image verification Requires external integration Built-in Sigstore support
Mutation support Supported (via webhooks) Native, extensive
Resource generation Not supported Supported
Cross-platform OPA works everywhere Kubernetes-specific
Community size Larger, more mature Growing rapidly

We recommend Kyverno for teams primarily focused on Kubernetes without existing OPA investment, and Gatekeeper for organizations using OPA across multiple systems or requiring complex policy logic that benefits from Rego's expressiveness.

Policy Categories and Examples

Effective admission control addresses multiple security domains. We organize policies into categories based on what they protect:

Image source policies restrict which registries and repositories can supply container images:

# Kyverno: Block images from public Docker Hub
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: block-public-dockerhub
spec:
  validationFailureAction: Enforce
  rules:
  - name: block-dockerhub
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Images from public Docker Hub are not allowed"
      pattern:
        spec:
          containers:
          - image: "!docker.io/* & !library/*"

Signature verification policies require cryptographic proof of artifact authenticity (covered in Section 18.2).

Vulnerability policies block images with known vulnerabilities above a threshold. These typically require integration with scanning systems:

# Kyverno: Check vulnerability scan results
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: check-vulnerability-scan
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-scan-annotation
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Images must have passing vulnerability scan"
      pattern:
        metadata:
          annotations:
            scan.myorg.io/status: "pass"

Configuration policies enforce security best practices for workload specifications:

# Kyverno: Require non-root containers
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-non-root
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-non-root
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Containers must run as non-root"
      pattern:
        spec:
          securityContext:
            runAsNonRoot: true
          containers:
          - securityContext:
              runAsNonRoot: true

Resource limit policies ensure workloads specify CPU and memory bounds, preventing resource exhaustion attacks:

# Kyverno: Require resource limits
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-limits
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "CPU and memory limits are required"
      pattern:
        spec:
          containers:
          - resources:
              limits:
                memory: "?*"
                cpu: "?*"

Policy Development and Testing Workflows

Policies are code and deserve the same development rigor as application code. Testing policies before deployment prevents outages caused by overly restrictive or incorrectly written policies.

Unit testing validates policy logic against sample resources. Kyverno provides a CLI for policy testing:

# test/require-non-root/test.yaml
policies:
  - ../policies/require-non-root.yaml
resources:
  - resources/pod-non-root.yaml
  - resources/pod-root.yaml
results:
  - policy: require-non-root
    rule: check-non-root
    resource: pod-non-root
    result: pass
  - policy: require-non-root
    rule: check-non-root
    resource: pod-root
    result: fail
kyverno test test/require-non-root/

For Gatekeeper, the gator CLI (Gatekeeper's testing tool, installable via go install github.com/open-policy-agent/gatekeeper/cmd/gator@latest) provides similar capabilities:

gator test policies/ --filename test-resources/

Integration testing deploys policies to a test cluster and validates expected behavior:

# CI/CD pipeline step
- name: Test policies
  run: |
    kind create cluster
    kubectl apply -f policies/
    # Should succeed
    kubectl apply -f test-resources/valid-pod.yaml
    # Should fail
    ! kubectl apply -f test-resources/invalid-pod.yaml

Policy linting catches common mistakes before deployment. Kyverno policies can be validated with:

kyverno validate policy.yaml

We recommend storing policies in Git, reviewing changes through pull requests, and running automated tests in CI before merging.

Graduated Enforcement: Audit Mode to Blocking

Deploying policies in blocking mode without testing against existing workloads risks widespread outages. A graduated rollout strategy minimizes disruption:

  1. Audit mode: Policies evaluate resources but only log violations without blocking. This reveals what would fail without impacting operations.

  2. Warn mode: Policies emit warnings visible to users but still allow resources to be created. This educates developers about upcoming requirements.

  3. Enforce mode: Policies block non-compliant resources. Only enable after confirming audit results are acceptable.

Kyverno supports all three modes via validationFailureAction:

spec:
  validationFailureAction: Audit  # or Warn, or Enforce

Gatekeeper uses enforcementAction:

spec:
  enforcementAction: dryrun  # or warn, or deny

We recommend running new policies in audit mode for at least one week, reviewing violations to identify legitimate workloads that need modification or exemption, then proceeding through warn mode before enabling enforcement.

Exception Handling Processes

No policy covers every legitimate use case. Some workloads require elevated privileges, specific images, or other policy exceptions. Effective admission control includes structured exception handling:

Namespace-based exclusions exempt specific namespaces from policies:

# Kyverno: Exclude kube-system
spec:
  rules:
  - name: check-non-root
    exclude:
      any:
      - resources:
          namespaces:
          - kube-system

Label-based exceptions allow workloads to opt out with explicit approval:

spec:
  rules:
  - name: check-non-root
    exclude:
      any:
      - resources:
          selector:
            matchLabels:
              security.myorg.io/exception-approved: "SEC-1234"

Time-bound exceptions require periodic review:

# Kyverno PolicyException (v1.9+)
apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
  name: allow-privileged-monitoring
  annotations:
    # Track expiration manually; use CleanupPolicy for automatic removal
    exception.myorg.io/expires: "2024-04-01"
    exception.myorg.io/ticket: "SEC-1234"
spec:
  exceptions:
  - policyName: disallow-privileged
    ruleNames:
    - check-privileged
  match:
    any:
    - resources:
        kinds:
        - Pod
        namespaces:
        - monitoring

Note: For automatic expiration of PolicyExceptions, combine this with a ClusterCleanupPolicy that uses Kyverno's time_add() and time_to_cron() JMESPath functions introduced in version 1.9.

We recommend requiring documented justification for exceptions, limiting their scope as narrowly as possible, and implementing regular reviews to remove stale exceptions.

Balancing Security with Operational Flexibility

Overly restrictive policies create pressure to bypass security controls entirely. The goal is policies strict enough to catch real security issues while flexible enough to accommodate legitimate operational needs.

Practical strategies include:

  • Start with high-confidence policies: Block images without signatures or from unknown registries before tackling configuration nuances
  • Provide escape hatches: Structured exceptions are better than frustrated developers disabling admission control
  • Communicate early: Announce policy changes before enforcement, giving teams time to adapt
  • Measure and iterate: Track exception requests to identify policies that are too strict or need refinement

Effective admission control should feel like guardrails on a mountain road—present to prevent catastrophe, not to slow normal travel. Security controls that work seamlessly encourage compliance; those that create friction encourage workarounds.