Enforce Image Tag Policy with Kyverno

Admission Controllers come built into Kubernetes, enforcing and validating new objects amongst other tasks. Take the admission controller NamespaceLifecycle. It's entire job is ensuring that namespaces can't be deleted when new objects are present within and also blocks the ambitious yet risk-adverse engineer from terminating the default namespace. Being as the built in controller plugins can't be edited, when you want to extend or generate new policies, Kyverno makes it possible to create dynamic policies in the cluster. We're talking policies that go beyond what comes out the box admission controller, for example denying a Deployment because it's label isn't consistent with your organizations standards. This control provided by Kyverno makes it easy for cluster admins to enfore strict guardrails on objects in the cluster along with mutating and even generating Kubernetes objects based on defined policies.
With this post we're going to create a policy that denies images of Redis older than what we specify. We are not saying a given version of Redis isn't secure, we just want to use it as an example. Big fans of the project! The quick example should give a small glimpse into the possibilities of Kyverno and how it can vastly bolster the security posture of any organizations Kubernetes cluster.
This guide assumes you have the following prepared:
- Kubernetes cluster with at least 2GB ram
- Kyverno deployed in the cluster, it's Helm chart can be found here
Deploy ClusterPolicy
The following manifest is what Kyverno defines as a ClusterPolicy. This type enforces policy cluster wide while its counterpart Policy is scoped to namespaces. With ClusterPolicy's, you first make a match of the resources you want to apply the rule against. Once defined, every policy will include a validate, mutate, generate, or verifyImages declaration.
In our example we're enforcing the policy on any Pods when they are submitted for creation. We emphasize any because of line 19 (more on this later). Then we declare the policy should validate and deny Pods that meet our condition.
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: enforce-redis-version
5 annotations:
6 policies.kyverno.io/title: Enforce Redis Version
7 policies.kyverno.io/category: Image Compliance
8 policies.kyverno.io/severity: medium
9 policies.kyverno.io/subject: Pod
10 policies.kyverno.io/description: >-
11 Deny Redis image tags that are older than '7.2.6'. This is to enforce
12 consistency for developers.
13spec:
14 validationFailureAction: Enforce
15 background: true
16 rules:
17 - name: enforce-image-tag
18 match:
19 any:
20 - resources:
21 kinds:
22 - Pod
23 operations:
24 - CREATE
25 validate:
26 message: "Redis tag must be higher that 7.2.6"
27 deny:
28 conditions:
29 all:
30 - key: "{{images.containers.redis.tag}}"
31 operator: LessThanOrEquals
32 value: "7.2.6"
- spec.validationFailureAction: How Kyverno will respond to objects that fail validation. Here, we define Enforce that will block the object from being created. This keys value could also be Audit which will allow the object to be created in the cluster but the attempt recorded in a policy report.
- spec.rules: This is where we define the matching rules and the objects we want to target for the policy. Whether it's what we have in our example a "match" or its counterpart and "exclude" condition this is how rules are targeted. In the example we're only targeting Pods during the CREATE operation exclusively. Without key spec.rules[0].match.any[0].resources.operations having a value of CREATE, I wasn't able to delete Pods that passed validation (Redis tags > 7.2.6).
What we're saying is "Only deny Pods that are trying to use a version of Redis that's older than 7.2.6. If a pod passed validation or is already deployed, do not validate it when I'm trying DELETE or UPDATE it".
- spec.validate: What's happening here is where the actual regulation is being defined. First is the message key, this string will output when the rule is in this case met.
- spec.validate.deny.conditions.all: The key "all" is important here. Since we're able to define multiple rules in the validate field, Kyverno uses this word to indicate a "or" type of logic. If we had more than one rule here, if any one is met Kyverno will trigger a denial and display our message. If we had "any" here instead, it would work like a logical "and". All the rules defined would trigger Kyverno and display the message.
- spec.validate.deny: Did not want to gloss over this key. Kyverno defines deny rules as opposites of pattern based rules. When a deny condition evaluates to true, Kyverno will block the resouce and raise our message. Kyverno states that deny rules are more powerful but more complex to write.
- spec.validate.deny.conditions.all[0].key: This value is a Kyverno variable. Kyverno is able to extract data from the Admission Controller and saves them in a uniform and consistent manner for us to write powerful yet reusable policies. I'm able to get the value in the variable {{images.container.redis.tag}}. This assumes that the name of the container will be "redis". Luckily for this quickstart it does!
It's also worth noting that in the last line we passed the semver string "7.2.6". Kyverno is smart enough to take that value and do greater than, less than operations against it amongst many other operations.
Apply your file and test your policy:
kubectl apply -f redis-tag.yaml