Using the Kyverno Admission Controller to Enforce Minimal Base Images

By
Neil Carpenter
September 25, 2025
Share this post

Cybersecurity is often spoken of in terms of paved roads and guard rails. Paved roads are patterns that we define to do things in a secure way and, conversely, guard rails are limits we set to prevent our users from doing things the wrong way.

We often get questions about guard rails for consumers of Minimus’s minimal, low-CVE images. The paved roads are immediately obvious – use Minimus base images and spend far less time and energy resolving (or contesting) findings from vulnerability scans. What’s less obvious is how to set up appropriate guard rails to enforce the use of our base images in the enterprise.

For organizations who have standardized on Kubernetes, one approach is to use an Admission Controller to enforce policies such as “you must use Minimus base images in what you deploy”. An Admission Controller is a Kubernetes service that reviews all changes to the cluster and either approves/denies them (a Validating Admission Controller) or, potentially, modifies them (a Mutating Admission Controller).

Today, we’re going to use Kyverno, which is an Admission Controller that has become popular for its simple, flexible approach to defining policies in YAML within the configuration of the Kubernetes cluster itself. This approach enables organizations to manage policy in the same way, and with the same tools, that they manage Kubernetes deployments and configuration.

Installing Kyverno Helm Chart

Depending on the scale of the clusters to be deployed on, there are a number of different approaches to deploying Kyverno. For this blog, we’re using a small, non-production Google GKE cluster so we’ve deployed using Minimus’s Kyverno Helm chart to take advantage of low-CVE images for our deployment.

Defining a Kyverno Policy to Enforce Minimal Base Images

While Kyverno can apply a wide variety of policies, we’re going to work with a relatively simple goal. We have a pipeline in GitHub that builds images and delivers them to Google Artifact Registry. Images are deployed from GAR to our GKE cluster in the `apps` namespace. Our policy requirement is that these images deployed to our cluster use a Minimus image as their base.

In order to define this policy, we need a reliable indicator of the base image.  Because we are building OCI images in our pipeline, we can use the common ​"org.opencontainers.image.base.name" annotation to check the name of the base image used.

Matching Kubernetes Resources with Kyverno Rules

The full policy YAML is available at the end of this article but we’ll break down the important pieces one by one. First, we’ll create a rule: we’re choosing to ENFORCE this rule although, in practice, these should be audit-only until you’re sure the policy won’t break anything important.

spec:
  validationFailureAction: Enforce
  rules:
  - name: allow-minimus-base
    match:
      any:
      - resources:
          kinds:
          - Pod
          namespaces:
          - apps

Our allow-minimus-base rule applies to (or matches) any Pod in the namespace apps.  

Setting Up the Validation

A Kubernetes Pod may have multiple containers in it, so we need to loop through them (using foreach) to ensure that all of the containers in this Pod comply with our policy.

We’re also using imageData, which requires Kyverno to pull a copy of the image to extract attributes to make a decision on. This is necessary to extract the annotations on the image to make decisions based on them; otherwise, the Admission Controller only has access to the configuration being sent to Kubernetes and not to the details of the image itself.

In the context, we define the imageData variable to point to the image itself, and then we define basename to extract the annotation we want to use.

    validate:
      message: >-
        The base image for this container is not a Minimus base image.
        Please contact the platform team for assistance.
      foreach:
      - list: "request.object.spec.[ephemeralContainers, initContainers, containers][]"
        context:
        - name: imageData
          imageRegistry: 
            reference: "{{ element.image }}"
        - name: basename
          variable:
            jmesPath: imageData.manifest.annotations."org.opencontainers.image.base.name"
            default: ''

Validating

Now, we’re ready to define our deny rule so that Kyverno knows what to reject (and, by extension, what to accept) for a new Pod in the apps namespace.

Using the basename variable that we defined in the context, we test against a wildcard pattern for any image with a base image that comes from reg.mini.dev.

        deny:
          conditions:
            any:
              - key: "{{ basename }}"
                operator: AnyNotIn
                value: "reg.mini.dev/*"

Put everything together, kubectl apply our policy, and we’re ready to test.

Testing

Let’s start with our python-library image, which is built on library/python:3.13 from Docker Hub. This is an image that we do not want deployed based on the policy we defined.

 kubectl run python-library -n apps --image us-central1-docker.pkg.dev/minimus-sa/python/python-library:latest
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:

resource Pod/apps/python-library was blocked due to the following policies

allowed-base-images:
  allow-minimus-base: 'validation failure: The base image for this container is not
    a Minimus base image. Please contact the platform team for assistance.'

Success! Now, let’s test with python-mini, which is built on top of Minimus’s python:3.13 image.

 kubectl run python-mini --image us-central1-docker.pkg.dev/minimus-sa/python/python-mini:latest
pod/python-mini created

Further Exercises: Next Steps for Kyverno Admission Controller Policies

Obviously, the policies we’ve defined today are functional guard rails; however, they represent only a portion of the policies that a security team might wish to apply. Some further thoughts on reasonable guard rails that might be applied:

  • You may also wish to validate images that come directly from Minimus’s registry for images like nginx and postgres that you might use directly.
  • It would also make sense to add checks for image signatures. Minimus uses cosign to sign all images, and extending this to the pipelines that build your images helps ensure that they are valid and haven’t been tampered with after build.

Secure Your Kubernetes Deployments with Minimus

By combining Kyverno with Minimus’s hardened, low-CVE base images, you can put practical guard rails in place and keep insecure builds from ever reaching production.

Looking to simplify secure image adoption across your Kubernetes clusters? Explore Minimus base images and start reducing vulnerabilities from the ground up.

Appendix: Full Policy

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: allowed-base-images
  annotations:
    policies.kyverno.io/title: Allowed Base Images
    policies.kyverno.io/category: Other
    policies.kyverno.io/severity: medium
    kyverno.io/kyverno-version: 1.7.0
    policies.kyverno.io/minversion: 1.7.0
    kyverno.io/kubernetes-version: "1.23"
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
     Minimal images, threat intelligence, flexible integration and alerting - 
     Minimus makes application security easy..
spec:
  validationFailureAction: Enforce
  rules:
  - name: allow-minimus-base
    match:
      any:
      - resources:
          kinds:
          - Pod
          namespaces:
          - apps
    preconditions:
      all:
      - key: "{{request.operation || 'BACKGROUND'}}"
        operator: NotEquals
        value: DELETE
    validate:
      message: >-
        The base image for this container is not a Minimus base image.
        Please contact the platform team for assistance.
      foreach:
      - list: "request.object.spec.[ephemeralContainers, initContainers, containers][]"
        context:
        - name: imageData
          imageRegistry: 
            reference: "{{ element.image }}"
        - name: basename
          variable:
            jmesPath: imageData.manifest.annotations."org.opencontainers.image.base.name"
            default: ''
        deny:
          conditions:
            any:
              - key: "{{ basename }}"
                operator: AnyNotIn
                value: "reg.mini.dev/*"

Share this post
Neil Carpenter
Principal Solutions Architect
Sign up for minimus

Avoid over 97% of container CVEs

Access hundreds of hardened images, secure Helm charts, the Minimus custom image builder, and more.