Skip to content

Latest commit

 

History

History
1030 lines (830 loc) · 36.3 KB

File metadata and controls

1030 lines (830 loc) · 36.3 KB

Generation of SLSA3+ provenance for arbitrary projects

This document explains how to generate SLSA provenance for projects for which there is no language or ecosystem specific builder available.

This can be done by adding an additional step to your existing Github Actions workflow to call a reusable workflow to generate generic SLSA provenance. We'll call this workflow the "generic workflow" from now on.

The generic workflow differs from ecosystem specific builders (like the Go builder) which build the artifacts as well as generate provenance. This project simply generates provenance as a separate step in an existing workflow.



Benefits of Provenance

Using the generic workflow will generate a non-forgeable attestation to the artifacts' digests using the identity of the GitHub workflow. This can be used to create a positive attestation to a software artifact coming from your repository.

That means that once your users verify the artifacts they have downloaded they can be sure that the artifacts were created by your repository's workflow and haven't been tampered with.

Generating Provenance

The generic workflow uses a GitHub Actions reusable workflow to generate the provenance.

Getting Started

To get started, you will need to add some steps to your current workflow. We will assume you have an existing GitHub Actions workflow to build your project.

Add a step to your workflow after you have built your project to generate a sha256 hash of your artifacts and base64 encode it.

Assuming you have a binary called binary-linux-amd64 you can use the sha256sum and base64 commands to create the digest. Here we use the -w0 to output the encoded data on one line and make it easier to use as a GitHub Actions output:

$ sha256sum artifact1 artifact2 ... | base64 -w0

This workflow expects the base64-subjects input to decode to a string conforming to the expected output of the sha256sum command. Specifically, the decoded output is expected to be comprised of a hash value followed by a space followed by the artifact name.

After you have encoded your digest, add a new job to call the reusable workflow.

provenance:
  permissions:
    actions: read # Needed for detection of GitHub Actions environment.
    id-token: write # Needed for provenance signing and ID
    contents: write # Needed for release uploads
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.build.outputs.hashes }}"

Note: Make sure that you reference the generator with a semantic version of the form @vX.Y.Z. More information here.

Here's an example of what it might look like all together.

jobs:
  # This step builds our artifacts, uploads them to the workflow run, and
  # outputs their digest.
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
    runs-on: ubuntu-latest
    steps:
      - name: Build artifacts
        run: |
          # These are some amazing artifacts.
          echo "foo" > artifact1
          echo "bar" > artifact2

      - name: Generate hashes
        shell: bash
        id: hash
        run: |
          # sha256sum generates sha256 hash for all artifacts.
          # base64 -w0 encodes to base64 and outputs on a single line.
          # sha256sum artifact1 artifact2 ... | base64 -w0
          echo "hashes=$(sha256sum artifact1 artifact2 | base64 -w0)" >> "$GITHUB_OUTPUT"

      - name: Upload artifact1
        uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3.1.0
        with:
          name: artifact1
          path: artifact1
          if-no-files-found: error
          retention-days: 5

      - name: Upload artifact2
        uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3.1.0
        with:
          name: artifact2
          path: artifact2
          if-no-files-found: error
          retention-days: 5

  # This step calls the generic workflow to generate provenance.
  provenance:
    needs: [build]
    permissions:
      actions: read
      id-token: write
      contents: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      # Upload provenance to a new release
      upload-assets: true

  # This step uploads our artifacts to the tagged GitHub release.
  release:
    needs: [build, provenance]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - name: Download artifact1
        uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # tag=v2.1.0
        with:
          name: artifact1

      - name: Download artifact2
        uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # tag=v2.1.0
        with:
          name: artifact2

      - name: Upload assets
        uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v0.1.14
        with:
          files: |
            artifact1
            artifact2

Referencing the SLSA generator

At present, the generator MUST be referenced by a tag of the form @vX.Y.Z, because the build will fail if you reference it via a shorter tag like @vX.Y or @vX or if you reference it by a hash.

For more information about this design decision and how to configure renovatebot,see the main repository README.md.

Private Repositories

Private repositories are supported with some caveats. Currently all builds generate and post a new entry in the public Rekor API server instance at rekor.sigstore.dev. This entry includes the repository name. This will cause the private repository name to leak and be discoverable via the public Rekor API server.

If this is ok with you, you can set the private-repository flag in order to opt in to publishing to the public Rekor instance from a private repository.

with:
  private-repository: true

If you do not set this flag then private repositories will generate an error in order to prevent leaking repository name information.

Support for private transparency log instances that would not leak repository name information is tracked on issue #372.

Supported Triggers

The following GitHub trigger events are fully supported and tested:

  • schedule
  • push (including new tags)
  • release
  • Manual run via workflow_dispatch

However, in practice, most triggers should work with the exception of pull_request. If you would like support for pull_request, please tell us about your use case on issue #358. If you have an issue with any other triggers please submit a new issue.

Workflow Inputs

The generic workflow accepts the following inputs:

Name Required Default Description
base64-subjects yes Artifact(s) for which to generate provenance, formatted the same as the output of sha256sum (SHA256 NAME\n[...]) and base64 encoded. The encoded value should decode to, for example: 90f3f7d6c862883ab9d856563a81ea6466eb1123b55bff11198b4ed0030cac86 foo.zip
upload-assets no false If true provenance is uploaded to a GitHub release for new tags.
provenance-name no "(subject name).intoto.jsonl" if a single subject. "multiple.intoto.json" if multiple subjects. The artifact name of the signed provenance. The file must have the intoto.jsonl extension.
attestation-name no "(subject name).intoto.jsonl" if a single subject. "multiple.intoto.json" if multiple subjects. The artifact name of the signed provenance. The file must have the intoto.jsonl extension. DEPRECATED: use provenance-name instead.
private-repository no false Set to true to opt-in to posting to the public transparency log. Will generate an error if false for private repositories. This input has no effect for public repositories. See Private Repositories.

Workflow Outputs

The generic workflow produces the following outputs:

Name Description
provenance-name The artifact name of the signed provenance.
attestation-name The artifact name of the signed provenance. DEPRECATED: use provenance-name instead.

Provenance Format

The project generates SLSA provenance with the following values.

Name Value Description
buildType "https://github.com/slsa-framework/slsa-github-generator/generic@v1" Identifies a generic GitHub Actions build.
metadata.buildInvocationID "[run_id]-[run_attempt]" The GitHub Actions run_id does not update when a workflow is re-run. Run attempt is added to make the build invocation ID unique.

Note: The generated provenance will probably be wrapped in a DSSE envelope and encoded in base64. Check the human-readable result running cat encoded-artifact.intoto.jsonl | jq -r '.payload' | base64 -d | jq.

Provenance Example

The following is an example of the generated provenance. Provenance is generated as an in-toto statement with a SLSA predicate.

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://slsa.dev/provenance/v0.2",
  "subject": [
    {
      "name": "ghcr.io/ianlewis/actions-test",
      "digest": {
        "sha256": "8ae83e5b11e4cc8257f5f4d1023081ba1c72e8e60e8ed6cacd0d53a4ca2d142b"
      }
    },
  ],
  "predicate": {
    "builder": {
      "id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.2.2"
    },
    "buildType": "https://github.com/slsa-framework/slsa-github-generator/generic@v1",
    "invocation": {
      "configSource": {
        "uri": "git+https://github.com/ianlewis/actions-test@refs/heads/main.git",
        "digest": {
          "sha1": "e491e4b2ce5bc76fb103729b61b04d3c46d8a192"
        },
        "entryPoint": ".github/workflows/generic-container.yml"
      },
      "parameters": {},
      "environment": {
        "github_actor": "ianlewis",
        "github_actor_id": "49289",
        "github_base_ref": "",
        "github_event_name": "push",
        "github_event_payload": {...},
        "github_head_ref": "",
        "github_ref": "refs/tags/v0.0.9",
        "github_ref_type": "tag",
        "github_repository_id": "474793590",
        "github_repository_owner": "ianlewis",
        "github_repository_owner_id": "49289",
        "github_run_attempt": "1",
        "github_run_id": "2556669934",
        "github_run_number": "12",
        "github_sha1": "e491e4b2ce5bc76fb103729b61b04d3c46d8a192"
      }
    },
    "metadata": {
      "buildInvocationID": "2556669934-1",
      "completeness": {
        "parameters": true,
        "environment": false,
        "materials": false
      },
      "reproducible": false
    },
    "materials": [
      {
        "uri": "git+https://github.com/ianlewis/actions-test@refs/tags/v0.0.9",
        "digest": {
          "sha1": "e491e4b2ce5bc76fb103729b61b04d3c46d8a192"
        }
      }
    ]
  }
}

Integration With Other Build Systems

This section explains how to generate non-forgeable SLSA provenance with existing build systems.

Provenance for GoReleaser

If you use GoReleaser to generate your build, you can easily generate SLSA3 provenance by updating your existing workflow with the steps indicated in the workflow below:

  1. Declare an outputs for the GoReleaser job:
jobs:
  goreleaser:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
  1. Add an id: run-goreleaser field to your goreleaser step:
    steps:
      [...]
      - name: Run GoReleaser
        id: run-goreleaser
        uses: goreleaser/goreleaser-action@b508e2e3ef3b19d4e4146d4f8fb3ba9db644a757 # tag=v3.2.0
  1. Add a step to generate the provenance subjects as shown below:
- name: Generate subject
  id: hash
  env:
    ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
  run: |
    set -euo pipefail
    hashes=$(echo $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join("  ") | sub("^sha256:";"")' | base64 -w0)
    echo "hashes=$hashes" >> $GITHUB_OUTPUT
  1. Call the generic workflow to generate provenance by declaring the job below:
provenance:
  needs: [goreleaser]
  permissions:
    actions: read # To read the workflow path.
    id-token: write # To sign the provenance.
    contents: write # To add assets to a release.
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
    upload-assets: true # upload to a new release

All in all, it will look as the following:

jobs:
  goreleaser:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3

      - name: Run GoReleaser
        id: run-goreleaser
        uses: goreleaser/goreleaser-action@b953231f81b8dfd023c58e0854a721e35037f28b # tag=v3

      - name: Generate subject
        id: hash
        env:
          ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
        run: |
          set -euo pipefail

          checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path')
          echo "hashes=$(cat $checksum_file | base64 -w0)" >> "$GITHUB_OUTPUT"

  provenance:
    needs: [goreleaser]
    permissions:
      actions: read # To read the workflow path.
      id-token: write # To sign the provenance.
      contents: write # To add assets to a release.
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
      upload-assets: true # upload to a new release

Provenance for Bazel

If you use Bazel to generate your artifacts, you can easily generate SLSA3 provenance by updating your existing workflow with steps indicated in the workflow below:

  1. Declare an outputs for the hashes:
jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
  1. Build your project and copy the binaries from bazel-bin path (i.e., Bazel sandbox) to the root of the repository for easier reference (this makes it easier to upload these to the release too!):
    steps:
      [...]
      - name: Build using bazel
        run: |
          # Your normal build workflow targets here
          bazel build //path/to/target_binary //path/to_another/binary

          # Copy the binaries.
          cp bazel-bin/path/to/target_binary .
          cp bazel-bin/path/to/another/binary .
  1. Add a step to generate the provenance subjects as shown below. Update the sha256 sum arguments to include all binaries that you generate provenance for:
- name: Generate subject
  id: hash
  run: |
    set -euo pipefail

    sha256sum target_binary binary > checksums

    echo "hashes=$(cat checksums | base64 -w0)" >> "$GITHUB_OUTPUT"
  1. Call the generic workflow to generate provenance by declaring the job below:
provenance:
  needs: [build]
  permissions:
    actions: read # To read the workflow path.
    id-token: write # To sign the provenance.
    contents: write # To add assets to a release.
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.build.outputs.hashes }}"
    upload-assets: true # Optional: Upload to a new release

All in all, it will look as the following:

jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3

      - name: Build using bazel
        run: |
          # Your normal build workflow targets here
          bazel build //path/to/target_binary //path/to_another/binary

          # Copy the binaries.
          cp bazel-bin/path/to/target_binary .
          cp bazel-bin/path/to/another/binary .

      - name: Generate subject
        id: hash
        run: |
          set -euo pipefail

          sha256sum target_binary binary > checksums

          echo "hashes=$(cat checksums | base64 -w0)" >> "$GITHUB_OUTPUT"

  provenance:
    needs: [build]
    permissions:
      actions: read # To read the workflow path.
      id-token: write # To sign the provenance.
      contents: write # To add assets to a release.
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true # Optional: Upload to a new release

Provenance for Java

If you develop with Java and use Maven or Gradle, you can easily generate SLSA3 provenance by updating your existing workflow with the steps indicated in the workflow below:

Maven

  1. Declare an outputs for the artifacts generated by the build and their hashes:
jobs:
  build:
    outputs:
      artifacts: ${{ steps.build.outputs.artifacts }}
      hashes: ${{ steps.hash.outputs.hashes }}
  1. Add an id: build field to your maven build step; and save the location of the maven output files for easier reference:
    steps:
      [...]
      - name: Build using maven
        id: build
        run: |
          # Your normal build workflow targets here
          mvn clean package

          # Save the location of the maven output files for easier reference
          ARTIFACT_PATTERN=./target/$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)*.jar
          echo "artifact_pattern=$ARTIFACT_PATTERN" >> "$GITHUB_OUTPUT"
  1. Add a step to generate the provenance subjects as shown below. Update the sha256 sum arguments to include all binaries that you generate provenance for:
- name: Generate subject
  id: hash
  run: |
    echo "hashes=$(sha256sum ${{ steps.build.outputs.artifact_pattern }} | base64 -w0)" >> "$GITHUB_OUTPUT"
  1. Call the generic workflow to generate provenance by declaring the job below:
provenance:
  needs: [build]
  permissions:
    actions: read # To read the workflow path.
    id-token: write # To sign the provenance.
    contents: write # To add assets to a release.
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.build.outputs.hashes }}"
    upload-assets: true # Optional: Upload to a new release

All in all, it will look as the following:

jobs:
  build:
    outputs:
      artifacts: ${{ steps.build.outputs.artifacts }}
      hashes: ${{ steps.hash.outputs.hashes }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3

      - name: Build using maven
        id: build
        run: |
          # Your normal build workflow targets here
          mvn clean package

          # Save the location of the maven output files for easier reference
          ARTIFACT_PATTERN=./target/$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)*.jar
          echo "artifact_pattern=$ARTIFACT_PATTERN" >> "$GITHUB_OUTPUT"

      - name: Generate subject
        id: hash
        run: |
          echo "hashes=$(sha256sum ${{ steps.build.outputs.artifact_pattern }} | base64 -w0)" >> "$GITHUB_OUTPUT"

      - name: Upload build artifacts
        uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
        with:
          name: maven-build-outputs
          path: ${{ steps.build.outputs.artifact_pattern }}
          if-no-files-found: error

  provenance:
    needs: [build]
    permissions:
      actions: read # To read the workflow path.
      id-token: write # To sign the provenance.
      contents: write # To add assets to a release.
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true # Optional: Upload to a new release

Gradle

  1. Declare an outputs for the artifacts generated by the build and their hashes:
jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
  1. Add an id: build field to your gradle build step:
    steps:
      [...]
      - name: Build using gradle
        id: build
        run: |
          # Your normal build workflow targets here
          ./gradlew clean build
  1. Add a step to generate the provenance subjects as shown below. Update the sha256 sum arguments to include all binaries that you generate provenance for. (This build assumes build artifacts are saved in ./build/libs).
- name: Generate subject
  id: hash
  run: |
    echo "hashes=$(sha256sum ./build/libs/* | base64 -w0)" >> "$GITHUB_OUTPUT"
  1. Call the generic workflow to generate provenance by declaring the job below:
provenance:
  needs: [build]
  permissions:
    actions: read
    id-token: write
    contents: read
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.build.outputs.hashes }}"
    upload-assets: true # Optional: Upload to a new release

All in all, it will look as the following:

jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}

    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3

      - name: Build using gradle
        id: build
        run: |
          # Your normal build workflow targets here
          ./gradlew clean build

      - name: Generate subject
        id: hash
        run: |
          echo "hashes=$(sha256sum ./build/libs/* | base64 -w0)" >> "$GITHUB_OUTPUT"

      - name: Upload build artifacts
        uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
        with:
          name: gradle-build-outputs
          path: ./build/libs/
          if-no-files-found: error

  provenance:
    needs: [build]
    permissions:
      actions: read
      id-token: write
      contents: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true # Optional: Upload to a new release

Provenance for Rust

If you use Cargo to generate your artifacts, you can easily generate SLSA3 provenance by updating your existing workflow with the steps indicated in the workflow below:

  1. Declare an outputs for the hashes:
jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
  1. Build your binaries. Then add a step to generate the provenance subjects as shown below. Update the sha256 sum arguments to include all binaries that you generate provenance for:
    steps:
      [...]
      - name: Build using cargo
        run: |
          # Your normal build workflow targets here.
          cargo build --release

          cp target/release/target_binary .

      # Generate the subject.
      - name: Generate subject
        id: hash
        run: |
          set -euo pipefail

          echo "hashes=$(sha256sum target_binary | base64 -w0)" >> "$GITHUB_OUTPUT"
  1. Call the generic workflow to generate provenance by declaring the job below:
provenance:
  needs: [build]
  permissions:
    actions: read # To read the workflow path.
    id-token: write # To sign the provenance.
    contents: write # To add assets to a release.
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.build.outputs.hashes }}"
    upload-assets: true # Optional: Upload to a new release

All in all, it will look as the following:

jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3

      - name: Build using cargo
        run: |
          # Your normal build workflow targets here.
          cargo build --release

          cp target/release/target_binary .

      # Generate the subject.
      - name: Generate subject
        id: hash
        run: |
          set -euo pipefail

          echo "hashes=$(sha256sum target_binary | base64 -w0)" >> "$GITHUB_OUTPUT"

  provenance:
    needs: [build]
    permissions:
      actions: read # To read the workflow path.
      id-token: write # To sign the provenance.
      contents: write # To add assets to a release.
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true # Optional: Upload to a new release

Provenance for Haskell

If you use Haskell (either via cabal or stack) to generate your artifacts, you can easily generate SLSA3 provenance by updating your existing workflow with the steps indicated in the workflow below.

  1. Declare an outputs for the hashes:
jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
  1. Build your binaries. Then add a step to generate the provenance subjects as shown below. Update the sha256 sum arguments to include all binaries that you generate provenance for:
    steps:
      [...]
      - name: Build using Haskell
        run: |
          # Your normal build workflow targets here.
          cabal build  # or stack build

          # Copy the binary to the root directory for easier reference
          # For Cabal, use the following command
          cp $(cabal list-bin .) .
          # For Stack, use the following command instead
          # cp $(stack path --local-install-root)/bin/target_binary .

      # Generate the subject.
      - name: Generate subject
        id: hash
        run: |
          set -euo pipefail

          echo "hashes=$(sha256sum target_binary | base64 -w0)" >> "$GITHUB_OUTPUT"
  1. Call the generic workflow to generate provenance by declaring the job below:
provenance:
  needs: [build]
  permissions:
    actions: read # To read the workflow path.
    id-token: write # To sign the provenance.
    contents: write # To add assets to a release.
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.build.outputs.hashes }}"
    upload-assets: true # Optional: Upload to a new release

All in all, it will look as the following:

jobs:
  build:
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
      - name: Setup Haskell
        uses: haskell/actions/setup@745062a754c3c4b70b87cb93937ad443096cc94d # tag=v1

      - name: Build using Haskell
        run: |
          # Your normal build workflow targets here.
          cabal build  # or stack build

          # Copy the binary to the root directory for easier reference
          # For Cabal, use the following command
          cp $(cabal list-bin .) .
          # For Stack, use the following command instead
          # cp $(stack path --local-install-root)/bin/target_binary .

      # Generate the subject.
      - name: Generate subject
        id: hash
        run: |
          set -euo pipefail

          echo "hashes=$(sha256sum target_binary | base64 -w0)" >> "$GITHUB_OUTPUT"

  provenance:
    needs: [build]
    permissions:
      actions: read # To read the workflow path.
      id-token: write # To sign the provenance.
      contents: write # To add assets to a release.
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true # Optional: Upload to a new release

Provenance for Python

If you develop with Python you can easily generate SLSA3 provenance by updating your existing workflow with the steps indicated in the workflow below:

  1. Declare an outputs for the artifacts generated by the build and their hashes:
jobs:
  build:
    name: "Build dists"    
    runs-on: "ubuntu-latest"    
    environment:      
      name: "publish"
      outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
  1. Add an id: build field to your python build step
steps:
  - name: "Checkout repository"        
    uses: "actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b" # tag=v3

  - name: "Setup Python"        
    uses: "actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984" # tag=v4
    with:  
      python-version: "3.x"

  - name: "Install dependencies"        
    run: python -m pip install build

  - name: Build using python
    id: build
    run: python -m build
  1. Add a step to generate the provenance subjects as shown below. Update the sha256 sum arguments to include all binaries that you generate provenance for:
- name: Generate subject
  id: hash
  run: |
    cd dist && echo "::set-output name=hashes::$(sha256sum * | base64 -w0)"
  1. Call the generic workflow to generate provenance by declaring the job below:
provenance:
  needs: [build]
  permissions:
    actions: read # To read the workflow path.
    id-token: write # To sign the provenance.
    contents: write # To add assets to a release.
  uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
  with:
    base64-subjects: "${{ needs.build.outputs.hashes }}"
    upload-assets: true # Optional: Upload to a new release

All in all, it will look as the following:

jobs:
  build:
    name: "Build dists"    
    runs-on: "ubuntu-latest"    
    environment:      
      name: "publish"
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
  steps:
    - name: "Checkout repository"        
      uses: "actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b" # tag=v3

    - name: "Setup Python"        
      uses: "actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984" # tag=v4       
      with:  
        python-version: "3.x"

    - name: "Install dependencies"        
      run: python -m pip install build

    - name: Build using Python
      id: build
      run: |
        python -m build

    - name: Generate subject
    id: hash
    run: |
      cd dist && echo "::set-output name=hashes::$(sha256sum * | base64 -w0)"

  provenance:
    needs: [build]
    permissions:
      actions: read # To read the workflow path.
      id-token: write # To sign the provenance.
      contents: write # To add assets to a release.
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true # Optional: Upload to a new release

Known Issues

error updating to TUF remote mirror: tuf: invalid key

Affected versions: v1.2.x

Workflows are currently failing with the error:

validating log entry: unable to fetch Rekor public keys from TUF repository, and not trusting the Rekor API for fetching public keys: updating local metadata and targets: error updating to TUF remote mirror: tuf: invalid key

This issue is currently tracked by issue #1163

You can work around this error by setting compile-generator input flag.

with:
  compile-generator: true

This will compile the generator binary used by the workflow instead of downloading the latest release. Make sure you continue to reference the workflow using a release tag in order to allow verification by slsa-verifier.

uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.2.2