├── .gitignore
├── assets
├── workflows.png
├── fulcio.mmd
└── fulcio.svg
├── go.mod
├── go.sum
├── Dockerfile
├── renovate.json
├── main.go
├── .github
└── workflows
│ └── app.yaml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/assets/workflows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liatrio/gh-trusted-builds-app/HEAD/assets/workflows.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/liatrio/gh-trusted-builds-app
2 |
3 | go 1.22
4 |
5 | toolchain go1.22.1
6 |
7 | require github.com/google/uuid v1.6.0
8 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22-alpine as builder
2 |
3 | WORKDIR /app
4 |
5 | COPY main.go go.mod go.sum ./
6 | RUN go build -o server ./
7 |
8 | ###
9 | FROM scratch
10 |
11 | WORKDIR /app
12 |
13 | COPY --from=builder /app/server .
14 |
15 | ENTRYPOINT ["/app/server"]
16 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base",
5 | ":semanticCommits",
6 | ":semanticCommitTypeAll(build)",
7 | ":semanticCommitScopeDisabled"
8 | ],
9 | "postUpdateOptions": [
10 | "gomodTidy"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "net/http"
8 |
9 | "github.com/google/uuid"
10 | )
11 |
12 | func main() {
13 | http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
14 | _, _ = w.Write([]byte("PONG"))
15 | })
16 |
17 | http.HandleFunc("/id", func(w http.ResponseWriter, r *http.Request) {
18 | id := uuid.New()
19 | _, _ = w.Write([]byte(fmt.Sprintf(`{"id": "%s"}`, id.String())))
20 | })
21 |
22 | http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
23 | _, _ = w.Write([]byte("OK"))
24 | })
25 |
26 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
27 | w.WriteHeader(http.StatusNotFound)
28 | })
29 |
30 | log.Println("Starting server on :8080")
31 | err := http.ListenAndServe(":8080", nil)
32 | if err != nil && !errors.Is(err, http.ErrServerClosed) {
33 | log.Fatalln("error starting server:", err)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.github/workflows/app.yaml:
--------------------------------------------------------------------------------
1 | name: app
2 | on:
3 | push:
4 | branches:
5 | - main
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build-and-push:
10 | uses: liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@v2.1.0
11 | permissions:
12 | actions: read
13 | id-token: write
14 | contents: read
15 | packages: write
16 | pull-requests: read
17 | security-scan:
18 | needs: [ build-and-push ]
19 | uses: liatrio/gh-trusted-builds-workflows/.github/workflows/scan-image.yaml@v2.1.0
20 | permissions:
21 | id-token: write
22 | contents: read
23 | packages: write
24 | with:
25 | digest: ${{ needs.build-and-push.outputs.digest }}
26 | policy-verification:
27 | needs:
28 | - build-and-push
29 | - security-scan
30 | uses: liatrio/gh-trusted-builds-workflows/.github/workflows/policy-verification.yaml@v2.1.0
31 | permissions:
32 | id-token: write
33 | contents: read
34 | packages: write
35 | with:
36 | digest: ${{ needs.build-and-push.outputs.digest }}
37 | deploy:
38 | needs:
39 | - build-and-push
40 | - policy-verification
41 | uses: liatrio/gh-trusted-builds-workflows/.github/workflows/demo-deploy.yaml@v2.1.0
42 | permissions:
43 | id-token: write
44 | packages: read
45 | contents: read
46 | with:
47 | digest: ${{ needs.build-and-push.outputs.digest }}
48 |
--------------------------------------------------------------------------------
/assets/fulcio.mmd:
--------------------------------------------------------------------------------
1 | %% npm install -g @mermaid-js/mermaid-cli
2 | %% mmdc -t dark -i fulcio.mmd -o fulcio.svg -b black
3 |
4 | sequenceDiagram
5 | autonumber
6 | participant CR as Container Registry
7 | participant Rekor
8 | participant Workflow
9 | participant GHA as GitHub Actions Issuer
10 | participant Fulcio
11 | participant CTL as Certificate Transparency Log
12 | Workflow->>Workflow: Build container image
13 | Workflow->>GHA: HTTP request to $ACTIONS_ID_TOKEN_REQUEST_URL with $ACTIONS_RUNTIME_TOKEN
14 | GHA-->>Workflow: ID token response
15 | Workflow->>Workflow: Generate public/private keypair
16 | Workflow->>Workflow: Create CSR or sign subject claim
17 | Workflow->>Fulcio: Signing certificate request
18 | Fulcio->>GHA: OpenID Connect discovery request
19 | GHA-->>Fulcio: OpenID Connect discovery response
20 | Fulcio->>GHA: JWKS request
21 | GHA-->>Fulcio: JWKS response
22 | Note over Fulcio,GHA: Note: these responses would likely be cached
23 | Fulcio->>Fulcio: Verify id token signature & claims
24 | Fulcio->>Fulcio: Create certificate from id token claims
25 | Fulcio->>CTL: Precertificate request for signed certificate timestamp (SCT)
26 | CTL-->>Fulcio: SCT response
27 | Fulcio-->>Workflow: Signing certificate response
28 | Workflow->>CR: Push Image & signature
29 | CR-->>Workflow: Registry response
30 | Workflow->>Rekor: Log entry request to upload signature & signing certificate
31 | Rekor-->>Workflow: Upload response
32 |
--------------------------------------------------------------------------------
/assets/fulcio.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gh-trusted-builds-app
2 |
3 | An example of using GitHub Actions [reusable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows) to create and deploy a trusted software artifact.
4 |
5 | ## Background
6 |
7 | These days, companies of all sizes and industries create software, from small scripts to complicated back-of-house inventory forecasting systems.
8 | For both security and compliance reasons, companies want to be able to demonstrate that the software they're using is secure.
9 | One way to achieve this goal is to have a trusted build system that can produce signed statements about the software artifacts it produces.
10 | An authenticated statement like this is called an attestation.
11 | Throughout the lifecycle of the artifact, the build system can produce attestations of different types in order to demonstrate that certain
12 | events occurred. For example:
13 |
14 | - multiple reviewers approved a particular code change
15 | - this container image was built from this code commit
16 | - a container image was scanned for vulnerabilities
17 |
18 | Later on, when the software is deployed, the deployment system can check for these attestations and verify that they meet the company's policy for
19 | software. Any attempted deployments that fail to meet the policy can be rejected, thereby preventing low quality or malicious software from entering the
20 | production environment.
21 |
22 | This repository is a demonstration of using GitHub Actions as that trusted build system. It's part of a number of repositories that you can find using the
23 | [automated-governance](https://github.com/search?q=topic%3Aautomated-governance+org%3Aliatrio&type=repositories) topic:
24 |
25 | - [`liatrio/github-trusted-builds-app`](https://github.com/liatrio/gh-trusted-builds-app): a very simple Go application
26 | - [`liatrio/github-trusted-builds-workflows`](https://github.com/liatrio/gh-trusted-builds-workflows): reusable workflows designed to be used by application teams
27 | - [`liatrio/github-trusted-builds-attestations`](https://github.com/liatrio/gh-trusted-builds-attestations): a tool for creating attestations of different types
28 | - [`liatrio/github-trusted-builds-policy`](https://github.com/liatrio/gh-trusted-builds-policy): Rego policy used to evaluate attestations
29 |
30 | ### Scenario
31 |
32 | In this demonstration, multiple teams must approve a software artifact for deployment to production.
33 | As a shorthand, we'll call these teams the "central" teams for their particular domain; as opposed to "application" teams, who are responsible for the development and maintenance of one or more applications.
34 |
35 | First up is the platform team, which is responsible for providing a Kubernetes cluster and deployment tooling.
36 | To facilitate automatic approvals, the platform team maintains a reusable GitHub Actions workflow that builds container images from source and pushes them to GitHub Container Registry.
37 | The images are annotated and signed by the workflow; in addition, a record of the signature is also
38 | uploaded to [Rekor](https://docs.sigstore.dev/rekor/overview/), which is a transparency log for supply chain security. In order to use the workflow, application teams only have to specify the reference to it in a job:
39 |
40 | ```yaml
41 | jobs:
42 | build-and-push:
43 | uses: liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@main
44 | ```
45 |
46 | The security team also needs to approve which artifacts are deployed to production.
47 | Like the platform team, they provide a reusable workflow that scans container images for vulnerabilities and another workflow that evaluates if an
48 | artifact meets enterprise policy. This latter workflow produces a [verification summary attestation](https://slsa.dev/verification_summary/v1) (VSA) which
49 | indicates if the artifact passed or failed policy. Later on, the platform team can check for the existence of this VSA and approve or deny deployment accordingly.
50 |
51 | ### Technologies
52 |
53 | Before diving into the workflows, it may be helpful to briefly review the core technologies used by the demo.
54 |
55 | #### in-toto Attestations
56 |
57 | Much of this demo is involved with producing attestations, or signed statements about a software artifact.
58 | A popular format for attestations comes from the [in-toto](https://in-toto.io/) project, which is focused on software supply-chain security.
59 | While this demonstration does not use in-toto directly, many open source projects outside in-toto use the in-toto attestation format; this includes all the attestations created in this demo.
60 |
61 | The in-toto format consists of several pieces:
62 | - predicate: structured information about a software artifact. The predicate type determines the structure and type of data available.
63 | - statement: contains the predicate and provides a list of subjects (software artifacts) described by the predicate.
64 | - envelope: the wrapper for the statement and any signatures.
65 |
66 | Here's an example of an in-toto attestation:
67 | ```json
68 | {
69 | "payloadType": "application/vnd.in-toto+json",
70 | "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9saWF0cmlvL2doLXRydXN0ZWQtYnVpbGRzLWFwcCIsImRpZ2VzdCI6eyJzaGEyNTYiOiI0ZDllNzdmZWRjYTI5MzkzZGZmOWEzOWIwNjVkMTU4YWFhOTY0NGMxOGIyYzUxZjc1ZmNhZjg0YjhjOTQxYWJmIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvdmVyaWZpY2F0aW9uX3N1bW1hcnkvdjAuMiIsInByZWRpY2F0ZSI6eyJpbnB1dF9hdHRlc3RhdGlvbnMiOlt7ImRpZ2VzdCI6eyJzaGEyNTYiOiJmMTQwZjg4NjA5NzQ5YWQ5OWUwNTgzYzUyZWNkNWNmMTZjMmU1ZTJjNTBjZDYxNDAzNjRhOTkxY2MxYWYyMmRhIn0sInVyaSI6Imh0dHBzOi8vcmVrb3Iuc2lnc3RvcmUuZGV2L2FwaS92MS9sb2cvZW50cmllcz9sb2dJbmRleD0yMTQ0OTgzMSJ9LHsiZGlnZXN0Ijp7InNoYTI1NiI6ImU3Y2M5ODk2OGU5MDJmMDA2M2I5YWQ0OTJiMjJlYzJkMGE2NDdmMzQzZWQxNjg5YzBhMzdjYjNlZjc2ZjljMzEifSwidXJpIjoiaHR0cHM6Ly9yZWtvci5zaWdzdG9yZS5kZXYvYXBpL3YxL2xvZy9lbnRyaWVzP2xvZ0luZGV4PTIxNDQ5ODM0In0seyJkaWdlc3QiOnsic2hhMjU2IjoiOTk1ZWY1ZjYwODkzOGYwM2JmNzQxNWI2NTIzYTVlYzFjYTIwZDFmMWNkMzdkNjUwYzIzM2FlYjM2NThmYjFmYSJ9LCJ1cmkiOiJodHRwczovL3Jla29yLnNpZ3N0b3JlLmRldi9hcGkvdjEvbG9nL2VudHJpZXM/bG9nSW5kZXg9MjE0NDk4NjEifV0sInBvbGljeSI6eyJ1cmkiOiJodHRwczovL2dpdGh1Yi5jb20vbGlhdHJpby9naC10cnVzdGVkLWJ1aWxkcy1wb2xpY3kvcmVsZWFzZXMvZG93bmxvYWQvdjEuMS4xL2J1bmRsZS50YXIuZ3oifSwicG9saWN5X2xldmVsIjoiU0xTQV9MRVZFTF8zIiwicmVzb3VyY2VfdXJpIjoiZ2hjci5pby9saWF0cmlvL2doLXRydXN0ZWQtYnVpbGRzLWFwcCIsInRpbWVfdmVyaWZpZWQiOiIyMDIzLTA1LTIzVDE3OjUyOjE0LjE4NjE2MTE2NVoiLCJ2ZXJpZmljYXRpb25fcmVzdWx0IjoiUEFTU0VEIiwidmVyaWZpZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vbGlhdHJpby9naC10cnVzdGVkLWJ1aWxkcy13b3JrZmxvd3MvLmdpdGh1Yi93b3JrZmxvd3MvcG9saWN5LXZlcmlmaWNhdGlvbi55YW1sQHJlZnMvaGVhZHMvbWFpbiJ9fX0=",
71 | "signatures": [
72 | {
73 | "keyid": "",
74 | "sig": "MEUCICYn68n2eOij6SLpgnzz1lyrW5dSixGRambvA/625DwiAiEAktVa8wx6mqSYpzzsVWUzcaAZcLsQYs/paYFRJGpSx2o="
75 | }
76 | ]
77 | }
78 | ```
79 |
80 | This is the envelope, which contains the in-toto statement in the payload field. Decoding the payload field shows the statement:
81 |
82 |
83 | example in-toto statement (click to expand)
84 |
85 | ```json
86 | {
87 | "_type": "https://in-toto.io/Statement/v1",
88 | "subject": [
89 | {
90 | "name": "ghcr.io/liatrio/gh-trusted-builds-app",
91 | "digest": {
92 | "sha256": "4d9e77fedca29393dff9a39b065d158aaa9644c18b2c51f75fcaf84b8c941abf"
93 | }
94 | }
95 | ],
96 | "predicateType": "https://slsa.dev/verification_summary/v0.2",
97 | "predicate": {
98 | "input_attestations": [
99 | {
100 | "digest": {
101 | "sha256": "f140f88609749ad99e0583c52ecd5cf16c2e5e2c50cd6140364a991cc1af22da"
102 | },
103 | "uri": "https://rekor.sigstore.dev/api/v1/log/entries?logIndex=21449831"
104 | },
105 | {
106 | "digest": {
107 | "sha256": "e7cc98968e902f0063b9ad492b22ec2d0a647f343ed1689c0a37cb3ef76f9c31"
108 | },
109 | "uri": "https://rekor.sigstore.dev/api/v1/log/entries?logIndex=21449834"
110 | },
111 | {
112 | "digest": {
113 | "sha256": "995ef5f608938f03bf7415b6523a5ec1ca20d1f1cd37d650c233aeb3658fb1fa"
114 | },
115 | "uri": "https://rekor.sigstore.dev/api/v1/log/entries?logIndex=21449861"
116 | }
117 | ],
118 | "policy": {
119 | "uri": "https://github.com/liatrio/gh-trusted-builds-policy/releases/download/v1.1.1/bundle.tar.gz"
120 | },
121 | "policy_level": "SLSA_LEVEL_3",
122 | "resource_uri": "ghcr.io/liatrio/gh-trusted-builds-app",
123 | "time_verified": "2023-05-23T17:52:14.186161165Z",
124 | "verification_result": "PASSED",
125 | "verifier": {
126 | "id": "https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/policy-verification.yaml@refs/heads/main"
127 | }
128 | }
129 | }
130 | ```
131 |
132 |
133 |
134 | This statement links a particular container image (`ghcr.io/liatrio/gh-trusted-builds-app@sha256:4d9e77fedca29393dff9a39b065d158aaa9644c18b2c51f75fcaf84b8c941abf`) to a predicate (`https://slsa.dev/verification_summary/v0.2`).
135 | We'll see more on this predicate type later. For more information about in-toto attestations, the [official specification](https://github.com/in-toto/attestation/tree/main/spec) is a good place to start.
136 |
137 | #### SLSA
138 |
139 | Software supply-chain security has been a growing concern in the past few years.
140 | [SLSA](https://slsa.dev/) (supply-chain levels for software artifacts) is an attempt to codify best practices around producing software artifacts by describing a series of standards and levels that a software artifact can obtain.
141 | A higher level indicates a more secure and robust software delivery process. The SLSA website gives a more [comprehensive overview](https://slsa.dev/spec/v1.0/levels) of the levels.
142 |
143 | The initial focus of SLSA is around how software is built, and the framework describes a [provenance attestation](https://slsa.dev/provenance/v1). Provenance links a software artifact to the source code from which it was created, as well as the method and environment in which the build ran.
144 | The project also provides some tools for creating provenance, like the [`slsa-github-generator`](https://github.com/slsa-framework/slsa-github-generator), which provides ways to create provenance attestations for different artifact types built in GitHub Actions.
145 | This demonstration uses the SLSA container generator to create a provenance attestation.
146 |
147 | #### Open Policy Agent
148 |
149 | Open Policy Agent (OPA) is a set of tools for writing and executing policies in a language called Rego.
150 | The policies are used to authorize some request or event, given input data. For example, this is part of a larger policy used in this demo to check that a pull request had at least one reviewer:
151 |
152 | ```rego
153 | package security.pullrequest
154 |
155 | default allow = false
156 |
157 | allow {
158 | count(violation) == 0
159 | }
160 |
161 | violation[msg] {
162 | count(input.predicate.reviewers) < 1
163 | msg := "pull request reviewers is less than 1"
164 | }
165 | ```
166 |
167 | This demonstration uses Rego in multiple places: first [`liatrio/github-trusted-builds-attestations`](https://github.com/liatrio/gh-trusted-builds-attestations) uses [policy](https://github.com/liatrio/gh-trusted-builds-policy) on
168 | the attestations generated by the workflow in order to produce a [verification summary attestation](https://slsa.dev/verification_summary/v1) (VSA). Later on, when the workflow attempts to deploy to a local Kubernetes cluster, the [Sigstore policy controller](https://docs.sigstore.dev/policy-controller/overview/)
169 | is configured with a Rego policy that checks the attributes of the VSA produced earlier.
170 |
171 | #### GitHub Actions: Reusable Workflows
172 |
173 | GitHub Actions has multiple ways to minimize duplication in a workflow -- one method is to create a [custom action](https://docs.github.com/en/actions/creating-actions/about-custom-actions).
174 | With [composite actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action), it's even possible to use wrap multiple external actions in a single reusable unit.
175 |
176 | However, for this demonstration, we're relying on a larger unit -- the [reusable workflow](https://docs.github.com/en/actions/using-workflows/reusing-workflows).
177 | A reusable workflow functions similarly to a composite action, in that it allows you to bundle multiple actions together and pass inputs/outputs between them.
178 | Instead of wrapping individual actions, the workflow can include one or more complete jobs, so we can perform separate tasks in parallel.
179 | In addition, a reusable workflow has different security features that make it desirable over other ways to reduce duplication in GitHub Actions.
180 |
181 | First and foremost is the fact that a reusable workflow runs in a separate virtual machine than other jobs in the caller's workflow.
182 | This is in contrast to an individual action, which always runs in the larger context of a job.
183 | Running on the same virtual machine as the workflow caller makes it difficult to access secrets in a way that doesn't also allow the caller to use them.
184 | It also means that a caller can manipulate the environment in order to do things like capture environment variables or snoop on network traffic.
185 | With reusable workflows, we can eliminate these concerns which would otherwise make it difficult for us to trust the attestations produced by the workflows.
186 |
187 | This demonstration also shows the use of least-privilege access with reusable workflows.
188 | Each reusable workflow is specifically granted the token permissions the caller will allow.
189 | This enables the caller repository to have a safer, read-only default to GitHub token permissions.
190 |
191 | #### GitHub Actions: OpenID Connect
192 |
193 | A GitHub Actions feature that complements reusable workflows is the [OpenID Connect (OIDC) integration](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect),
194 | which allows a job to acquire an [OIDC id token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken), which is a [JSON web token (JWT)](https://jwt.io/) signed by GitHub Actions.
195 |
196 | A JWT is a popular token format that contains three sections: a header, claims, and signature. The header contains metadata about the token, while the claims typically contain information
197 | about the entity described by the token, as well as details needed to validate the token, such as the token issuer and expiry. Finally, the signature encompasses the header and claims, so that by verifying the signature,
198 | we know that we can trust the information in the other two sections.
199 |
200 | In the case of GitHub Actions, the claims in the token describe the running workflow:
201 |
202 | ```json
203 | {
204 | "jti": "79ba45c1-a46f-4605-b68d-8207b5a5287f",
205 | "sub": "repo:liatrio/gh-trusted-builds-app:ref:refs/heads/main",
206 | "aud": "https://github.com/liatrio",
207 | "ref": "refs/heads/main",
208 | "sha": "0c960da8e1b1844d14ac4048b60f2ec892ed21a3",
209 | "repository": "liatrio/gh-trusted-builds-app",
210 | "repository_owner": "liatrio",
211 | "repository_owner_id": "5726618",
212 | "run_id": "4724225373",
213 | "run_number": "22",
214 | "run_attempt": "1",
215 | "repository_visibility": "public",
216 | "repository_id": "627556067",
217 | "actor_id": "9082799",
218 | "actor": "alexashley",
219 | "workflow": "app",
220 | "head_ref": "",
221 | "base_ref": "",
222 | "event_name": "workflow_dispatch",
223 | "ref_type": "branch",
224 | "workflow_ref": "liatrio/gh-trusted-builds-app/.github/workflows/app.yaml@refs/heads/main",
225 | "workflow_sha": "0c960da8e1b1844d14ac4048b60f2ec892ed21a3",
226 | "job_workflow_ref": "liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main",
227 | "job_workflow_sha": "f1e5fbbfa4f750e221038adca773bca75ec38c3d",
228 | "runner_environment": "github-hosted",
229 | "iss": "https://token.actions.githubusercontent.com",
230 | "nbf": 1681753278,
231 | "exp": 1681754178,
232 | "iat": 1681753878
233 | }
234 | ```
235 |
236 | One claim in particular, `job_workflow_ref`, is instrumental to the security properties of this demonstration; it indicates the workflow path and Git reference that were used in this job. You may also notice the similarly-named `workflow_ref` claim, which is the path to the running workflow definition.
237 | When using a normal workflow, the values of these two claims is the same, but when a reusable workflow is called by another workflow, the value of `job_workflow_ref` will be the path and Git ref of the reusable workflow.
238 | Because the token is signed by GitHub Actions, as long as we validate the token signature and claims, we can trust that only a running instance of the workflow is in possession of the token.
239 | This property allows us to create federated trust in order to access [cloud provider resources](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers) or even request [code-signing certificates](https://github.com/sigstore/fulcio).
240 |
241 | In order to validate tokens, we need to know what keys were used to sign the tokens. GitHub Actions hosts an OpenID Connect provider at https://token.actions.githubusercontent.com, which includes an [OIDC discovery document](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata) at the `/.well-known/openid-configuration` endpoint:
242 |
243 | ```json
244 | {
245 | "issuer": "https://token.actions.githubusercontent.com",
246 | "jwks_uri": "https://token.actions.githubusercontent.com/.well-known/jwks",
247 | "subject_types_supported": [
248 | "public",
249 | "pairwise"
250 | ],
251 | "response_types_supported": [
252 | "id_token"
253 | ],
254 | "claims_supported": [
255 | "sub",
256 | "aud",
257 | "exp",
258 | "iat",
259 | "iss",
260 | "jti",
261 | "nbf",
262 | "ref",
263 | "repository",
264 | "repository_id",
265 | "repository_owner",
266 | "repository_owner_id",
267 | "run_id",
268 | "run_number",
269 | "run_attempt",
270 | "actor",
271 | "actor_id",
272 | "workflow",
273 | "workflow_ref",
274 | "workflow_sha",
275 | "head_ref",
276 | "base_ref",
277 | "event_name",
278 | "ref_type",
279 | "environment",
280 | "environment_node_id",
281 | "job_workflow_ref",
282 | "job_workflow_sha",
283 | "repository_visibility",
284 | "runner_environment"
285 | ],
286 | "id_token_signing_alg_values_supported": [
287 | "RS256"
288 | ],
289 | "scopes_supported": [
290 | "openid"
291 | ]
292 | }
293 | ```
294 |
295 | The `jwks_uri` field ([JSON web key set](https://datatracker.ietf.org/doc/html/rfc7517#section-1)) contains a URL that can be used to grab the public keys which are needed to verify the signature on the JWTs issued
296 | by GitHub Actions.
297 |
298 | Finally, it's worth mentioning how a job receives an id token, as it's not one of the standard OAuth2 grants.
299 | In order to acquire an id token, a job must first be configured with the `id-token` permission:
300 |
301 | ```yaml
302 | jobs:
303 | demo:
304 | permissions:
305 | id-token: write
306 | ```
307 |
308 | Later on, once the job is running, it will have access to the following environment variables:
309 | - `ACTIONS_ID_TOKEN_REQUEST_URL`
310 | - `ACTIONS_RUNTIME_TOKEN`
311 |
312 | The job can then make an HTTP `POST` request to `$ACTIONS_ID_TOKEN_REQUEST_URL` with `$ACTIONS_RUNTIME_TOKEN` in the authorization header (using the [bearer token scheme](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1)), and the job will receive an
313 | id token in the `value` field of the JSON response.
314 |
315 | For a more in-depth overview of using GitHub Actions' OIDC feature with reusable workflows, the SLSA GitHub Generator [specifications](https://github.com/slsa-framework/slsa-github-generator/blob/main/SPECIFICATIONS.md)
316 | go into additional detail.
317 |
318 | #### Sigstore: Cosign
319 |
320 | Cosign is a tool from the [Sigstore project](https://www.sigstore.dev/) for signing and verifying container images, blobs, and attestations. It also supports running [OPA](https://www.openpolicyagent.org/docs/latest/) or [CUE](https://cuelang.org/docs/) policies
321 | against those attestations. In this demonstration, we'll use cosign to sign both a container image and a number of attestations about the image.
322 |
323 | #### Sigstore: Policy Controller
324 |
325 | Another Sigstore project, the [policy controller](https://docs.sigstore.dev/policy-controller/overview/), is used to gate Kubernetes deployments with OPA or CUE policy.
326 | It can verify that images are signed and check the attestations against a supplied policy. We'll use it in this demo to check that our container image has an attestation signed by the right identity.
327 |
328 | #### Sigstore: Rekor Transparency Log
329 |
330 | [Rekor](https://docs.sigstore.dev/rekor/overview/) is an open-source software supply chain tool that’s part of the larger Sigstore project.
331 | It consists of a [transparency log](https://transparency.dev/verifiable-data-structures/#verifiable-log) server and a command line tool to interact with the server. While there is a [public instance](https://rekor.sigstore.dev/), enterprises can also deploy and run Rekor on their own infrastructure.
332 |
333 | Rekor has a number of properties that make it useful as an attestation store; first, its underlying storage is a robust, append-only log – new entries can be added, but old entries cannot be updated or deleted.
334 | Rekor’s implementation also provides the ability to demonstrate that an entry is actually included in the log, which users can verify through what’s called an inclusion proof.
335 | Similarly, by recording certain metadata about the log, users can also verify over time that the log hasn’t been tampered with and that only new entries have been created (through a consistency proof).
336 |
337 | Rekor is used to store container image signatures, along with the public key or certificate that's needed to verify the signature. It can also store [in-toto attestations](https://in-toto.io/), along with a few other [built-in record types](https://docs.sigstore.dev/rekor/sign-upload/).
338 |
339 | #### Sigstore: Fulcio Certificate Authority
340 |
341 | Fulcio is a certificate authority for code-signing -- it issues short-lived (10 minute) certificates that anyone can use to sign images or attestations.
342 | The [official documentation](https://docs.sigstore.dev/fulcio/certificate-issuing-overview/) goes into more detail, but at a high level this is the flow for acquiring a code-signing certificate:
343 |
344 | 1. Acquire an id token from an issuer that Fulcio is configured to trust. This demo uses the GitHub Actions id token
345 | 1. Generate a temporary public/private key pair - `cosign` will do this automatically
346 | 1. Prove that you control the key pair by doing one of the following:
347 | - Create a certificate signing request (CSR)
348 | - Grab the `sub` claim from the id token and sign it
349 | 1. Make a request to Fulcio with the id token and the proof from the previous step
350 | 1. Fulcio will validate the id token and check the provided proof
351 | 1. If the request is valid, Fulcio will issue a code-signing certificate and populate the certificate fields with information from the id token
352 | - When using a GitHub Actions OIDC token, the issuer will be `https://token.actions.githubusercontent.com` and the subject will be the value of the `job_workflow_ref` claim
353 | 1. Depending on the deployment type, Fulcio will also submit a request to a certificate transparency log to include the certificate in the logs. The response from the certificate transparency log will include a signed timestamp that will be added to the certificate.
354 |
355 | After receiving the certificate, `cosign` or other tools can sign images or attestations with the private key generated earlier, and then upload the signature and certificate to Rekor.
356 | Once the signing is finished, the key pair can be discarded, as the certificate will be in Rekor for validation later.
357 |
358 | For a more concrete example, this is a sequence diagram that shows how a GitHub Actions workflow might build & sign a container image:
359 |
360 | 
361 |
362 | Later, when someone tries to validate the signature, they can check that the certificate chains up to the Fulcio root, that the signature happened during the window when the certificate was valid,
363 | and, most importantly, verify that the certificate identity matches the expected signer.
364 | Anyone can use the [Sigstore public good Fulcio instance](https://fulcio.sigstore.dev/) to get a certificate, so it's important that you only trust signatures from identities that you trust.
365 |
366 | #### Software Bill of Materials (SBOM)
367 |
368 | A software bill of materials describes the components that make up a software artifact.
369 | The SBOM includes the dependencies of an artifact and may include licensing information about the artifact and its dependencies.
370 | While an SBOM can be useful to enforce internal licensing standards, it's also valuable for vulnerability analysis; this is especially true when there's a need to retroactively look at software running in production and determine if it's vulnerable to a new attack.
371 | If an organization already has SBOMs for the software it's running, it becomes much easier to tell if there's an impact from a new vulnerability.
372 |
373 | There are several standard formats, like [SPDX](https://spdx.dev/) from the Linux Foundation and [CycloneDX](https://cyclonedx.org/) from OWASP.
374 | Fortunately, there are a number of tools for generating and working with SBOMs and most tools understand multiple standards. In this demonstration, we're using SPDX as the format and [Syft](https://github.com/anchore/syft) as the tool to generate SBOMs.
375 |
376 | ## Workflows
377 |
378 | The demo makes use of several reusable workflows defined in [`liatrio/gh-trusted-builds-workflows`](https://github.com/liatrio/gh-trusted-builds-workflows).
379 | Each workflow is owned by either the platform or security teams.
380 |
381 | 
382 |
383 | ### Platform: Build & Push
384 |
385 | The platform team's `build-and-push` workflow is split into several jobs:
386 | - `detect-workflow`
387 | - `build`
388 | - `push`
389 | - `sign`
390 | - `source-attestations`
391 | - `provenance`
392 | - `sbom`
393 |
394 | The `build` job uses Docker to build a container image, but doesn't push it to a container registry.
395 | Instead, the job outputs a tar file that will be pushed by the next job.
396 | The reason to split build and push is that the build step is executing untrusted code from the application team; consequently, we don't want that job to be able to request the id token that's used for signing or to try to push a malicious image.
397 | It's also often the case that organizations will use a central registry for all teams, and the authenticated machine identity is likely to have access to more repositories than the one used by the application image.
398 | In that case, the `build` job could even push images to other repositories; while the authentication in this demo is scoped to the `liatrio/gh-trusted-builds-app` repository, it's still important to separate these steps for those reasons.
399 |
400 | So the `build` job only has the permissions it needs in order to checkout the repo:
401 |
402 | ```yaml
403 | jobs:
404 | build:
405 | permissions:
406 | contents: read
407 | ```
408 |
409 | Next up is the push job, which loads the tar file from the build job and pushes it to GitHub Container Registry (GHCR). Like the build job, it only has the permissions it needs to write to the registry:
410 |
411 | ```yaml
412 | jobs:
413 | push:
414 | permissions:
415 | packages: write
416 | ```
417 |
418 | With the image built and stored in GHCR, we can now sign the image in the `sign` job.
419 | This job has access to the workflow id token so that it can request a signing certificate from Fulcio; the job also needs permission to push the signature to the registry, so it has that access as well:
420 |
421 | ```yaml
422 | jobs:
423 | sign:
424 | permissions:
425 | id-token: write
426 | contents: read
427 | packages: write
428 | ```
429 |
430 | The `sign` job uses `cosign` to sign the image and annotate it with the workflow run:
431 |
432 | ```yaml
433 | - name: Sign
434 | run: |
435 | cosign sign \
436 | --annotations liatr.io/github-actions-run-link='${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' \
437 | --annotations liatr.io/signed-off-by=platform-team \
438 | --rekor-url ${{ inputs.rekorUrl }} \
439 | --fulcio-url ${{ inputs.fulcioUrl }} \
440 | --yes ghcr.io/${{ github.repository }}@${{ needs.push.outputs.digest }}
441 | ```
442 |
443 | Next, the `provenance` and `source-attestations` job both produce attestations that are signed by the workflow.
444 | The `provenance` job uses the container generator from [`slsa-framework/slsa-github-generator`](https://github.com/slsa-framework/slsa-github-generator) to produce a [provenance attestation](https://slsa.dev/provenance/v1) that links the container image and source code, along with some metadata about how the artifact was produced.
445 | Because the workflow is only using the generator, it doesn't have the full context necessary to populate the entire provenance, so some fields aren't present.
446 |
447 | Here's an example of the provenance generated by this job ([Rekor entry](https://search.sigstore.dev/?logIndex=21341723)):
448 |
449 |
450 | SLSA Provenance attestation (click to expand)
451 |
452 | ```yaml
453 | _type: https://in-toto.io/Statement/v0.1
454 | predicateType: https://slsa.dev/provenance/v0.2
455 | subject:
456 | - name: ghcr.io/liatrio/gh-trusted-builds-app
457 | digest:
458 | sha256: 6c3bf887638f7c0d86731e6208befa1b439e465cb435465d982c50609553b514
459 | predicate:
460 | builder:
461 | id: >-
462 | https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main
463 | buildType: https://github.com/slsa-framework/slsa-github-generator/container@v1
464 | invocation:
465 | configSource:
466 | uri: git+https://github.com/liatrio/gh-trusted-builds-app@refs/heads/main
467 | digest:
468 | sha1: e1f1d4396181766e12fca22f2ba856e8154b4304
469 | entryPoint: .github/workflows/app.yaml
470 | parameters: {}
471 | environment:
472 | github_actor: rcoy-v
473 | github_actor_id: '9846738'
474 | github_base_ref: ''
475 | github_event_name: push
476 | github_event_payload:
477 | after: e1f1d4396181766e12fca22f2ba856e8154b4304
478 | base_ref: null
479 | before: c4df379485ab80b62ae0cc57c611348a3015f944
480 | commits:
481 | - author:
482 | email: ryanv@liatrio.com
483 | name: Ryan Vance
484 | username: rcoy-v
485 | committer:
486 | email: noreply@github.com
487 | name: GitHub
488 | username: web-flow
489 | distinct: true
490 | id: e1f1d4396181766e12fca22f2ba856e8154b4304
491 | message: 'docs: remove extra newline (#1)'
492 | timestamp: '2023-05-22T10:27:27-05:00'
493 | tree_id: e5dc0b7046c70012c4b84bc7ede6f969efe79edf
494 | url: >-
495 | https://github.com/liatrio/gh-trusted-builds-app/commit/e1f1d4396181766e12fca22f2ba856e8154b4304
496 | compare: >-
497 | https://github.com/liatrio/gh-trusted-builds-app/compare/c4df379485ab...e1f1d4396181
498 | created: false
499 | deleted: false
500 | forced: false
501 | head_commit:
502 | author:
503 | email: ryanv@liatrio.com
504 | name: Ryan Vance
505 | username: rcoy-v
506 | committer:
507 | email: noreply@github.com
508 | name: GitHub
509 | username: web-flow
510 | distinct: true
511 | id: e1f1d4396181766e12fca22f2ba856e8154b4304
512 | message: 'docs: remove extra newline (#1)'
513 | timestamp: '2023-05-22T10:27:27-05:00'
514 | tree_id: e5dc0b7046c70012c4b84bc7ede6f969efe79edf
515 | url: >-
516 | https://github.com/liatrio/gh-trusted-builds-app/commit/e1f1d4396181766e12fca22f2ba856e8154b4304
517 | organization:
518 | avatar_url: https://avatars.githubusercontent.com/u/5726618?v=4
519 | description: Enterprise Delivery Transformation, DevOps, Cloud Native Automation
520 | events_url: https://api.github.com/orgs/liatrio/events
521 | hooks_url: https://api.github.com/orgs/liatrio/hooks
522 | id: 5726618
523 | issues_url: https://api.github.com/orgs/liatrio/issues
524 | login: liatrio
525 | members_url: https://api.github.com/orgs/liatrio/members{/member}
526 | node_id: MDEyOk9yZ2FuaXphdGlvbjU3MjY2MTg=
527 | public_members_url: https://api.github.com/orgs/liatrio/public_members{/member}
528 | repos_url: https://api.github.com/orgs/liatrio/repos
529 | url: https://api.github.com/orgs/liatrio
530 | pusher:
531 | email: ryanv@liatrio.com
532 | name: rcoy-v
533 | ref: refs/heads/main
534 | repository:
535 | allow_forking: true
536 | archive_url: >-
537 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/{archive_format}{/ref}
538 | archived: false
539 | assignees_url: >-
540 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/assignees{/user}
541 | blobs_url: >-
542 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/git/blobs{/sha}
543 | branches_url: >-
544 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/branches{/branch}
545 | clone_url: https://github.com/liatrio/gh-trusted-builds-app.git
546 | collaborators_url: >-
547 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/collaborators{/collaborator}
548 | comments_url: >-
549 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/comments{/number}
550 | commits_url: >-
551 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/commits{/sha}
552 | compare_url: >-
553 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/compare/{base}...{head}
554 | contents_url: >-
555 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/contents/{+path}
556 | contributors_url: >-
557 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/contributors
558 | created_at: 1684768749
559 | default_branch: main
560 | deployments_url: >-
561 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/deployments
562 | description: null
563 | disabled: false
564 | downloads_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/downloads
565 | events_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/events
566 | fork: false
567 | forks: 0
568 | forks_count: 0
569 | forks_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/forks
570 | full_name: liatrio/gh-trusted-builds-app
571 | git_commits_url: >-
572 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/git/commits{/sha}
573 | git_refs_url: >-
574 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/git/refs{/sha}
575 | git_tags_url: >-
576 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/git/tags{/sha}
577 | git_url: git://github.com/liatrio/gh-trusted-builds-app.git
578 | has_discussions: false
579 | has_downloads: true
580 | has_issues: true
581 | has_pages: false
582 | has_projects: true
583 | has_wiki: true
584 | homepage: null
585 | hooks_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/hooks
586 | html_url: https://github.com/liatrio/gh-trusted-builds-app
587 | id: 643991426
588 | is_template: false
589 | issue_comment_url: >-
590 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/issues/comments{/number}
591 | issue_events_url: >-
592 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/issues/events{/number}
593 | issues_url: >-
594 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/issues{/number}
595 | keys_url: >-
596 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/keys{/key_id}
597 | labels_url: >-
598 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/labels{/name}
599 | language: Go
600 | languages_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/languages
601 | license: null
602 | master_branch: main
603 | merges_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/merges
604 | milestones_url: >-
605 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/milestones{/number}
606 | mirror_url: null
607 | name: gh-trusted-builds-app
608 | node_id: R_kgDOJmKHgg
609 | notifications_url: >-
610 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/notifications{?since,all,participating}
611 | open_issues: 0
612 | open_issues_count: 0
613 | organization: liatrio
614 | owner:
615 | avatar_url: https://avatars.githubusercontent.com/u/5726618?v=4
616 | email: cloudservices@liatrio.com
617 | events_url: https://api.github.com/users/liatrio/events{/privacy}
618 | followers_url: https://api.github.com/users/liatrio/followers
619 | following_url: https://api.github.com/users/liatrio/following{/other_user}
620 | gists_url: https://api.github.com/users/liatrio/gists{/gist_id}
621 | gravatar_id: ''
622 | html_url: https://github.com/liatrio
623 | id: 5726618
624 | login: liatrio
625 | name: liatrio
626 | node_id: MDEyOk9yZ2FuaXphdGlvbjU3MjY2MTg=
627 | organizations_url: https://api.github.com/users/liatrio/orgs
628 | received_events_url: https://api.github.com/users/liatrio/received_events
629 | repos_url: https://api.github.com/users/liatrio/repos
630 | site_admin: false
631 | starred_url: https://api.github.com/users/liatrio/starred{/owner}{/repo}
632 | subscriptions_url: https://api.github.com/users/liatrio/subscriptions
633 | type: Organization
634 | url: https://api.github.com/users/liatrio
635 | private: false
636 | pulls_url: >-
637 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/pulls{/number}
638 | pushed_at: 1684769247
639 | releases_url: >-
640 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/releases{/id}
641 | size: 0
642 | ssh_url: git@github.com:liatrio/gh-trusted-builds-app.git
643 | stargazers: 0
644 | stargazers_count: 0
645 | stargazers_url: >-
646 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/stargazers
647 | statuses_url: >-
648 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/statuses/{sha}
649 | subscribers_url: >-
650 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/subscribers
651 | subscription_url: >-
652 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/subscription
653 | svn_url: https://github.com/liatrio/gh-trusted-builds-app
654 | tags_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/tags
655 | teams_url: https://api.github.com/repos/liatrio/gh-trusted-builds-app/teams
656 | topics: []
657 | trees_url: >-
658 | https://api.github.com/repos/liatrio/gh-trusted-builds-app/git/trees{/sha}
659 | updated_at: '2023-05-22T15:19:19Z'
660 | url: https://github.com/liatrio/gh-trusted-builds-app
661 | visibility: public
662 | watchers: 0
663 | watchers_count: 0
664 | web_commit_signoff_required: false
665 | sender:
666 | avatar_url: https://avatars.githubusercontent.com/u/9846738?v=4
667 | events_url: https://api.github.com/users/rcoy-v/events{/privacy}
668 | followers_url: https://api.github.com/users/rcoy-v/followers
669 | following_url: https://api.github.com/users/rcoy-v/following{/other_user}
670 | gists_url: https://api.github.com/users/rcoy-v/gists{/gist_id}
671 | gravatar_id: ''
672 | html_url: https://github.com/rcoy-v
673 | id: 9846738
674 | login: rcoy-v
675 | node_id: MDQ6VXNlcjk4NDY3Mzg=
676 | organizations_url: https://api.github.com/users/rcoy-v/orgs
677 | received_events_url: https://api.github.com/users/rcoy-v/received_events
678 | repos_url: https://api.github.com/users/rcoy-v/repos
679 | site_admin: false
680 | starred_url: https://api.github.com/users/rcoy-v/starred{/owner}{/repo}
681 | subscriptions_url: https://api.github.com/users/rcoy-v/subscriptions
682 | type: User
683 | url: https://api.github.com/users/rcoy-v
684 | github_head_ref: ''
685 | github_ref: refs/heads/main
686 | github_ref_type: branch
687 | github_repository_id: '643991426'
688 | github_repository_owner: liatrio
689 | github_repository_owner_id: '5726618'
690 | github_run_attempt: '1'
691 | github_run_id: '5047631192'
692 | github_run_number: '4'
693 | github_sha1: e1f1d4396181766e12fca22f2ba856e8154b4304
694 | metadata:
695 | buildInvocationID: 5047631192-1
696 | completeness:
697 | parameters: true
698 | environment: false
699 | materials: false
700 | reproducible: false
701 | materials:
702 | - uri: git+https://github.com/liatrio/gh-trusted-builds-app@refs/heads/main
703 | digest:
704 | sha1: e1f1d4396181766e12fca22f2ba856e8154b4304
705 | ```
706 |
707 |
708 |
709 | In addition to the provenance, the `build-and-push` workflow's `source-attestation` job produces a custom pull request attestation using [`liatrio/gh-trusted-builds-attestations`](https://github.com/liatrio/gh-trusted-builds-attestations).
710 | This attestation ties the commit to the pull request and includes information about whether the pull request was approved and which individuals reviewed it.
711 | Many enterprises have policies that require a minimum number of reviewers, so this attestation can be used to require that artifacts were built only from approved source code changes.
712 |
713 | Here's an example of a pull request attestation produced by the workflow ([Rekor entry](https://search.sigstore.dev/?logIndex=21341724)):
714 |
715 | ```yaml
716 | _type: https://in-toto.io/Statement/v0.1
717 | predicateType: https://liatr.io/attestations/github-pull-request/v1
718 | subject:
719 | - name: git+https://github.com/liatrio/gh-trusted-builds-app.git
720 | digest:
721 | sha1: e1f1d4396181766e12fca22f2ba856e8154b4304
722 | - name: ghcr.io/liatrio/gh-trusted-builds-app
723 | digest:
724 | sha256: 6c3bf887638f7c0d86731e6208befa1b439e465cb435465d982c50609553b514
725 | predicate:
726 | link: https://github.com/liatrio/gh-trusted-builds-app/pull/1
727 | title: 'docs: remove extra newline'
728 | author: rcoy-v
729 | mergedBy: rcoy-v
730 | createdAt: '2023-05-22T15:27:05Z'
731 | mergedAt: '2023-05-22T15:27:27Z'
732 | base: main
733 | head: rcoy-v-patch-1
734 | approved: true
735 | reviewers:
736 | - name: alexashley
737 | approved: true
738 | reviewLink: >-
739 | https://github.com/liatrio/gh-trusted-builds-app/pull/1#pullrequestreview-1436887240
740 | timestamp: '2023-05-22T15:27:18Z'
741 | contributors:
742 | - name: rcoy-v
743 | predicateCreatedAt: '2023-05-22T15:28:48.369418041Z'
744 | ```
745 |
746 | Finally, the last job in this workflow, called `sbom`, produces a software bill of materials using [Syft](https://github.com/anchore/syft).
747 | The workflow runs Syft against the pushed image and produces an SBOM in the [SPDX](https://spdx.dev/) format.
748 | Then the job runs `cosign attest` in order to sign and upload the SBOM as an attestation.
749 | While there's support in Syft for attesting the SBOM through `sfyt attest`, we're using `cosign` directly for more fine-grained configuration:
750 |
751 | ```
752 | $ syft -o spdx-json --file sbom.spdx.json ghcr.io/${{ github.repository }}@${{ needs.push.outputs.digest }}
753 |
754 | $ cosign attest --predicate="sbom.spdx.json" \
755 | --rekor-url ${{ inputs.rekorUrl }} \
756 | --type spdxjson \
757 | --fulcio-url ${{ inputs.fulcioUrl }} \
758 | --yes \
759 | ghcr.io/${{ github.repository }}@${{ needs.push.outputs.digest }}
760 | ```
761 |
762 | For an example of an SBOM, here's an attestation from one of the workflow runs ([Rekor log entry](https://search.sigstore.dev/?logIndex=21791183)):
763 |
764 |
765 | SPDX bill of materials (click to expand)
766 |
767 | ```yaml
768 | _type: https://in-toto.io/Statement/v0.1
769 | predicateType: https://spdx.dev/Document
770 | subject:
771 | - name: ghcr.io/liatrio/gh-trusted-builds-app
772 | digest:
773 | sha256: 294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
774 | predicate:
775 | SPDXID: SPDXRef-DOCUMENT
776 | creationInfo:
777 | created: '2023-05-26T22:02:06Z'
778 | creators:
779 | - 'Organization: Anchore, Inc'
780 | - 'Tool: syft-0.82.0'
781 | licenseListVersion: '3.20'
782 | dataLicense: CC0-1.0
783 | documentNamespace: >-
784 | https://anchore.com/syft/image/ghcr.io/liatrio/gh-trusted-builds-app@sha256-294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc-5b3e747f-d8dc-4a0b-a01f-60d9ed082d68
785 | files:
786 | - SPDXID: SPDXRef-File-app-d20c3eddd3b3b879
787 | checksums:
788 | - algorithm: SHA1
789 | checksumValue: '0000000000000000000000000000000000000000'
790 | comment: >-
791 | layerID:
792 | sha256:53ea96ed00f53fed01d48a16b049a99938902ed5ee4517e57f464d7fabacb33f
793 | copyrightText: ''
794 | fileName: /app
795 | fileTypes:
796 | - OTHER
797 | licenseConcluded: NOASSERTION
798 | - SPDXID: SPDXRef-File-app-server-50b4875c6ae53f25
799 | checksums:
800 | - algorithm: SHA256
801 | checksumValue: a947dcd63d19e76e860f32f8bdd33ca47297d10d8e6f1a2eb913224d93fe9fe4
802 | comment: >-
803 | layerID:
804 | sha256:53ea96ed00f53fed01d48a16b049a99938902ed5ee4517e57f464d7fabacb33f
805 | copyrightText: ''
806 | fileName: /app/server
807 | fileTypes:
808 | - APPLICATION
809 | - BINARY
810 | licenseConcluded: NOASSERTION
811 | name: >-
812 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
813 | packages:
814 | - SPDXID: >-
815 | SPDXRef-Package-go-module-github.com-liatrio-gh-trusted-builds-app-1808e23a377c2b90
816 | copyrightText: NOASSERTION
817 | downloadLocation: NOASSERTION
818 | externalRefs:
819 | - referenceCategory: SECURITY
820 | referenceLocator: cpe:2.3:a:liatrio:gh-trusted-builds-app:\(devel\):*:*:*:*:*:*:*
821 | referenceType: cpe23Type
822 | - referenceCategory: SECURITY
823 | referenceLocator: cpe:2.3:a:liatrio:gh_trusted_builds_app:\(devel\):*:*:*:*:*:*:*
824 | referenceType: cpe23Type
825 | - referenceCategory: PACKAGE-MANAGER
826 | referenceLocator: pkg:golang/github.com/liatrio/gh-trusted-builds-app@(devel)
827 | referenceType: purl
828 | licenseConcluded: NOASSERTION
829 | licenseDeclared: NOASSERTION
830 | name: github.com/liatrio/gh-trusted-builds-app
831 | sourceInfo: 'acquired package info from go module information: /app/server'
832 | versionInfo: (devel)
833 | relationships:
834 | - comment: >-
835 | evident-by: indicates the package's existence is evident by the given
836 | file
837 | relatedSpdxElement: SPDXRef-File-app-server-50b4875c6ae53f25
838 | relationshipType: OTHER
839 | spdxElementId: >-
840 | SPDXRef-Package-go-module-github.com-liatrio-gh-trusted-builds-app-1808e23a377c2b90
841 | - relatedSpdxElement: SPDXRef-DOCUMENT
842 | relationshipType: DESCRIBES
843 | spdxElementId: SPDXRef-DOCUMENT
844 | spdxVersion: SPDX-2.3
845 | ```
846 |
847 |
848 |
849 |
850 | ### Security: Image Scan
851 |
852 | Now that the image is built, it needs to be scanned for vulnerabilities by an approved image scanner.
853 | The security team uses [Trivy](https://github.com/aquasecurity/trivy) for this, because it supports outputting the scan results in a format that `cosign` can use to create an attestation:
854 |
855 | ```yaml
856 | steps:
857 | - name: Trivy Scan
858 | uses: aquasecurity/trivy-action@0.9.2
859 | with:
860 | image-ref: ghcr.io/${{ github.repository }}@${{ inputs.digest }}
861 | format: 'cosign-vuln'
862 | output: trivy.report.json
863 | env:
864 | TRIVY_USERNAME: ${{ github.actor }}
865 | TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
866 | ```
867 |
868 | The `scan-image` workflow only contains a single job called `scan`, which takes as an input the digest of the image created in the `build-and-push` workflow.
869 | It invokes the Trivy GitHub Action and then uses `cosign attest` to upload the vulnerability results as an attestation to Rekor.
870 |
871 | This is an example of what the vulnerability attestation looks like ([Rekor entry](https://search.sigstore.dev/?logIndex=21341756)):
872 |
873 |
874 | Vulnerability attestation (click to expand)
875 |
876 | ```yaml
877 | _type: https://in-toto.io/Statement/v0.1
878 | predicateType: https://cosign.sigstore.dev/attestation/vuln/v1
879 | subject:
880 | - name: ghcr.io/liatrio/gh-trusted-builds-app
881 | digest:
882 | sha256: 6c3bf887638f7c0d86731e6208befa1b439e465cb435465d982c50609553b514
883 | predicate:
884 | invocation:
885 | parameters: null
886 | uri: ''
887 | event_id: ''
888 | builder.id: ''
889 | scanner:
890 | uri: pkg:github/aquasecurity/trivy@0.38.1
891 | version: 0.38.1
892 | db:
893 | uri: ''
894 | version: ''
895 | result:
896 | ArtifactName: >-
897 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:6c3bf887638f7c0d86731e6208befa1b439e465cb435465d982c50609553b514
898 | ArtifactType: container_image
899 | Metadata:
900 | DiffIDs:
901 | - >-
902 | sha256:0c2717ceeb1e6dec4b2a748974b77fd8f13ac5c3d9d434f80f2c1b58c83f31ae
903 | - >-
904 | sha256:8a90007133baa3918410eca530757f8e7c65aff8421ac010a185ff51c2f88e80
905 | ImageConfig:
906 | architecture: amd64
907 | config:
908 | Entrypoint:
909 | - /app/server
910 | Env:
911 | - >-
912 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
913 | Labels:
914 | org.opencontainers.image.created: '2023-05-22T15:27:46.155Z'
915 | org.opencontainers.image.description: ''
916 | org.opencontainers.image.licenses: ''
917 | org.opencontainers.image.revision: e1f1d4396181766e12fca22f2ba856e8154b4304
918 | org.opencontainers.image.source: https://github.com/liatrio/gh-trusted-builds-app
919 | org.opencontainers.image.title: gh-trusted-builds-app
920 | org.opencontainers.image.url: https://github.com/liatrio/gh-trusted-builds-app
921 | org.opencontainers.image.version: main
922 | WorkingDir: /app
923 | created: '2023-05-22T15:28:09.391753905Z'
924 | history:
925 | - comment: buildkit.dockerfile.v0
926 | created: '2023-05-22T15:27:48.389191081Z'
927 | created_by: WORKDIR /app
928 | - comment: buildkit.dockerfile.v0
929 | created: '2023-05-22T15:28:09.391753905Z'
930 | created_by: 'COPY /app/server . # buildkit'
931 | - comment: buildkit.dockerfile.v0
932 | created: '2023-05-22T15:28:09.391753905Z'
933 | created_by: ENTRYPOINT ["/app/server"]
934 | empty_layer: true
935 | os: linux
936 | rootfs:
937 | diff_ids:
938 | - >-
939 | sha256:0c2717ceeb1e6dec4b2a748974b77fd8f13ac5c3d9d434f80f2c1b58c83f31ae
940 | - >-
941 | sha256:8a90007133baa3918410eca530757f8e7c65aff8421ac010a185ff51c2f88e80
942 | type: layers
943 | ImageID: >-
944 | sha256:dfde59c7ffd4a99f202c45669cc6311dadc906d82f7e40f445ebc7a226bd8781
945 | RepoDigests:
946 | - >-
947 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:6c3bf887638f7c0d86731e6208befa1b439e465cb435465d982c50609553b514
948 | SchemaVersion: 2
949 | metadata:
950 | scanStartedOn: '2023-05-22T15:29:20.193667225Z'
951 | scanFinishedOn: '2023-05-22T15:29:20.193667225Z'
952 | ```
953 |
954 |
955 |
956 | The image produced by the demo is very simple, and as of this writing doesn't contain any vulnerabilities.
957 | However, if there were vulnerabilities in the image, they would appear in the attestation and policy could be used to reject artifacts that either had too many vulnerabilities or had certain high-risk CVEs.
958 |
959 | ### Security: Policy Verification
960 |
961 | Now that we've attested to the number of code reviewers, built and signed an image, attested to image provenance and vulnerabilities, it's time to evaluate those attestations.
962 | While it would be possible to use a workflow or [Kubernetes validating admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) to evaluate all the attestations,
963 | it would be cumbersome to have that controller know everything that needs to be validated about the image. Especially considering that enterprises will refine their internal policies over time and need to make adjustments to every deployment gate.
964 |
965 | Instead, we can evaluate all the attestations up to this point and produce a [verification summary attestation](https://slsa.dev/verification_summary/v1), which will attest that the container image either passed or failed a particular policy.
966 |
967 | In order to do this, we use [`liatrio/gh-trusted-builds-attestations`](https://github.com/liatrio/gh-trusted-builds-attestations) to pull the attestations for an image, verify that the attestations were signed by the expected workflows, and
968 | then run [policy](https://github.com/liatrio/gh-trusted-builds-policy) against those attestations in order to check if the image meets the standards enforced by the policy.
969 |
970 | Either way, the attestations tool will produce a VSA that can be used later to prevent low quality or malicious images from being deployed. Here's an example of what the VSA produced by the workflow looks like ([Rekor entry](https://search.sigstore.dev/?logIndex=21341780)):
971 |
972 | ```yaml
973 | _type: https://in-toto.io/Statement/v1
974 | subject:
975 | - name: ghcr.io/liatrio/gh-trusted-builds-app
976 | digest:
977 | sha256: 6c3bf887638f7c0d86731e6208befa1b439e465cb435465d982c50609553b514
978 | predicateType: https://slsa.dev/verification_summary/v0.2
979 | predicate:
980 | input_attestations:
981 | - digest:
982 | sha256: 1c735dab58c44079863b1a5e209617f8357409d63203e2309894e0b0d1e0ffaa
983 | uri: https://rekor.sigstore.dev/api/v1/log/entries?logIndex=21341724
984 | - digest:
985 | sha256: cdcf6759b454b26b6f151b2913c2949d4b1bef8c97d78085553f55ededc79f15
986 | uri: https://rekor.sigstore.dev/api/v1/log/entries?logIndex=21341756
987 | policy:
988 | uri: >-
989 | https://github.com/liatrio/gh-trusted-builds-policy/releases/download/v1.1.1/bundle.tar.gz
990 | policy_level: SLSA_LEVEL_3
991 | resource_uri: ghcr.io/liatrio/gh-trusted-builds-app
992 | time_verified: '2023-05-22T15:29:58.061215474Z'
993 | verification_result: PASSED
994 | verifier:
995 | id: >-
996 | https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/policy-verification.yaml@refs/heads/main
997 | ```
998 |
999 | Internally, the `policy-verification` workflow has two jobs:
1000 | - `detect-workflow`
1001 | - `verify`
1002 |
1003 | The `detect-workflow` job determines the reusable workflow ref by requesting an id token and grabbing the `job_workflow_ref` claim, which is used later by the `verify` job to populate the `verifier.id` field in the SLSA VSA.
1004 | This job may be removed in the future if GitHub enhances the `github` context by making the `job_workflow_ref` information available directly.
1005 |
1006 | Next, the `verify` job invokes [`liatrio/gh-trusted-builds-attestations`](https://github.com/liatrio/gh-trusted-builds-attestations) with the `vsa` subcommand:
1007 |
1008 | ```yaml
1009 | - name: Create Verification Summary Attestation
1010 | env:
1011 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1012 | run: |
1013 | attestation vsa \
1014 | --artifact-uri ghcr.io/${{ github.repository }} \
1015 | --artifact-digest ${{ inputs.digest }} \
1016 | --policy-url "https://github.com/liatrio/gh-trusted-builds-policy/releases/download/v1.1.1/bundle.tar.gz" \
1017 | --verifier-id ${{ github.server_url }}/${{ needs.metadata.outputs.jobWorkflowRef }} \
1018 | --fulcio-url ${{ inputs.fulcioUrl }} \
1019 | --rekor-url ${{ inputs.rekorUrl }}
1020 | ```
1021 |
1022 | The `GITHUB_TOKEN` environment variable is used to query GHCR for attestations and also to push the VSA to the registry after verification is finished.
1023 | Like the other jobs that produce attestations, it also needs the ability to request an id token:
1024 |
1025 | ```yaml
1026 | jobs:
1027 | verify:
1028 | permissions:
1029 | id-token: write
1030 | contents: read
1031 | packages: write
1032 | ```
1033 |
1034 | ### Platform: Deployment
1035 |
1036 | Finally, we'd like to actually deploy the artifact that the workflow built.
1037 | In an enterprise setting, this may not be part of the same workflow that built the image, but the deployment is included here to show an end-to-end picture.
1038 |
1039 | In this workflow, we first download [`k3d`](https://k3d.io/v5.5.1/), which is a lightweight Kubernetes distribution that runs in Docker.
1040 | It'll serve as our deployment target for the demo, as we can use it to spin up a temporary Kubernetes cluster in the GitHub Actions runner.
1041 |
1042 | Once it's installed, we can create a simple cluster called `demo`:
1043 |
1044 | ```shell
1045 | $ k3d cluster create --agents 1 --no-lb --wait demo
1046 | ```
1047 |
1048 | Now we can install and configure the Sigstore [`policy-controller`](https://docs.sigstore.dev/policy-controller/overview/), which is what we'll use to verify the VSA produced by the `policy-verification` workflow.
1049 | The `policy-controller` has a custom `ClusterImagePolicy` Kubernetes resource that we need to create in order to tell the controller what attestations to verify.
1050 | For the ease of development, this policy is hard-coded in the workflow:
1051 |
1052 | ```yaml
1053 | apiVersion: policy.sigstore.dev/v1alpha1
1054 | kind: ClusterImagePolicy
1055 | metadata:
1056 | name: demo
1057 | spec:
1058 | images:
1059 | - glob: "ghcr.io/liatrio/gh-trusted-builds-app**"
1060 | authorities:
1061 | - name: attestation
1062 | keyless:
1063 | url: https://fulcio.sigstore.dev
1064 | trustRootRef:
1065 | identities:
1066 | - issuer: https://token.actions.githubusercontent.com
1067 | subject: https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/policy-verification.yaml@refs/heads/main
1068 | ctlog:
1069 | url: https://rekor.sigstore.dev
1070 | trustRootRef:
1071 | attestations:
1072 | - name: has-passing-vsa
1073 | predicateType: "https://slsa.dev/verification_summary/v0.2"
1074 | policy:
1075 | type: rego
1076 | data: |
1077 | package sigstore
1078 | default isCompliant = false
1079 | isCompliant {
1080 | input.predicate.verifier.id == "https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/policy-verification.yaml@refs/heads/main"
1081 | input.predicate.verification_result == "PASSED"
1082 | }
1083 | ```
1084 |
1085 | This `ClusterImagePolicy` says we're expecting an attestation of type `https://slsa.dev/verification_summary/v0.2` that was signed with a certificate issued from the GitHub Actions issuer via Fulcio, and that the certificate was issued to the `policy-verification` workflow.
1086 | In the attestation policy, we also check that the VSA result was `PASSED` and that the verifier was the one we expect.
1087 |
1088 | If the policy controller allows the deployment, then the end result won't look any different from a normal Kubernetes deployment.
1089 | However, if image fails to meet policy, the deployment will be blocked and this message will be returned from the Kubernetes API server:
1090 |
1091 | ```
1092 | error: failed to create deployment: admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: demo: spec.template.spec.containers[0].image
1093 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:4c2a8a95f7ecc73abc62abd699dfb33579dd29ecde4f44f009dbbca6305609e4 failed evaluating rego policy for type has-passing-vsa: policy is not compliant for query 'isCompliant = data.sigstore.isCompliant'
1094 | ```
1095 |
1096 | Unfortunately, it doesn't appear that there's much feedback on what aspect of the policy failed. In this case, it's a very simple policy, which would make it easier to understand what failed.
1097 | However, there's more that could be done to make the error understandable for end-users.
1098 |
1099 | ## Verification
1100 |
1101 | While the image attestations are validated by [`liatrio/gh-trusted-builds-attestations`](https://github.com/liatrio/gh-trusted-builds-attestations) and the `policy-controller`, it's also possible to validate the steps that were done in
1102 | this demo by using `cosign` directly.
1103 |
1104 | To demonstrate, we'll use this [workflow run](https://github.com/liatrio/gh-trusted-builds-app/actions/runs/5095256269) which produced the image `ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc`.
1105 |
1106 | To get an overview of the images' attestations and signatures, we can use `cosign tree`:
1107 |
1108 | ```shell
1109 | $ 📦 Supply Chain Security Related artifacts for an image: ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1110 | └── 💾 Attestations for an image tag: ghcr.io/liatrio/gh-trusted-builds-app:sha256-294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc.att
1111 | ├── 🍒 sha256:86f0c07ab8720d5676dd38aa3c46c2179475f760d7fdbdeb8374292a00f86226
1112 | ├── 🍒 sha256:937fea3f79cff9469147cadb127e7721fb66eaee20e228756cad7c20b77296d8
1113 | ├── 🍒 sha256:e4a4239487d5a33cd3836d2654c7b76148b1810d1005c34796be3499021ed297
1114 | ├── 🍒 sha256:28dc5117fe59531af747159a7227f3f0a21175581b13873cce15912e3aaef204
1115 | └── 🍒 sha256:c4b6fb8b2b0e352135d21635d6724c93587370a179e61c39a32089d4d85274e8
1116 | └── 🔐 Signatures for an image tag: ghcr.io/liatrio/gh-trusted-builds-app:sha256-294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc.sig
1117 | └── 🍒 sha256:010e941fc66633c54e29f2576dcb1e1c642a8c9313781b668dec26903a7998af
1118 | ```
1119 |
1120 | First, we'll verify that the image was signed by the platform team's `build-and-push` workflow:
1121 |
1122 | ```shell
1123 | $ cosign verify \
1124 | --annotations liatr.io/signed-off-by=platform-team \
1125 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1126 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main \
1127 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1128 | --rekor-url https://rekor.sigstore.dev \
1129 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1130 |
1131 | Verification for ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc --
1132 | The following checks were performed on each of these signatures:
1133 | - The specified annotations were verified.
1134 | - The cosign claims were validated
1135 | - Existence of the claims in the transparency log was verified offline
1136 | - The code-signing certificate was verified using trusted certificate authority certificates
1137 | ```
1138 |
1139 | The `verify` subcommand will also output the signature, so we can manually inspect the other fields for more information:
1140 |
1141 |
1142 | Image signature (click to expand)
1143 |
1144 | ```json
1145 | [
1146 | {
1147 | "critical": {
1148 | "identity": {
1149 | "docker-reference": "ghcr.io/liatrio/gh-trusted-builds-app"
1150 | },
1151 | "image": {
1152 | "docker-manifest-digest": "sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc"
1153 | },
1154 | "type": "cosign container image signature"
1155 | },
1156 | "optional": {
1157 | "1.3.6.1.4.1.57264.1.1": "https://token.actions.githubusercontent.com",
1158 | "1.3.6.1.4.1.57264.1.2": "workflow_dispatch",
1159 | "1.3.6.1.4.1.57264.1.3": "54a0e5823b30c4fb8d0ff93b532e64d9478e012d",
1160 | "1.3.6.1.4.1.57264.1.4": "app",
1161 | "1.3.6.1.4.1.57264.1.5": "liatrio/gh-trusted-builds-app",
1162 | "1.3.6.1.4.1.57264.1.6": "refs/heads/main",
1163 | "Bundle": {
1164 | "SignedEntryTimestamp": "MEUCIQDLeeJTlGROlwuXen9V8c0vA0gNjh1kCgFI21I7hDlBDQIgQ9mpdPXzkNt3Sg/KupxNTUH6JvcOEa0JvAxdimybue8=",
1165 | "Payload": {
1166 | "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIwMTBlOTQxZmM2NjYzM2M1NGUyOWYyNTc2ZGNiMWUxYzY0MmE4YzkzMTM3ODFiNjY4ZGVjMjY5MDNhNzk5OGFmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNnMW4vVkZqV2xlL3V2bWpYMWszL3dTYWhKb3Y5c0hPY0VsRnp2L2gvT3Z3SWhBT0VvYnVLNFd6VzhQQ2cvVUptT2V2ODRHb0YwQ0RqRjVHQmlVN0s0dUFkcyIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaEJSRU5EUW05WFowRjNTVUpCWjBsVlEwVmhOVmhxTm5sSFdqSjZXazloVDAweFNta3JjRkJGTTJJMGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcE5kMDVVU1RKTmFrbDNUVlJGTTFkb1kwNU5hazEzVGxSSk1rMXFTWGhOVkVVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZsY1hORlFuZDBUMXBUU1hkSldUaEZSVmhQVGswMWJVdFFPQ3RUYW5wcE1EZEpkQzhLV2pkTk1ESm1hbE5rZEhCWFkwNTBjelpEZG5CNGNXeFhLekZVWm10TlRWVnFRVzkyY2l0WlFUQjRXbFZIYlRWM2R6WlBRMEpoVVhkbloxZG5UVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZyZDJaSENqTXlMMXBXVDBJNVIxZFNhSFJXYzFFeGJGbFdWemhaZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJWbldVUldVakJTUVZGSUwwSklRWGRpYjFwellVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKNGNGbFlVbmxoVnpoMldqSm5kQXBrU0VveFl6TlNiRnBETVdsa1YyeHpXa2hOZEdReU9YbGhNbHB6WWpOa2VreDVOVzVoV0ZKdlpGZEpkbVF5T1hsaE1scHpZak5rZWt3eVNqRmhWM2hyQ2t4WFJuVmFRekYzWkZoT2IweHViR2hpVjNoQlkyMVdiV041T1c5YVYwWnJZM2s1ZEZsWGJIVk5SR3RIUTJselIwRlJVVUpuTnpoM1FWRkZSVXN5YURBS1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFaRU0xYW1JeU1IZElkMWxMUzNkWlFncENRVWRFZG5wQlFrRm5VVkprTWpsNVlUSmFjMkl6WkdaYVIyeDZZMGRHTUZreVozZE9aMWxMUzNkWlFrSkJSMFIyZWtGQ1FYZFJiMDVVVW1oTlIxVXhDazlFU1hwWmFrMTNXWHBTYlZscWFHdE5SMXB0VDFST2FVNVVUWGxhVkZrd1drUnJNRTU2YUd4TlJFVjVXa1JCVWtKbmIzSkNaMFZGUVZsUEwwMUJSVVVLUWtGT2FHTklRWGRMZDFsTFMzZFpRa0pCUjBSMmVrRkNRbEZSWkdKSGJHaGtTRXB3WW5rNWJtRkRNVEJqYmxaNlpFZFdhMHhYU2pGaFYzaHJZM2t4YUFwalNFRjNTRkZaUzB0M1dVSkNRVWRFZG5wQlFrSm5VVkJqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxRWMwZERhWE5IUVZGUlFtYzNPSGRCVVdkRkNreFJkM0poU0ZJd1kwaE5Oa3g1T1RCaU1uUnNZbWsxYUZrelVuQmlNalY2VEcxa2NHUkhhREZaYmxaNldsaEthbUl5TlRCYVZ6VXdURzFPZG1KVVFqZ0tRbWR2Y2tKblJVVkJXVTh2VFVGRlNrSkhORTFpUjJnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemx6WVZkR01HTnRiSFpNTW1SdlRGaFNlUXBrV0U0d1dsZFJkRmx1Vm5CaVIxSjZURmhrZG1OdGRHMWlSemt6WTNrNGRWb3liREJoU0ZacFRETmtkbU50ZEcxaVJ6a3pZM2s1YVdSWGJITmFRekZvQ21KdFVYUmpTRlo2WVVNMU5WbFhNWE5SU0Vwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFUUkNaMjl5UW1kRlJVRlpUeTlOUVVWTFFrTnZUVXRFUVhvS1dtcG5OVTU2YXpGWmFtaHJUbTFOTkZsdFNUTk9NazE1VGtSWmVWcFhVVFJOZWxwb1RXcG9iVnBVUW1oTlZFSnFXVmRWZDBoUldVdExkMWxDUWtGSFJBcDJla0ZDUTNkUlVFUkJNVzVoV0ZKdlpGZEpkR0ZIT1hwa1IxWnJUVVZCUjBOcGMwZEJVVkZDWnpjNGQwRlJkMFZOWjNkM1lVaFNNR05JVFRaTWVUbHVDbUZZVW05a1YwbDFXVEk1ZEV3eWVIQlpXRko1WVZjNGRsb3laM1JrU0VveFl6TlNiRnBETVdsa1YyeHpXa2hOZEZsWVFuZE5SR2RIUTJselIwRlJVVUlLWnpjNGQwRlJNRVZMWjNkdlRsUlNhRTFIVlRGUFJFbDZXV3BOZDFsNlVtMVphbWhyVFVkYWJVOVVUbWxPVkUxNVdsUlpNRnBFYXpCT2VtaHNUVVJGZVFwYVJFRm1RbWR2Y2tKblJVVkJXVTh2VFVGRlQwSkNSVTFFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFWcENaMjl5UW1kRlJVRlpUeTlOUVVWUUNrSkJjMDFEVkZrd1RYcHJOVTFVVVhsT2FrRnhRbWR2Y2tKblJVVkJXVTh2VFVGRlVVSkNkMDFIYldnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWUtZbE01YzJGWFJqQmpiV3gyVFVKalIwTnBjMGRCVVZGQ1p6YzRkMEZTUlVWRFVYZElUbFJqZVU1cVdYaFBSRUp5UW1kdmNrSm5SVVZCV1U4dlRVRkZVd3BDUmpCTlZ6Sm9NR1JJUW5wUGFUaDJXakpzTUdGSVZtbE1iVTUyWWxNNWMyRlhSakJqYld4MlRESmtiMHhZVW5sa1dFNHdXbGRSZEZsdVZuQmlSMUo2Q2t4WFJuZGpRemgxV2pKc01HRklWbWxNTTJSMlkyMTBiV0pIT1ROamVUbG9ZMGhCZFdWWFJuUmlSVUo1V2xkYWVrd3lhR3haVjFKNlRESXhhR0ZYTkhjS1QwRlpTMHQzV1VKQ1FVZEVkbnBCUWtWM1VYRkVRMmN4VGtkRmQxcFVWVFJOYWs1cFRYcENhazVIV21sUFIxRjNXbTFaTlUweVNURk5la3BzVG1wU2F3cFBWRkV6VDBkVmQwMVVTbXROUTBWSFEybHpSMEZSVVVKbk56aDNRVkpSUlVWM2QxSmtNamw1WVRKYWMySXpaR1phUjJ4NlkwZEdNRmt5WjNkWmQxbExDa3QzV1VKQ1FVZEVkbnBCUWtaUlVsWkVSazV2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2RzYUdSSVNuQmllVGx1WVVNeE1HTnVWbm9LWkVkV2EweFhTakZoVjNoclkza3hhR05JUVhaWlYwNHdZVmM1ZFdONU9YbGtWelY2VEhwVmQwOVVWWGxPVkZsNVRtcHJkbGxZVWpCYVZ6RjNaRWhOZGdwTlZFTkNhVkZaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamRDU0d0QlpIZENNVUZPTURsTlIzSkhlSGhGZVZsNGEyVklTbXh1VG5kTGFWTnNOalF6YW5sMENpODBaVXRqYjBGMlMyVTJUMEZCUVVKcFJtOVZhMWhOUVVGQlVVUkJSVmwzVWtGSloyTk1VSEpwTjJWcVp5dGhjMnA2Tm5OdGJHOXNUekpXUmtoSFZVNEtNa2hSV0RsTWFtOTFiakJUWW01QlEwbEhjRlpHUkhaWFpXTk9WbEIwVkU1elRWSTVaSFZuWnpGaGJWcEpWM0pRYlV0WlFYaFFNM0ZZZDNaUlRVRnZSd3BEUTNGSFUwMDBPVUpCVFVSQk1tdEJUVWRaUTAxUlEwbFBNakY1TlhSUVpHMUJkRGhJY1ROdFdEbE1SbEZGZEdWeWVHTjNVM0Z2YTFobE1USldSbVZLQ2xWRWMyVTBjSGhhU1VoaFpWYzVUa2hRVDNSdWRsQnJRMDFSUkZOSGRIWkdOVVprUjBsa1RFRjJZMnQxZDNJeFEyeHBXSE5uV25GNlkyMHpZVkY0WlVrS2RIZHhlbFJqV2taS1NXWnFha3hOTmtoTVRrWnNVRkpDTlRsTlBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifX19fQ==",
1167 | "integratedTime": 1685138477,
1168 | "logIndex": 21791129,
1169 | "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
1170 | }
1171 | },
1172 | "Issuer": "https://token.actions.githubusercontent.com",
1173 | "Subject": "https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main",
1174 | "githubWorkflowName": "app",
1175 | "githubWorkflowRef": "refs/heads/main",
1176 | "githubWorkflowRepository": "liatrio/gh-trusted-builds-app",
1177 | "githubWorkflowSha": "54a0e5823b30c4fb8d0ff93b532e64d9478e012d",
1178 | "githubWorkflowTrigger": "workflow_dispatch",
1179 | "liatr.io/github-actions-run-link": "https://github.com/liatrio/gh-trusted-builds-app/actions/runs/5095256269",
1180 | "liatr.io/signed-off-by": "platform-team"
1181 | }
1182 | }
1183 | ]
1184 | ```
1185 |
1186 |
1187 |
1188 | Now we'll verify each attestation that the pipeline produced using the `cosign verify-attestation` command
1189 | - `https://liatr.io/attestations/github-pull-request/v1`
1190 | - `https://slsa.dev/provenance/v0.2`
1191 | - `https://cosign.sigstore.dev/attestation/vuln/v1`
1192 | - `https://spdx.dev/Document`
1193 | - `https://slsa.dev/verification_summary/v0.2`
1194 |
1195 | First up is the custom pull request attestation. It was produced in the `build-and-push` workflow, so attestation verification looks similar to verifying the image:
1196 |
1197 | ```shell
1198 | $ cosign verify-attestation \
1199 | --type https://liatr.io/attestations/github-pull-request/v1 \
1200 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1201 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main \
1202 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1203 | --rekor-url https://rekor.sigstore.dev \
1204 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1205 |
1206 | Verification for ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc --
1207 | The following checks were performed on each of these signatures:
1208 | - The cosign claims were validated
1209 | - Existence of the claims in the transparency log was verified offline
1210 | - The code-signing certificate was verified using trusted certificate authority certificates
1211 | Certificate subject: https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main
1212 | Certificate issuer URL: https://token.actions.githubusercontent.com
1213 | GitHub Workflow Trigger: workflow_dispatch
1214 | GitHub Workflow SHA: 54a0e5823b30c4fb8d0ff93b532e64d9478e012d
1215 | GitHub Workflow Name: app
1216 | GitHub Workflow Trigger liatrio/gh-trusted-builds-app
1217 | GitHub Workflow Ref: refs/heads/main
1218 | ```
1219 |
1220 | Now we can do the same for the SLSA provenance attestation. This is an attestation type that's natively understood by `cosign`, so we can use `--type slsaprovenance`:
1221 |
1222 | ```shell
1223 | $ cosign verify-attestation \
1224 | --type slsaprovenance \
1225 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1226 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main \
1227 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1228 | --rekor-url https://rekor.sigstore.dev \
1229 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1230 |
1231 | Verification for ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc --
1232 | The following checks were performed on each of these signatures:
1233 | - The cosign claims were validated
1234 | - Existence of the claims in the transparency log was verified offline
1235 | - The code-signing certificate was verified using trusted certificate authority certificates
1236 | Certificate subject: https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main
1237 | Certificate issuer URL: https://token.actions.githubusercontent.com
1238 | GitHub Workflow Trigger: workflow_dispatch
1239 | GitHub Workflow SHA: 54a0e5823b30c4fb8d0ff93b532e64d9478e012d
1240 | GitHub Workflow Name: app
1241 | GitHub Workflow Trigger liatrio/gh-trusted-builds-app
1242 | GitHub Workflow Ref: refs/heads/main
1243 | ```
1244 |
1245 | Next, we can check for the vulnerability attestation produced in the `scan-image` workflow. This is another attestation type that `cosign` is familiar with, so we can use `--type vuln`:
1246 |
1247 | ```shell
1248 | $ cosign verify-attestation \
1249 | --type vuln \
1250 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1251 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/scan-image.yaml@refs/heads/main \
1252 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1253 | --rekor-url https://rekor.sigstore.dev \
1254 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1255 |
1256 | Verification for ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc --
1257 | The following checks were performed on each of these signatures:
1258 | - The cosign claims were validated
1259 | - Existence of the claims in the transparency log was verified offline
1260 | - The code-signing certificate was verified using trusted certificate authority certificates
1261 | Certificate subject: https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/scan-image.yaml@refs/heads/main
1262 | Certificate issuer URL: https://token.actions.githubusercontent.com
1263 | GitHub Workflow Trigger: workflow_dispatch
1264 | GitHub Workflow SHA: 54a0e5823b30c4fb8d0ff93b532e64d9478e012d
1265 | GitHub Workflow Name: app
1266 | GitHub Workflow Trigger liatrio/gh-trusted-builds-app
1267 | GitHub Workflow Ref: refs/heads/main
1268 | ```
1269 |
1270 | Similarly, we can verify the SBOM attestation. Like the SLSA provenance and vulnerability attestation types, this is another format the `cosign` understands natively:
1271 |
1272 | ```shell
1273 | $ cosign verify-attestation \
1274 | --type spdxjson \
1275 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1276 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main \
1277 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1278 | --rekor-url https://rekor.sigstore.dev \
1279 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1280 |
1281 | Verification for ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc --
1282 | The following checks were performed on each of these signatures:
1283 | - The cosign claims were validated
1284 | - Existence of the claims in the transparency log was verified offline
1285 | - The code-signing certificate was verified using trusted certificate authority certificates
1286 | Certificate subject: https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main
1287 | Certificate issuer URL: https://token.actions.githubusercontent.com
1288 | GitHub Workflow Trigger: workflow_dispatch
1289 | GitHub Workflow SHA: 54a0e5823b30c4fb8d0ff93b532e64d9478e012d
1290 | GitHub Workflow Name: app
1291 | GitHub Workflow Trigger liatrio/gh-trusted-builds-app
1292 | GitHub Workflow Ref: refs/heads/main
1293 | ```
1294 |
1295 | Lastly, we can check the verification summary attestation produced by [`liatrio/gh-trusted-builds-attestations`](https://github.com/liatrio/gh-trusted-builds-attestations):
1296 |
1297 | ```shell
1298 | $ cosign verify-attestation \
1299 | --type https://slsa.dev/verification_summary/v0.2 \
1300 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1301 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/policy-verification.yaml@refs/heads/main \
1302 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1303 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1304 |
1305 | Verification for ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc --
1306 | The following checks were performed on each of these signatures:
1307 | - The cosign claims were validated
1308 | - Existence of the claims in the transparency log was verified offline
1309 | - The code-signing certificate was verified using trusted certificate authority certificates
1310 | Certificate subject: https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/policy-verification.yaml@refs/heads/main
1311 | Certificate issuer URL: https://token.actions.githubusercontent.com
1312 | GitHub Workflow Trigger: workflow_dispatch
1313 | GitHub Workflow SHA: 54a0e5823b30c4fb8d0ff93b532e64d9478e012d
1314 | GitHub Workflow Name: app
1315 | GitHub Workflow Trigger liatrio/gh-trusted-builds-app
1316 | GitHub Workflow Ref: refs/heads/main
1317 | ```
1318 |
1319 | Even after verifying the image signature and attestations, there may still be checks we wish to do on the individual attestations, which is where [policy](https://github.com/liatrio/gh-trusted-builds-policy) comes in.
1320 | For instance, we could check the pull request attestation indicates that multiple reviewers approved a change or that our container isn't vulnerable to a particular CVE.
1321 |
1322 | Of course, it's also helpful to know what failed verification looks like. What happens if an image is missing an attestation?
1323 | We can simulate that by asking cosign to verify an attestation we know doesn't exist:
1324 |
1325 | ```shell
1326 | $ cosign verify-attestation \
1327 | --type foo \
1328 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1329 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main \
1330 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1331 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1332 |
1333 | Error: none of the attestations matched the predicate type: foo, found: https://liatr.io/attestations/github-pull-request/v1,https://slsa.dev/provenance/v0.2,https://spdx.dev/Document
1334 | main.go:74: error during command execution: none of the attestations matched the predicate type: foo, found: https://liatr.io/attestations/github-pull-request/v1,https://slsa.dev/provenance/v0.2,https://spdx.dev/Document
1335 | ```
1336 |
1337 | We tried to ask `cosign` to verify the existence of a `foo` attestation, signed by the `build-and-push` workflow, and because there is no `foo` attestation, `cosign` will report that it wasn't able to find one.
1338 |
1339 | The output will be similar if we ask `cosign` to verify an attestation that does exist, but was signed by a different identity.
1340 | In this case, we'll try to verify that the vuln attestation was signed by the `build-and-push` workflow, when it was actually signed by the `scan-image` workflow.
1341 |
1342 | ```shell
1343 | $ cosign verify-attestation \
1344 | --type vuln \
1345 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \
1346 | --certificate-identity https://github.com/liatrio/gh-trusted-builds-workflows/.github/workflows/build-and-push.yaml@refs/heads/main \
1347 | --certificate-github-workflow-repository liatrio/gh-trusted-builds-app \
1348 | ghcr.io/liatrio/gh-trusted-builds-app@sha256:294bafb143807a4afe6b90e6b8b208b9616798effc48e4018b6b9eef9a6ef6bc
1349 |
1350 | Error: none of the attestations matched the predicate type: vuln, found: https://liatr.io/attestations/github-pull-request/v1,https://slsa.dev/provenance/v0.2,https://spdx.dev/Document
1351 | main.go:74: error during command execution: none of the attestations matched the predicate type: vuln, found: https://liatr.io/attestations/github-pull-request/v1,https://slsa.dev/provenance/v0.2,https://spdx.dev/Document
1352 | ```
1353 |
1354 | In this case, the output is very similar, even though we know that the `vuln` attestation does exist. However, `cosign` first filters the signatures by the signer identities, so it's only looking at the identity that we specified (i.e., the `build-and-push` workflow).
1355 |
1356 | ## Additional Resources
1357 |
1358 | - [Sigstore Security Model](https://docs.sigstore.dev/security/) - an overview of the security model for different Sigstore components.
1359 | - [in-toto specification](https://github.com/in-toto/attestation/tree/main/spec)
1360 | - [Fulcio certificate issuance overview](https://github.com/sigstore/fulcio/blob/main/docs/how-certificate-issuing-works.md)
1361 | - [Fulcio certificate extensions](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md)
1362 | - [transparency.dev](https://transparency.dev/) - an overview of the verifiable data structures behind Rekor and Fulcio's certificate transparency log.
1363 |
--------------------------------------------------------------------------------