├── .gitattributes ├── .github ├── attestations.png └── workflows │ ├── ci.yml │ └── e2e.yml ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md └── action.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/** -diff linguist-generated=true -------------------------------------------------------------------------------- /.github/attestations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github-early-access/generate-build-provenance/5fb744556d7e95958990349de0ddb84909c219bb/.github/attestations.png -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - 'releases/*' 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | 14 | test-action-linux: 15 | name: GitHub Actions Test (Linux) 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | attestations: write 20 | id-token: write 21 | 22 | steps: 23 | - name: Checkout 24 | id: checkout 25 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 26 | - name: Test Local Action 27 | id: test-action 28 | uses: ./ 29 | with: 30 | subject-digest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' 31 | subject-name: 'subject' 32 | 33 | test-action-windows: 34 | name: GitHub Actions Test (Windows) 35 | runs-on: windows-latest 36 | permissions: 37 | contents: read 38 | attestations: write 39 | id-token: write 40 | 41 | steps: 42 | - name: Checkout 43 | id: checkout 44 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 45 | - name: Test Local Action 46 | id: test-action 47 | uses: ./ 48 | with: 49 | subject-path: ${{ github.workspace }}\README.md 50 | 51 | test-action-oci: 52 | name: GitHub Actions Test (OCI) 53 | runs-on: ubuntu-latest 54 | permissions: 55 | contents: read 56 | attestations: write 57 | id-token: write 58 | packages: write 59 | env: 60 | REGISTRY: ghcr.io 61 | IMAGE_NAME: ${{ github.repository }} 62 | 63 | steps: 64 | - name: Checkout 65 | id: checkout 66 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 67 | - name: Build Dockerfile 68 | run: | 69 | cat < Dockerfile 70 | FROM scratch 71 | COPY README.md . 72 | EOF 73 | - name: Login to GHCR 74 | uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 75 | with: 76 | registry: ${{ env.REGISTRY }} 77 | username: ${{ github.actor }} 78 | password: ${{ secrets.GITHUB_TOKEN }} 79 | - name: Build and push container image 80 | id: push 81 | uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 82 | with: 83 | context: . 84 | push: true 85 | tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest 86 | - name: Test Local Action 87 | id: test-action 88 | uses: ./ 89 | with: 90 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 91 | subject-digest: ${{ steps.push.outputs.digest }} 92 | push-to-registry: true 93 | 94 | test-action-private: 95 | name: GitHub Actions Test (Private) 96 | runs-on: ubuntu-latest 97 | permissions: 98 | contents: read 99 | attestations: write 100 | id-token: write 101 | 102 | steps: 103 | - name: Checkout 104 | id: checkout 105 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 106 | - name: Test Local Action 107 | id: test-action 108 | env: 109 | INPUT_PRIVATE-SIGNING: 'true' 110 | uses: ./ 111 | with: 112 | subject-digest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' 113 | subject-name: 'subject' 114 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: End-to-End Testing 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - 'releases/*' 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | e2e-oci: 14 | name: Publish/Verify (Container Image) 15 | runs-on: ubuntu-latest 16 | permissions: 17 | attestations: write 18 | contents: read 19 | id-token: write 20 | services: 21 | registry: 22 | image: registry@sha256:860f379a011eddfab604d9acfe3cf50b2d6e958026fb0f977132b0b083b1a3d7 # 2.8.3 23 | ports: 24 | - 5000:5000 25 | env: 26 | REGISTRY: localhost:5000 27 | IMAGE_NAME: test 28 | IMAGE_TAG: latest 29 | steps: 30 | - name: Checkout 31 | id: checkout 32 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 33 | - name: Update GH CLI 34 | uses: dev-hanz-ops/install-gh-cli-action@8fff9050dae2d81b38f94500d8b74ad1d1d47410 # v0.2.0 35 | with: 36 | gh-cli-version 2.49.0 37 | - name: Build Dockerfile 38 | run: | 39 | cat < Dockerfile 40 | FROM scratch 41 | COPY README.md . 42 | EOF 43 | - name: Build and push container image 44 | id: push 45 | uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 46 | with: 47 | context: . 48 | push: true 49 | tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} 50 | - name: Attest image 51 | id: attest-action 52 | uses: ./ 53 | with: 54 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 55 | subject-digest: ${{ steps.push.outputs.digest }} 56 | - name: Verify attestation 57 | env: 58 | GH_TOKEN: ${{ github.token }} 59 | run: | 60 | gh attestation verify oci://${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} -R ${{ github.repository }} 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # OS metadata 94 | .DS_Store 95 | Thumbs.db 96 | 97 | # Ignore built ts files 98 | __tests__/runner/* 99 | 100 | # IDE files 101 | .idea 102 | .vscode 103 | *.code-workspace 104 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @github-early-access/package-security 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 GitHub, Inc. and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generate-build-provenance 2 | 3 | GitHub Action to create, sign and upload a build provenance attestation for 4 | artifacts built as part of a workflow. 5 | 6 | **NOTE:** This action has been deprecated in favor of 7 | [`actions/attest-build-provenance`](https://github.com/actions/attest-build-provenance). 8 | All users are encouraged to move to the new action -- this repository will not 9 | receive further updates and may be removed at some point. 10 | 11 | The inputs to the `attest-build-provenance` action are identical to 12 | `generate-build-provenance`, so migrating to the new version is as simple as 13 | updating the `uses:` value in your workflows to reference the new name: 14 | 15 | ```text 16 | github-early-access/generate-build-provenance@main 17 | ``` 18 | 19 | becomes 20 | 21 | ```text 22 | actions/attest-build-provenance@v1 23 | ``` 24 | 25 | Attestations generated with the new action use a newer version of the Sigstore 26 | [bundle format][5] (v0.3.1 vs v0.2.1) and require version [2.49.0][7] or later 27 | of the `gh` CLI to verify. 28 | 29 | ## Usage 30 | 31 | Within the GitHub Actions workflow which builds some artifact you would like to 32 | attest, 33 | 34 | 1. Ensure that the following permissions are set: 35 | 36 | ```yaml 37 | permissions: 38 | id-token: write 39 | attestations: write 40 | contents: read # optional, usually required 41 | ``` 42 | 43 | The `id-token` permission gives the action the ability to mint the OIDC token 44 | necessary to request a Sigstore signing certificate. The `attestations` 45 | permission is necessary to persist the attestation. 46 | 47 | > **NOTE**: The set of required permissions will be refined in a future 48 | > release. 49 | 50 | 1. After your artifact build step, add the following: 51 | 52 | ```yaml 53 | - uses: github-early-access/generate-build-provenance@main 54 | with: 55 | subject-path: '${{ github.workspace }}/PATH_TO_FILE' 56 | ``` 57 | 58 | The `subject-path` parameter should identity the artifact for which you want 59 | to generate an attestation. 60 | 61 | ### What is being attested? 62 | 63 | The generated attestation is a [SLSA provenance][2] document which captures 64 | non-falsifiable information about the GitHub Actions run in which the subject 65 | artifact was created: 66 | 67 | ```json 68 | { 69 | "_type": "https://in-toto.io/Statement/v1", 70 | "subject": [ 71 | { 72 | "name": "some-app", 73 | "digest": { 74 | "sha256": "7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32" 75 | } 76 | } 77 | ], 78 | "predicateType": "https://slsa.dev/provenance/v1", 79 | "predicate": { 80 | "buildDefinition": { 81 | "buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1", 82 | "externalParameters": { 83 | "workflow": { 84 | "ref": "refs/heads/main", 85 | "repository": "https://github.com/user/app", 86 | "path": ".github/workflows/build.yml" 87 | } 88 | }, 89 | "internalParameters": { 90 | "github": { 91 | "event_name": "push", 92 | "repository_id": "706279790", 93 | "repository_owner_id": "19792534" 94 | } 95 | }, 96 | "resolvedDependencies": [ 97 | { 98 | "uri": "git+https://github.com/user/app@refs/heads/main", 99 | "digest": { 100 | "gitCommit": "c607ef44660b66df4d10b0dd6b01f56ec98f293f" 101 | } 102 | } 103 | ] 104 | }, 105 | "runDetails": { 106 | "builder": { 107 | "id": "https://github.com/actions/runner/github-hosted" 108 | }, 109 | "metadata": { 110 | "invocationId": "https://github.com/user/app/actions/runs/1880241037/attempts/1" 111 | } 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | The provenance statement is signed with a short-lived, [Sigstore][1]-issued 118 | certificate. 119 | 120 | If the repository initiating the GitHub Actions workflow is public, the public 121 | instance of Sigstore will be used to generate the attestation signature. If the 122 | repository is private, it will use the GitHub private Sigstore instance. 123 | 124 | ### Where does the attestation go? 125 | 126 | On the actions summary page for a repository you'll see an "Attestations" link 127 | which will take you to a list of all the attestations generated by workflows in 128 | that repository. 129 | 130 | ![Actions summary view](./.github/attestations.png) 131 | 132 | ### How are attestations verified? 133 | 134 | Attestations can be verified using the [gh-attestation][3] extension for the 135 | [GitHub CLI][4]. 136 | 137 | ## Customization 138 | 139 | See [action.yml](action.yml) 140 | 141 | ### Inputs 142 | 143 | - `subject-path` - Path to the artifact for which the provenance will be 144 | generated. 145 | 146 | Must specify exactly one of `subject-path` or `subject-digest`. Wildcards can 147 | be used to identify more than one artifact. 148 | 149 | - `subject-digest` - Digest of the subject for which the provenance will be 150 | generated. 151 | 152 | Only SHA-256 digests are accepted and the supplied value must be in the form 153 | "sha256:\". Must specify exactly one of `subject-path` or 154 | `subject-digest`. 155 | 156 | - `subject-name` - Subject name as it should appear in the provenance statement. 157 | 158 | Required when the subject is identified by the `subject-digest` parameter. 159 | When attesting container images, the name should be the fully qualified image 160 | name. 161 | 162 | - `push-to-registry` - If true, the signed attestation is pushed to the 163 | container registry identified by the `subject-name`. Default: `false`. 164 | 165 | - `github-token` - Token used to make authenticated requests to the GitHub API. 166 | Default: `${{ github.token }}`. 167 | 168 | The supplied token must have the permissions necessary to write attestations 169 | to the repository. 170 | 171 | ### Outputs 172 | 173 | - `bundle` - The JSON-serialized [Sigstore bundle][5] containing the attestation 174 | and related verification material. 175 | 176 | ## Sample Workflows 177 | 178 | ### Identify Artifact by Path 179 | 180 | For the basic use case, simply add the `generate-build-provenance` action to 181 | your workflow and supply the path to the artifact for which you want to generate 182 | build provenance. 183 | 184 | ```yaml 185 | name: build-with-provenance 186 | 187 | on: 188 | workflow_dispatch: 189 | 190 | jobs: 191 | build: 192 | permissions: 193 | id-token: write 194 | attestations: write 195 | contents: read 196 | 197 | steps: 198 | - name: Checkout 199 | uses: actions/checkout@v4 200 | - name: Build artifact 201 | run: make some-app 202 | - name: Attest artifact 203 | uses: github-early-access/generate-build-provenance@main 204 | with: 205 | subject-path: '${{ github.workspace }}/some-app' 206 | ``` 207 | 208 | ### Identify Artifacts by Wildcard 209 | 210 | If you are generating multiple artifacts, you can generate build provenance for 211 | each artifact by using a wildcard in the `subject-path` input. 212 | 213 | ```yaml 214 | name: build-wildcard-with-provenance 215 | 216 | on: 217 | workflow_dispatch: 218 | 219 | jobs: 220 | build: 221 | permissions: 222 | id-token: write 223 | attestations: write 224 | contents: read 225 | 226 | steps: 227 | - name: Checkout 228 | uses: actions/checkout@v4 229 | - name: Set up Go 230 | uses: actions/setup-go@v4 231 | - name: Run GoReleaser 232 | uses: goreleaser/goreleaser-action@v5 233 | with: 234 | distribution: goreleaser 235 | version: latest 236 | args: release --clean 237 | env: 238 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 239 | - name: Attest artifact 240 | uses: github-early-access/generate-build-provenance@main 241 | with: 242 | subject-path: 'dist/**/my-bin-*' 243 | ``` 244 | 245 | For supported wildcards along with behavior and documentation, see 246 | [@actions/glob][6] which is used internally to search for files. 247 | 248 | ### Container Image 249 | 250 | When working with container images you may not have a `subject-path` value you 251 | can supply. In this case you can invoke the action with the `subject-name` and 252 | `subject-digest` inputs. 253 | 254 | If you want to publish the attestation to the container registry with the 255 | `push-to-registry` option, it is important that the `subject-name` specify the 256 | fully-qualified image name (e.g. "ghcr.io/user/app" or 257 | "acme.azurecr.io/user/app"). Do NOT include a tag as part of the image name -- 258 | the specific image being attested is identified by the supplied digest. 259 | 260 | > **NOTE**: When pushing to Docker Hub, please use "index.docker.io" as the 261 | > registry portion of the image name. 262 | 263 | ```yaml 264 | name: build-image-with-provenance 265 | 266 | on: 267 | push: 268 | branches: [main] 269 | 270 | jobs: 271 | build: 272 | runs-on: ubuntu-latest 273 | permissions: 274 | id-token: write 275 | packages: write 276 | attestations: write 277 | contents: read 278 | env: 279 | REGISTRY: ghcr.io 280 | IMAGE_NAME: ${{ github.repository }} 281 | 282 | steps: 283 | - name: Checkout 284 | uses: actions/checkout@v4 285 | - name: Login to GitHub Container Registry 286 | uses: docker/login-action@v3 287 | with: 288 | registry: ${{ env.REGISTRY }} 289 | username: ${{ github.actor }} 290 | password: ${{ secrets.GITHUB_TOKEN }} 291 | - name: Build and push image 292 | id: push 293 | uses: docker/build-push-action@v5.0.0 294 | with: 295 | context: . 296 | push: true 297 | tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest 298 | - name: Attest image 299 | uses: github-early-access/generate-build-provenance@main 300 | with: 301 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 302 | subject-digest: ${{ steps.push.outputs.digest }} 303 | push-to-registry: true 304 | ``` 305 | 306 | [1]: https://www.sigstore.dev/ 307 | [2]: https://slsa.dev/spec/v1.0/provenance 308 | [3]: https://github.com/github-early-access/gh-attestation 309 | [4]: https://cli.github.com/ 310 | [5]: 311 | https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto 312 | [6]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns 313 | [7]: https://github.com/cli/cli/releases/tag/v2.49.0 314 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Generate Build Provenance' 2 | description: 'Generate provenance attestations for build artifacts' 3 | author: 'GitHub' 4 | 5 | inputs: 6 | github-token: 7 | description: > 8 | The GitHub token used to make authenticated API requests. 9 | default: ${{ github.token }} 10 | required: false 11 | subject-path: 12 | description: > 13 | Path to the artifact for which provenance will be generated. Must specify 14 | exactly one of "subject-path" or "subject-digest". 15 | required: false 16 | subject-digest: 17 | description: > 18 | Digest of the subject for which provenance will be generated. Must be in 19 | the form "algorithm:hex_digest" (e.g. "sha256:abc123..."). Must specify 20 | exactly one of "subject-path" or "subject-digest". 21 | required: false 22 | subject-name: 23 | description: > 24 | Subject name as it should appear in the provenance statement. Required 25 | unless "subject-path" is specified, in which case it will be inferred from 26 | the path. 27 | push-to-registry: 28 | description: > 29 | Whether to push the provenance statement to the image registry. Requires 30 | that the "subject-name" parameter specify the fully-qualified image name 31 | and that the "subject-digest" parameter be specified. Defaults to false. 32 | default: false 33 | required: false 34 | 35 | outputs: 36 | bundle: 37 | description: > 38 | The JSON-serialized Sigstore bundle containing the signed provenance 39 | statement and related verification material. 40 | value: ${{ steps.translate-output.outputs.bundle }} 41 | 42 | runs: 43 | using: 'composite' 44 | steps: 45 | - uses: actions/attest-build-provenance@v1 46 | id: attest 47 | with: 48 | subject-path: ${{ inputs.subject-path }} 49 | subject-digest: ${{ inputs.subject-digest }} 50 | subject-name: ${{ inputs.subject-name }} 51 | push-to-registry: ${{ inputs.push-to-registry }} 52 | github-token: ${{ inputs.github-token }} 53 | - id: translate-output 54 | if: runner.os != 'Windows' 55 | shell: bash 56 | run: | 57 | read -r line < ${{ steps.attest.outputs.bundle-path }} 58 | echo "bundle=$line" >> $GITHUB_OUTPUT 59 | - id: upgrade-notice 60 | shell: bash 61 | run: | 62 | echo "### Deprecation Notice" >> $GITHUB_STEP_SUMMARY 63 | echo "Please migrate from \`github-early-access/generate-build-provenance\` to \`actions/attest-build-provenance\`" >> $GITHUB_STEP_SUMMARY 64 | --------------------------------------------------------------------------------