Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions app/cli/api/attestation/v1/crafting_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
package v1

import (
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"errors"
"fmt"
)

type NormalizedMaterialOutput struct {
Expand All @@ -25,18 +26,25 @@ type NormalizedMaterialOutput struct {
Content []byte
}

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

return nil
if a := m.GetContainerImage(); a != nil {
return &NormalizedMaterialOutput{a.Name, a.Digest, a.IsSubject, nil}, nil
}

if a := m.GetString_(); a != nil {
return &NormalizedMaterialOutput{Content: []byte(a.Value)}, nil
}

if a := m.GetArtifact(); a != nil {
return &NormalizedMaterialOutput{a.Name, a.Digest, a.IsSubject, a.Content}, nil
}

return nil, fmt.Errorf("unknown material: %s", m.MaterialType)
}
109 changes: 109 additions & 0 deletions app/cli/api/attestation/v1/crafting_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// Copyright 2023 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1

import (
"testing"

schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/stretchr/testify/assert"
)

func TestNormalizeOutput(t *testing.T) {
artifactBasedMaterial := &Attestation_Material{
MaterialType: schemaapi.CraftingSchema_Material_SARIF,
M: &Attestation_Material_Artifact_{
Artifact: &Attestation_Material_Artifact{
Name: "name", Digest: "deadbeef", IsSubject: true, Content: []byte("content"),
},
},
}

artifactBasedMaterialWant := &NormalizedMaterialOutput{
Name: "name", Digest: "deadbeef", IsOutput: true, Content: []byte("content"),
}

containerMaterial := &Attestation_Material{
MaterialType: schemaapi.CraftingSchema_Material_CONTAINER_IMAGE,
M: &Attestation_Material_ContainerImage_{
ContainerImage: &Attestation_Material_ContainerImage{
Name: "name", Digest: "deadbeef", IsSubject: true,
},
},
}

containerMaterialWant := &NormalizedMaterialOutput{
Name: "name", Digest: "deadbeef", IsOutput: true,
}

keyValMaterial := &Attestation_Material{
MaterialType: schemaapi.CraftingSchema_Material_STRING,
M: &Attestation_Material_String_{
String_: &Attestation_Material_KeyVal{
Id: "id", Value: "value",
},
},
}

keyValWant := &NormalizedMaterialOutput{
Content: []byte("value"),
}

testCases := []struct {
name string
material *Attestation_Material
want *NormalizedMaterialOutput
wantErr string
}{
{
name: "nil material",
wantErr: "material not provided",
},
{
name: "empty material",
material: &Attestation_Material{},
wantErr: "unknown material: MATERIAL_TYPE_UNSPECIFIED",
},
{
name: "artifact based material",
material: artifactBasedMaterial,
want: artifactBasedMaterialWant,
},
{
name: "Container image material",
material: containerMaterial,
want: containerMaterialWant,
},
{
name: "keyval material",
material: keyValMaterial,
want: keyValWant,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := (tc.material).NormalizedOutput()
if tc.wantErr != "" {
assert.EqualError(t, err, tc.wantErr)
return
}

assert.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}
5 changes: 4 additions & 1 deletion internal/attestation/renderer/chainloop/v01.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ func outputChainloopMaterials(att *v1.Attestation, onlyOutput bool) []*Provenanc
mdef := materials[mdefName]

artifactType := mdef.MaterialType
nMaterial := mdef.NormalizedOutput()
nMaterial, err := mdef.NormalizedOutput()
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we continue on error, can you add a comment here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because v01 is actually deprecated and in fact we should probably remove it. Just to not to add more code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have added a comment on the reasoning, sorry.

continue
}

// Skip if we are expecting to show only the materials marked as output
if onlyOutput && !nMaterial.IsOutput {
Expand Down
23 changes: 18 additions & 5 deletions internal/attestation/renderer/chainloop/v02.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,14 @@ func NewChainloopRendererV02(att *v1.Attestation, builderVersion, builderDigest
}

func (r *RendererV02) Predicate() (interface{}, error) {
normalizedMaterials, err := outputMaterials(r.att, false)
if err != nil {
return nil, fmt.Errorf("error normalizing materials: %w", err)
}

return ProvenancePredicateV02{
ProvenancePredicateCommon: predicateCommon(r.builder, r.att),
Materials: outputMaterials(r.att, false),
Materials: normalizedMaterials,
}, nil
}

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

for _, m := range outputMaterials(r.att, true) {
normalizedMaterials, err := outputMaterials(r.att, true)
if err != nil {
return nil, fmt.Errorf("error normalizing materials: %w", err)
}

for _, m := range normalizedMaterials {
if m.Digest != nil {
subjects = append(subjects, in_toto.Subject{
Name: m.Name,
Expand All @@ -98,7 +108,7 @@ func builtInAnnotation(name string) string {
return fmt.Sprintf("%s%s", builtInAnnotationPrefix, name)
}

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

artifactType := mdef.MaterialType
nMaterial := mdef.NormalizedOutput()
nMaterial, err := mdef.NormalizedOutput()
if err != nil {
return nil, fmt.Errorf("error normalizing material: %w", err)
}

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

return res
return res, nil
}

// Implement NormalizablePredicate interface
Expand Down