Skip to content

Latest commit

 

History

History
338 lines (239 loc) · 25.1 KB

BYOB.md

File metadata and controls

338 lines (239 loc) · 25.1 KB

Build Your Own Builder (BYOB) Framework

Design Overview

The Build Your Own Builder (BYOB) framework makes it simple to make an existing GitHub Action SLSA3 compliant. Instead of handling the complexity around reuseable workflows, signing, intoto, Sigstore, etc, you can simply delegate orchestration and provenance generation to the BYOB framework.

The diagram below depicts the different components of the BYOB framework. We'll cover the different portions in our overview.

Screenshot

Project Workflow (PW)

The Project Workflow (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 Tool Reuseable Workflow (TRW):

- uses: npm/builder/.github/workflows/slsa.3.yml@vx.y.z

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

The tool repository hosts the builder invoked by PWs. The tool repository MUST be public. The repository contains two components, the Tool Reuseable Workflow and the Tool Callback Action.

Tool Reusable Workflow (TRW)

The "Tool Reusable Workflow" (TRW) is the SLSA compliant builder that will "wrap" an existing GitHub 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 GitHub Action that is invoked by the BYOB framework in an isolated GitHub job. The TCA does the following:

  • Sets the environment. For example, if the builder wants to build Go projects, the TCA would install the Go compiler.
  • Calls your existing GitHub 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.
  • Outputs 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, the SLSA Setup Action and the SLSA Reuseable Workflow.

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/setup-generic@v2.0.0

SLSA Reusable Workflow (SRW)

The SLSA Reuseable Workflow (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/delegator_generic_slsa3.yml@v2.0.0
  with:
    slsa-token: ${{ needs.slsa-setup.outputs.slsa-token }}

Example: Convert a GitHub Action to be SLSA3 Compliant

In this example, we will assume there is an existing GitHub Action which builds an artifact. The Action does the following:

See the full action.yml.

Some more advanced topics are ommitted for clarity, but can be found in the Section: Hardening. Once you have completed this example and the provenance example, we recommend following the steps in Section: Hardening, as it represents best security practice.

Before you begin: understand 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.

Step 1: 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 discuss in Section: SRW Setup how to redact certain inputs that might be sensitive, such as username, from the provenance.

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 to define the artifact and the status. Our example uses additional outputs to provide metadata about the built artifacts and their provenance. 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. How to handle this isolation is discussed in Section: SRW Setup. The TRW outputs provides the metadata necessary to download these files, and we will discuss them in Section: Upload Attestations.

Step 2: SRW Setup

Our next step is to initialize the SRW framework. To do this, the TRW must invoke the setup-generic Action. The relevant code calls the SSA as follows:

uses: slsa-framework/slsa-github-generator/actions/delegator/setup-generic@v2.0.0
  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.

Step 3: 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/delegator_generic_slsa3.yml@v2.0.0
  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.

Step 4: Creating a TCA

The call that we constructed in Step 3 will run the SRW and invoke the callback Action, which we will define in this step. 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"
          }
        }
      ]
    }
  ]
}

Step 5: 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 simply print the filename. You may instead upload them to a GitHub release, a registry, etc.

You may want to return 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 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/builder_example_slsa3.yml@v0.0.1
    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 upload the 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. To do that, follow these 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.