diff --git a/pkg/chains/formats/slsa/internal/compare/slsacompare.go b/pkg/chains/formats/slsa/internal/compare/slsacompare.go new file mode 100644 index 0000000000..3edc5ed5cb --- /dev/null +++ b/pkg/chains/formats/slsa/internal/compare/slsacompare.go @@ -0,0 +1,82 @@ +/* +Copyright 2023 The Tekton 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 compare + +import ( + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" +) + +// SLSAV1CompareOptions returns the comparison options for sorting some slice fields in +// SLSA v1 statement including ResourceDescriptor and Subject. +func SLSAV1CompareOptions() []cmp.Option { + // checking content + uri + digest should be sufficient here based on the fact that + // a ResourceDescriptor MUST specify one of uri, digest or content at a minimum. + // Source: https://github.com/in-toto/attestation/blob/main/spec/v1/resource_descriptor.md#fields + resourceDescriptorSort := func(x, y slsa.ResourceDescriptor) bool { + if string(x.Content) != string(y.Content) { + return string(x.Content) < string(y.Content) + } + if x.URI != y.URI { + return x.URI < y.URI + } + return lessDigestSet(x.Digest, y.Digest) + } + + subjectSort := func(x, y in_toto.Subject) bool { + if x.Name != y.Name { + return x.Name < y.Name + } + return lessDigestSet(x.Digest, y.Digest) + } + + return []cmp.Option{ + cmpopts.SortSlices(resourceDescriptorSort), + cmpopts.SortSlices(subjectSort), + } +} + +// MaterialsCompareOption returns the comparison option to sort and compare a +// list of Materials. +func MaterialsCompareOption() cmp.Option { + materialsSort := func(x, y common.ProvenanceMaterial) bool { + if x.URI != y.URI { + return x.URI < y.URI + } + return lessDigestSet(x.Digest, y.Digest) + } + return cmpopts.SortSlices(materialsSort) +} + +func lessDigestSet(x, y common.DigestSet) bool { + for algo, digestX := range x { + digestY, ok := y[algo] + if !ok { + // Algorithm not present in y, x is considered greater. + return false + } + // Compare the digests lexicographically. + if digestX != digestY { + return digestX < digestY + } + // The digests are equal, check the next algorithm. + } + + // All algorithms in x have corresponding entries in y, so check if y has more algorithms. + return len(x) < len(y) +} diff --git a/pkg/chains/formats/slsa/internal/material/material_test.go b/pkg/chains/formats/slsa/internal/material/material_test.go index 7d0f995316..409c27248a 100644 --- a/pkg/chains/formats/slsa/internal/material/material_test.go +++ b/pkg/chains/formats/slsa/internal/material/material_test.go @@ -26,6 +26,7 @@ import ( "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" "github.com/tektoncd/chains/internal/backport" "github.com/tektoncd/chains/pkg/artifacts" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" @@ -360,7 +361,7 @@ func TestPipelineMaterials(t *testing.T) { if err != nil { t.Error(err) } - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, compare.MaterialsCompareOption()); diff != "" { t.Errorf("Materials(): -want +got: %s", diff) } } @@ -394,7 +395,7 @@ func TestStructuredResultPipelineMaterials(t *testing.T) { if err != nil { t.Errorf("error while extracting materials: %v", err) } - if diff := cmp.Diff(want, got); diff != "" { + if diff := cmp.Diff(want, got, compare.MaterialsCompareOption()); diff != "" { t.Errorf("materials(): -want +got: %s", diff) } } diff --git a/pkg/chains/formats/slsa/v1/intotoite6_test.go b/pkg/chains/formats/slsa/v1/intotoite6_test.go index 8ae993dcd5..01e422053a 100644 --- a/pkg/chains/formats/slsa/v1/intotoite6_test.go +++ b/pkg/chains/formats/slsa/v1/intotoite6_test.go @@ -23,6 +23,7 @@ import ( "github.com/tektoncd/chains/pkg/artifacts" "github.com/tektoncd/chains/pkg/chains/formats" "github.com/tektoncd/chains/pkg/chains/formats/slsa/attest" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1/pipelinerun" "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1/taskrun" "github.com/tektoncd/chains/pkg/chains/objects" @@ -365,7 +366,7 @@ func TestPipelineRunCreatePayload(t *testing.T) { t.Errorf("unexpected error: %s", err.Error()) } // Sort Materials since their order can vary and result in flakes - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, compare.MaterialsCompareOption()); diff != "" { t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff) } } @@ -582,7 +583,7 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) { t.Errorf("unexpected error: %s", err.Error()) } // Sort Materials since their order can vary and result in flakes - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, compare.MaterialsCompareOption()); diff != "" { t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff) } } diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go index e0b8e39846..29e3e17f7d 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go @@ -26,6 +26,7 @@ import ( "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" @@ -356,7 +357,7 @@ func TestGenerateAttestation(t *testing.T) { if err != nil { t.Errorf("unwant error: %s", err.Error()) } - if diff := cmp.Diff(want, got); diff != "" { + if diff := cmp.Diff(want, got, compare.SLSAV1CompareOptions()...); diff != "" { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go index 87f0aff8d5..fb0b949cab 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go @@ -25,6 +25,7 @@ import ( v1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/internal/backport" "github.com/tektoncd/chains/pkg/artifacts" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" @@ -507,7 +508,7 @@ func TestPipelineRun(t *testing.T) { if err != nil { t.Error(err) } - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, compare.SLSAV1CompareOptions()...); diff != "" { t.Errorf("PipelineRunResolvedDependencies(): -want +got: %s", diff) } } @@ -542,7 +543,7 @@ func TestPipelineRunStructuredResult(t *testing.T) { if err != nil { t.Errorf("error while extracting resolvedDependencies: %v", err) } - if diff := cmp.Diff(want, got); diff != "" { + if diff := cmp.Diff(want, got, compare.SLSAV1CompareOptions()...); diff != "" { t.Errorf("resolvedDependencies(): -want +got: %s", diff) } } diff --git a/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go b/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go index 97eef48e33..32dd4d9940 100644 --- a/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/tektoncd/chains/pkg/chains/formats" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/config" @@ -496,7 +497,7 @@ func TestPipelineRunCreatePayload1(t *testing.T) { if err != nil { t.Errorf("unexpected error: %s", err.Error()) } - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, compare.SLSAV1CompareOptions()...); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } }