diff --git a/.github/linters/tsconfig.json b/.github/linters/tsconfig.json index cc87883..a34cf90 100644 --- a/.github/linters/tsconfig.json +++ b/.github/linters/tsconfig.json @@ -5,8 +5,5 @@ "noEmit": true }, "include": ["../../__tests__/**/*", "../../src/**/*"], - "exclude": ["../../dist", "../../node_modules", "../../coverage", "*.json"], - "references": [ - { "path": "./packages/attest" } - ] + "exclude": ["../../dist", "../../node_modules", "../../coverage", "*.json"] } diff --git a/README.md b/README.md index 4a01b00..4698c3b 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,125 @@ -# attest +# `actions/attest` -GitHub Action to sign and upload [in-toto Attestation Predicates][0] for -artifacts as part of a workflow. +Generate signed attestations for workflow artifacts. Internally powered by the +[@actions/attest][1] package. + +Attestations bind some subject (a named artifact along with its digest) to a +predicate (some assertion about that subject) using the [in-toto][2] format. +[Predicates][3] consist of a type URI and a JSON object containing +type-dependent parameters. + +A verifiable signature is generated for the attestation using a short-lived +[Sigstore][4]-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][5]. ## 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` + 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@main + - uses: actions/attest@v1 with: - predicate-path: './predicate.json' - predicate-type: 'https://in-toto.io/attestation/release/v0.1' - subject-path: '${{ github.workspace }}/PATH_TO_FILE' + subject-path: '' + predicate-type: '' + predicate-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 based on the inputs of `predicate-path` and `predicate-type` - -```json -{ - "_type": "https://in-toto.io/Statement/v1", - "subject": [ - { - "name": "subject", - "digest": { - "sha256": "7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32" - } - } - ], - "predicateType": $predicateType, - "predicate": { - ... - } -} -``` - -The predicate 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 attestation. The `predicate-type` can be any of the the + [vetted predicate types][3] or a custom value. The `predicate-path` + identifies a file containg the JSON-encoded predicate parameters. ### Inputs -- `predicate` - String containing the value for the attestation predicate. +See [action.yml](action.yml) - Must supply exactly one of "predicate-path" or "predicate". +```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: -- `predicate-path` - Path to the file which contains the content for the - attestation predicate. - - Must supply exactly one of "predicate-path" or "predicate". + # 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: -- `predicate-type` - URI identifying the type of the predicate. + # 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: -- `subject-path` - Path to the artifact for which the predicate statement will be - generated. + # URI identifying the type of the predicate. + predicate-type: - Must specify exactly one of `subject-path` or `subject-digest`. Wildcards can - be used to identify more than one artifact. + # JSON string containing the value for the attestation predicate. Must + # supply exactly one of "predicate-path" or "predicate". + predicate: -- `subject-digest` - Digest of the subject for which the predicate statement - will be generated. + # Path to the file which contains the JSON content for the attestation + # predicate. Must supply exactly one of "predicate-path" or "predicate". + predicate-path: - 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`. + # 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: -- `subject-name` - Subject name as it should appear in the predicate 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` action to -your workflow and supply the path to the artifact for which you want to generate -attestation. +Attestations are saved in the JSON-serialized [Sigstore bundle][6] 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][7] +format). + +## Examples + +### Identify Subject by Path + +For the basic use case, simply add the `attest` action to your workflow and +supply the path to the artifact for which you want to generate attestation. ```yaml -name: attest +name: build-attest on: workflow_dispatch: @@ -152,63 +134,35 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Build artifact - run: make some-app + run: make my-app - name: Attest - uses: actions/attest@main + uses: actions/attest@v1 with: - predicate: | - '{ "purl": $YOUR_PURL, "releaseId": $YOUR_RELEASE_ID }' - predicate-type: 'https://in-toto.io/attestation/release/v0.1' - subject-path: '${{ github.workspace }}/some-app' + subject-path: '${{ github.workspace }}/my-app' + predicate-type: 'https://example.com/predicate/v1' + predicate: '{}' ``` -### Identify Artifacts by Wildcard +### Identify Subjects by Wildcard -If you are generating multiple artifacts, you can generate attest 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-attest - -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@main - with: - predicate: | - '{ "purl": $YOUR_PURL, "releaseId": $YOUR_RELEASE_ID }' - predicate-type: 'https://in-toto.io/attestation/release/v0.1' - subject-path: 'dist/**/my-bin-*' +- uses: actions/attest@v1 + with: + subject-path: 'dist/**/my-bin-*' + predicate-type: 'https://example.com/predicate/v1' + predicate: '{}' ``` For supported wildcards along with behavior and documentation, see -[@actions/glob][5] which is used internally to search for files. +[@actions/glob][8] 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 @@ -220,7 +174,7 @@ the specific image being attested is identified by the supplied digest. > registry portion of the image name. ```yaml -name: build-image-with-attest +name: build-attested-image on: push: @@ -254,19 +208,23 @@ jobs: push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - name: Attest - uses: actions/attest@main + uses: actions/attest@v1 id: attest with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} subject-digest: ${{ steps.push.outputs.digest }} - predicate: | - '{ "purl": $YOUR_PURL, "releaseId": $YOUR_RELEASE_ID }' predicate-type: 'https://in-toto.io/attestation/release/v0.1' + predicate: '{"purl":"pkg:oci/..."}' + push-to-registry: true ``` -[0]: https://github.com/in-toto/attestation/blob/main/spec/predicates/README.md -[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/in-toto/attestation/tree/main/spec/predicates#in-toto-attestation-predicates +[4]: https://www.sigstore.dev/ +[5]: https://cli.github.com/ +[6]: + https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto +[7]: https://jsonlines.org/ +[8]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns