Are you looking to write PSPs for your Kubernetes cluster in the easiest way possible without learning a new language? Say Ki to Kyverno.
What is Kyverno?
Kyverno, in simple terms, is a policy engine for Kubernetes. It allows us to create Policies for our Kubernetes cluster on different levels. It enables us to validate, change, and create resources based on the policies we define.
With the recent deprecation of Pod Security Policies (PSPs), Policy Management is even more important for your cluster from a security point of view. Kyverno makes it very easy for us as it allows us to choose from more than 75 pre-written policies or create our own by writing custom YAML files.
How does Kyverno work?
Kyverno works like an admission controller for your Kubernetes cluster. Before passing a request to the etcd, the API request initially is sent to Kyverno where it mutates and validates the policies configured by us. Then the request is passed on to the etcd if it is valid and in accordance with the rules.
- Kyverno receives an AdmissionReview request whenever a Kubernetes API is called.
- Now Kyverno mutates the policies if requested in the API request.
- Once Kyverno mutates the Policies, it waits for the CRD schema to be validated and gets the result to validate the policies.
- Based on the policy, Kyverno generates resources, logs events, and creates Policy Reports.
- If the AdmissionReview Request is valid and in accordance with all the policies, the AdmissionReview Response is sent further to the etcd.
- If the AdmissionReview Request is invalid with the mode set as Enforce, Kyverno blocks the resource creation, and it does not send a response to the etcd.
- If the AdmissionReview Request is invalid with the mode set as Audit, Kyerno logs the event, and it sends a corresponding response to the etcd.
Now that we know what Kyverno is and how it works on a very high level, let’s get into policies now.
What is a Kyverno Policy?
A Kyverno policy is a collection of rules. Whenever we receive an API request to our Kubernetes cluster, we validate it with a set of rules.
A Policy consists of different clauses such as:
- Match: It selects resources that match the given criteria.
- Exclude: It selects all but excludes resources that match the specified criteria.
Match and Exclude are used to select resources, users, user groups, service accounts, namespaced roles, and cluster-wide roles.
- Validate: It validates the specified property of the resource, and only if the property is in accordance with the rule, it allows for resource creation.
- Mutate: It modifies matching resources.
- Generate: It creates additional resources.
The above three are used with the properties of the resources matched above, with our match and exclude clauses.
Types of Policies:
We talked about these three policies in brief above. Let’s clear our concept by understanding the fundamental difference between them. We can write three types of policies:
- Validation Policy: Here, we write rules to check the mandatory properties of the resource. If the properties and the rules match, it passes on the API request.
- Mutation Policy: Here, we patch incoming requests to modify the resources. Kyverno executes these before the validation policies.
- Generation Policy: Here, we create additional objects and resources. For example, when creating a namespace, add roles to the namespace or add a default-deny network policy or add a non-root user policy
Understanding a Policy
Let’s take an example policy and break it down to its atomic state and understand it line by line:
Policy: Require Run As Non-Root
The below policy validates that all the pods are running as non-root users i.e. users without any sudo/root permissions.
policies.kyverno.io/category: Pod Security Standards (Restricted)
Containers must be required to run as non-root users. This policy ensures
`runAsNonRoot` is set to `true`.
- name: check-containers
Running as root is not allowed. The fields spec.securityContext.runAsNonRoot,
spec.initContainers[*].securityContext.runAsNonRoot must be `true`.
The above file was jargon for you? Don’t worry we are going to break it line by line and understand it completely below.
- The Kverno API version referenced here is v1.
- We define the kind of entity the following definitions apply to, in this case its the cluster.
- We provide concise information in the metadata. This makes it is easier to debug and react to when we violate a policy. We provide a relevant name, some annotations defining the policy’s category, severity, etc.
- The spec is where you actually define your policy, that is, mention all the relevant properties (here on the cluster level).
- Kyverno has this really cool feature that enables us to validate the older resources created before a policy gets into action. We implement this by setting the background to true.
- The validationFailureAction defines what action to take when the validation fails. If the validation fails,
- in Audit mode: it just audits it in the logs and allows the resource creation
- in Enforce mode: it blocks the resource creation
- Rules basically contain the kind of resources the policy is defined for, the message to display when a validation fails, and the actual validation.
- We name the rule accordingly to maintain easy readability.
- We now match the resources of kinds: Pod. So, in simple terms, we are selecting all the pods available in the cluster. As discussed earlier, one can use match and exclude to get resources based on the conditions provided.
- The validate contains the core logic of the properties your selected resource should have. For example, here it validates if the user does not have any sudo permissions.
- The message depicts what is to be displayed on your CLI if this validation fails.
- In different scenarios, the content (here: securityContext) is defined at different levels, for example, the Pod level or the Container level.
- anyPattern allows you to handle such situations where you can provide various patterns wherein even 1 of them satisfying the pattern is enough. In simple words, it works like an “or” condition where any one of the provided patterns should match with the given resource.
- Here, as we have two scenarios for our spec, and we need only one of them to make our request true for the given policy (as it is nested under the anyPattern).
- One where we define the securityContext at the pod level:
- We validate that the pod’s SecurityContext has the runAsNonRoot property set to true. runAsNonRoot basically means that when the resource is mutated or validated, it should not have any sudo or administrative privileges.
- We now validate that all the containers in this pod that have the context securityContext and a property runAsNonRoot should have its value as true.
- Next we validate that if any initContainers have the context securityContext and have a property runAsNonRoot should have its value set to true.
2. One where we define the securityContext at the container level:
- We validate that the container’s SecurityContext has the runAsNonRoot property set to true. The runAsNonRoot has been explained above.
- We now validates that all the present initContainers should have the context securityContext and subsequently have a property runAsNonRoot with its value set to true.
This marks the end of your first in-detail analysis of a Kyverno Policy. I hope it’s the first of many more such policies you write and apply for your clusters for securely managing your Kubernetes clusters.
For more information about Kyverno, check out their GitHub repositories here. Adios amigos.