TT Lab
Get started
Learn Learning paths Courses

KCA — Kyverno Certified Associate

Comparing validate Rules Against the Built-in Policies

Continue in TT Lab

Goal

Write a required-label rule and a tag-prohibition rule as a Kyverno ClusterPolicy, and also put the same check into a real cluster as Kubernetes' built-in ValidatingAdmissionPolicy to see the difference between the two approaches.

Why it matters

To decide whether to adopt a policy engine, you need to know the reach of the built-in features. If all you need is to check one field with CEL, ValidatingAdmissionPolicy is enough, and then there is one fewer component to operate and one fewer thing to worry about for webhook certificate renewal. What justifies Kyverno are the things the built-in policy cannot do, such as generate, mutate, and image verification. By building the two manifests side by side in this lab, you come away with a hands-on feel for where that boundary lies. One more thing: practicing giving each rule a different enforcement level is the heart of a real adoption procedure.

Steps

  1. Create the /root/kca-validate/ directory, write apiVersion kyverno.io/v1, kind ClusterPolicy, and metadata.name kca-require-app-label in policy.yaml, and set spec.rules[0].name to check-app-label.
  2. Put Pod and Deployment in spec.rules[0].match.any[0].resources.kinds, and put kca-app in namespaces in the same place.
  3. Fill in spec.rules[0].validate.message, and use validate.pattern to require that app.kubernetes.io/name in metadata.labels be a non-empty value.
  4. Set spec.rules[0].validate.failureAction to Enforce. Do not use the policy-level spec.validationFailureAction.
  5. Add a second rule deny-latest-tag. Fill in validate.message, put a key, operator, and value that point to the container image in validate.deny.conditions.all[0], and then set this rule's validate.failureAction to Audit.
  6. Put kube-system and kyverno in spec.rules[0].exclude.any[0].resources.namespaces.
  7. Actually create the ValidatingAdmissionPolicy kca-require-app-label in the cluster. spec.failurePolicy is Fail, spec.matchConstraints catches the deployments of the apps group on CREATE and UPDATE, and spec.validations[0].expression must be a CEL expression that checks for the presence of the app.kubernetes.io/name label.
  8. In the cluster, create the namespace kca-app with the label kca=enforced, and actually create the ValidatingAdmissionPolicyBinding kca-require-app-label-binding. Put kca-require-app-label in spec.policyName, Deny in spec.validationActions, and kca: enforced in spec.matchResources.namespaceSelector.matchLabels.

Notes

The ClusterPolicy skeleton

Create the /root/kca-validate/ directory, write apiVersion kyverno.io/v1, kind ClusterPolicy, and metadata.name kca-require-app-label in policy.yaml, and set spec.rules[0].name to check-app-label.

ClusterPolicy is a cluster-scoped resource of the kyverno.io group. rules is an array, and every rule must have a name.

What to select

Put Pod and Deployment in spec.rules[0].match.any[0].resources.kinds, and put kca-app in namespaces in the same place.

match.any is an array, and inside the resources of each element you put kinds, namespaces, names, and selector. Narrowing both the target kinds and the namespaces reduces the number of requests that go through the webhook.

Require a label with pattern matching

Fill in spec.rules[0].validate.message, and use validate.pattern to require that app.kubernetes.io/name in metadata.labels be a non-empty value.

pattern draws the shape of the object to check as it is and then puts an operator in the value position. Recall which operator requires a non-empty value.

Set the enforcement level per rule

Set spec.rules[0].validate.failureAction to Enforce. Do not use the policy-level spec.validationFailureAction.

The policy-level field is deprecated, so you must not leave it in place. The enforcement level has moved down into the validate block, and if you do not specify it, the default is observation mode.

Add a second rule with deny.conditions

Add a second rule deny-latest-tag. Fill in validate.message, put a key, operator, and value that point to the container image in validate.deny.conditions.all[0], and then set this rule's validate.failureAction to Audit.

A condition has three slots: key, operator, and value. Whether to group with all or any makes a difference in meaning when there are several conditions. Put only this rule in observation mode.

Exclude system namespaces

Put kube-system and kyverno in spec.rules[0].exclude.any[0].resources.namespaces.

The structure of exclude is the same as match. If a policy blocks Kyverno itself or control plane components, recovery becomes difficult.

The same rule as a built-in policy

Actually create the ValidatingAdmissionPolicy kca-require-app-label in the cluster. spec.failurePolicy is Fail, spec.matchConstraints catches the deployments of the apps group on CREATE and UPDATE, and spec.validations[0].expression must be a CEL expression that checks for the presence of the app.kubernetes.io/name label.

ValidatingAdmissionPolicy is a built-in Kubernetes resource, not a CRD, so you can just apply it. The expression is CEL, and you access the object being checked with object.

The binding and the target namespace

In the cluster, create the namespace kca-app with the label kca=enforced, and actually create the ValidatingAdmissionPolicyBinding kca-require-app-label-binding. Put kca-require-app-label in spec.policyName, Deny in spec.validationActions, and kca: enforced in spec.matchResources.namespaceSelector.matchLabels.

A built-in policy is a pair of two objects, the policy and the binding. Without a binding the policy merely exists and no request goes through it, and the binding also decides what action to take.