Skip to content

18.3 Admission Control and Policy Enforcement

From Advisory to Enforcement

Without admission control, security is advisory. Developers might receive alerts about unsigned images or vulnerabilities, but nothing stops deployment under deadline pressure. Admission control transforms security requirements from suggestions into guardrails.

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)1 is a general-purpose policy engine that evaluates policies written in Rego2, 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.

Gatekeeper3 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

Kyverno4 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.

Gatekeeper vs Kyverno

Gatekeeper (OPA/Rego): Powerful policy language, cross-platform, steeper learning curve. Kyverno: Kubernetes-native YAML, easier adoption, built-in mutation capabilities. Choose based on team expertise and cross-platform needs.

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 Sigstore5:

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"
            subjectRegExp: "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 audit and blocking behavior with validationFailureAction in many policies, while newer validation rules can use per-rule failureAction. Use emitWarning when you want audit-mode violations surfaced as admission warnings:

spec:
  validationFailureAction: Audit  # or Enforce

# In newer validate rules:
validate:
  failureAction: Audit
  emitWarning: true

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.