diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e670321..b432f78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,11 +84,11 @@ jobs: subject-digest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' subject-name: 'subject' github-token: ${{ secrets.GITHUB_TOKEN }} - format: 'spdx' + sbom-format: 'spdx' - name: Run attest-sbom with cyclonedx format uses: ./ with: subject-digest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' subject-name: 'subject' github-token: ${{ secrets.GITHUB_TOKEN }} - format: 'cyclonedx' + sbom-format: 'cyclonedx' diff --git a/README.md b/README.md index 7ba289f..30ffe34 100644 --- a/README.md +++ b/README.md @@ -1,185 +1,127 @@ -# attest-sbom +# `actions/attest-sbom` -GitHub Action to create, sign and upload a SBOM (Software Bill of Materials) -attestation for artifacts built as part of a workflow. +Generate signed SBOM attestations for workflow artifacts. Internally powered by +the [@actions/attest-sbom][1] package. + +Attestations bind some subject (a named artifact along with its digest) to a a +Software Bill of Materials (SBOM) using the [in-toto][2] format. The action +accepts SBOMs which have been generated by external tools or can generate one +automatically by invoking the [anchore/sbom-action][3]. Externally generated +SBOMs must be in either the [SPDX][4] or [CycloneDX][5] JSON-serialized format. + +A verifiable signature is generated for the attestation using a short-lived +[Sigstore][6]-issued signing certificate. If the repository initiating the +GitHub Actions workflow is public, the public-good instance of Sigstore will be +used to generate the attestation signature. If the repository is +private/internal, it will use the GitHub private Sigstore instance. + +Once the attestation has been created and signed, it will be uploaded to the GH +attestations API and associated with the repository from which the workflow was +initiated. + +Attestations can be verified using the `attestation` command in the [GitHub +CLI][7]. ## Usage Within the GitHub Actions workflow which builds some artifact you would like to -attest, +attest: 1. Ensure that the following permissions are set: ```yaml permissions: id-token: write - contents: write + contents: write # TODO: Update this ``` The `id-token` permission gives the action the ability to mint the OIDC token necessary to request a Sigstore signing certificate. The `contents` permission is necessary to persist the attestation. - > **NOTE**: The set of required permissions will be refined in a future - > release. - -1. After your artifact build step, add the following: +1. Add the following to your workflow after your artifact has been built: ```yaml - - uses: actions/attest-sbom@main + - uses: actions/attest-sbom@v1 with: - path: - subject-path: '${{ github.workspace }}/PATH_TO_FILE' - format: 'spdx' + subject-path: '' ``` The `subject-path` parameter should identity the artifact for which you want - to generate an attestation. - -### What is being attested? - -The generated attestation is a Software Bill of Materials (SBOM), which is -essentially a detailed list of all the components in a software artifact. - -```json -{ - "_type": "https://in-toto.io/Statement/v1", - "subject": [ - { - "name": "subject", - "digest": { - "sha256": "7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32" - } - } - ], - "predicateType": "https://spdx.dev/Document/v2.3", - "predicate": { - "spdxVersion": "SPDX-2.3", - "dataLicense": "CC0-1.0", - "SPDXID": "SPDXRef-DOCUMENT", - "name": "./", - "documentNamespace": "https://anchore.com/syft/dir/80b363b6-87f4-4162-853f-60d402537d20", - "creationInfo": { - "licenseListVersion": "3.22", - "creators": [ - "Organization: Anchore, Inc", - "Tool: syft-0.103.1" - ], - "created": "2024-01-31T18:22:50Z" - }, - "packages": [ - { - "name": "@ampproject/remapping", - "SPDXID": "SPDXRef-Package-npm--ampproject-remapping-5266573ba4f24a42", - "versionInfo": "2.2.1", - "supplier": "NOASSERTION", - "downloadLocation": "NOASSERTION", - "filesAnalyzed": false, - "sourceInfo": - "acquired package info from installed node module manifest file: /yarn.lock", - "licenseConcluded": "NOASSERTION", - "licenseDeclared": "Apache-2.0", - "copyrightText": "NOASSERTION", - "externalRefs": [ - { - "referenceCategory": "SECURITY", - "referenceType": "cpe23Type", - "referenceLocator": - "cpe:2.3:a:\\@ampproject\\/remapping:\\@ampproject\\/remapping:2.2.1:*:*:*:*:*:*:*" - }, - { - "referenceCategory": "PACKAGE-MANAGER", - "referenceType": "purl", - "referenceLocator": "pkg:npm/%40ampproject/remapping@2.2.1" - } - ] - } - ] - } -} -``` - -The SBOM statement is signed with a short-lived, [Sigstore][1]-issued -certificate. - -If the repository initiating the GitHub Actions workflow is public, the public -instance of Sigstore will be used to generate the attestation signature. If the -repository is private, it will use the GitHub private Sigstore instance. - -### Where does the attestation go? - -On the actions summary page for a repository you'll see an "Attestations" link -which will take you to a list of all the attestations generated by workflows in -that repository. - -![Actions summary view](./.github/attestations.png) - -### How are attestations verified? - -Attestations can be verified using the [gh-attestation][2] extension for the -[GitHub CLI][3]. - -## Customization - -See [action.yml](action.yml) + to generate an SBOM attestation. When no other inputs are specified, the + action will automatically generate an SPDX SBOM by scanning the + `github.workspace` directory. ### Inputs -- `path` - A path to a directory on the filesystem to scan and generate SBOM. +See [action.yml](action.yml) - If `sbom-path` is specified, the path will not be used to generate SBOM. +```yaml +- uses: actions/attest@v1 + with: + # Path to the artifact serving as the subject of the attestation. Must + # specify exactly one of "subject-path" or "subject-digest". + subject-path: -- `sbom-path` - A path to a directory on the filesystem which contains SBOM for signing - and uploading. + # SHA256 digest of the subject for for the attestation. Must be in the form + # "sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one + # of "subject-path" or "subject-digest". + subject-digest: - If `sbom-path` is not specified, the default implementation will be used - `path` to generate SBOM for signing. + # Subject name as it should appear in the attestation. Required unless + # "subject-path" is specified, in which case it will be inferred from the + # path. + subject-name: -- `format` - The format of the SBOM. Either `spdx` or `cyclonedx` + # Path to the JSON-formatted SBOM file to attest. When specified, the + # "scan-path" and "sbom-format" inputs are ignored. + sbom-path: -- `subject-path` - Path to the artifact for which the SBOM statement will be - generated. + # Path on the filesystem to scan for SBOM generation. Ignored if "sbom-path" + # is specified. Defaults to ${{ github.workspace }} + scan-path: - Must specify exactly one of `subject-path` or `subject-digest`. Wildcards can - be used to identify more than one artifact. + # Format to use for the generated SBOM output. Supported formats are + # "spdx" and "cyclonedx". Ignored if "sbom-path" is specified. Defaults to + # "spdx". + sbom-format: -- `subject-digest` - Digest of the subject for which the SBOM statement will be - generated. + # Whether to push the attestation to the image registry. Requires that the + # "subject-name" parameter specify the fully-qualified image name and that + # the "subject-digest" parameter be specified. Defaults to false. + push-to-registry: - Only SHA-256 digests are accepted and the supplied value must be in the form - "sha256:\". Must specify exactly one of `subject-path` or - `subject-digest`. - -- `subject-name` - Subject name as it should appear in the SBOM statement. - - Required when the subject is identified by the `subject-digest` parameter. - When attesting container images, the name should be the fully qualified image - name. - -- `push-to-registry` - If true, the signed attestation is pushed to the - container registry identified by the `subject-name`. Default: `false`. - -- `github-token` - Token used to make authenticated requests to the GitHub API. - Default: `${{ github.token }}`. - - The supplied token must have the permissions necessary to write attestations - to the repository. + # The GitHub token used to make authenticated API requests. Default is + # ${{ github.token }} + github-token: +``` ### Outputs -- `bundle-path` - The file path of JSON-serialized [Sigstore bundle][4] containing - the attestation and related verification material. + -## Sample Workflows +| Name | Description | Example | +| ------------- | -------------------------------------------------------------- | ----------------------- | +| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestaion.jsonl` | -### Identify Artifact by Path + -For the basic use case, simply add the `attest-sbom` action to -your workflow and supply the path to the artifact for which you want to generate -SBOM. +Attestations are saved in the JSON-serialized [Sigstore bundle][8] format. + +If multiple subjects are being attested at the same time, each attestation will +be written to the output file on a separate line (using the [JSON Lines][9] +format). + +## Examples + +### Identify Subject and SBOM by Path + +For the basic use case, simply add the `attest-sbom` action to your workflow and +supply the path to the artifact and SBOM for which you want to generate +attestation. ```yaml -name: attest-sbom +name: build-attest on: workflow_dispatch: @@ -194,61 +136,35 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Build artifact - run: make some-app - - name: Attest SBOM - uses: actions/attest-sbom@main + run: make my-app + - name: Generate SBOM + run: make sbom + - name: Attest + uses: actions/attest-sbom@v1 with: - subject-path: '${{ github.workspace }}/some-app' - path: '.' - format: 'spdx' + subject-path: '${{ github.workspace }}/my-app' + sbom-path: '${{ github.workspace }}/my-app.sbom.spdx.json' ``` -### Identify Artifacts by Wildcard +### Identify Subjects by Wildcard -If you are generating multiple artifacts, you can generate sbom for -each artifact by using a wildcard in the `subject-path` input. +If you are generating multiple artifacts, you can generate an attestation for +each by using a wildcard in the `subject-path` input. ```yaml -name: build-wildcard-with-sbom - -on: - workflow_dispatch: - -jobs: - build: - permissions: - id-token: write - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v4 - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 - with: - distribution: goreleaser - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Attest artifact - uses: actions/attest-sbom@main - with: - subject-path: 'dist/**/my-bin-*' - path: '.' - format: 'spdx' +- uses: actions/attest-sbom@v1 + with: + subject-path: 'dist/**/my-bin-*' + sbom-path: '${{ github.workspace }}/my-bin.sbom.spdx.json' ``` For supported wildcards along with behavior and documentation, see -[@actions/glob][5] which is used internally to search for files. +[@actions/glob][10] which is used internally to search for files. ### Container Image -When working with container images you may not have a `subject-path` value you -can supply. In this case you can invoke the action with the `subject-name` and -`subject-digest` inputs. +When working with container images you can invoke the action with the +`subject-name` and `subject-digest` inputs. If you want to publish the attestation to the container registry with the `push-to-registry` option, it is important that the `subject-name` specify the @@ -260,7 +176,7 @@ the specific image being attested is identified by the supplied digest. > registry portion of the image name. ```yaml -name: build-image-with-sbom +name: build-attested-image on: push: @@ -293,27 +209,26 @@ jobs: context: . push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - - name: Generate SBOM from Docker Image - id: sbom - uses: anchore/sbom-action@v0 + - name: Generate SBOM + run: make sbom + - name: Attest + uses: actions/attest-sbom@v1 + id: attest with: - image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - registry-username: ${{ github.actor }} - registry-password: ${{ secrets.GITHUB_TOKEN }} - format: "spdx-json" - output-file: "sbom.json" - - name: Attest SBOM - uses: actions/attest-sbom@main - id: attest-sbom - with: - sbom-path: "sbom.json" subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} subject-digest: ${{ steps.push.outputs.digest }} - format: "spdx" + sbom-path: 'sbom.cyclonedx.json' + push-to-registry: true ``` -[1]: https://www.sigstore.dev/ -[2]: https://github.com/github-early-access/gh-attestation -[3]: https://cli.github.com/ -[4]: https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto -[5]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns +[1]: https://github.com/actions/toolkit/tree/main/packages/attest +[2]: https://github.com/in-toto/attestation/tree/main/spec/v1 +[3]: https://github.com/anchore/sbom-action +[4]: https://spdx.dev/ +[5]: https://cyclonedx.org/ +[6]: https://www.sigstore.dev/ +[7]: https://cli.github.com/ +[8]: + https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto +[9]: https://jsonlines.org/ +[10]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns diff --git a/action.yml b/action.yml index 3e17fe9..e4c5dd0 100644 --- a/action.yml +++ b/action.yml @@ -3,37 +3,39 @@ description: 'Generate SBOM attestations for build artifacts' author: 'GitHub' inputs: - path: - required: false - description: "A path to a directory on the filesystem to scan" - default: "." - - format: - required: false - description: "The SBOM format to export" - default: "spdx-json" - - github-token: - description: > - The GitHub token used to make authenticated API requests. - default: ${{ github.token }} - required: false subject-path: description: > - Path to the artifact for which provenance will be generated. Must specify - exactly one of "subject-path" or "subject-digest". + Path to the artifact serving as the subject of the attestation. Must + specify exactly one of "subject-path" or "subject-digest". required: false subject-digest: description: > - Digest of the subject for which provenance will be generated. Must be in - the form "algorithm:hex_digest" (e.g. "sha256:abc123..."). Must specify - exactly one of "subject-path" or "subject-digest". + SHA256 digest of the subject for for the attestation. Must be in the form + "sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one of + "subject-path" or "subject-digest". required: false subject-name: description: > - Subject name as it should appear in the provenance statement. Required - unless "subject-path" is specified, in which case it will be inferred from - the path. + Subject name as it should appear in the attestation. Required unless + "subject-path" is specified, in which case it will be inferred from the + path. + sbom-path: + description: > + Path to the JSON-formatted SBOM file to attest. When specified, the + "scan-path" and "sbom-format" inputs are ignored. + required: false + scan-path: + description: > + Path on the filesystem to scan for SBOM generation. Ignored if "sbom-path" + is specified. + default: ${{ github.workspace }} + required: false + sbom-format: + description: > + Format to use for the generated SBOM output. Supported formats are "spdx" + and "cyclonedx". Ignored if "sbom-path" is specified. + default: 'spdx' + required: false push-to-registry: description: > Whether to push the provenance statement to the image registry. Requires @@ -41,11 +43,11 @@ inputs: and that the "subject-digest" parameter be specified. Defaults to false. default: false required: false - sbom-path: + github-token: description: > - Path to the SBOM file to generate sbom statement. + The GitHub token used to make authenticated API requests. + default: ${{ github.token }} required: false - default: '' outputs: bundle-path: description: 'The path to the file containing the attestation bundle(s).' @@ -53,49 +55,44 @@ outputs: runs: using: 'composite' - steps: + steps: - name: Generate random SBOM output file name if: ${{ inputs.sbom-path == '' }} - run: echo "SBOM_FILENAME=${{ runner.temp }}/sbom_$(openssl rand -hex 6).json" >> $GITHUB_ENV + run: + echo "SBOM_FILENAME=${{ runner.temp }}/sbom_$(openssl rand -hex 6).json" >> $GITHUB_ENV shell: bash - name: SBOM format check if: ${{ inputs.sbom-path == '' }} run: | - if [ "${{inputs.format}}" != "spdx-json" ] && [ "${{inputs.format}}" != "cyclonedx-json" ] && [ "${{inputs.format}}" != "spdx" ] && [ "${{inputs.format}}" != "cyclonedx" ] ]; then - echo "Invalid SBOM format. Supported formats are spdx-json, cyclonedx-json, spdx, cyclonedx" + if [ "${{inputs.sbom-format}}" != "spdx" ] && [ "${{inputs.sbom-format}}" != "cyclonedx" ] ]; then + echo "Invalid SBOM format. Supported formats are spdx and cyclonedx." exit 1 - fi - echo "SBOM_FORMAT=${{inputs.format}}" >> $GITHUB_ENV - if [ "${{inputs.format}}" == "spdx" ]; then + elif [ "${{inputs.sbom-format}}" == "spdx" ]; then echo "SBOM_FORMAT=spdx-json" >> $GITHUB_ENV - elif [ "${{inputs.format}}" == "cyclonedx" ]; then + elif [ "${{inputs.sbom-format}}" == "cyclonedx" ]; then echo "SBOM_FORMAT=cyclonedx-json" >> $GITHUB_ENV fi shell: bash - name: Generate SBOM if: ${{ inputs.sbom-path == '' }} - uses: anchore/sbom-action@v0 + uses: anchore/sbom-action@b6a39da80722a2cb0ef5d197531764a89b5d48c3 # v0.15.8 with: - path: ${{inputs.path}} + path: ${{inputs.scan-path}} output-file: ${{env.SBOM_FILENAME}} format: ${{env.SBOM_FORMAT}} - config: ${{inputs.config}} - - uses: actions/attest-sbom/generate-sbom-statement@main - id: generate-sbom-statement + - uses: actions/attest-sbom/generate-sbom-predicate@readme + id: generate-sbom-predicate with: - github-token: ${{ inputs.github-token }} - subject-path: ${{ inputs.subject-path }} - subject-digest: ${{ inputs.subject-digest }} - subject-name: ${{ inputs.subject-name }} - push-to-registry: ${{ inputs.push-to-registry }} sbom-path: ${{ inputs.sbom-path || env.SBOM_FILENAME }} - uses: actions/attest@main id: attest with: - github-token: ${{ inputs.github-token }} subject-path: ${{ inputs.subject-path }} subject-digest: ${{ inputs.subject-digest }} subject-name: ${{ inputs.subject-name }} push-to-registry: ${{ inputs.push-to-registry }} - predicate-type: ${{ steps.generate-sbom-statement.outputs.predicate-type }} - predicate-path: ${{ steps.generate-sbom-statement.outputs.predicate-path }} + predicate-type: + ${{ steps.generate-sbom-predicate.outputs.predicate-type }} + predicate-path: + ${{ steps.generate-sbom-predicate.outputs.predicate-path }} + github-token: ${{ inputs.github-token }} diff --git a/generate-sbom-predicate/action.yml b/generate-sbom-predicate/action.yml new file mode 100644 index 0000000..608e24e --- /dev/null +++ b/generate-sbom-predicate/action.yml @@ -0,0 +1,19 @@ +name: 'Generate SBOM Predicate' +description: 'Generate SBOM predicate' +author: 'GitHub' + +inputs: + sbom-path: + description: > + Path to the SBOM file to generate sbom statement + required: false +outputs: + predicate-path: + description: > + The path to the JSON-serialized of the attestation predicate + predicate-type: + description: > + URI identifying the type of the predicate. +runs: + using: node20 + main: ../dist/index.js diff --git a/generate-sbom-statement/action.yml b/generate-sbom-statement/action.yml deleted file mode 100644 index c4da417..0000000 --- a/generate-sbom-statement/action.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: 'Generate SBOM Statement' -description: 'Generate SBOM Statement for build artifacts' -author: 'GitHub' - -inputs: - github-token: - description: > - The GitHub token used to make authenticated API requests. - default: ${{ github.token }} - required: false - subject-path: - description: > - Path to the artifact for which provenance will be generated. Must specify - exactly one of "subject-path" or "subject-digest". - required: false - subject-digest: - description: > - Digest of the subject for which provenance will be generated. Must be in - the form "algorithm:hex_digest" (e.g. "sha256:abc123..."). Must specify - exactly one of "subject-path" or "subject-digest". - required: false - subject-name: - description: > - Subject name as it should appear in the provenance statement. Required - unless "subject-path" is specified, in which case it will be inferred from - the path. - push-to-registry: - description: > - Whether to push the provenance statement to the image registry. Requires - that the "subject-name" parameter specify the fully-qualified image name - and that the "subject-digest" parameter be specified. Defaults to false. - default: false - required: false - sbom-path: - description: > - Path to the SBOM file to generate sbom statement - required: false -outputs: - predicate-path: - description: > - The path to the JSON-serialized of the attestation predicate - predicate-type: - description: > - URI identifying the type of the predicate. -runs: - using: node20 - main: ../dist/index.js \ No newline at end of file