Skip to content

Latest commit

 

History

History
326 lines (231 loc) · 24.6 KB

BYOB.md

File metadata and controls

326 lines (231 loc) · 24.6 KB

Build Your Own Builder (BYOB) Framework

Design Overview

The Build Your Own Builder (BYOB) framework makes it simple to make an existing GitHub Acton SLSA3 compliant. You delegate orchestration and provenance generation to the BYOB framework. You don't need to be aware of all the complexity around reusable workflows, signing, intoto, Sigstore, or SLSA.

The diagram below depicts the different components of the BYOB framework.

Screenshot

The Build Your Own Builder (BYOB) framework makes it simple to make an existing GitHub Acton SLSA3 compliant. By delegating orchestration and provenance generation to the BYOB framework., you need not be aware of reusable workflows, signing, intoto, Sigstore and other shenanigans.

Project Workflow (PW)

On the left, the end-user project workflow (PW) is depicted. The PW is hosted in the repository that wants to build an artifact. As part of a build, the PW invokes the SLSA compliant builder defined by the TRW:

- uses: npm/builder/.github/workflows/[email protected]

The example snippet shows the invocation of a builder with path .github/workflows/slsa.3.yml from the GitHub's npm/builder repository.

Tool Repository

This is the tool repository hosting the builder invoked by PWs. The tool repository MUST be public. The repository contains two components:

Tool Reusable Workflow (TRW)

The "Tool Reusable Workflow" (TRW) is the SLSA compliant builder that will "wrap" an existing Action. End users' PWs invoke the TRW to build their artifacts. The TRW workflow file must be created as part of the integration.

Tool Callback Action (TCA)

The "Tool Callback Action" (TCA) is the Action that is invoked by the BYOB framework in an isolated GitHub job. The TCA is also hosted in the tool repository. The TCA's role is threefold:

  • Set the environment. For example, if the builder wants to build Go projects, the TCA would install the Go compiler.
  • Call your existing Action. For example, if the builder wants to make the GoReleaser Action SLSA compliant, the TCA would call the existing goreleaser/goreleaser-action after it has set up the environment.
  • Output attestation metadata (name, binaries and hashes) that are used by the framework to generate SLSA provenance.

SLSA GitHub Repository

The slsa-github-generator repository hosts the code for the BYOB framework maintained by the OpenSSF SLSA tooling team. There are two main components you will use for your integration.

SLSA Setup Action (SSA)

The setup-generic Action is used to initialize the BYOB framework. It returns a so-called "SLSA token" which is used in later steps:

- uses: slsa-framework/slsa-github-generator/actions/delegator/[email protected]

SLSA Reusable Workflow (SRW)

The SRW acts as the build's orchestrator. It calls the TCA, generates provenance, and returns the provenance to its TRW caller. A TRW would typically call the SRW as follows:

- uses: slsa-framework/slsa-github-generator/.github/workflow/[email protected]
  with:
    slsa-token: ${{ needs.slsa-setup.outputs.slsa-token }}

Integration Steps

In this example, we will assume there is an existing GitHub Action which builds an artifact. The Action is fairly simple: it just echos the parameters into the artifact. It also takes a username, password and token to retrieve / push information from a remote registry. Like popular release Actions, it releases the built artifact to GitHub releases. It outputs the name of the built artifact and the status of the build. See the full action.yml.

Supported Triggers

Only the following event types are supported as recommended by the SLSA specifications:

Supported event type Event description
create Creation of a git tag or branch.
release Creation or update of a GitHub release.
push Creation or update of a git tag or branch.
workflow_dispatch Manual trigger of a workflow.

pull_request events are currently not supported. If you would like support for pull_request, please tell us about your use case on issue #358. If you have an issue related to any other triggers please submit a new issue.

TRW inputs

The first step for our integration is to create our TRW file and define its inputs. The inputs should mirror those of the existing Action above that we want to make SLSA compliant.

Inputs

Inputs that have low entropy are defined under the inputs section. Unlike Action inputs, you may define the type (boolean, number, or string) of each input. You may also provide a default value. The inputs will be attested to in the generated provenance. We will in Section: SRW Setup how to redact certain inputs from the provenance, such as the username that may be considered sensitive.

We also declare an additional rekor-log-public boolean input. Given that the name of the repository will be available in the provenance and will be uploaded to the public transparency log, we need users to acknowledge that they are aware that private repository names will be made public. We encourage all TRWs to define this option. For public repositories, the value of the input is set to true by default by the SRW. For private repositories, users should set if to true when calling the TRW.

Secrets

Unlike Actions, secrets are defined under a separate secrets section. Secrets should only be high-entropy values. Do not set username or other low-entropy PII as secrets, as it may intermittently fail due to this unresolved GitHub issue. Secrets may be marked as optional. Unlike for Actions, secrets cannot have default values: in our example, the token secret has no default value, whereas the original Action had one. We will see in Section: Invocation of Existing Action how to set default values in the TCA.

Outputs

The outputs from the TCA may be returned to the PW as well. To do this, use the outputs section. There are other outputs set as well in our example: Those provide metadata about the built artifacts and their provenance, and we will discuss them in Section: Upload Attestations.

Important Notes

One key difference between the Action and reusable workflow is isolation. The SRW runs on a different VM than the TRW; and the TRW runs on a different VM from the PW. This means that the artifact built by the TCA (which is managed by the SRW) is not accessible directly by the TRW. The SRW needs to share these files with the TRW; which may also share them with the PW. We will see in the following sections how to do that. The TRW outputs provides the metadata necessary to download these files, and we will discuss them in Section: Upload Attestations.

SRW Setup

To initialize the SRW framework, the TRW must invoke the setup-generic. The relevant code calls the SSA as follows:

uses: slsa-framework/slsa-github-generator/actions/delegator/[email protected]
  with:
    slsa-workflow-recipient: "delegator_generic_slsa3.yml"
    slsa-rekor-log-public: ${{ inputs.rekor-log-public }}
    slsa-runner-label: "ubuntu-latest"
    slsa-build-action-path: "./internal/callback_action"
    slsa-workflow-inputs: ${{ toJson(inputs) }}
    slsa-workflow-masked-inputs: username

Let's go through the parameters:

  • slsa-workflow-recipient is the name of the SRW we are initializing. This is the workflow that we will call to run the build in our example.
  • slsa-rekor-log-public is simply the same as the TRW's slsa-rekor-log-public input, so we just set the value with the TRW's value.
  • slsa-runner-label is the runner label to run the build on. We currently only support ubuntu runners, but we will add support for other runners in the future.
  • slsa-build-action-path is the path to our TCA, relative to the root of the repository.
  • slsa-workflow-inputs are the inputs to the TRW, which the provenance will attest to. These inputs are also provided to the TCA by the BYOB framework.
  • slsa-workflow-masked-inputs is a list of comma separated field names that are redacted from the generated SLSA provenance. In this example, we're telling the TRW that the username input should be redacted. Any TRW secrets are separate from inputs and thus are automatically excluded from the provenance.

SRW Invocation

Once we have initialized the SRW, we call the SRW:

slsa-run:
  needs: [slsa-setup]
  permissions:
    id-token: write # For signing.
    contents: write # For asset uploads.
    packages: write # For package uploads.
    actions: read # For the entrypoint.
  uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
  with:
    slsa-token: ${{ needs.slsa-setup.outputs.slsa-token }}
  secrets:
    secret1: ${{ inputs.password }}
    secret2: ${{ inputs.token }}

In addition to the token, we also provide the secrets. Up to 15 secrets are supported. Secrets are simply passed to the TCA. They are not included in provenance.

Creating a TCA

The call above will run the SRW and invoke the callback Action, so let's see how to define it now. The Action code is available under internal/callback_action.

Inputs

The inputs to the TCA are pre-defined, so you just have to follow their definition:

Outputs

We declare the same outputs as the existing Actions. These outputs are made available to the TRW by the BYOB framework. They may be returned by the TRW to the PW.

Invocation of Existing Action

We invoke the existing Action by its path and pass it the inputs by extracting them from the slsa-workflow-inputs argument:

uses: ./../__TOOL_CHECKOUT_DIR__
id: build
  with:
    artifact: ${{ fromJson(inputs.slsa-workflow-inputs).artifact }}
    content: ${{ fromJson(inputs.slsa-workflow-inputs).content }}
    username: ${{ fromJson(inputs.slsa-workflow-inputs).username }}
    password: ${{ inputs.slsa-workflow-secret1 }}
    token: ${{ inputs.slsa-workflow-secret2 || github.token }}

Note that the ./../__TOOL_CHECKOUT_DIR__ is the path where the TRW repository is checked out by the BYOB framework, so it's accessible locally. You can then call your existing action at the path ./../__TOOL_CHECKOUT_DIR__/path/to/action where /path/to/action is the path to your action's action.yml relative to the repository root. In the above example, we are assuming our action.yml is defined in the repository root. Notice how we populate the token field: If the user has not passed a value to inputs.slsa-workflow-secret2, we default to using the GitHub token github.token.

Generation of Metadata Layout File

The last thing to do in the TCA is to generate the metadata layout file to indicate to the BYOB platform which files to attest to, and which attestations to generate. You can ask the platform to generate several attestations, each attestating to one or more artifacts. The snippet below indicates a single attestation attesting to a single built artifact my-artifact. When the BYOB framework generates the attestation, it will add the .build.slsa extension to it.

{
  "version": 1,
  "attestations": [
    {
      "name": "my-artifact",
      "subjects": [
        {
          "name": "my-artifact",
          "digest": {
            "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
          }
        }
      ]
    }
  ]
}

Upload Attestations

In a final "publish" job of the TRW, we download the attestations and do whatever we'd like with them. In our example, we upload them as release assets to a GitHub release. You may instead upload them to a registry, etc.

Also think about returning the attestation to the PW in case end-users want to publish the artifacts and attestations themselves. If you do so, we encourage you to create a secure-download-attestation Action for your users, e.g. under a download folder in your repository. This will improve user experience as they won't have to be aware of the SLSA repository and its framework.

PW Integration

PW Call

The PW workflow will call your builder as follows:

jobs:
  build:
    permissions:
      id-token: write # For signing
      contents: read # For asset release.
      actions: read # For getting workflow run info.
    uses: laurentsimon/byob-doc/.github/workflows/[email protected]
    with:
      artifact: my-artifact
      content: "hello world"
    secrets:
      password: ${{ secrets.PASSWORD }}

Provenance Example

TODO

Hardening

If you've made it thus far, congratulations! You have built a SLSA3 compliant builder. In this section, we provide additional guidance and tips to harden your implementation.

Least Privileged TCA

In the example of Section: Integration Steps, we assumed that the existing Action released assets on GitHub. This is a common feature across build / release Actions. Depending on the use case, this requires the Action to have access to:

  • contents: write: token permissions: to upload GitHub assets to GitHub releases. This also grants the Action the ability to push code to the PW repository.
  • packages: write: to upload a package on GitHub registry.
  • secrets: used to log into a registry to publish a package

Building an artifact or a package includes downloading dependencies. Every once in a while, dependencies built into a final package may turn out to be malicious. In these rare cases, the PW maintainers and its downstream users will start an incidence response to determine what systems may have been compromised by a rogue dependency. In certain ecosystems like npm or python, dependencies may run arbitrary code as part of the build process, which means they have access to sensitive passwords and the permissions granted to the TCA. To reduce the consequences of a rogue dependency, we recommend following the principle of least privilege, and only give the minimal permissions to the TCA. Let's see how to update our initial integration to do that.

Low-Permission SRW

The first thing to do is to use a "low permission SRW". The SRW we used in our original integration is delegator_generic_slsa3.yml, which calls the TCA with the permissions for pushing release assets and publishing packages. In order to reduce the number of permissions the TCA is called with, we recommend you use delegator_lowperms-generic_slsa3.yml instead. This workflow does not give the TCA the dangerous permissions above, and only gives it contents: read for repository read access. To update your integration:

Update TCA

The next thing to do is to not uploads asset to the GitHub release within the existing Action and update the TCA to securely share the built artifacts with the TRW. (The TRW will later be updated to publish the artifacts). To update the TCA:

Update TRW

Now we need to download the artifact and publish it from the TRW. Do do that, follow the steps:

Best SDLC Practices

It is important you follow best development practices for your code, including your TRW, TCA and existing Action. In particular:

  • Harden your CI, e.g., set your top-level workflow permissions to read-only.
  • Pin your depenencies by hash except the delegator workflow, to avoid dependency confusion attacks and speed up incidence response.
  • If you download binaries, verify their SLSA provenance before running them. Use the installer action to install and use slsa-verifier.
  • Install or use a tool like OSSF Scorecard to verify you're comprehensively looking at your SDLC.