Skip to content

Commit 6b8eb29

Browse files
make sure the predicate is a SLSA build provenance
1 parent 9a46228 commit 6b8eb29

File tree

7 files changed

+145
-9
lines changed

7 files changed

+145
-9
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ The following three examples are provided:
135135
originating from a list of organizations, and built with a reusable
136136
workflow from a list of provided repositories.
137137

138+
Assuming the policy for verifying images originating from a a specific
139+
repository is updated to contain the expected repositories, apply
140+
them to OPA Gatekeeper with the following command:
141+
142+
```
143+
$ kubectl apply -f validation/from-repo-constraint-template.yaml
144+
$ kubectl apply -f validation/from-repo-constraint.yaml
145+
```
146+
138147
## Uninstall
139148

140149
```

rego/fixtures.rego

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,100 @@ reusable := {
193193
"status_code": 200,
194194
"system_error": ""
195195
}
196+
197+
non_provenance := {
198+
"errors": [],
199+
"responses": [
200+
[
201+
"ghcr.io/octoorg/octorepo:reusable",
202+
[
203+
{
204+
"mediaType": "application/vnd.dev.sigstore.verificationresult+json;version=0.1",
205+
"signature": {
206+
"certificate": {
207+
"buildConfigDigest": "bac1792245301bd84d20bcffa453b4ddf19e4f7b",
208+
"buildConfigURI": "https://github.com/octoorg/octorepo/.github/workflows/build.yaml@refs/heads/main",
209+
"buildSignerDigest": "123ba287d1eb31c27b6dd35b57d6c0b79be00a57",
210+
"buildSignerURI": "https://github.com/buildorg/build-scripts/.github/workflows/image.yml@refs/heads/main",
211+
"buildTrigger": "workflow_dispatch",
212+
"certificateIssuer": "CN=Fulcio Intermediate l2,O=GitHub\\, Inc.",
213+
"githubWorkflowName": "Build image w/ attestation",
214+
"githubWorkflowRef": "refs/heads/main",
215+
"githubWorkflowRepository": "octoorg/octorepo",
216+
"githubWorkflowSHA": "bac1792245301bd84d20bcffa453b4ddf19e4f7b",
217+
"githubWorkflowTrigger": "workflow_dispatch",
218+
"issuer": "https://token.actions.githubusercontent.com",
219+
"runInvocationURI": "https://github.com/octoorg/octorepo/actions/runs/14078191773/attempts/1",
220+
"runnerEnvironment": "github-hosted",
221+
"sourceRepositoryDigest": "bac1792245301bd84d20bcffa453b4ddf19e4f7b",
222+
"sourceRepositoryIdentifier": "123525123",
223+
"sourceRepositoryOwnerIdentifier": "98762123",
224+
"sourceRepositoryOwnerURI": "https://github.com/octoorg",
225+
"sourceRepositoryRef": "refs/heads/main",
226+
"sourceRepositoryURI": "https://github.com/octoorg/octorepo",
227+
"sourceRepositoryVisibilityAtSigning": "private",
228+
"subjectAlternativeName": "https://github.com/buildorg/build-scripts/.github/workflows/image.yml@refs/heads/main"
229+
}
230+
},
231+
"statement": {
232+
"_type": "https://in-toto.io/Statement/v1",
233+
"predicate": {
234+
"buildDefinition": {
235+
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
236+
"externalParameters": {
237+
"workflow": {
238+
"path": ".github/workflows/build.yaml",
239+
"ref": "refs/heads/main",
240+
"repository": "https://github.com/octoorg/octorepo"
241+
}
242+
},
243+
"internalParameters": {
244+
"github": {
245+
"event_name": "workflow_dispatch",
246+
"repository_id": "123525123",
247+
"repository_owner_id": "98762123",
248+
"runner_environment": "github-hosted"
249+
}
250+
},
251+
"resolvedDependencies": [
252+
{
253+
"digest": {
254+
"gitCommit": "bac1792245301bd84d20bcffa453b4ddf19e4f7b"
255+
},
256+
"uri": "git+https://github.com/octoorg/octorepo@refs/heads/main"
257+
}
258+
]
259+
},
260+
"runDetails": {
261+
"builder": {
262+
"id": "https://github.com/buildorg/build-scripts/.github/workflows/image.yml@refs/heads/main"
263+
},
264+
"metadata": {
265+
"invocationId": "https://github.com/octoorg/octorepo/actions/runs/14078191773/attempts/1"
266+
}
267+
}
268+
},
269+
"predicateType": "https://slsa.dev/custom/v1",
270+
"subject": [
271+
{
272+
"digest": {
273+
"sha256": "2059296ab5f2646f4db0fc14c0ffeb26e9967808ca960d5ce237204ad47d89af"
274+
},
275+
"name": "ghcr.io/octoorg/octorepo"
276+
}
277+
]
278+
},
279+
"verifiedTimestamps": [
280+
{
281+
"timestamp": "2025-03-26T07:58:59Z",
282+
"type": "TimestampAuthority",
283+
"uri": "timestamp.githubapp.com"
284+
}
285+
]
286+
}
287+
]
288+
]
289+
],
290+
"status_code": 200,
291+
"system_error": ""
292+
}

rego/policies.rego

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,29 @@ package policies
22

33
fromOrg (resp, orgs) if {
44
some i, j, k, l
5+
provenance := "https://slsa.dev/provenance/v1"
6+
7+
provenance == resp.responses[i][j][k].statement.predicateType
58
orgUri := resp.responses[i][j][k].signature.certificate.sourceRepositoryOwnerURI
69
# Prefix the org name with / before doing comparisson
710
endswith(orgUri, concat("", ["/", orgs[l]]))
811
}
912

1013
fromRepo (resp, repos) if {
1114
some i, j, k, l
15+
provenance := "https://slsa.dev/provenance/v1"
16+
17+
provenance == resp.responses[i][j][k].statement.predicateType
1218
uri := resp.responses[i][j][k].signature.certificate.sourceRepositoryURI
1319
# Prefix the repo name with / before doing comparisson
1420
endswith(uri, concat("", ["/", repos[l]]))
1521
}
1622

1723
fromOrgAndSignerRepo(resp, orgs, signerRepos) if {
1824
some i, j, k, l, m
25+
provenance := "https://slsa.dev/provenance/v1"
26+
27+
provenance == resp.responses[i][j][k].statement.predicateType
1928
orgUri := resp.responses[i][j][k].signature.certificate.sourceRepositoryOwnerURI
2029
signerUri := resp.responses[i][j][k].signature.certificate.buildSignerURI
2130
# Verify source owner org is allowed

rego/policies_test.rego

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ test_from_org_invalid if {
2222
not policies.fromOrg(fixtures.octo_org, ["unkown", "octoorga", "ctoorg", "aoctoorg"])
2323
}
2424

25+
test_from_org_non_provenance if {
26+
not policies.fromOrg(fixtures.non_provenance, ["octoorg"])
27+
}
28+
2529
# From repo should pass if at least one repo is valid
2630
test_from_repo_pass if {
2731
policies.fromRepo(fixtures.octo_org, ["unkown/unkown", "octoorg/octorepo"])
@@ -41,6 +45,10 @@ test_from_repo_invalid if {
4145
not policies.fromRepo(fixtures.octo_org, ["unkown/unkown", "ctoorg/octorepo", "aoctoorg/octorepo", "octoorga/octorepo", "octoorg/aoctorepo", "octoorg/octorep", "octoorg/octorepoa"])
4246
}
4347

48+
test_from_repo_non_provenance if {
49+
not policies.fromRepo(fixtures.non_provenance, ["octoorg/octorepo"])
50+
}
51+
4452
# Same repo and signer
4553
test_with_signer_pass if {
4654
policies.fromOrgAndSignerRepo(fixtures.octo_org, ["unknown", "octoorg"], ["unkown/octorepo", "octoorg/octorepo"])
@@ -53,27 +61,31 @@ test_with_signer_pass if {
5361

5462
# Empty input
5563
test_with_signer_empty if {
56-
not policies.fromOrgAndSignerRepo(fixtures.octo_org, [], [])
64+
not policies.fromOrgAndSignerRepo(fixtures.reusable, [], [])
5765
}
5866

5967
test_with_signer_empty if {
60-
not policies.fromOrgAndSignerRepo(fixtures.octo_org, [""], [])
68+
not policies.fromOrgAndSignerRepo(fixtures.reusable, [""], [])
6169
}
6270

6371
test_with_signer_empty if {
64-
not policies.fromOrgAndSignerRepo(fixtures.octo_org, [], [""])
72+
not policies.fromOrgAndSignerRepo(fixtures.reusable, [], [""])
6573
}
6674

6775
test_with_signer_empty if {
68-
not policies.fromOrgAndSignerRepo(fixtures.octo_org, [""], [""])
76+
not policies.fromOrgAndSignerRepo(fixtures.reusable, [""], [""])
6977
}
7078

7179
# Verify that no prefix weakness exists for the orgs
72-
test_from_repo_invalid if {
73-
not policies.fromOrgAndSignerRepo(fixtures.octo_org, ["unkown", "ctoorg", "octoor", "aoctoorg", "octoorga"], ["octoorg/octorepo"])
80+
test_with_signer_invalid if {
81+
not policies.fromOrgAndSignerRepo(fixtures.reusable, ["unkown", "ctoorg", "octoor", "aoctoorg", "octoorga"], ["octoorg/octorepo"])
7482
}
7583

7684
# Verify that no prefix weakness exists for the signer repos
77-
test_from_repo_invalid if {
78-
not policies.fromOrgAndSignerRepo(fixtures.octo_org, ["octoorg"], ["ctoorg/octorepo", "octoorg/octorep", "octoor/octorepo", "octoorg/ctorepo"])
85+
test_with_signer_invalid if {
86+
not policies.fromOrgAndSignerRepo(fixtures.reusable, ["octoorg"], ["ctoorg/octorepo", "octoorg/octorep", "octoor/octorepo", "octoorg/ctorepo"])
87+
}
88+
89+
test_with_signer_non_provenance if {
90+
not policies.fromOrgAndSignerRepo(fixtures.non_provenance, ["octoorg"], ["buildorg/build-scripts"])
7991
}

validation/from-org-constraint-template.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ spec:
3838
3939
fromOrg(resp, orgs) {
4040
some i, j, k, l
41+
provenance := "https://slsa.dev/provenance/v1"
42+
43+
provenance == resp.responses[i][j][k].statement.predicateType
4144
orgUri := resp.responses[i][j][k].signature.certificate.sourceRepositoryOwnerURI
4245
# Prefix the org name with / before doing comparisson
4346
endswith(orgUri, concat("", ["/", orgs[l]]))

validation/from-org-with-signer-constraint-template.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ spec:
3838
3939
fromOrgWithSigner(resp, orgs, signerRepos) {
4040
some i, j, k, l, m
41+
provenance := "https://slsa.dev/provenance/v1"
42+
43+
provenance == resp.responses[i][j][k].statement.predicateType
4144
orgUri := resp.responses[i][j][k].signature.certificate.sourceRepositoryOwnerURI
4245
signerUri := resp.responses[i][j][k].signature.certificate.buildSignerURI
4346
# Verify source owner org is allowed
@@ -47,7 +50,7 @@ spec:
4750
# find the occurence of `/.github/` and trim everything after it
4851
p := indexof(signerUri, "/.github/")
4952
signerRepoTrim := substring(signerUri, 0, p)
50-
# add back the / prefix to get proper delimiter when doing comparison
53+
# add back the / prefix to get proper delimiter when doing comparisson
5154
signerRepo := concat("", ["/", signerRepoTrim])
5255
endswith(signerRepo, concat("", ["/", signerRepos[m]]))
5356
}

validation/from-repo-constraint-template.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ spec:
3838
3939
fromRepo(resp, repos) {
4040
some i, j, k, l
41+
provenance := "https://slsa.dev/provenance/v1"
42+
43+
provenance == resp.responses[i][j][k].statement.predicateType
4144
uri := resp.responses[i][j][k].signature.certificate.sourceRepositoryURI
4245
# Prefix the repo name with / before doing comparisson
4346
endswith(uri, concat("", ["/", repos[l]]))

0 commit comments

Comments
 (0)