Aqua Blog

Securing GitHub Actions with Trivy and Cosign

Securing GitHub Actions with Trivy and Cosign

One of the advantages of automated CI/CD pipelines is that they’re a great place to implement regular security controls and checks. Using GitHub Actions, it’s easy to improve the security of your containers by automating vulnerability scanning and digital signing of container images on a regular basis. In this post, we’ll go over how to set up and secure a CI/CD pipeline using GitHub Actions, Cosign, and Trivy.

Adding an action to an existing repository

In a GitHub repository, you can add new workflows from the Actions tab.

GitHub repo new workflow from actions tab

GitHub provides a set of pre-packaged actions. We’re going to use one of them, publish docker container, as a starting point. When you click configure, GitHub automatically adds a new action to the repository. For this action, the repository that we’re using as a base must have a Dockerfile present.

GitHub repository Dockerfile present

When I first looked at this action, I got a pleasant surprise, which is that Cosign is present by default! Cosign is part of the Sigstore project. It provides an easy-to-use digital signing process that can be easily integrated into other parts of your environment for example, allowing only signed images to run in a Kubernetes cluster. GitHub added Cosign as a default option to this workflow in December last year, so hopefully, over time we’ll see increasing adoption. For the signing of artifacts to become a widely used feature, it needs adoption, so steps like this are really helpful.

One thing that’s different about using Cosign, as it’s set up in this action, is that no user key management is needed for signing, which has often been a barrier to adoption. Instead, Cosign gets a short-lived key matching the identity of the GitHub repository and stores the signature on a transparency log, so it can be verified later. From the user’s perspective, this makes the process much simpler.

Adding Trivy to our action

Individual GitHub actions can carry out a number of steps, so it makes sense to add our vulnerability scanning at the same time as we’re building our Docker image. Here we can leverage Trivy’s GitHub Action to add vulnerability scanning and use GitHub code scanning to view the results. Code scanning is free for all public repositories, but might not be available on private repos.

To make this work, there are a couple of places where we need to add some instructions. The first is in the permissions block at the start of the build. To upload our results to the code scanning page, we need the security-events: write permission. Once we’ve got that, our permissions block should look like this:

GitHub Trivy Action permissions block

Now to add the actions themselves. The example from Trivy’s GitHub repository assumes we’re using Docker Hub, so we’ll need to change it a bit. The image-ref has a set of environment variables that should match the output of the previous build action.

  - name: Run Trivy vulnerability scanner
       uses: aquasecurity/trivy-action@master
       with:
         image-ref: {% raw %} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} {% endraw %}
         format: 'sarif'
         output: 'trivy-results.sarif'

     - name: Upload Trivy scan results to GitHub Security tab
       uses: github/codeql-action/upload-sarif@v1
       with:
         sarif_file: 'trivy-results.sarif'

With that information added, our action should work automatically, and we’ll get the information about noted vulnerabilities in the GUI:

noted vulnerabilities in the GUI

Downloading and checking our image

Now that we’ve got this CI/CD pipeline working, we can download our image from GitHub Container Registry and verify the signature. For this example, I’m using one of my repositories, alpine-containertools.

docker pull ghcr.io/raesene/alpine-containertools:main

Once the image is pulled, we can verify the signature. At the moment, the keyless signing approach that this GitHub action uses is experimental, so we need to pass a flag to Cosign to verify the image.

COSIGN_EXPERIMENTAL=1 cosign verify ghcr.io/raesene/alpine-containertools:main

This will verify and report on which key signed the image and confirm a good signature.

Conclusion

Using a combination of GitHub Actions, Cosign, and Trivy, it’s relatively easy to start improving your software supply chain security. While signing and scanning images aren’t the whole story, they provide a good starting point, giving visibility into potential issues and allowing users of your container images to verify that the images haven’t been modified by unauthorized parties after signing.

Rory McCune
Rory was a Cloud Native Security Advocate at Aqua. He has worked in the Information and IT Security arena for the last 20 years in a variety of roles. He is an active member of the container security community having delivered presentations at a variety of IT and Information security conferences. He has also presented at major containerization conferences and is an author of the CIS Benchmarks for Docker and Kubernetes and main author of the Mastering Container Security training course which has been delivered at numerous industry conferences including Blackhat USA.