diff --git a/assets/images/2024-05-30-understanding-github-artifact-attestations/sigstore.excalidraw b/assets/images/2024-05-30-understanding-github-artifact-attestations/sigstore.excalidraw new file mode 100644 index 0000000..7ef0586 --- /dev/null +++ b/assets/images/2024-05-30-understanding-github-artifact-attestations/sigstore.excalidraw @@ -0,0 +1,672 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 439, + "versionNonce": 1183051029, + "index": "a0", + "isDeleted": false, + "id": "4Tti92--0H7gjpAUScH3G", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 342.17364501953125, + "y": 286.07106018066406, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 296.2780151367187, + "height": 71.73760986328125, + "seed": 286998613, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "vYBURjhpnidE-WDIsIZDb" + }, + { + "id": "aTbcjlKf330UEpgFWTOzf", + "type": "arrow" + }, + { + "id": "v8DBp9AgXxaiFDDDZEYSp", + "type": "arrow" + }, + { + "id": "9cECIUVZDwqies7kKZQT5", + "type": "arrow" + }, + { + "id": "JaMoxQRsdp7k7NFEJb32C", + "type": "arrow" + } + ], + "updated": 1716807618801, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 285, + "versionNonce": 1802150302, + "index": "a1", + "isDeleted": false, + "id": "vYBURjhpnidE-WDIsIZDb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 374.3327865600586, + "y": 309.4398651123047, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 231.95973205566406, + "height": 25, + "seed": 1024425179, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1716808927627, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "attest-build-provenance", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "4Tti92--0H7gjpAUScH3G", + "originalText": "attest-build-provenance", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 218, + "versionNonce": 1763496539, + "index": "a2", + "isDeleted": false, + "id": "0yxlK86e283LeVjHXFVRS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 336.0631103515625, + "y": 105.1209716796875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 304.36248779296875, + "height": 51.36630249023438, + "seed": 1762557781, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "rUeGhcyqfkHSNZvEEli-S" + }, + { + "id": "aTbcjlKf330UEpgFWTOzf", + "type": "arrow" + } + ], + "updated": 1716808230290, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 186, + "versionNonce": 1329614658, + "index": "a3", + "isDeleted": false, + "id": "rUeGhcyqfkHSNZvEEli-S", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 424.29441833496094, + "y": 118.30412292480469, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 127.89987182617188, + "height": 25, + "seed": 215913147, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1716808927627, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "GitHub OIDC", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0yxlK86e283LeVjHXFVRS", + "originalText": "GitHub OIDC", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 299, + "versionNonce": 1648194523, + "index": "a4", + "isDeleted": false, + "id": "DfzDvf0kupVnu5v3j4-dJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1012.3241882324219, + "y": 275.79872131347656, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 143.46673583984375, + "height": 60.37509155273437, + "seed": 1342436219, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "B6kV5n9xaGtiYWzrbFDYT" + }, + { + "id": "v8DBp9AgXxaiFDDDZEYSp", + "type": "arrow" + }, + { + "id": "9cECIUVZDwqies7kKZQT5", + "type": "arrow" + } + ], + "updated": 1716808336741, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 281, + "versionNonce": 2132852190, + "index": "a5", + "isDeleted": false, + "id": "B6kV5n9xaGtiYWzrbFDYT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1057.257583618164, + "y": 293.48626708984375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.599945068359375, + "height": 25, + "seed": 1808649243, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1716808927627, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Fulcio", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "DfzDvf0kupVnu5v3j4-dJ", + "originalText": "Fulcio", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 669, + "versionNonce": 219423541, + "index": "a7", + "isDeleted": false, + "id": "aTbcjlKf330UEpgFWTOzf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 364.69518081008573, + "y": 159.5433349609375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.9754205556057514, + "height": 120.10044860839844, + "seed": 216623963, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1716808234285, + "link": null, + "locked": false, + "startBinding": { + "elementId": "0yxlK86e283LeVjHXFVRS", + "focus": 0.8038160034155345, + "gap": 3.056060791015625 + }, + "endBinding": { + "elementId": "4Tti92--0H7gjpAUScH3G", + "focus": -0.869910819869183, + "gap": 6.427276611328125 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2.9754205556057514, + 120.10044860839844 + ] + ] + }, + { + "type": "arrow", + "version": 397, + "versionNonce": 1944385365, + "index": "a9", + "isDeleted": false, + "id": "v8DBp9AgXxaiFDDDZEYSp", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 638.822265625, + "y": 280.80894470214844, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 372.73468017578125, + "height": 57.728271484375, + "seed": 721866933, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1716808341319, + "link": null, + "locked": false, + "startBinding": { + "elementId": "4Tti92--0H7gjpAUScH3G", + "focus": 0.06478487244907707, + "gap": 5.262115478515625 + }, + "endBinding": { + "elementId": "DfzDvf0kupVnu5v3j4-dJ", + "focus": 0.43871401398986964, + "gap": 7.846358297481288 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 184.5411376953125, + -57.728271484375 + ], + [ + 372.73468017578125, + -12.856581686153163 + ] + ] + }, + { + "type": "arrow", + "version": 366, + "versionNonce": 483958203, + "index": "aA", + "isDeleted": false, + "id": "9cECIUVZDwqies7kKZQT5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1029.2630772047282, + "y": 349.2771627591415, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 390.867752497697, + "height": 41.44968965785068, + "seed": 1154908885, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1716808336741, + "link": null, + "locked": false, + "startBinding": { + "elementId": "DfzDvf0kupVnu5v3j4-dJ", + "focus": -0.6953780624930885, + "gap": 13.103349892930567 + }, + "endBinding": { + "elementId": "4Tti92--0H7gjpAUScH3G", + "focus": 0.28067077697491943, + "gap": 3.053680419921875 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -194.57252544691573, + 41.44968965785068 + ], + [ + -390.867752497697, + 11.585187704725683 + ] + ] + }, + { + "type": "text", + "version": 350, + "versionNonce": 247011074, + "index": "aB", + "isDeleted": false, + "id": "d8zVG_lUN7ppSKEnjkQva", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 659.6842041015625, + "y": 281.55731201171875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 338.09967041015625, + "height": 50, + "seed": 74649499, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "v8DBp9AgXxaiFDDDZEYSp", + "type": "arrow" + }, + { + "id": "9cECIUVZDwqies7kKZQT5", + "type": "arrow" + } + ], + "updated": 1716808927628, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "2. Exchange OIDC token for cert\n1.3.6.1.4.1.57264.1.5: ianlewis/myrepo", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "2. Exchange OIDC token for cert\n1.3.6.1.4.1.57264.1.5: ianlewis/myrepo", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1169, + "versionNonce": 1423936725, + "index": "aD", + "isDeleted": false, + "id": "JaMoxQRsdp7k7NFEJb32C", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 468.7886747620609, + "y": 362.2537078857422, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 65.84621367910228, + "height": 112.4518975932437, + "seed": 2053577813, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1716808398239, + "link": null, + "locked": false, + "startBinding": { + "elementId": "4Tti92--0H7gjpAUScH3G", + "gap": 4.445037841796875, + "focus": 0.26616533660553987 + }, + "endBinding": { + "elementId": "-UBP1XbU24_6HLIZz9DI-", + "gap": 10.0413818359375, + "focus": -0.5528664715956453 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 65.84621367910228, + 112.4518975932437 + ] + ] + }, + { + "type": "rectangle", + "version": 662, + "versionNonce": 282307765, + "index": "aE", + "isDeleted": false, + "id": "-UBP1XbU24_6HLIZz9DI-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 541.6451416015625, + "y": 481.89488220214844, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 414.524658203125, + "height": 210, + "seed": 149404923, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "JaMoxQRsdp7k7NFEJb32C", + "type": "arrow" + }, + { + "type": "text", + "id": "75jpBpq96NoePeKzaTuxU" + } + ], + "updated": 1716808398238, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 650, + "versionNonce": 642339358, + "index": "aEV", + "isDeleted": false, + "id": "75jpBpq96NoePeKzaTuxU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 579.8576354980469, + "y": 486.89488220214844, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 338.09967041015625, + "height": 200, + "seed": 333134555, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1716808927628, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Provenance Predicate\n{\"repository\": \"ianlewis/myrepo\"}\n\nProvenance Signature\n\"sig\": \"MEUCIAgh6r2tXsy2eKzC9\"\n\nSigning Certificate (-private key)\n1.3.6.1.4.1.57264.1.5: ianlewis/myrepo", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "-UBP1XbU24_6HLIZz9DI-", + "originalText": "Provenance Predicate\n{\"repository\": \"ianlewis/myrepo\"}\n\nProvenance Signature\n\"sig\": \"MEUCIAgh6r2tXsy2eKzC9\"\n\nSigning Certificate (-private key)\n1.3.6.1.4.1.57264.1.5: ianlewis/myrepo", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 328, + "versionNonce": 994240194, + "index": "aJ", + "isDeleted": false, + "id": "xSz3qJzciydTDWgMyyWTk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 372.94181060791016, + "y": 188.7642059326172, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 254.11968994140625, + "height": 50, + "seed": 1432804475, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1716808927628, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "1. Get OIDC Token\nrepository: ianlewis/myrepo", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "1. Get OIDC Token\nrepository: ianlewis/myrepo", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 249, + "versionNonce": 1003839070, + "index": "aL", + "isDeleted": false, + "id": "2S39J5jXoPh8BgAJnnBMO", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 595.114501953125, + "y": 446.00880432128906, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 301.0596923828125, + "height": 25, + "seed": 2093164027, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1716808927628, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "3. Generate Provenace Bundle", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "3. Generate Provenace Bundle", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/assets/images/2024-05-30-understanding-github-artifact-attestations/sigstore.png b/assets/images/2024-05-30-understanding-github-artifact-attestations/sigstore.png new file mode 100644 index 0000000..56b31c1 Binary files /dev/null and b/assets/images/2024-05-30-understanding-github-artifact-attestations/sigstore.png differ diff --git a/en/_posts/2024-05-30-understanding-github-artifact-attestations.md b/en/_posts/2024-05-30-understanding-github-artifact-attestations.md new file mode 100644 index 0000000..825ffe2 --- /dev/null +++ b/en/_posts/2024-05-30-understanding-github-artifact-attestations.md @@ -0,0 +1,312 @@ +--- +layout: post +title: "Understanding GitHub Artifact Attestations" +date: 2024-05-30 00:00:00 +0000 +permalink: /en/understanding-github-artifact-attestations +blog: en +tags: security slsa +render_with_liquid: false +--- + +GitHub recently introduced [Artifact +Attestations](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/), +a beta feature that enhances the security of Open Source software supply +chains. By linking artifacts to their source code repositories and GitHub +Actions, it ensures that artifacts are not built with malicious or unknown code +or on potentially compromised devices. + +GitHub's blog post and +[documentation](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds#about-slsa-levels-for-artifact-attestations) +provides a comprehensive explanation of how Artifact Attestations work. +However, some questions remain unanswered, such as the specific security +measures implemented by the verification process, the reasons behind achieving +[SLSA Build Level +2](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds#about-slsa-levels-for-artifact-attestations) +instead of L3, and potential avenues for further improvement. + +I’ll try to shed some light on these aspects, explore areas where enhancements +could be made, and discuss how Artifact Attestations can be leveraged to +achieve SLSA Build Level 3. But first we need to understand its architectural +details and their relationship with SLSA levels. + +### Architecture + +Generating attestations is done using the +[`attest-build-provenance`](https://github.com/actions/attest-build-provenance) +GitHub action. Github’s blog post does a good job of explaining how it works so +I won’t rehash it fully here. Instead, I’ll summarize the flow and highlight +some additional information that will be important later. + +![Architecture Diagram](/assets/images/2024-05-30-understanding-github-artifact-attestations/sigstore.png "Architecture Diagram") + +1. `attest-build-provenance` requests an OIDC token from the GitHub OIDC + provider. This OIDC token contains [information about the + build](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token) + in the token claims. + + Here's what a typical OIDC token's fields look like: + + ```json + { + "aud": "https://github.com/octo-org", + "iss": "https://token.actions.githubusercontent.com", + "job_workflow_ref": "octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main", + "runner_environment": "github-hosted", + "repository": "octo-org/octo-repo", + "sha": "example-sha", + "ref": "refs/heads/main", + "repository_id": "74", + "repository_owner": "octo-org", + "repository_owner_id": "65", + "workflow": "example-workflow", + "event_name": "workflow_dispatch", + "run_id": "example-run-id", + "run_number": "10", + "run_attempt": "2", + "repository_visibility": "private" + // ... + } + ``` + +2. The OIDC token is sent to a [Sigstore + Fulcio](https://github.com/sigstore/fulcio) server (either the public + instance or GitHub’s private one). [Fulcio can recognize and validate + GitHub’s OIDC + tokens](https://docs.sigstore.dev/certificate_authority/oidc-in-fulcio/#github) + and, after verifying its signature, issues a certificate in exchange for the + OIDC token. This certificate includes much of the information from the OIDC + token [mapped into its OID extension + fields](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#mapping-oidc-token-claims-to-fulcio-oids) + as OID claims. + + ```shell + $ openssl x509 -in certificate.crt -text -noout + ... + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature + X509v3 Extended Key Usage: + Code Signing + X509v3 Subject Key Identifier: + 40:16:4D:A9:02:0E:97:E7:40:BA:A1:72:87:1A:1B:D0:FF:A4:30:FF + X509v3 Authority Key Identifier: + DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F + X509v3 Subject Alternative Name: critical + URI:https://github.com/ianlewis/gha-artifact-attestations-test/.github/workflows/artifact-attestations.basic.yml@refs/heads/main + 1.3.6.1.4.1.57264.1.1: + https://token.actions.githubusercontent.com + 1.3.6.1.4.1.57264.1.2: + push + 1.3.6.1.4.1.57264.1.3: + 9e68bb76632788dc01c6596298fb015f46b7fe0f + 1.3.6.1.4.1.57264.1.4: + Test Artifact Attestations + 1.3.6.1.4.1.57264.1.5: + ianlewis/gha-artifact-attestations-test + 1.3.6.1.4.1.57264.1.6: + refs/heads/main + 1.3.6.1.4.1.57264.1.8: + .+https://token.actions.githubusercontent.com + 1.3.6.1.4.1.57264.1.9: + .|https://github.com/ianlewis/gha-artifact-attestations-test/.github/workflows/artifact-attestations.basic.yml@refs/heads/main + 1.3.6.1.4.1.57264.1.10: + .(9e68bb76632788dc01c6596298fb015f46b7fe0f + 1.3.6.1.4.1.57264.1.11: + github-hosted . + 1.3.6.1.4.1.57264.1.12: + .:https://github.com/ianlewis/gha-artifact-attestations-test + ... + ``` + +3. A SLSA + [predicate](https://github.com/in-toto/attestation/blob/main/spec/v1/predicate.md) + is generated and the provenance statement is signed with the returned + certificate’s private key. The resulting signature is combined with the + provenance to create a full attestation bundle and this bundle is recorded + in GitHub’s attestation store. + + The SLSA predicate looks something like this: + + ```json + { + "buildDefinition": { + "buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1", + "externalParameters": { + "workflow": { + "ref": "refs/heads/main", + "repository": "https://github.com/ianlewis/gha-artifact-attestations-test", + "path": ".github/workflows/artifact-attestations.basic.yml" + } + }, + "internalParameters": { + "github": { + "event_name": "push", + "repository_id": "803607921", + "repository_owner_id": "49289" + } + }, + "resolvedDependencies": [ + { + "uri": "git+https://github.com/ianlewis/gha-artifact-attestations-test@refs/heads/main", + "digest": { + "gitCommit": "9e68bb76632788dc01c6596298fb015f46b7fe0f" + } + } + ] + }, + "runDetails": { + "builder": { + "id": "https://github.com/actions/runner/github-hosted" + }, + "metadata": { + "invocationId": "https://github.com/ianlewis/gha-artifact-attestations-test/actions/runs/9171197474/attempts/1" + } + } + } + ``` + +I'm leaving out some details but this is the general flow for attestation. So as +a result we have _both_ an attestation bundle in JSON format _and_ this +includes the Sigstore certificate with some OID claims set. + +After we have an artifact and attestation, as a user, we need to be able to +verify it before we use it. Verification works something like the following. The +code for this is found in the [`gh attestations verify` +command](https://github.com/cli/cli/tree/trunk/pkg/cmd/attestation/verify) +for GitHub’s official CLI tool. + +1. The attestation is downloaded from the GitHub Attestations API using the + artifact’s digest. +2. The attestation’s signature is verified against the public key used to sign + it, and to the certificate chain for either the GitHub Fulcio instance, or + the public Sigstore Fulcio instance (the root certificates are managed using + [TUF](https://theupdateframework.io/)). +3. The expected values for the owner or repo given by the user are matched + against the signing certificate’s OID claims. + +Notice that nowhere here is it actually necessary use the contents of the SLSA +predicate for verification. It isn't really necessary if you are treat the +certificate itself as the provenance. + +Next, let's discuss some of the trade-offs of this architecture. + +### A Good User Experience + +One of the positive aspects of Artifact Attestations is its user experience. By +providing a GitHub Action, GitHub gives users flexibility when integrating this +into their GitHub Actions workflows. All it takes is to add a job step to your +workflow and pass it a path to your artifact file. + +``` +- name: Attest Build Provenance + uses: actions/attest-build-provenance@897ed5eab6ed058a474202017ada7f40bfa52940 # v1.0.0 + with: + subject-path: "bin/my-artifact.tar.gz" +``` + +Easy-to-use UX is really important for security because it increases adoption. +This can’t be understated and is an often overlooked aspect of security +software. + +### Why SLSA Build L2 and not L3? + +One downside of the `attest-build-provenance` action is that it only meets the +requirements of SLSA Build L2 when used on its own. + +The main reason is that, by itself, it doesn’t meet this requirement for [SLSA +Build L3](https://slsa.dev/spec/v1.0/levels#build-l3-hardened-builds): + +_> prevent secret material used to sign the provenance from being accessible to the user-defined build steps._ + +Because GitHub Actions are run in the same job VM with other build steps there +is a chance that the user-defined build steps could access the secret material, +namely the certificate’s private key provided by Fulcio, and use it for +nefarious purposes. Normally an attacker, by compromising the build, might be +able to use this key material to make up their own provenance for a malicious +artifact and sign it with the key. + +However, as we’ll see, this kind of attack is somewhat mitigated by using Sigstore. + +### SLSA Build L2+? + +By using Sigstore’s Fulcio, the certificate used to sign the provenance contains +much of the SLSA predicate's information in its OID claims. These claims are +signed by part of Fulcio instance’s certificate chain and thus cannot be +modified by the user-defined build steps unless the Sigstore instance or +GitHub’s OIDC provider are compromised. + +While the SLSA predicate itself might be modified the certificate OID claims +cannot. So GitHub can check the OID claims against the expected values to verify +them even though the user-defined build steps had access to the signing key. +This is why verification doesn’t rely on the predicate and instead relies on the +certificate’s OID claims for verification. + +Some folks have colloquially referred to this combination of Sigstore and SLSA +Build L2 as SLSA Build L2+ since it provides some of the benefits of SLSA Build +L3 without actually fulfilling all of the requirements of L3. + +### The Limits of SLSA Build L2+ + +However, this comes with a few caveats. + +**First**, a compromised build with access to signing keys poses the threat of +using the key to sign and [publish malicious packages on a mirror +repository](https://checkmarx.com/blog/over-170k-users-affected-by-attack-using-fake-python-infrastructure/). +Sigstore's Fulcio mitigates this by providing short-lived keys with a validity +of 10 minutes, but attackers could still exfiltrate the key and use the key +within this time period to leave less evidence. + +**Second**, if user-defined build steps have access to the signing keys, +**only** the information in the certificate's OID claims is trustworthy. +Information not in the OID claims can't be included in the SLSA predicate, +affecting more complex data like GitHub Actions workflow +[inputs](https://docs.github.com/en/actions/learn-github-actions/contexts#inputs-context) +and +[vars](https://docs.github.com/en/actions/learn-github-actions/contexts#vars-context) +contexts. While these can't attack the provenance, they could be used to attack +the build without leaving a trace, making incident response more challenging. + +**Third**, the architecture heavily relies on Sigstore's Fulcio Certificate +Authority for mapping OID claims, making it difficult to use private PKI to sign +and verify provenance. While this isn't necessarily a significant concern for +open-source projects, some enterprises have such requirements. + +### Achieving SLSA Build L3 with GitHub Artifact Attestations + +In the documentation [SLSA Build L3 is described as +achievable](https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/#an-effortless-user-experience) +but it focuses on SLSA Build L2(+) and L3 is left as an exercise for the reader. +So what would it take to achieve SLSA Build L3 with GitHub Artifact +Attestations? + +One way that we have used in the +[slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator/tree/main) +project is to use [reusable +workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows). +This uses the fact that code in reusable workflows are isolated from the main +workflow because they run on jobs executed on [separate virtual +machines](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#the-components-of-github-actions). + +However, this comes with the downside that it is not quite as user friendly as a +simple GitHub Action. Passing data, especially files, between jobs is more +complicated and we are sometimes forced to expose that complexity to the user. +Given that there are significant security improvements, I think it’s worth it. + +### Conclusion + +Trust in GitHub’s Artifact Attestations trust really lies in the Sigstore +certificate and its OID claims so the certificate itself effectively functions +as the provenance. SLSA doesn’t even mandate that provenance be in SLSA format, +but the architecture of Artifact Attestations does make it a bit more +complicated to reason about the security implications. You shouldn't need to +trust the build when creating attestations. + +Artifact attestations are a really exciting new feature on GitHub and a great +step in the right direction. I would strongly consider integrating it into your +workflows, but first take a look at +[slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator/) +and see if SLSA L3 isn’t achievable for your projects. + +_Thanks to [Hayden Blauzvern](https://twitter.com/haydentherapper), [Ramon +Petgrave](https://twitter.com/thePetgrave), and [Laurent +Simon](https://twitter.com/lsim99) for reviewing this post._