Davide Imola –

Securing the Software Supply Chain with SLSA and SBOMs in GitHub Actions

Nowadays, security is becoming a top priority for software development teams. With the rise of cyberattacks and data breaches, it’s crucial to make your software more secure.

Increasing the security of your software is not only about writing more secure code. It’s also about securing your software supply chain.

What is a Software Supply Chain?

Traditionally, a supply chain is anything that’s needed to deliver your product, including all the components you use. Of course if we talk about software, the supply chain is all the software components you use to build your application.

From the libraries you use to the tools you rely on, everything is part of your software supply chain. And securing it is crucial to prevent attacks and data breaches.

Just to give you an idea, in 2021, the SolarWinds attack showed how a single compromised component can lead to a massive data breach. Or the Log4j vulnerability that affected thousands of applications worldwide. These are just a few examples of how important it is to secure your software supply chain.

SLSA and SBOMs

Two key concepts to secure your software supply chain are SLSA and SBOMs.

SLSA stands for Supply Chain Levels for Software Artifacts. It’s a framework that defines a set of security requirements for software artifacts. It helps you ensure that your software is secure and trustworthy. So you can be confident that your software is not compromised and it’s coming from a trusted source.

In the other hand, SBOM stands for Software Bill of Materials. It’s a list of all the components used to build your software. It helps you understand what’s inside your software and what vulnerabilities it may have. Like a nutrition label for software.

Together, SLSA and SBOMs help you secure your software supply chain by ensuring that your software is secure, trustworthy, and transparent.

To implement SLSA and SBOM in your software supply chain I suggest you to use tools like Sigstore and Syft inside your CI/CD pipelines.

Implementing SLSA and SBOMs in GitHub Actions

For the purpose of this article, I’ll show you how I have implemented SLSA and SBOMs in GitHub Actions using Sigstore and Syft.

I have decided to use GitHub Actions because it’s a popular CI/CD tool that integrates seamlessly with GitHub repositories. It’s also easy to use and configure, and it’s free for public repositories, which makes it a great choice for open-source projects or trial projects.

The steps to implement SLSA and SBOMs in GitHub Actions are as follows:

  • Publishing OCI Images on Registry
  • Signing the image with Sigstore and pushing the signature
  • Generating SBOMs with Syft and pushing them to the registry

Publishing OCI Images on Registry

The first step is to create an Action that publishes your OCI images to a registry. This Action will be responsible for building an OCI image and pushing it to a registry.

To make it easier, we are going to use Docker and the GitHub Registry. Here’s an example of how you can create this Action:

name: Publish

on:
  push:
    branches:
      - main

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Get Vars
        id: get_vars
        run: |
          echo "::set-output name=ts::$(date +%s)"
          echo "::set-output name=sha::$(git rev-parse --short HEAD)"
          echo "::set-output name=branch::$(git rev-parse --abbrev-ref HEAD)"
      - name: Log in to the Container registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Docker meta
        id: docker_meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}/app
          tags: |
            type=sha
            type=raw,${{ steps.get_vars.outputs }}-${{ steps.meta.outputs.sha }}-${{ steps.meta.outputs.ts }}
      - name: Build and push
        uses: docker/build-push-action@v6
        id: build-and-push
        with:
          push: true
          tags: ${{steps.docker_meta.outputs.tags}}
          context: .
          network: host

This Action will build an Docker image with the tag main-<sha>-<timestamp> and push it to the GitHub Registry in ghcr.io/<owner>/<repo>/app.

Signing the image with Sigstore and pushing the signature

After you have published your OCI image to the registry, the next step is to sign it. To do this, you can use Sigstore, which is a project handled by the OpenSSF that provides a way to sign your software artifacts.

Given the previous Action, you can add a few other steps to sign the image:

- name: Install Cosign
  uses: sigstore/cosign-installer@v3.5.0
- name: Sign the images with GitHub OIDC Token
  env:
    DIGEST: ${{ steps.build-and-push.outputs.digest }}
    TAGS: ${{ steps.docker_meta.outputs.tags }}
  run: |
    images=""
    for tag in ${TAGS}; do
      images+="${tag}@${DIGEST} "
    done
    cosign sign --yes ${images}

Cosign is a tool that allows you to sign your images using the Sigstore infrastructure. It uses an asymmetric key pair to sign the image and then pushes the signature to the registry.

In this example, we are signing the image with the GitHub OIDC token, which is a token that GitHub provides to authenticate with the GitHub Registry.

This step will sign the digest of the image and push the signature to the registry, so anyone can verify the image’s integrity.

Generating SBOMs with Syft and pushing them to the registry