Image Signing and Attestation with Trivy, Kyverno, and Cosign

Image Signing and Attestation with Trivy, Kyverno, and Cosign

security

In this tutorial, we are combining three amazing security tools: Trivy, Cosign and Kyverno to sign, attest container images and enforce those through Policies in our Kubernetes cluster.


This blog post is just a quick guide on how to scan and verify container images with Trivy, Kyverno and Cosign

  • Trivy: an all in one, cloud native security scanner 
  • Cosign: Cosign is a command line utility that can sign and verify software artifact, such as container images. 
  • Kyverno: Kubernetes Native Policy Management 

Prerequisites 

This tutorial will use the following GitHub repository: https://github.com/Cloud-Native-Security/website

Next, ensure that 

Step one: build and sign a container image 

First, we will build the container image for the website repository. Note that you can use one of your applications.  

git clone https://github.com/Cloud-Native-Security/website 
cd website 
docker build -t anaisurlichs/signed-example:0.1 . 

 Note: Replace the DockerHub “anaisurlichs” handle with your own DockerHub Handle. 

Push the container image to your DockerHub Account 

We need to push the container image to DockerHub:

docker push anaisurlichs/signed-example:0.1 

Sign the container image with Cosign 

 First, we need to generate a new cosign keypair: 

cosign generate-key-pair 

 The private key generated will be used to sign our artefact – never ever share it with anyone, otherwise they can pretend you signed artefacts that you never touched – and the public key can be shared with people to verify that you are the one who signed specific artefacts.

Once the container image is in your DockerHub account, you can go ahead and use Cosign to sign it. In this case, we are going to use the SHA of the container image tag that you can find on the DockerHub site of the image tag. Alternatively, run the following Docker command to get the SHA for your container image: 

docker image ls --digests 
cosign sign --key cosign.key docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd

Scan the container Image with Trivy for vulnerabilities & generate an attestation 

trivy image --ignore-unfixed  --format cosign-vuln --output vuln.json docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd 

 Create an attestation:  

cosign attest --key cosign.key --type vuln --predicate vuln.json docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd 

The attestation basically says that you are the one who created a vulnerability scan for this container image. 

Verify the Attestation: 

cosign verify-attestation --key cosign.pub --type vuln anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd 

Create a Kubernetes cluster and install Kyverno Helm CHart 

kind create cluster --name kyverno 

 Note that you can use any other cluster as well. I am just using KinD here.

Next, install the Kyverno Helm Chart in your cluster as detailed in the following tutorial: 

https://anaisurl.com/defining-kubernetes-native-policies-with-kyverno-full-tutorial/  

And on their documentation: https://kyverno.io/docs/installation/methods/  

# 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 

 Install a policy 

apiVersion: kyverno.io/v1 
kind: ClusterPolicy 
metadata: 
  name: check-vulnerabilities 
spec: 
  validationFailureAction: Enforce 
  background: false 
  webhookTimeoutSeconds: 30 
  failurePolicy: Fail 
  rules: 
    - name: checking-vulnerability-scan-not-older-than-one-hour 
      match: 
        any: 
        - resources: 
            kinds: 
              - Pod 
      verifyImages: 
      - imageReferences: 
        - "*" 
        attestations: 
        - type: https://cosign.sigstore.dev/attestation/vuln/v1 
          conditions: 
          - all: 
            - key: "{{ time_since('','{{ metadata.scanFinishedOn }}', '') }}" 
              operator: LessThanOrEquals 
              value: "1h" 
          attestors: 
          - count: 1 
            entries: 
            - keys: 
                publicKeys: |- 
                  -----BEGIN PUBLIC KEY----- 
                  abc 
                  xyz 
                  -----END PUBLIC KEY----- 

Replace the lines below the `publicKeys` part with your public key i.e. what is in the cosign.pub file. 

Next, apply the policy: 

kubectl apply –f policy.yaml 

 

Verify only the signed image can be deployed to your cluster 

Next, try to install an unsigned container image to your cluster: 

kubectl run app-unsigned --image=docker.io/anaisurlichs/cns-website:0.1.1 

As you can see, this does not work. 

Lastly, try to install the container image that you have signed and that contains the attestation: 

kubectl run app-signed --image= docker.io/anaisurlichs/signed-example@sha256:c5911ac313e0be82a740bd726dc290e655800a9588424ba4e0558c705d1287fd 

Lastly, clean up your cluster: 

kubectl delete pod app-signed 
kubectl delete –f policy.yaml 

 What’s next 

Huge shout out to Unni for helping me in the Trivy Slack community to make this tutorial work!! 

You can find a list of various Kyverno policies in their documentation, go try them out! 

Also, have a look at the other Trivy features. 

If you enjoyed this tutorial, make sure to give it a thumbs up on my YouTube channel!