Skip to content

Commit f5ede4c

Browse files
authored
fix(attestation): generic normalization function (#367)
Signed-off-by: Miguel Martinez Trivino <[email protected]>
1 parent bb02759 commit f5ede4c

File tree

4 files changed

+152
-19
lines changed

4 files changed

+152
-19
lines changed

app/cli/api/attestation/v1/crafting_state.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
package v1
1717

1818
import (
19-
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
19+
"errors"
20+
"fmt"
2021
)
2122

2223
type NormalizedMaterialOutput struct {
@@ -25,18 +26,25 @@ type NormalizedMaterialOutput struct {
2526
Content []byte
2627
}
2728

28-
func (m *Attestation_Material) NormalizedOutput() *NormalizedMaterialOutput {
29-
switch m.MaterialType {
30-
case schemaapi.CraftingSchema_Material_ARTIFACT, schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON, schemaapi.CraftingSchema_Material_SBOM_SPDX_JSON:
31-
a := m.GetArtifact()
32-
return &NormalizedMaterialOutput{a.Name, a.Digest, a.IsSubject, a.Content}
33-
case schemaapi.CraftingSchema_Material_CONTAINER_IMAGE:
34-
a := m.GetContainerImage()
35-
return &NormalizedMaterialOutput{a.Name, a.Digest, a.IsSubject, nil}
36-
case schemaapi.CraftingSchema_Material_STRING:
37-
a := m.GetString_()
38-
return &NormalizedMaterialOutput{Content: []byte(a.Value)}
29+
// NormalizedOutput returns a common representation of the properties of a material
30+
// regardless of how it's been encoded.
31+
// For example, it's common to have materials based on artifacts, so we want to normalize the output
32+
func (m *Attestation_Material) NormalizedOutput() (*NormalizedMaterialOutput, error) {
33+
if m == nil {
34+
return nil, errors.New("material not provided")
3935
}
4036

41-
return nil
37+
if a := m.GetContainerImage(); a != nil {
38+
return &NormalizedMaterialOutput{a.Name, a.Digest, a.IsSubject, nil}, nil
39+
}
40+
41+
if a := m.GetString_(); a != nil {
42+
return &NormalizedMaterialOutput{Content: []byte(a.Value)}, nil
43+
}
44+
45+
if a := m.GetArtifact(); a != nil {
46+
return &NormalizedMaterialOutput{a.Name, a.Digest, a.IsSubject, a.Content}, nil
47+
}
48+
49+
return nil, fmt.Errorf("unknown material: %s", m.MaterialType)
4250
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//
2+
// Copyright 2023 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package v1
17+
18+
import (
19+
"testing"
20+
21+
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestNormalizeOutput(t *testing.T) {
26+
artifactBasedMaterial := &Attestation_Material{
27+
MaterialType: schemaapi.CraftingSchema_Material_SARIF,
28+
M: &Attestation_Material_Artifact_{
29+
Artifact: &Attestation_Material_Artifact{
30+
Name: "name", Digest: "deadbeef", IsSubject: true, Content: []byte("content"),
31+
},
32+
},
33+
}
34+
35+
artifactBasedMaterialWant := &NormalizedMaterialOutput{
36+
Name: "name", Digest: "deadbeef", IsOutput: true, Content: []byte("content"),
37+
}
38+
39+
containerMaterial := &Attestation_Material{
40+
MaterialType: schemaapi.CraftingSchema_Material_CONTAINER_IMAGE,
41+
M: &Attestation_Material_ContainerImage_{
42+
ContainerImage: &Attestation_Material_ContainerImage{
43+
Name: "name", Digest: "deadbeef", IsSubject: true,
44+
},
45+
},
46+
}
47+
48+
containerMaterialWant := &NormalizedMaterialOutput{
49+
Name: "name", Digest: "deadbeef", IsOutput: true,
50+
}
51+
52+
keyValMaterial := &Attestation_Material{
53+
MaterialType: schemaapi.CraftingSchema_Material_STRING,
54+
M: &Attestation_Material_String_{
55+
String_: &Attestation_Material_KeyVal{
56+
Id: "id", Value: "value",
57+
},
58+
},
59+
}
60+
61+
keyValWant := &NormalizedMaterialOutput{
62+
Content: []byte("value"),
63+
}
64+
65+
testCases := []struct {
66+
name string
67+
material *Attestation_Material
68+
want *NormalizedMaterialOutput
69+
wantErr string
70+
}{
71+
{
72+
name: "nil material",
73+
wantErr: "material not provided",
74+
},
75+
{
76+
name: "empty material",
77+
material: &Attestation_Material{},
78+
wantErr: "unknown material: MATERIAL_TYPE_UNSPECIFIED",
79+
},
80+
{
81+
name: "artifact based material",
82+
material: artifactBasedMaterial,
83+
want: artifactBasedMaterialWant,
84+
},
85+
{
86+
name: "Container image material",
87+
material: containerMaterial,
88+
want: containerMaterialWant,
89+
},
90+
{
91+
name: "keyval material",
92+
material: keyValMaterial,
93+
want: keyValWant,
94+
},
95+
}
96+
97+
for _, tc := range testCases {
98+
t.Run(tc.name, func(t *testing.T) {
99+
got, err := (tc.material).NormalizedOutput()
100+
if tc.wantErr != "" {
101+
assert.EqualError(t, err, tc.wantErr)
102+
return
103+
}
104+
105+
assert.NoError(t, err)
106+
assert.Equal(t, tc.want, got)
107+
})
108+
}
109+
}

internal/attestation/renderer/chainloop/v01.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ func outputChainloopMaterials(att *v1.Attestation, onlyOutput bool) []*Provenanc
116116
mdef := materials[mdefName]
117117

118118
artifactType := mdef.MaterialType
119-
nMaterial := mdef.NormalizedOutput()
119+
nMaterial, err := mdef.NormalizedOutput()
120+
if err != nil {
121+
continue
122+
}
120123

121124
// Skip if we are expecting to show only the materials marked as output
122125
if onlyOutput && !nMaterial.IsOutput {

internal/attestation/renderer/chainloop/v02.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,14 @@ func NewChainloopRendererV02(att *v1.Attestation, builderVersion, builderDigest
4949
}
5050

5151
func (r *RendererV02) Predicate() (interface{}, error) {
52+
normalizedMaterials, err := outputMaterials(r.att, false)
53+
if err != nil {
54+
return nil, fmt.Errorf("error normalizing materials: %w", err)
55+
}
56+
5257
return ProvenancePredicateV02{
5358
ProvenancePredicateCommon: predicateCommon(r.builder, r.att),
54-
Materials: outputMaterials(r.att, false),
59+
Materials: normalizedMaterials,
5560
}, nil
5661
}
5762

@@ -71,7 +76,12 @@ func (r *RendererV02) Header() (*in_toto.StatementHeader, error) {
7176
},
7277
}
7378

74-
for _, m := range outputMaterials(r.att, true) {
79+
normalizedMaterials, err := outputMaterials(r.att, true)
80+
if err != nil {
81+
return nil, fmt.Errorf("error normalizing materials: %w", err)
82+
}
83+
84+
for _, m := range normalizedMaterials {
7585
if m.Digest != nil {
7686
subjects = append(subjects, in_toto.Subject{
7787
Name: m.Name,
@@ -98,7 +108,7 @@ func builtInAnnotation(name string) string {
98108
return fmt.Sprintf("%s%s", builtInAnnotationPrefix, name)
99109
}
100110

101-
func outputMaterials(att *v1.Attestation, onlyOutput bool) []*slsa_v1.ResourceDescriptor {
111+
func outputMaterials(att *v1.Attestation, onlyOutput bool) ([]*slsa_v1.ResourceDescriptor, error) {
102112
// Sort material keys to stabilize output
103113
keys := make([]string, 0, len(att.GetMaterials()))
104114
for k := range att.GetMaterials() {
@@ -113,7 +123,10 @@ func outputMaterials(att *v1.Attestation, onlyOutput bool) []*slsa_v1.ResourceDe
113123
mdef := materials[mdefName]
114124

115125
artifactType := mdef.MaterialType
116-
nMaterial := mdef.NormalizedOutput()
126+
nMaterial, err := mdef.NormalizedOutput()
127+
if err != nil {
128+
return nil, fmt.Errorf("error normalizing material: %w", err)
129+
}
117130

118131
// Skip if we are expecting to show only the materials marked as output
119132
if onlyOutput && !nMaterial.IsOutput {
@@ -157,7 +170,7 @@ func outputMaterials(att *v1.Attestation, onlyOutput bool) []*slsa_v1.ResourceDe
157170
res = append(res, material)
158171
}
159172

160-
return res
173+
return res, nil
161174
}
162175

163176
// Implement NormalizablePredicate interface

0 commit comments

Comments
 (0)