KCA — Kyverno Certified Associate
Comparing validate Rules Against the Built-in Policies
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
- Create the
/root/kca-validate/directory, write apiVersionkyverno.io/v1, kindClusterPolicy, andmetadata.namekca-require-app-labelinpolicy.yaml, and setspec.rules[0].nametocheck-app-label. - Put
PodandDeploymentinspec.rules[0].match.any[0].resources.kinds, and putkca-appinnamespacesin the same place. - Fill in
spec.rules[0].validate.message, and usevalidate.patternto require thatapp.kubernetes.io/nameinmetadata.labelsbe a non-empty value. - Set
spec.rules[0].validate.failureActiontoEnforce. Do not use the policy-levelspec.validationFailureAction. - Add a second rule
deny-latest-tag. Fill invalidate.message, put a key, operator, and value that point to the container image invalidate.deny.conditions.all[0], and then set this rule'svalidate.failureActiontoAudit. - Put
kube-systemandkyvernoinspec.rules[0].exclude.any[0].resources.namespaces. - Actually create the ValidatingAdmissionPolicy
kca-require-app-labelin the cluster.spec.failurePolicyisFail,spec.matchConstraintscatches thedeploymentsof theappsgroup on CREATE and UPDATE, andspec.validations[0].expressionmust be a CEL expression that checks for the presence of theapp.kubernetes.io/namelabel. - In the cluster, create the namespace
kca-appwith the labelkca=enforced, and actually create the ValidatingAdmissionPolicyBindingkca-require-app-label-binding. Putkca-require-app-labelinspec.policyName,Denyinspec.validationActions, andkca: enforcedinspec.matchResources.namespaceSelector.matchLabels.
Notes
- For CEL examples, a form that combines
has(object.metadata.labels)with indexing is common.kubectl explain validatingadmissionpolicy.spec.validationshelps. - To check the file, pull out just a part, as in
yq '.spec.rules[1].validate' /root/kca-validate/policy.yaml. - Common mistake 1: adding a rule-level field while leaving
spec.validationFailureActionin place. In this lab you must remove the policy-level field to pass. - Common mistake 2: applying only the ValidatingAdmissionPolicy without a binding. Even though the policy exists, no request goes through it.
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.