# Production Kubernetes Security: Enforcing Zero Trust with Kyverno & OPA Gatekeeper

Scanning and signing can confirm an image is acceptable, but they don't prevent unsafe images from running. [**Admission control**](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) fills this gap. However, many teams either skip this step or only partially implement it, often by just adding a NetworkPolicy and considering the job finished.

This article builds on the last one, where I covered securing the [**supply chain with Trivy, Cosign, and Falco.**](https://www.linkedin.com/pulse/production-kubernetes-security-building-zero-trust-supply-rafay-d0wrf/) I explained that admission control stops harmful workloads from running, not just detecting them after the fact.

In this post, I'll explain how admission control works in Kubernetes, compare [**Kyverno**](https://kyverno.io/) and [**Gatekeeper**](https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/) based on real-world use, and share policies to block common risks like **root containers**, **unsigned images**, **mutable tags**, and **workloads** with unnecessary host access.

### **How Kubernetes Admission Control Works**

Every request to the Kubernetes API goes through **authentication, authorisation, and admission** before the object is persisted.

*   **Authentication** checks who is making the request. **Authorisation**, typically handled by RBAC, determines whether a person is permitted to perform the requested action.
    
*   Admission control evaluates the submitted object. Admission plugins and webhooks can allow, modify, or reject the request based on defined policies.
    
*   Many teams overlook this key security step. RBAC might allow someone to create Pods, but it does not verify whether those Pods run as root, use privileged containers, or pull images from untrusted sources. Authorisation answers, **“Can you do this?”** Admission control answers, **“Does this follow our security policies?”**
    

![](https://cdn.hashnode.com/uploads/covers/62d3d92a2f40e31decd8c583/f5849b2d-f90f-452b-a06b-7eaa6efcf1c4.png align="center")

### **Why Scanning and Signing Alone Aren’t Enough**

CI security controls are only effective when deployments follow the expected path. In real production environments, that assumption can break. An engineer may apply a manifest directly with kubectl during an incident, a CI runner could be compromised, or someone might deploy an old manifest that still uses privileged settings.

These situations often cause configuration drift. Admission control helps by adding a checkpoint inside the cluster. It checks workloads as they enter the Kubernetes API, so even if someone skips the usual CI/CD process, admission control can still block risky deployments. CI checks catch problems before deployment, while admission control prevents them from entering the cluster.

### **Mutating Webhooks vs Validating Webhooks**

Kubernetes processes **mutating admission first**, followed by **validating admission**.

*   **Mutating** policies can modify a workload by adding labels, setting default values, or injecting a sidecar.
    
*   **Validating** policies review the workload and then decide to allow or reject it.
    

**For example,** Kyverno can automatically add **runAsNonRoot: true** if it is missing. A validating policy, on the other hand, can block workloads that do not meet this requirement. For important security settings, I prefer validation. It is safer to reject an insecure workload and tell the developer what to fix than to quietly change security-related settings.

### **Managing Policy as Code with GitOps**

Treat security policies as code. Keep them in Git, review changes through pull requests, and use GitOps tools such as Argo CD or Flux to synchronise them with your clusters.

This applies to both **namespace-level security settings** and **Kyverno or Gatekeeper policies**. Avoid managing production policies manually with kubectl, because changes can become difficult to review, reproduce, or audit.

### **Enforcing Pod Security Standards at the Namespace Level**

Before adding custom admission policies, Kubernetes already provides [**Pod Security Admission (PSA)**](https://kubernetes.io/docs/concepts/security/pod-security-admission/) as a built-in baseline. PSA applies Pod Security Standards at the **namespace level**. For example:

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
```

This acts as the **first layer of enforcement**. It sets a basic level of Pod security and doesn't require an extra policy engine. PSA doesn't address every organisation’s unique needs. Tools like Kyverno or Gatekeeper can then add controls for **trusted registries, image signatures, resource requirements, and other organisation-specific security requirements.**

### **Extending the Baseline with Kyverno**

With the baseline in place, we can add more specific admission policies. For example:

*   **Blocking Root and Privileged Containers**
    

```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-root-and-privileged
spec:
  rules:
    - name: check-runasnonroot
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        failureAction: Enforce
        message: "Containers must run as non-root and cannot be privileged."
        pattern:
          spec:
            securityContext:
              runAsNonRoot: true
            containers:
              - securityContext:
                  privileged: false
```

*   **Requiring CPU and Memory Limits on Every Pod**
    

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

*   **Restricting Deployments to Trusted Registries**
    

```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-image-registries
spec:
  rules:
    - name: allowed-registries
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        failureAction: Enforce
        message: "Images must come from myregistry.io."
        pattern:
          spec:
            containers:
              - image: "myregistry.io/*"
```

*   **Verifying Cosign Image Signatures at Admission**
    

```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  background: false
  rules:
    - name: verify-image-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "myregistry.io/*"
          failureAction: Enforce
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      <your-cosign-public-key>
                      -----END PUBLIC KEY-----
```

*   **Blocking ‘latest’ and Other Mutable Image Tags**
    

```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  rules:
    - name: require-image-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        failureAction: Enforce
        message: "Images must use an immutable tag or digest, not 'latest'."
        pattern:
          spec:
            containers:
              - image: "!*: latest"
```

*   **Restricting hostPath, hostNetwork, and Privilege Escalation**
    

```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-host-access
spec:
  rules:
    - name: disallow-host-namespaces
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        failureAction: Enforce
        message: "hostPath, hostNetwork, and privilege escalation are not allowed."
        pattern:
          spec:
            =(hostNetwork): false
            =(volumes):
              - X(hostPath): null
            containers:
              - securityContext:
                  allowPrivilegeEscalation: false
```

Allowing these settings lets a compromised pod break out and access the node directly. Most application workloads do not need this access. Block it by default and allow exceptions only for specific system components that truly need it, with clear documentation.

### **Audit, Warn, Enforce: The Correct Rollout Order**

*   Start new policies in **Audit** mode to identify existing violations without blocking workloads. Review the results, test the policies, and fix legitimate violations before moving to **Enforce**.
    
*   For **Pod Security Admission**, Kubernetes also provides **Warn** mode, which shows violations to users without blocking the request. Avoid switching directly to **Enforce** in a live cluster. Roll out policies gradually, starting with non-critical workloads, to prevent unexpected deployment failures.
    

### **The Full Pipeline: From Pull Request to Running Pod**

*   Developer opens a pull request; Trivy scans dependencies and secrets.
    
*   On merge, CI builds the image, and Trivy scans it for HIGH and CRITICAL findings.
    
*   CI generates an SBOM, and Cosign signs the image with a key stored in a Secrets Manager.
    
*   The image ships to the registry; ArgoCD or Flux syncs the manifest to the cluster.
    
*   Kyverno or Gatekeeper checks the workload against every policy: signature, registry, tag, resource limits, root and privilege settings, host access.
    
*   If it passes, it's scheduled, and Falco starts watching it; if it fails, it's rejected before a single container starts.
    

![](https://cdn.hashnode.com/uploads/covers/62d3d92a2f40e31decd8c583/c3be6997-8518-4785-8d45-68b03ea95d72.png align="center")

**There are four stages in one direction:** CI/CD, the registry, admission control in front of the API server, and Falco runtime monitoring. Nothing reaches runtime without passing through the first three.

### **Real-World Attack Scenarios**

These are not just theoretical risks. Recent threat intelligence indicates that attackers are targeting **CI/CD pipelines, container registries, and Kubernetes control planes** as part of software supply chain attacks. [**Google Threat Intelligence**](https://cloud.google.com/blog/topics/threat-intelligence/preparation-hardening-destructive-attacks?utm_source=chatgpt.com) specifically highlights registry poisoning, unauthorised Kubernetes deployments, privileged workloads, and unsigned or modified container images as relevant attack patterns.

*   **Direct kubectl Deployment:** During an incident, an engineer bypasses the CI/CD pipeline and deploys a workload quickly. If the manifest runs as root or uses excessive privileges, admission policies can reject it before the workload starts. This creates an enforcement boundary even when the normal deployment process is bypassed.
    
*   **Repointed latest Tag:** An attacker with registry access can replace a legitimate image with a malicious version. Google Threat Intelligence specifically identifies **container registry poisoning** as a risk when compromised developer or CI/CD credentials overwrite legitimate images. Blocking latest and preferring immutable image references reduces this attack surface.
    
*   **Compromised CI Runner:** If an attacker takes over a CI/CD workflow or runner, they can use its permissions to upload a changed image or steal credentials. Recent [**GitHub security advice points**](https://github.blog/changelog/2026-07-28-github-actions-holds-potentially-malicious-workflows-for-approval/?utm_source=chatgpt.com) out that attackers are targeting CI/CD automation, including workflows and credential theft. By requiring a valid Cosign signature at admission, you add another security check. Even if a bad image gets into the registry, the cluster can block it before it runs.
    

**The key takeaway is that** CI/CD security, registry controls, and admission control should not be treated as separate defences. If one layer is compromised, the next layer should still have enough trust information to stop the workload from reaching production.

### **Handling Exceptions Without Losing Control**

Security policies may require exceptions for legitimate workloads, especially system components that require elevated permissions. The goal is to make those exceptions **controlled and auditable**, not eliminate them.

*   **Define an owner:** Every exception should have a clear owner and reason.
    
*   **Manage exceptions as code:** Keep Kyverno PolicyException or Gatekeeper exemptions in Git and review them through pull requests.
    
*   **Keep them narrow:** Scope exceptions only to the workloads that actually need them.
    
*   **Review them regularly:** Add an expiry or review date so temporary exceptions don't quietly become permanent security gaps.
    

### **Common Mistakes with Kyverno and Gatekeeper**

*   Write small, focused policies instead of trying to cover everything in one. Debugging a rejected deployment with a large, complex policy is hard.
    
*   Remember to use background scanning. Both tools can check resources already running in the cluster, not just new ones. This helps catch drift from before you set up the policy.
    
*   Watch out for policy sprawl. Many overlapping policies with no clear owner can be worse than none, because no one knows what is actually being enforced.
    
*   Do not think of admission control as the final step. It is an important layer, but not the whole security strategy.
    

### **Where Kubernetes Policy Enforcement Is Headed**

Kubernetes' built-in ValidatingAdmissionPolicy brings simpler policy enforcement directly into the API server. For more advanced needs, Kyverno and Gatekeeper are still strong options. At the same time, Sigstore and SLSA are moving supply-chain security toward stronger provenance, automated verification, and less manual key management.

### **References**

*   Kubernetes documentation, [**kubernetes.io/docs**](http://kubernetes.io/docs)
    
*   CNCF, Cloud Native Security Whitepaper, [**cncf.io**](http://cncf.io)
    
*   Kyverno documentation, [**kyverno.io**](http://kyverno.io)
    
*   OPA Gatekeeper documentation, [**open-policy-agent.github.io/gatekeeper**](http://open-policy-agent.github.io/gatekeeper)
    
*   Sigstore project, sigstoredev
    
*   SLSA framework, slsadev
    
*   Falco documentation, [**falco.org**](http://falco.org)
    

**Next in this series:** Production Kubernetes Security: Securing Kubernetes Runtime, Network & Workload Identity
