Defining Kubernetes native policies with Kyverno – full tutorial
There are multiple ways for controlling the resources that get deployed to your Kubernetes environment including GitOps and automating deployments through CI/CD pipelines so that only a few people require the kubeconfig. However, what if all measures set in place fail?
This is exactly where Kubernetes-native policies can provide additional security.
In this blog post, we will show you how to
- Use Kyverno in your Kubernetes cluster,
- Deploy policies that ensure only container images with a signature get deployed
- And provide an overview of the Kyverno CLI
The Video Tutorial:
What is Kyverno
Kyverno makes it possible to set up Kubernetes-native policies inside your cloud native infrastructure to validate the resources that get deployed. The deployed resources can then be used to validate, mutate or generate Kubernetes resources. At the time of writing this blog post, Kyverno is a CNCF incubating project with 2.8k stars on GitHub. (Give it a star if you like the project ⭐)
The benefit of defining policies through Kubernetes manifests are
- If everything is a Kubernetes resource, you can use the same tools and processes to manage every aspect of your application. For instance, Kyverno can be used through the `krew` Kubernetes-plugin tool in `kubectl` and the Kyverno CLI connects directly to kubectl and your kubeconfig.
- Policies can be enforced from within the cluster, making enforcement more reliable.
Feature List
A full feature list is provided below:
- policies as Kubernetes resources (no new language to learn!)
- validate, mutate, or generate any resource
- verify container images for software supply chain security
- inspect image metadata
- match resources using label selectors and wildcards
- validate and mutate using overlays (like Kustomize!)
- synchronize configurations across Namespaces
- block non-conformant resources using admission controls, or report policy violations
- test policies and validate resources using the Kyverno CLI, in your CI/CD pipeline, before applying to your cluster
- manage policies as code using familiar tools like git and kustomize
Taken from the official documentation. Source
Hands-On tutorial
The resources used in the following section can be found in the following GitHub repository.
Prerequisites
- Helm Installed
- A running Kubernetes cluster & kubectl connected to the cluster
Ensure that your environment is working by running the following commands:
❯ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kyverno-control-plane Ready control-plane,master 46d v1.21.1
Note that I am running a one node KinD Kubernetes cluster.
helm version
version.BuildInfo{Version:"v3.9.1", GitCommit:"a7c043acb5ff905c261cfdc923a35776ba5e66e4", GitTreeState:"clean", GoVersion:"go1.17.5"}
Installing the Kyverno CLI
This tutorial will use the Kyverno CLI in the last section. If you would like to follow along, please make sure to install the Kyverno CLI. The CLI can either be used locally on your machine or through your CI/CD platform.
The Kyverno documentation provides several different installation options.
In our case, we will use Homebrew:
brew install kyverno
Alternatively, the CLI can also be used through the kubectl krew plugin manager:
# Install Kyverno CLI using kubectl krew plugin manager
kubectl krew install kyverno
# test the Kyverno CLI kubectl
kyverno version
Installing the Kyverno Helm Chart in your Kubernetes Cluster
In this tutorial, we are going to use the Kyverno Helm Chart to deploy Kyverno inside our Kubernetes cluster.
# Add the Kyverno Helm repository to your repository list helm
repo add kyverno https://kyverno.github.io/kyverno/
# Scan your Helm repositories to fetch the latest available charts
helm repo update
# Install the Kyverno Helm chart into a new namespace called "kyverno"
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
Note that if you have a cluster with more than two nodes, or you need to have your deployments more reliable, then you probably want to install Kyverno with a replica count of 3. In case one of the pods is down, the others will still be able to respond.
helm install kyverno kyverno/kyverno -n kyverno --create-namespace --set replicaCount=3
NAME: kyverno
LAST DEPLOYED: Tue Aug 30 14:59:15 2022
NAMESPACE: kyverno
STATUS: deployed
REVISION: 1
NOTES:
Chart version: v2.5.3
Kyverno version: v1.7.3
Thank you for installing kyverno! Your release is named kyverno.
⚠️ WARNING: Setting replicas count below 3 means Kyverno is not running in high availability mode.
💡 Note: There is a trade-off when deciding which approach to take regarding Namespace exclusions. Please see the documentation at https://kyverno.io/docs/installation/#security-vs-operability to understand the risks.
You can find further configuration options of the Helm Chart in the values.yaml file.
The following is important: If all your Kyverno replicas are down and cannot be reached to validate your Kubernetes manifests, by default, Kyverno will let the policy checks and thus, the deployment of those manifests fail.
Next, ensure that Kyverno has started its pods and is running in your cluster:
❯ kubectl get deployment -n kyverno
NAME READY UP-TO-DATE AVAILABLE AGE
kyverno 1/1 1 1 37s
Defining Policies
The Kyverno documentation provides a vast list of Kubernetes policies that you can deploy to Kyverno as YAML manifests. These include application-specific policies, such as policies for your ArgoCD deployment, and many more.
If you are new to Kubernetes policies, a policy basically defines which fields should or should not be present in your Kubernetes manifests, what values are or are not valid for the field and how the policy should be enforced. For instance, the following policy will ensure that the container image used in my pods has an attestation:
enforce-attestation.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image
spec:
validationFailureAction: enforce
background: false
webhookTimeoutSeconds: 10
failurePolicy: Fail
rules:
- name: check-image
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "*:*"
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvcLByTJSlRc6dSsBdZct3UrQQUu7
/Q78ncxSQDu821I933Q3lVEXrd14km1CuWM7sHZdFNzVHM5sPMiY6Rj1NA==
-----END PUBLIC KEY-----
The Kyverno verifyImages rule uses Cosign to verify container image signatures and in-toto attestations stored in an OCI registry. If you are new to attestations, Cosign or in-toto, have a look at the previous video on the Aqua Open Source YouTube channel:
Creating an SBOM Attestation with Trivy and Cosign from Sigstore
The policy rule check fails if the signature is not found in the OCI registry, or if the image was not signed using the specified key.
Let’s look at the core parts of the policy in more detail:
- kind: ClusterPolicy – if it is a `Policy` it will be namespace specific but if it is a `ClusterPolicy`, the policy will be applied across the cluster
- validationFailureAction: enforce – `enforce` means that when the policy is not met by the resource, an error will be thrown and the deployment of the resource in the cluster will fail. Alternatively, `audit` can be used to get a report on the resource violation
- resources – specifies the resource on which the policy should be enforced
- The last part first checks that the image has a signature that matches the public key provided.
A lot of times malicious images can be deployed because a malicious container registry `sounds like` or is `written like` the official registry. With this policy, we can ensure that we only use images that have been created by a trusted party of whom we know the public key.
We can then apply the YAML manifest like any other Kubernetes resource:
kubectl apply -f example-policies/enforce-attestation.yaml
clusterpolicy.kyverno.io/check-image configured
Lastly, we want to ensure that our policy works. First, we are going to apply a deployment that is not from our container registry:
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
Error from server: error when creating "https://k8s.io/examples/application/deployment.yaml": admission webhook "mutate.kyverno.svc-fail" denied the request:
resource Deployment/default/nginx-deployment was blocked due to the following policies
check-image:
autogen-check-image: |
failed to verify signature for docker.io/nginx:1.14.2: .attestors[0].entries[0].keys: no matching signatures:
As you can see, the deployment failed.
Only when we deploy a container image from the registry specified that contains a signature that matches our public key, the deployment will work:
kubectl apply -f deployment.yaml
deployment.apps/react-application created
Using the Kyverno CLI
The Kyverno CLI has three main commands, namely:
- Apply
- Test
- JP
- Version
This section will demonstrate at a high level how to use the apply
and the test
command.
1. Apply
- Dry-run resources before applying them to the cluster
- Determine the effectiveness of a policy
- Can show any mutated resources
kyverno apply example-policies/enforce-attestation.yaml --resource ./deployment.yaml
Applying 1 policy to 1 resource...
(Total number of result count may vary as the policy is mutated by Kyverno. To check the mutated policy please try with log level 5)
pass: 1, fail: 0, warn: 0, error: 0, skip: 2
The `apply` command can also be used to retrieve a report of your Kyverno policy checks:
2. Test
The test
command scans a Git repository or local folder and runs the tests within.
The command recursively looks for YAML files with policy test declarations (described below) with a specified file name and then executes those tests.
The following can be defined in the test manifest:
- pass: The resource passes the policy definition. To validate rules which are written with a deny statement, this will not be a possible result. mutate rules can declare a pass.
- skip: The resource does not meet either the match or exclude block, or does not pass the preconditions statements. To validate rules which are written with a deny statement, this is a possible result. If a rule contains certain conditional anchors which are not satisfied, the result may also be scored as a skip.
- fail: The resource does not pass the policy definition. Typically used to validate rules with pattern-style policy definitions.
- warn: Setting the annotation policies.kyverno.io/scored to "false" on a resource or policy which would otherwise fail will be considered a warning.
Taken from the official documentation. Source
To use the Kyverno test command, we will need two things, a policy YAML file and then a test YAML file. The test YAML file will reference the policy and the resource (YAML manifest such as a deployment or pod) that you want to verify.
The following policy YAML file ensures that we are not using the latest tag in our image and that we specify a tag:
disallow-latest-tag.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
annotations:
policies.kyverno.io/category: Best Practices
policies.kyverno.io/description: >-
The ':latest' tag is mutable and can lead to unexpected errors if the
image changes. A best practice is to use an immutable tag that maps to
a specific version of an application pod.
spec:
validationFailureAction: audit
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
clusterRoles:
- cluster-admin
validate:
message: "An image tag is required."
pattern:
spec:
containers:
- image: "*:*"
- name: validate-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using a mutable image tag e.g. 'latest' is not allowed."
pattern:
spec:
containers:
- image: "!*:latest"
The disallow-latest-tag.yaml and our deployment.yaml are referenced within the kyverno-test.yaml manifest:
name: disallow_latest_tag
policies:
- disallow-latest-tag.yaml
resources:
- deployment.yaml
results:
- policy: disallow-latest-tag
rule: require-image-tag
resource: cns-website
kind: Deployment
result: pass
- policy: disallow-latest-tag
rule: validate-image-tag
resource: cns-website
kind: Deployment
result: pass
Next, we can use the kyverno-test.yaml to check our Kubernetes manifest; specifically that we defined an image tag, otherwise the latest tag would be used, and we have not defined the `latest tag` as the image tag.
kyverno test ./
With the kyverno test
command, you can ensure that your manifests meet the requirements and standards of your organisation before trying to deploy them.
What’s next
This blog post covered:
- An overview of Kyverno
- Deploying Kyverno inside your Kubernetes cluster with Helm
- Defining a policy, deploying the policy and testing it
- Using the Kyverno CLI
If you enjoyed this blog post, make sure to subscribe to the following channels for more content: