Skip to content

Commit bb9c479

Browse files
authored
fix(attestations): add compatibility with old policy evaluations (#1756)
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
1 parent 3c6d89f commit bb9c479

File tree

4 files changed

+190
-20
lines changed

4 files changed

+190
-20
lines changed

app/controlplane/internal/service/attestation.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ func extractPolicyEvaluations(in map[string][]*chainloop.PolicyEvaluation) map[s
460460

461461
eval := &cpAPI.PolicyEvaluation{
462462
Name: ev.Name,
463-
MaterialName: ev.MaterialName,
463+
MaterialName: ev.GetMaterialName(),
464464
Body: ev.Body,
465465
Sources: ev.Sources,
466466
Annotations: ev.Annotations,
@@ -473,11 +473,12 @@ func extractPolicyEvaluations(in map[string][]*chainloop.PolicyEvaluation) map[s
473473
Requirements: ev.Requirements,
474474
}
475475

476-
if ev.PolicyReference != nil {
477-
orgName, _ := ev.PolicyReference.GetAnnotations().AsMap()["organization"].(string)
476+
if ev.GetPolicyReference() != nil {
477+
r := ev.GetPolicyReference()
478+
orgName, _ := r.GetAnnotations().AsMap()["organization"].(string)
478479
eval.PolicyReference = &cpAPI.PolicyReference{
479-
Name: ev.PolicyReference.Name,
480-
Digest: ev.PolicyReference.Digest,
480+
Name: r.Name,
481+
Digest: r.Digest,
481482
Organization: orgName,
482483
}
483484
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{
2+
"type": "https://in-toto.io/Statement/v1",
3+
"subject": [
4+
{
5+
"name": "chainloop.workflow.skipped",
6+
"digest": {
7+
"sha256": "d0c4d6f5a0c8d151588aaf35658040405a44428c2486c01f1f55ef8d29ece97f"
8+
}
9+
},
10+
{
11+
"name": "index.docker.io/bitnami/nginx",
12+
"digest": {
13+
"sha256": "580ac09da7771920dfd0c214964e7bfe4c27903bcbe075769a4044a67c9a390a"
14+
},
15+
"annotations": {
16+
"chainloop.material.image.is_latest_tag": false,
17+
"chainloop.material.name": "skynet-control-plane",
18+
"chainloop.material.type": "CONTAINER_IMAGE"
19+
}
20+
}
21+
],
22+
"predicate_type": "chainloop.dev/attestation/v0.2",
23+
"predicate": {
24+
"buildType": "chainloop.dev/workflowrun/v0.1",
25+
"builder": {
26+
"id": "chainloop.dev/cli/dev@sha256:59e14f1a9de709cdd0e91c36b33e54fcca95f7dba1dc7169a7f81986e02108e5"
27+
},
28+
"materials": [
29+
{
30+
"annotations": {
31+
"chainloop.material.name": "build-ref",
32+
"chainloop.material.type": "STRING"
33+
},
34+
"content": "YS1zdHJpbmc="
35+
},
36+
{
37+
"annotations": {
38+
"chainloop.material.name": "rootfs",
39+
"chainloop.material.type": "ARTIFACT"
40+
},
41+
"digest": {
42+
"sha256": "cfc7d8e24d21ade921d720228ad1693de59dab45ff679606940be75b7bf660dc"
43+
},
44+
"name": "Makefile"
45+
},
46+
{
47+
"annotations": {
48+
"chainloop.material.image.is_latest_tag": false,
49+
"chainloop.material.name": "skynet-control-plane",
50+
"chainloop.material.type": "CONTAINER_IMAGE"
51+
},
52+
"digest": {
53+
"sha256": "580ac09da7771920dfd0c214964e7bfe4c27903bcbe075769a4044a67c9a390a"
54+
},
55+
"name": "index.docker.io/bitnami/nginx"
56+
},
57+
{
58+
"annotations": {
59+
"chainloop.material.name": "skynet-sbom",
60+
"chainloop.material.type": "SBOM_CYCLONEDX_JSON"
61+
},
62+
"digest": {
63+
"sha256": "16159bb881eb4ab7eb5d8afc5350b0feeed1e31c0a268e355e74f9ccbe885e0c"
64+
},
65+
"name": "sbom.cyclonedx.json"
66+
}
67+
],
68+
"metadata": {
69+
"contractName": "chainloop-skipped",
70+
"contractVersion": "1",
71+
"finishedAt": "2023-05-03T17:25:12.743426076Z",
72+
"initializedAt": "2023-05-03T17:22:12.743426076Z",
73+
"name": "skipped",
74+
"organization": "foobar",
75+
"project": "chainloop",
76+
"projectVersion": "v0.150.0",
77+
"projectVersionPrerelease": true,
78+
"team": "",
79+
"workflowID": "94208094-b8d3-4b38-b1f1-c609c47c49ea",
80+
"workflowRunID": "e4cec971-6f4f-442a-8de0-d12ddc4667f2"
81+
},
82+
"policyAttBlocked": false,
83+
"policyBlockBypassEnabled": false,
84+
"policyCheckBlockingStrategy": "ADVISORY",
85+
"policy_evaluations": {
86+
"sbom": [
87+
{
88+
"annotations": {
89+
"category": "sbom"
90+
},
91+
"description": "Checks that the SBOM is not older than a specified threshold. Supports CycloneDX.\n",
92+
"material_name": "sbom",
93+
"name": "sbom-freshness",
94+
"policy_reference": {
95+
"annotations": {
96+
"name": "sbom-freshness",
97+
"organization": ""
98+
},
99+
"digest": {
100+
"sha256": "e9b750847ba8a5439a0a43963d22cb5c5a9568de5fdcd2db21d9615c76870c2a"
101+
},
102+
"name": "sbom-freshness",
103+
"uri": "file://policy-sbom-freshness.yaml"
104+
},
105+
"skipped": false,
106+
"type": "SBOM_CYCLONEDX_JSON",
107+
"violations": [
108+
{
109+
"message": "SBOM created at: 2020-08-02T21:27:04Z which is too old (freshness limit set to 5 days)",
110+
"subject": "sbom-freshness"
111+
}
112+
],
113+
"with": {
114+
"limit": "5"
115+
}
116+
}
117+
]
118+
},
119+
"policyHasViolations": true,
120+
"runnerType": "GITHUB_ACTION"
121+
}
122+
}

pkg/attestation/renderer/chainloop/v02.go

+42-15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type ProvenancePredicateV02 struct {
4343
Materials []*intoto.ResourceDescriptor `json:"materials,omitempty"`
4444
// Map materials and policies
4545
PolicyEvaluations map[string][]*PolicyEvaluation `json:"policyEvaluations,omitempty"`
46+
// Used to read policy evaluations from old attestations
47+
PolicyEvaluationsFallback map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"`
48+
4649
// Whether the attestation has policy violations
4750
PolicyHasViolations bool `json:"policyHasViolations"`
4851
// Whether we want to block the attestation on policy violations
@@ -61,20 +64,40 @@ const (
6164
)
6265

6366
type PolicyEvaluation struct {
64-
Name string `json:"name"`
65-
MaterialName string `json:"materialName,omitempty"`
66-
Body string `json:"body,omitempty"`
67-
Sources []string `json:"sources,omitempty"`
68-
PolicyReference *intoto.ResourceDescriptor `json:"policyReference,omitempty"`
69-
Description string `json:"description,omitempty"`
70-
Annotations map[string]string `json:"annotations,omitempty"`
71-
Violations []*PolicyViolation `json:"violations,omitempty"`
72-
With map[string]string `json:"with,omitempty"`
73-
Type string `json:"type"`
74-
Skipped bool `json:"skipped"`
75-
SkipReasons []string `json:"skipReasons,omitempty"`
76-
GroupReference *intoto.ResourceDescriptor `json:"groupReference,omitempty"`
77-
Requirements []string `json:"requirements,omitempty"`
67+
Name string `json:"name"`
68+
MaterialName string `json:"materialName,omitempty"`
69+
// Needed to read old attestations
70+
MaterialNameFallback string `json:"material_name,omitempty"`
71+
Body string `json:"body,omitempty"`
72+
Sources []string `json:"sources,omitempty"`
73+
PolicyReference *intoto.ResourceDescriptor `json:"policyReference,omitempty"`
74+
// Support old attestations
75+
PolicyReferenceFallback *intoto.ResourceDescriptor `json:"policy_reference,omitempty"`
76+
Description string `json:"description,omitempty"`
77+
Annotations map[string]string `json:"annotations,omitempty"`
78+
Violations []*PolicyViolation `json:"violations,omitempty"`
79+
With map[string]string `json:"with,omitempty"`
80+
Type string `json:"type"`
81+
Skipped bool `json:"skipped"`
82+
SkipReasons []string `json:"skipReasons,omitempty"`
83+
GroupReference *intoto.ResourceDescriptor `json:"groupReference,omitempty"`
84+
Requirements []string `json:"requirements,omitempty"`
85+
}
86+
87+
func (e *PolicyEvaluation) GetPolicyReference() *intoto.ResourceDescriptor {
88+
r := e.PolicyReference
89+
if r == nil {
90+
r = e.PolicyReferenceFallback
91+
}
92+
return r
93+
}
94+
95+
func (e *PolicyEvaluation) GetMaterialName() string {
96+
n := e.MaterialName
97+
if n == "" {
98+
n = e.MaterialNameFallback
99+
}
100+
return n
78101
}
79102

80103
type PolicyViolation struct {
@@ -374,7 +397,11 @@ func (p *ProvenancePredicateV02) GetMaterials() []*NormalizedMaterial {
374397
}
375398

376399
func (p *ProvenancePredicateV02) GetPolicyEvaluations() map[string][]*PolicyEvaluation {
377-
return p.PolicyEvaluations
400+
evs := p.PolicyEvaluations
401+
if len(evs) == 0 && len(p.PolicyEvaluationsFallback) > 0 {
402+
evs = p.PolicyEvaluationsFallback
403+
}
404+
return evs
378405
}
379406

380407
func (p *ProvenancePredicateV02) HasPolicyViolations() bool {

pkg/attestation/renderer/chainloop/v02_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,23 @@ func TestNormalizeMaterial(t *testing.T) {
274274
})
275275
}
276276
}
277+
278+
func TestPolicyEvaluationsField(t *testing.T) {
279+
raw, err := os.ReadFile("testdata/attestation-pe-snake.json")
280+
require.NoError(t, err)
281+
282+
var st *intoto.Statement
283+
err = json.Unmarshal(raw, &st)
284+
require.NoError(t, err)
285+
286+
var predicate ProvenancePredicateV02
287+
err = extractPredicate(st, &predicate)
288+
require.NoError(t, err)
289+
290+
assert.Len(t, predicate.GetPolicyEvaluations(), 1)
291+
evs := predicate.GetPolicyEvaluations()["sbom"]
292+
assert.Len(t, evs, 1)
293+
ev := evs[0]
294+
assert.Equal(t, "sbom", ev.GetMaterialName())
295+
assert.NotNil(t, ev.GetPolicyReference())
296+
}

0 commit comments

Comments
 (0)