Artifact attestations

19 Aug 2024

Boosting supply chain security

3 minutes reading time

Palm ReaderUnabridged

#Overview

I have been waiting for something like this for quite some time, and a few months ago, GitHub rolled out its Artifact Attestations (still in beta). Powered by Sigstore, the idea is simple—these attestations ensure the build environment and the source code. Artifacts are linked to their repositories and the build process with cryptographically signed claims—attestations. Consumers then use these attestations to verify binaries (packages, images, SBOM, etc.) with GitHub's CLI application (v2.49.0 or higher). Presently, there is no other tool besides gh that could be used to verify attestations. But this should change soon.

Ian Lewis wrote an informative breakdown in which he expands on the internals of the main utility action (actions/attest-build-provenance).

#Basic deployment

The attest-build-provenance action is the main tool required for basic integration.

permissions:
  id-token: write
  contents: read
  attestations: write
- name: Attest
  uses: actions/attest-build-provenance@v1
  with:
    subject-path: 'PATH/TO/BINARY'

All set! Swell!

#Software Bill Of Materials

Use sbom-action with attest-sbom to process files like cargo.lock or requirements.txt:

- name: Generate SBOM
  uses: anchore/sbom-action@v0
  with:
    path: ./
    artifact-name: sbom.spdx
 
- name: Attest SBOM
  uses: actions/attest-sbom@v1
  with:
    subject-path: 'PATH/TO/BINARY'
    sbom-path: 'sbom.spdx'

#Container images

This setup will push images to the container registry (push-to-registry). Docker's build-push-action could be used to build the image and provide outputs.digest to the attestation step.

permissions:
  id-token: write
  contents: read
  attestations: write
  packages: write
- name: Attest
  uses: actions/attest-build-provenance@v1
  with:
    subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
    subject-digest: ${{ steps.build.outputs.digest }}
    push-to-registry: true

#Verification

Verify attestations prior usage:

- name: Verify attestation
  shell: bash
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  run: |
    gh attestation verify PATH/TO/BINARY \
      --owner "$GITHUB_REPOSITORY_OWNER"

#SLSA Build L3

The attest-build-provenance action by itself in the regular workflow can meet the requirements for SLSA Build Level 2. To harden the build to L3, the attesting workflow must be isolated from the main workflow:

Prevent secret material used to sign the provenance from being accessible to the user-defined build steps.

This could be achieved with reusable workflows that are executed in separate VMs. I will use my xtxf repository as an example.

Move all release steps to a new workflow, set on to a workflow_call trigger, and add required permissions.

cd.yml:

name: CD
on:
  workflow_call:
    inputs:
      tag_name:
        required: true
        type: string

permissions:
  id-token: write
  contents: read
  attestations: read

env:
  ZIG_VERSION: 0.13.0

jobs:
  release:
    name: Create release
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - uses: taiki-e/create-gh-release-action@v1
        with:
          changelog: CHANGELOG.md
          token: ${{ secrets.GITHUB_TOKEN }}

  build-linux:
    name: Build / Linux
    runs-on: ubuntu-latest
    needs: [release]
    permissions:
      id-token: write
      contents: write
      attestations: write

    strategy:
      fail-fast: false
      matrix:
        target:
          - "x86-linux"
          - "x86_64-linux"
          - "aarch64-linux"

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          persist-credentials: false

      - name: Install Zig
        uses: goto-bus-stop/setup-zig@v2
        with:
          version: ${{ env.ZIG_VERSION }}

      - name: Build
        run: |
          zig build -Dtarget=${{ matrix.target }} --release=safe
          mv zig-out/bin/xtxf xtxf
          tar -czvf xtxf-${{ matrix.target}}.tar.gz xtxf

      - name: Attest
        uses: actions/attest-build-provenance@v1
        with:
          subject-path: 'xtxf-${{ matrix.target}}.tar.gz'

      - name: Upload
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

        run: |
          gh release upload ${{ inputs.tag_name }} \
          xtxf-${{ matrix.target }}.tar.gz

      - name: Verify attestation
        shell: bash
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

        run: |
          gh attestation verify xtxf-${{ matrix.target}}.tar.gz \
          --owner "$GITHUB_REPOSITORY_OWNER"

Call CD from a test workflow if all release requirements are met. In this case, version tags, successful test jobs, and repository owner are used to trigger the release workflow.

ci.yml:

name: CI
on: [push, pull_request]

permissions:
  id-token: write
  contents: read
  attestations: read

env:
  ZIG_VERSION: 0.13.0

jobs:
  test:
    name: Test
    strategy:
      matrix:
        os: [ubuntu, macos]
        mode: ["fast", "safe", "small", "off"]

    runs-on: ${{ matrix.os }}-latest
    steps:
      - uses: actions/checkout@v4
      - uses: goto-bus-stop/setup-zig@v2
        with:
          version: ${{ env.ZIG_VERSION }}

      - name: Build
        run: zig build --release=${{ matrix.mode }} \
        --verbose --summary all
      - name: Test
        run: zig build test --release=${{ matrix.mode }} \
        --summary all

  release:
    name: Release
    needs: [test]
    permissions:
      contents: write
      id-token: write
      attestations: write

    if: |
      github.repository_owner == 'charlesrocket' &&
      startsWith(github.ref, 'refs/tags/')

    uses: ./.github/workflows/cd.yml
    with:
      tag_name: ${{ github.ref_name }}

Now the attestation steps are isolated in a reusable workflow, satisfying salsa L3 requirements. This workflow will be triggered only by tags in the original repository.

A live version of the example is available here.

Use the repository's Actions management section or a direct link to access the attestations (API).

Documentation

#Conclusion

With forge-proof paper trails, artifact attestations bring new levels of transparency and security to the modern trust model. On the other hand, all attestation workflows have to be executed exclusively by GitHub-owned runners; this would be a dealbreaker for many operations.