From 3daa525c3254de77742551d90b4856045b73d66a Mon Sep 17 00:00:00 2001 From: Renzo Rojas Silva Date: Sun, 28 Apr 2024 11:50:55 -0400 Subject: [PATCH 1/2] Add new v2alpha4 version for TaskRuns This new version will now process the information from any associated StepAction from the executed TaskRun. Also, the way chains read results from TaskRun to populate the `subjects` field is changing: now the user has to explicitly mark a result as a subject using an bject type-hinted tag (*ARTIFACT_OUTPUTS) + the new `isBuildArtifact` property in the value --- docs/config.md | 3 +- docs/slsa-provenance.md | 101 ++++- .../task-with-object-type-hinting.yaml | 52 +++ pkg/artifacts/signable.go | 70 +++- pkg/artifacts/signable_test.go | 197 ++++++++- pkg/artifacts/structured.go | 4 +- pkg/chains/formats/all/all.go | 1 + pkg/chains/formats/format.go | 2 + pkg/chains/formats/slsa/extract/extract.go | 48 ++- .../formats/slsa/extract/extract_test.go | 169 ++++++++ .../formats/slsa/extract/v1beta1/extract.go | 4 +- .../build_definition/build_definition.go | 52 +++ .../build_definition/build_definition_test.go | 116 ++++++ .../internal_parameters.go | 19 + .../internal_parameters_test.go | 73 ++-- .../slsa/internal/material/material.go | 32 +- .../slsa/internal/material/material_test.go | 155 +++++++ .../internal/material/v1beta1/material.go | 4 +- .../slsa/internal/metadata/metadata.go | 28 ++ .../slsa/internal/metadata/metadata_test.go | 86 ++++ .../slsa/internal/provenance/provenance.go | 43 ++ .../resolved_dependencies.go | 17 +- .../resolved_dependencies_test.go | 74 +++- .../formats/slsa/internal/results/results.go | 67 +++ .../slsa/internal/results/results_test.go | 198 +++++++++ .../taskrun-multiple-subjects.json | 72 ++++ .../slsa/testdata/slsa-v2alpha4/taskrun1.json | 166 ++++++++ .../slsa/testdata/slsa-v2alpha4/taskrun2.json | 115 ++++++ .../slsa/v2alpha3/internal/taskrun/taskrun.go | 78 +--- .../v2alpha3/internal/taskrun/taskrun_test.go | 151 +------ .../slsa/v2alpha4/internal/taskrun/taskrun.go | 69 ++++ .../v2alpha4/internal/taskrun/taskrun_test.go | 169 ++++++++ pkg/chains/formats/slsa/v2alpha4/slsav2.go | 73 ++++ .../formats/slsa/v2alpha4/slsav2_test.go | 386 ++++++++++++++++++ pkg/chains/objects/objects.go | 97 +++++ pkg/chains/storage/oci/legacy.go | 2 +- pkg/config/config.go | 2 +- test/examples_test.go | 44 +- test/test_utils.go | 42 +- .../slsa/v2alpha4/task-output-image.json | 62 +++ .../task-with-object-type-hinting.json | 155 +++++++ 41 files changed, 2981 insertions(+), 317 deletions(-) create mode 100644 examples/v2alpha4/task-with-object-type-hinting.yaml create mode 100644 pkg/chains/formats/slsa/internal/build_definition/build_definition.go create mode 100644 pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go create mode 100644 pkg/chains/formats/slsa/internal/metadata/metadata.go create mode 100644 pkg/chains/formats/slsa/internal/metadata/metadata_test.go create mode 100644 pkg/chains/formats/slsa/internal/provenance/provenance.go create mode 100644 pkg/chains/formats/slsa/internal/results/results.go create mode 100644 pkg/chains/formats/slsa/internal/results/results_test.go create mode 100644 pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun-multiple-subjects.json create mode 100644 pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun1.json create mode 100644 pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun2.json create mode 100644 pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go create mode 100644 pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go create mode 100644 pkg/chains/formats/slsa/v2alpha4/slsav2.go create mode 100644 pkg/chains/formats/slsa/v2alpha4/slsav2_test.go create mode 100644 test/testdata/slsa/v2alpha4/task-output-image.json create mode 100644 test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json diff --git a/docs/config.md b/docs/config.md index 2f8e70433c..a0383a4472 100644 --- a/docs/config.md +++ b/docs/config.md @@ -21,7 +21,7 @@ Supported keys include: | Key | Description | Supported Values | Default | | :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | :-------- | -| `artifacts.taskrun.format` | The format to store `TaskRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3` | `in-toto` | +| `artifacts.taskrun.format` | The format to store `TaskRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3`, `slsa/v2alpha4` | `in-toto` | | `artifacts.taskrun.storage` | The storage backend to store `TaskRun` signatures in. Multiple backends can be specified with comma-separated list ("tekton,oci"). To disable the `TaskRun` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `tekton` | | `artifacts.taskrun.signer` | The signature backend to sign `TaskRun` payloads with. | `x509`, `kms` | `x509` | @@ -29,6 +29,7 @@ Supported keys include: > > - `slsa/v1` is an alias of `in-toto` for backwards compatibility. > - `slsa/v2alpha3` corresponds to the slsav1.0 spec. and uses latest [`v1` Tekton Objects](https://tekton.dev/docs/pipelines/pipeline-api/#tekton.dev/v1). Recommended format for new chains users who want the slsav1.0 spec. +> - `slsa/v2alpha4` corresponds to the slsav1.0 spec. and uses latest [`v1` Tekton Objects](https://tekton.dev/docs/pipelines/pipeline-api/#tekton.dev/v1). It reads type-hinted results from [StepActions](https://tekton.dev/docs/pipelines/pipeline-api/#tekton.dev/v1alpha1.StepAction). Recommended format for new chains users who want the slsav1.0 spec. ### PipelineRun Configuration diff --git a/docs/slsa-provenance.md b/docs/slsa-provenance.md index 12c67512c1..eb606c32a1 100644 --- a/docs/slsa-provenance.md +++ b/docs/slsa-provenance.md @@ -38,7 +38,7 @@ The following shows the mapping between slsa version and formatter name. | SLSA Version | Formatter Name | | ------------ | ---------------------- | -| v1.0 | `slsa/v2alpha3` | +| v1.0 | `slsa/v2alpha3` and `slsa/v2alpha4` | | v0.2 | `slsa/v1` or `in-toto` | To configure Task-level provenance version @@ -319,6 +319,105 @@ spec: +## `v2alpha4` formatter + +Starting with version `v2alpha4`, the type-hinted object results value now can include a new boolean flag called `isBuildArtifact`. When set to `true`, this flag indicates the output artifact should be considered as `subject` in the executed TaskRun/PipelineRun. + +The `isBuildArtifact` can be set in results whose type-hint uses the `*ARTIFACT_OUTPUTS` format. Results using the `IMAGES` and `*IMAGE_URL` / `*IMAGE_DIGEST` type-hint format will still be considered as `subject` automatically; all other results will be clasified as `byProduct` + +For instance, in the following TaskRun: + +```yaml +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + name: image-build +spec: + taskSpec: + results: + - name: first-ARTIFACT_OUTPUTS + description: The first artifact built + type: object + properties: + uri: {} + digest: {} + + - name: second-ARTIFACT_OUTPUTS + description: The second artifact built + type: object + properties: + uri: {} + digest: {} + isBuildArtifact: {} + + - name: third-IMAGE_URL + type: string + - name: third-IMAGE_DIGEST + type: string + + - name: IMAGES + type: string + steps: + - name: dummy-build + image: bash:latest + script: | + echo -n "{\"uri\":\"gcr.io/foo/img1\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\"}" > $(results.first-ARTIFACT_OUTPUTS.path) + + echo -n "{\"uri\":\"gcr.io/foo/img2\", \"digest\":\"sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5\", \"isBuildArtifact\":\"true\"}" > $(results.second-ARTIFACT_OUTPUTS.path) + + echo -n "gcr.io/foo/bar" | tee $(results.third-IMAGE_URL.path) + echo -n "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" | tee $(results.third-IMAGE_DIGEST.path) + + echo -n "gcr.io/test/img3@sha256:2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b, gcr.io/test/img4@sha256:ef334b5d9704da9b325ed6d4e3e5327863847e2da6d43f81831fd1decbdb2213" | tee $(results.IMAGES.path) +``` + +`second-ARTIFACT_OUTPUTS`, `third-IMAGE_URL`/`third-IMAGE_DIGEST`, and `IMAGES` will be considered as `subject`. `first-ARTIFACT_OUTPUTS` doesn't specify `isBuildArtifact: true` so it is not count as `subject`. + +Chains' `v2alpha4` formatter now automatically reads type-hinted results from StepActions associated to the executed TaskRun; users no longer need to manually surface these results from the StepActions when the appropriate type hints are in place. For instance, with the following TaskRun: + +```yaml +apiVersion: tekton.dev/v1alpha1 +kind: StepAction +metadata: + name: img-builder +spec: + image: busybox:glibc + + results: + - name: first-ARTIFACT_OUTPUTS + description: The first artifact built + type: object + properties: + uri: {} + digest: {} + isBuildArtifact: {} + + - name: second-IMAGE_URL + type: string + - name: second-IMAGE_DIGEST + type: string + + script: | + echo -n "{\"uri\":\"gcr.io/foo/img1\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\", \"isBuildArtifact\": \"true\" }" > $(step.results.first-ARTIFACT_OUTPUTS.path) + + echo -n "gcr.io/foo/bar" > $(step.results.second-IMAGE_URL.path) + echo -n "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" > $(step.results.second-IMAGE_DIGEST.path) +--- +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + name: taskrun +spec: + taskSpec: + steps: + - name: action-runner + ref: + name: img-builder +``` + +Chains Will read `first-ARTIFACT_OUTPUTS` and `second-IMAGE_URL/second-IMAGE_DIGEST` from the StepAction and clasify them as a `subject`. + + ## Besides inputs/outputs Tekton Chains is also able to capture the feature flags being used for Tekton Pipelines controller and the origin of the build configuration file with immutable references such as task.yaml and pipeline.yaml. However, those fields in Tekton Pipelines are gated by a dedicated feature flag. Therefore, the feature flag needs to be enabled to let Tekton Pipelines controller to populate these fields. diff --git a/examples/v2alpha4/task-with-object-type-hinting.yaml b/examples/v2alpha4/task-with-object-type-hinting.yaml new file mode 100644 index 0000000000..61b1fdfc9f --- /dev/null +++ b/examples/v2alpha4/task-with-object-type-hinting.yaml @@ -0,0 +1,52 @@ +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + name: image-build +spec: + taskSpec: + results: + - name: first-ARTIFACT_OUTPUTS + description: The first artifact built + type: object + properties: + uri: {} + digest: {} + + - name: second-ARTIFACT_OUTPUTS + description: The second artifact built + type: object + properties: + uri: {} + digest: {} + isBuildArtifact: {} + + - name: third-IMAGE_URL + type: string + - name: third-IMAGE_DIGEST + type: string + + - name: IMAGES + type: string + + - name: fourth-ARTIFACT_OUTPUTS + type: object + properties: + uri: {} + digest: {} + isBuildArtifact: {} + + + steps: + - name: dummy-build + image: bash:latest + script: | + echo -n "{\"uri\":\"gcr.io/foo/img1\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\"}" > $(results.first-ARTIFACT_OUTPUTS.path) + + echo -n "{\"uri\":\"gcr.io/foo/img2\", \"digest\":\"sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5\", \"isBuildArtifact\":\"true\"}" > $(results.second-ARTIFACT_OUTPUTS.path) + + echo -n "gcr.io/foo/bar" | tee $(results.third-IMAGE_URL.path) + echo -n "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" | tee $(results.third-IMAGE_DIGEST.path) + + echo -n "gcr.io/test/img3@sha256:2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b" | tee $(results.IMAGES.path) + + echo -n "{\"uri\":\"gcr.io/test/img4\", \"digest\":\"sha256:basd-sha\", \"isBuildArtifact\":\"true\"}" > $(results.fourth-ARTIFACT_OUTPUTS.path) \ No newline at end of file diff --git a/pkg/artifacts/signable.go b/pkg/artifacts/signable.go index d3fbf885d3..cd3108209d 100644 --- a/pkg/artifacts/signable.go +++ b/pkg/artifacts/signable.go @@ -38,6 +38,10 @@ const ( ArtifactsOutputsResultName = "ARTIFACT_OUTPUTS" OCIScheme = "oci://" GitSchemePrefix = "git+" + isBuildArtifactField = "isBuildArtifact" + OCIImageURLResultName = "IMAGE_URL" + OCIImageDigestResultName = "IMAGE_DIGEST" + OCIImagesResultName = "IMAGES" ) var ( @@ -191,22 +195,23 @@ func (oa *OCIArtifact) ExtractObjects(ctx context.Context, obj objects.TektonObj } // Now check TaskResults - resultImages := ExtractOCIImagesFromResults(ctx, obj) + resultImages := ExtractOCIImagesFromResults(ctx, obj.GetResults()) objs = append(objs, resultImages...) return objs } -func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) []interface{} { +// ExtractOCIImagesFromResults returns all the results marked as OCIImage type-hint result. +func ExtractOCIImagesFromResults(ctx context.Context, results []objects.Result) []interface{} { logger := logging.FromContext(ctx) objs := []interface{}{} extractor := structuredSignableExtractor{ - uriSuffix: "IMAGE_URL", - digestSuffix: "IMAGE_DIGEST", + uriSuffix: OCIImageURLResultName, + digestSuffix: OCIImageDigestResultName, isValid: hasImageRequirements, } - for _, s := range extractor.extract(ctx, obj) { + for _, s := range extractor.extract(ctx, results) { dgst, err := name.NewDigest(fmt.Sprintf("%s@%s", s.URI, s.Digest)) if err != nil { logger.Errorf("error getting digest: %v", err) @@ -216,8 +221,8 @@ func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) } // look for a comma separated list of images - for _, key := range obj.GetResults() { - if key.Name != "IMAGES" { + for _, key := range results { + if key.Name != OCIImagesResultName { continue } imgs := strings.FieldsFunc(key.Value.StringVal, split) @@ -256,7 +261,7 @@ func ExtractSignableTargetFromResults(ctx context.Context, obj objects.TektonObj return true }, } - return extractor.extract(ctx, obj) + return extractor.extract(ctx, obj.GetResults()) } // FullRef returns the full reference of the signable artifact in the format of URI@DIGEST @@ -264,12 +269,12 @@ func (s *StructuredSignable) FullRef() string { return fmt.Sprintf("%s@%s", s.URI, s.Digest) } -// RetrieveMaterialsFromStructuredResults retrieves structured results from Tekton Object, and convert them into materials. -func RetrieveMaterialsFromStructuredResults(ctx context.Context, obj objects.TektonObject, categoryMarker string) []common.ProvenanceMaterial { +// RetrieveMaterialsFromStructuredResults retrieves structured results from Object Results, and convert them into materials. +func RetrieveMaterialsFromStructuredResults(ctx context.Context, objResults []objects.Result) []common.ProvenanceMaterial { logger := logging.FromContext(ctx) // Retrieve structured provenance for inputs. mats := []common.ProvenanceMaterial{} - ssts := ExtractStructuredTargetFromResults(ctx, obj, ArtifactsInputsResultName) + ssts := ExtractStructuredTargetFromResults(ctx, objResults, ArtifactsInputsResultName) for _, s := range ssts { alg, digest, err := ParseDigest(s.Digest) if err != nil { @@ -286,7 +291,7 @@ func RetrieveMaterialsFromStructuredResults(ctx context.Context, obj objects.Tek // ExtractStructuredTargetFromResults extracts structured signable targets aim to generate intoto provenance as materials within TaskRun results and store them as StructuredSignable. // categoryMarker categorizes signable targets into inputs and outputs. -func ExtractStructuredTargetFromResults(ctx context.Context, obj objects.TektonObject, categoryMarker string) []*StructuredSignable { +func ExtractStructuredTargetFromResults(ctx context.Context, objResults []objects.Result, categoryMarker string) []*StructuredSignable { logger := logging.FromContext(ctx) objs := []*StructuredSignable{} if categoryMarker != ArtifactsInputsResultName && categoryMarker != ArtifactsOutputsResultName { @@ -295,7 +300,7 @@ func ExtractStructuredTargetFromResults(ctx context.Context, obj objects.TektonO // TODO(#592): support structured results using Run results := []objects.Result{} - for _, res := range obj.GetResults() { + for _, res := range objResults { results = append(results, objects.Result{ Name: res.Name, Value: res.Value, @@ -316,6 +321,41 @@ func ExtractStructuredTargetFromResults(ctx context.Context, obj objects.TektonO return objs } +// ExtractBuildArtifactsFromResults extracts all the structured signable targets from the given results, only processing the ones marked as build artifacts. +func ExtractBuildArtifactsFromResults(ctx context.Context, results []objects.Result) (objs []*StructuredSignable) { + logger := logging.FromContext(ctx) + + for _, res := range results { + valid, err := IsBuildArtifact(res) + if err != nil { + logger.Debugf("ExtractBuildArtifactsFromResults failed validatin artifact %v, ignoring artifact, err: %v", res.Name, err) + continue + } + if valid { + logger.Debugf("Extracted Build artifact data from Result %s, %s", res.Value.ObjectVal["uri"], res.Value.ObjectVal["digest"]) + objs = append(objs, &StructuredSignable{URI: res.Value.ObjectVal["uri"], Digest: res.Value.ObjectVal["digest"]}) + } + } + return +} + +// IsBuildArtifact indicates if a given result was marked as a Build Artifact. +func IsBuildArtifact(res objects.Result) (bool, error) { + if !strings.HasSuffix(res.Name, ArtifactsOutputsResultName) { + return false, nil + } + + if res.Value.ObjectVal == nil { + return false, fmt.Errorf("%s should be an object: %v", res.Name, res.Value.ObjectVal) + } + + if res.Value.ObjectVal[isBuildArtifactField] != "true" { + return false, nil + } + + return isValidArtifactOutput(res) +} + func isStructuredResult(res objects.Result, categoryMarker string) (bool, error) { if !strings.HasSuffix(res.Name, categoryMarker) { return false, nil @@ -323,6 +363,10 @@ func isStructuredResult(res objects.Result, categoryMarker string) (bool, error) if res.Value.ObjectVal == nil { return false, fmt.Errorf("%s should be an object: %v", res.Name, res.Value.ObjectVal) } + return isValidArtifactOutput(res) +} + +func isValidArtifactOutput(res objects.Result) (bool, error) { if res.Value.ObjectVal["uri"] == "" { return false, fmt.Errorf("%s should have uri field: %v", res.Name, res.Value.ObjectVal) } diff --git a/pkg/artifacts/signable_test.go b/pkg/artifacts/signable_test.go index 5cbdab0847..98c303c0ac 100644 --- a/pkg/artifacts/signable_test.go +++ b/pkg/artifacts/signable_test.go @@ -328,32 +328,26 @@ func TestOCIArtifact_ExtractObjects(t *testing.T) { } func TestExtractOCIImagesFromResults(t *testing.T) { - tr := &v1.TaskRun{ - Status: v1.TaskRunStatus{ - TaskRunStatusFields: v1.TaskRunStatusFields{ - Results: []v1.TaskRunResult{ - {Name: "img1_IMAGE_URL", Value: *v1.NewStructuredValues("img1")}, - {Name: "img1_IMAGE_DIGEST", Value: *v1.NewStructuredValues(digest1)}, - {Name: "img2_IMAGE_URL", Value: *v1.NewStructuredValues("img2")}, - {Name: "img2_IMAGE_DIGEST", Value: *v1.NewStructuredValues(digest2)}, - {Name: "IMAGE_URL", Value: *v1.NewStructuredValues("img3")}, - {Name: "IMAGE_DIGEST", Value: *v1.NewStructuredValues(digest1)}, - {Name: "img4_IMAGE_URL", Value: *v1.NewStructuredValues("img4")}, - {Name: "img5_IMAGE_DIGEST", Value: *v1.NewStructuredValues("sha123:abc")}, - {Name: "empty_str_IMAGE_DIGEST", Value: *v1.NewStructuredValues("")}, - {Name: "empty_str_IMAGE_URL", Value: *v1.NewStructuredValues("")}, - }, - }, - }, + results := []objects.Result{ + {Name: "img1_IMAGE_URL", Value: *v1.NewStructuredValues("img1")}, + {Name: "img1_IMAGE_DIGEST", Value: *v1.NewStructuredValues(digest1)}, + {Name: "img2_IMAGE_URL", Value: *v1.NewStructuredValues("img2")}, + {Name: "img2_IMAGE_DIGEST", Value: *v1.NewStructuredValues(digest2)}, + {Name: "IMAGE_URL", Value: *v1.NewStructuredValues("img3")}, + {Name: "IMAGE_DIGEST", Value: *v1.NewStructuredValues(digest1)}, + {Name: "img4_IMAGE_URL", Value: *v1.NewStructuredValues("img4")}, + {Name: "img5_IMAGE_DIGEST", Value: *v1.NewStructuredValues("sha123:abc")}, + {Name: "empty_str_IMAGE_DIGEST", Value: *v1.NewStructuredValues("")}, + {Name: "empty_str_IMAGE_URL", Value: *v1.NewStructuredValues("")}, } - obj := objects.NewTaskRunObjectV1(tr) + want := []interface{}{ createDigest(t, fmt.Sprintf("img1@%s", digest1)), createDigest(t, fmt.Sprintf("img2@%s", digest2)), createDigest(t, fmt.Sprintf("img3@%s", digest1)), } ctx := logtesting.TestContextWithLogger(t) - got := ExtractOCIImagesFromResults(ctx, obj) + got := ExtractOCIImagesFromResults(ctx, results) sort.Slice(got, func(i, j int) bool { a := got[i].(name.Digest) b := got[j].(name.Digest) @@ -495,7 +489,7 @@ func TestExtractStructuredTargetFromResults(t *testing.T) { {URI: "gcr.io/foo/bar", Digest: digest_sha512}, } ctx := logtesting.TestContextWithLogger(t) - gotInputs := ExtractStructuredTargetFromResults(ctx, objects.NewTaskRunObjectV1(tr), ArtifactsInputsResultName) + gotInputs := ExtractStructuredTargetFromResults(ctx, objects.NewTaskRunObjectV1(tr).GetResults(), ArtifactsInputsResultName) if diff := cmp.Diff(gotInputs, wantInputs, cmpopts.SortSlices(func(x, y *StructuredSignable) bool { return x.Digest < y.Digest })); diff != "" { t.Errorf("Inputs are not as expected: %v", diff) } @@ -504,7 +498,7 @@ func TestExtractStructuredTargetFromResults(t *testing.T) { {URI: "projects/test-project/locations/us-west4/repositories/test-repo/mavenArtifacts/com.google.guava:guava:31.0-jre", Digest: digest1}, {URI: "com.google.guava:guava:31.0-jre.pom", Digest: digest2}, } - gotOutputs := ExtractStructuredTargetFromResults(ctx, objects.NewTaskRunObjectV1(tr), ArtifactsOutputsResultName) + gotOutputs := ExtractStructuredTargetFromResults(ctx, objects.NewTaskRunObjectV1(tr).GetResults(), ArtifactsOutputsResultName) opts := append(ignore, cmpopts.SortSlices(func(x, y *StructuredSignable) bool { return x.Digest < y.Digest })) if diff := cmp.Diff(gotOutputs, wantOutputs, opts...); diff != "" { t.Error(diff) @@ -548,7 +542,7 @@ func TestRetrieveMaterialsFromStructuredResults(t *testing.T) { }, } ctx := logtesting.TestContextWithLogger(t) - gotMaterials := RetrieveMaterialsFromStructuredResults(ctx, objects.NewTaskRunObjectV1(tr), ArtifactsInputsResultName) + gotMaterials := RetrieveMaterialsFromStructuredResults(ctx, objects.NewTaskRunObjectV1(tr).GetResults()) if diff := cmp.Diff(gotMaterials, wantMaterials, ignore...); diff != "" { t.Fatalf("Materials not the same %s", diff) @@ -654,6 +648,165 @@ func TestValidateResults(t *testing.T) { } } +func TestExtractBuildArtifactsFromResults(t *testing.T) { + tests := []struct { + name string + results []objects.Result + expectedBuildArtifacts []*StructuredSignable + }{ + { + name: "structured result without isBuildArtifact property", + results: []objects.Result{ + { + Name: "result-ARTIFACT_OUTPUTS", + + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "uri": "gcr.io/foo/bar", + "digest": digest1, + }, + }, + }, + }, + }, + { + name: "structured result without expected schema", + results: []objects.Result{ + { + Name: "bad-type-ARTIFACT_OUTPUTS", + Value: v1.ParamValue{ + StringVal: "not-expected-type-value", + }, + }, + { + Name: "bad-url-field-ARTIFACT_OUTPUTS", + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "url": "foo.com", + isBuildArtifactField: "true", + }, + }, + }, + { + Name: "bad-commit-field-ARTIFACT_OUTPUTS", + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "uri": "gcr.io/foo/bar", + "commit": digest1, + isBuildArtifactField: "true", + }, + }, + }, + { + Name: "bad-digest-value-ARTIFACT_OUTPUTS", + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "uri": "gcr.io/foo/bar", + "digest": "sha512:baddigestvalue", + isBuildArtifactField: "true", + }, + }, + }, + }, + }, + { + name: "structured result without expected type-hint", + results: []objects.Result{ + { + Name: "result-ARTIFACT_OBJ", + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "uri": "gcr.io/foo/bar", + "digest": digest1, + isBuildArtifactField: "true", + }, + }, + }, + { + Name: "result-ARTIFACT_URI", + Value: v1.ParamValue{ + StringVal: "gcr.io/foo/bar", + }, + }, + { + Name: "result-ARTIFACT_DIGEST", + Value: v1.ParamValue{ + StringVal: digest1, + }, + }, + { + Name: "result-IMAGE_URL", + Value: v1.ParamValue{ + StringVal: "gcr.io/foo/bar", + }, + }, + { + Name: "result-IMAGE_DIGEST", + Value: v1.ParamValue{ + StringVal: digest1, + }, + }, + { + Name: "IMAGES", + Value: v1.ParamValue{ + ArrayVal: []string{ + fmt.Sprintf("img1@sha256:%v", digest1), + fmt.Sprintf("img2@sha256:%v", digest2), + }, + }, + }, + }, + }, + { + name: "structured result mark as build artifact", + results: []objects.Result{ + { + Name: "result-1-ARTIFACT_OUTPUTS", + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "uri": "gcr.io/foo/bar", + "digest": digest1, + isBuildArtifactField: "true", + }, + }, + }, + { + Name: "result-2-ARTIFACT_OUTPUTS", + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "uri": "gcr.io/bar/foo", + "digest": digest2, + isBuildArtifactField: "false", + }, + }, + }, + { + Name: "result-3-ARTIFACT_OUTPUTS", + Value: v1.ParamValue{ + ObjectVal: map[string]string{ + "uri": "gcr.io/repo/test", + "digest": digest3, + }, + }, + }, + }, + expectedBuildArtifacts: []*StructuredSignable{ + {URI: "gcr.io/foo/bar", Digest: digest1}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + gotBuildArtifacts := ExtractBuildArtifactsFromResults(ctx, test.results) + if diff := cmp.Diff(gotBuildArtifacts, test.expectedBuildArtifacts); diff != "" { + t.Fatalf("Materials not the same %s", diff) + } + }) + } +} + func createDigest(t *testing.T, dgst string) name.Digest { result, err := name.NewDigest(dgst) if err != nil { diff --git a/pkg/artifacts/structured.go b/pkg/artifacts/structured.go index 4e9efb1a9b..b0fa18c429 100644 --- a/pkg/artifacts/structured.go +++ b/pkg/artifacts/structured.go @@ -36,7 +36,7 @@ type structuredSignableExtractor struct { isValid func(StructuredSignable) bool } -func (b *structuredSignableExtractor) extract(ctx context.Context, obj objects.TektonObject) []StructuredSignable { +func (b *structuredSignableExtractor) extract(ctx context.Context, results []objects.Result) []StructuredSignable { logger := logging.FromContext(ctx) partials := map[string]StructuredSignable{} @@ -51,7 +51,7 @@ func (b *structuredSignableExtractor) extract(ctx context.Context, obj objects.T }, } - for _, res := range obj.GetResults() { + for _, res := range results { for suffix, setFn := range suffixes { if suffix == "" { continue diff --git a/pkg/chains/formats/all/all.go b/pkg/chains/formats/all/all.go index 0ba9226420..7e4ba3dd0b 100644 --- a/pkg/chains/formats/all/all.go +++ b/pkg/chains/formats/all/all.go @@ -18,4 +18,5 @@ import ( _ "github.com/tektoncd/chains/pkg/chains/formats/simple" _ "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1" _ "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha3" + _ "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha4" ) diff --git a/pkg/chains/formats/format.go b/pkg/chains/formats/format.go index 00c3990cda..797240f999 100644 --- a/pkg/chains/formats/format.go +++ b/pkg/chains/formats/format.go @@ -33,6 +33,7 @@ const ( PayloadTypeInTotoIte6 config.PayloadType = "in-toto" PayloadTypeSlsav1 config.PayloadType = "slsa/v1" PayloadTypeSlsav2alpha3 config.PayloadType = "slsa/v2alpha3" + PayloadTypeSlsav2alpha4 config.PayloadType = "slsa/v2alpha4" ) var ( @@ -40,6 +41,7 @@ var ( PayloadTypeInTotoIte6: {}, PayloadTypeSlsav1: {}, PayloadTypeSlsav2alpha3: {}, + PayloadTypeSlsav2alpha4: {}, } payloaderMap = map[config.PayloadType]PayloaderInit{} ) diff --git a/pkg/chains/formats/slsa/extract/extract.go b/pkg/chains/formats/slsa/extract/extract.go index 2cc4f4861b..510d13c06a 100644 --- a/pkg/chains/formats/slsa/extract/extract.go +++ b/pkg/chains/formats/slsa/extract/extract.go @@ -101,7 +101,7 @@ func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []i logger := logging.FromContext(ctx) var subjects []intoto.Subject - imgs := artifacts.ExtractOCIImagesFromResults(ctx, obj) + imgs := artifacts.ExtractOCIImagesFromResults(ctx, obj.GetResults()) for _, i := range imgs { if d, ok := i.(name.Digest); ok { subjects = artifact.AppendSubjects(subjects, intoto.Subject{ @@ -128,7 +128,7 @@ func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []i }) } - ssts := artifacts.ExtractStructuredTargetFromResults(ctx, obj, artifacts.ArtifactsOutputsResultName) + ssts := artifacts.ExtractStructuredTargetFromResults(ctx, obj.GetResults(), artifacts.ArtifactsOutputsResultName) for _, s := range ssts { splits := strings.Split(s.Digest, ":") alg := splits[0] @@ -202,3 +202,47 @@ func RetrieveAllArtifactURIs(ctx context.Context, obj objects.TektonObject, deep } return result } + +// SubjectsFromBuildArtifact returns the software artifacts/images produced by the TaskRun/PipelineRun in the form of standard +// subject field of intoto statement. The detection is based on type hinting. To be read as a software artifact the +// type hintint should: +// - use one of the following type-hints: +// - Use the *ARTIFACT_OUTPUTS object type-hinting suffix. The value associated with the result should be an object +// with the fields `uri`, `digest`, and `isBuildArtifact` set to true. +// - Use the IMAGES type-hint +// - Use the *IMAGE_URL / *IMAGE_DIGEST type-hint suffix +func SubjectsFromBuildArtifact(ctx context.Context, results []objects.Result) []intoto.Subject { + var subjects []intoto.Subject + logger := logging.FromContext(ctx) + buildArtifacts := artifacts.ExtractBuildArtifactsFromResults(ctx, results) + for _, ba := range buildArtifacts { + splits := strings.Split(ba.Digest, ":") + if len(splits) != 2 { + logger.Errorf("Error procesing build artifact %v, digest %v malformed. Build artifact skipped", ba.FullRef(), ba.Digest) + continue + } + + alg := splits[0] + digest := splits[1] + subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + Name: ba.URI, + Digest: common.DigestSet{ + alg: digest, + }, + }) + } + + imgs := artifacts.ExtractOCIImagesFromResults(ctx, results) + for _, i := range imgs { + if d, ok := i.(name.Digest); ok { + subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + Name: d.Repository.Name(), + Digest: common.DigestSet{ + "sha256": strings.TrimPrefix(d.DigestStr(), "sha256:"), + }, + }) + } + } + + return subjects +} diff --git a/pkg/chains/formats/slsa/extract/extract_test.go b/pkg/chains/formats/slsa/extract/extract_test.go index 583a727170..7a9b18828d 100644 --- a/pkg/chains/formats/slsa/extract/extract_test.go +++ b/pkg/chains/formats/slsa/extract/extract_test.go @@ -271,6 +271,175 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { } } +func TestSubjectsFromBuildArtifact(t *testing.T) { + tests := []struct { + name string + results []objects.Result + expectedSubjects []intoto.Subject + }{ + { + name: "no type-hinted build artifacts", + results: []objects.Result{ + { + Name: "result2_ARTIFACT_URL", + Value: *v1.NewStructuredValues("gcr.io/test/img4"), + }, + { + Name: "result2_ARTIFACT_DIGEST", + Value: *v1.NewStructuredValues("sha256:jh5f72309sf937b16a914eea7cb81ebaa8f2b0a13833797acd26dty46529ihm"), + }, + { + Name: "result3_ARTIFACT_OUTPUTS", + Value: *v1.NewObject(map[string]string{ + "uri": "gcr.io/img5", + "digest": "sha256:7492314e32aa75ff1f2cfea35b7dda85d8831929d076aab52420c3400c8c65d8", + }), + }, + }, + }, + { + name: "type-hinted build artifacts", + results: []objects.Result{ + { + Name: "result1_IMAGE_URL", + Value: *v1.NewStructuredValues("gcr.io/test/img1"), + }, + { + Name: "result1_IMAGE_DIGEST", + Value: *v1.NewStructuredValues("sha256:52e18b100a8da6e191a1955913ba127b75a8b38146cd9b0f573ec1d8e8ecd135"), + }, + { + Name: "IMAGES", + Value: *v1.NewStructuredValues( + "gcr.io/test/img2@sha256:2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b, " + + "gcr.io/test/img3@sha256:ef334b5d9704da9b325ed6d4e3e5327863847e2da6d43f81831fd1decbdb2213", + ), + }, + { + Name: "result2_ARTIFACT_OUTPUTS", + Value: *v1.NewObject(map[string]string{ + "uri": "gcr.io/test/img4", + "digest": "sha256:910700c5ace59f70588c4e2a38ed131146c9f65c94379dfe12376075fc2f338f", + "isBuildArtifact": "true", + }), + }, + { + Name: "result3_ARTIFACT_OUTPUTS", + Value: *v1.NewObject(map[string]string{ + "uri": "gcr.io/test/img5", + "digest": "sha256:7492314e32aa75ff1f2cfea35b7dda85d8831929d076aab52420c3400c8c65d8", + "isBuildArtifact": "true", + }), + }, + }, + expectedSubjects: []intoto.Subject{ + { + Name: "gcr.io/test/img4", + Digest: map[string]string{ + "sha256": "910700c5ace59f70588c4e2a38ed131146c9f65c94379dfe12376075fc2f338f", + }, + }, + { + Name: "gcr.io/test/img5", + Digest: map[string]string{ + "sha256": "7492314e32aa75ff1f2cfea35b7dda85d8831929d076aab52420c3400c8c65d8", + }, + }, + { + Name: "gcr.io/test/img1", + Digest: map[string]string{ + "sha256": "52e18b100a8da6e191a1955913ba127b75a8b38146cd9b0f573ec1d8e8ecd135", + }, + }, + { + Name: "gcr.io/test/img2", + Digest: map[string]string{ + "sha256": "2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b", + }, + }, + { + Name: "gcr.io/test/img3", + Digest: map[string]string{ + "sha256": "ef334b5d9704da9b325ed6d4e3e5327863847e2da6d43f81831fd1decbdb2213", + }, + }, + }, + }, + { + name: "no repetead type-hinted build artifacts", + results: []objects.Result{ + { + Name: "result1_ARTIFACT_OUTPUTS", + Value: *v1.NewObject(map[string]string{ + "uri": "gcr.io/test/img1", + "digest": "sha256:8b7b3e8b124f937b16a914eea7cb81ebaa8f2b0a13833797acd26d67edf4e056", + "isBuildArtifact": "true", + }), + }, + { + Name: "result2_ARTIFACT_OUTPUTS", + Value: *v1.NewObject(map[string]string{ + "uri": "gcr.io/test/img1", + "digest": "sha256:8b7b3e8b124f937b16a914eea7cb81ebaa8f2b0a13833797acd26d67edf4e056", + "isBuildArtifact": "true", + }), + }, + { + Name: "IMAGES", + Value: *v1.NewStructuredValues( + "gcr.io/test/img1@sha256:8b7b3e8b124f937b16a914eea7cb81ebaa8f2b0a13833797acd26d67edf4e056", + ), + }, + }, + expectedSubjects: []intoto.Subject{ + { + Name: "gcr.io/test/img1", + Digest: map[string]string{ + "sha256": "8b7b3e8b124f937b16a914eea7cb81ebaa8f2b0a13833797acd26d67edf4e056", + }, + }, + }, + }, + { + name: "malformed digests", + results: []objects.Result{ + { + Name: "result1_IMAGE_URL", + Value: *v1.NewStructuredValues("gcr.io/test/img1"), + }, + { + Name: "result1_IMAGE_DIGEST", + Value: *v1.NewStructuredValues("sha256@52e18b100a8da6e191a1955913ba127b75a8b38146cd9b0f573ec1d8e8ecd135"), + }, + { + Name: "IMAGES", + Value: *v1.NewStructuredValues( + "gcr.io/test/img2@sha256@2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b", + ), + }, + { + Name: "result2_ARTIFACT_OUTPUTS", + Value: *v1.NewObject(map[string]string{ + "uri": "gcr.io/test/img5", + "digest": "sha256@7492314e32aa75ff1f2cfea35b7dda85d8831929d076aab52420c3400c8c65d8", + "isBuildArtifact": "true", + }), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + got := extract.SubjectsFromBuildArtifact(ctx, test.results) + if diff := cmp.Diff(test.expectedSubjects, got); diff != "" { + t.Errorf("Wrong subjects from build artifacts, +got -want, diff=%s", diff) + } + }) + } +} + func createTaskRunObjectWithResults(results map[string]string) objects.TektonObject { trResults := []v1.TaskRunResult{} prefix := 0 diff --git a/pkg/chains/formats/slsa/extract/v1beta1/extract.go b/pkg/chains/formats/slsa/extract/v1beta1/extract.go index cb630ba26f..06f6899e53 100644 --- a/pkg/chains/formats/slsa/extract/v1beta1/extract.go +++ b/pkg/chains/formats/slsa/extract/v1beta1/extract.go @@ -94,7 +94,7 @@ func SubjectsFromTektonObjectV1Beta1(ctx context.Context, obj objects.TektonObje logger := logging.FromContext(ctx) var subjects []intoto.Subject - imgs := artifacts.ExtractOCIImagesFromResults(ctx, obj) + imgs := artifacts.ExtractOCIImagesFromResults(ctx, obj.GetResults()) for _, i := range imgs { if d, ok := i.(name.Digest); ok { subjects = artifact.AppendSubjects(subjects, intoto.Subject{ @@ -121,7 +121,7 @@ func SubjectsFromTektonObjectV1Beta1(ctx context.Context, obj objects.TektonObje }) } - ssts := artifacts.ExtractStructuredTargetFromResults(ctx, obj, artifacts.ArtifactsOutputsResultName) + ssts := artifacts.ExtractStructuredTargetFromResults(ctx, obj.GetResults(), artifacts.ArtifactsOutputsResultName) for _, s := range ssts { splits := strings.Split(s.Digest, ":") alg := splits[0] diff --git a/pkg/chains/formats/slsa/internal/build_definition/build_definition.go b/pkg/chains/formats/slsa/internal/build_definition/build_definition.go new file mode 100644 index 0000000000..655bdbf701 --- /dev/null +++ b/pkg/chains/formats/slsa/internal/build_definition/build_definition.go @@ -0,0 +1,52 @@ +/* +Copyright 2024 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 builddefinition + +import ( + "context" + + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_types" + externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" + internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters" + resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" + "github.com/tektoncd/chains/pkg/chains/objects" +) + +// GetTaskRunBuildDefinition returns the buildDefinition for the given TaskRun based on the configured buildType. This will default to the slsa buildType +func GetTaskRunBuildDefinition(ctx context.Context, tro *objects.TaskRunObjectV1, buildType string, resolveOpts resolveddependencies.ResolveOptions) (slsa.ProvenanceBuildDefinition, error) { + rd, err := resolveddependencies.TaskRun(ctx, resolveOpts, tro) + if err != nil { + return slsa.ProvenanceBuildDefinition{}, err + } + + externalParams := externalparameters.TaskRun(tro) + + buildDefinitionType := buildType + if buildDefinitionType == "" { + buildDefinitionType = buildtypes.SlsaBuildType + } + + internalParams, err := internalparameters.GetInternalParamters(tro, buildDefinitionType) + if err != nil { + return slsa.ProvenanceBuildDefinition{}, err + } + + return slsa.ProvenanceBuildDefinition{ + BuildType: buildDefinitionType, + ExternalParameters: externalParams, + InternalParameters: internalParams, + ResolvedDependencies: rd, + }, nil +} diff --git a/pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go b/pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go new file mode 100644 index 0000000000..ab91a0835c --- /dev/null +++ b/pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go @@ -0,0 +1,116 @@ +/* +Copyright 2024 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 builddefinition + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" + internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters" + resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/internal/objectloader" +) + +func TestGetBuildDefinition(t *testing.T) { + tr, err := objectloader.TaskRunFromFile("../../testdata/slsa-v2alpha4/taskrun1.json") + if err != nil { + t.Fatal(err) + } + + tr.Annotations = map[string]string{ + "annotation1": "annotation1", + } + tr.Labels = map[string]string{ + "label1": "label1", + } + + ctx := context.Background() + + tro := objects.NewTaskRunObjectV1(tr) + tests := []struct { + name string + buildType string + want slsa.ProvenanceBuildDefinition + err error + }{ + { + name: "test slsa build type", + buildType: "https://tekton.dev/chains/v2/slsa", + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.SLSAInternalParameters(tro), + }, + err: nil, + }, + { + name: "test default build type", + buildType: "", + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.SLSAInternalParameters(tro), + }, + err: nil, + }, + { + name: "test tekton build type", + buildType: "https://tekton.dev/chains/v2/slsa-tekton", + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa-tekton", + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.TektonInternalParameters(tro), + }, + err: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + rd, err := resolveddependencies.TaskRun(ctx, resolveddependencies.ResolveOptions{}, tro) + if err != nil { + t.Fatalf("Error resolving dependencies: %v", err) + } + tc.want.ResolvedDependencies = rd + + bd, err := GetTaskRunBuildDefinition(context.Background(), tro, tc.buildType, resolveddependencies.ResolveOptions{}) + if err != nil { + t.Fatalf("Did not expect an error but got %v", err) + } + + if diff := cmp.Diff(tc.want, bd); diff != "" { + t.Errorf("getBuildDefinition(): -want +got: %v", diff) + } + }) + } +} + +func TestUnsupportedBuildType(t *testing.T) { + tr, err := objectloader.TaskRunFromFile("../../testdata/slsa-v2alpha4/taskrun1.json") + if err != nil { + t.Fatal(err) + } + + got, err := GetTaskRunBuildDefinition(context.Background(), objects.NewTaskRunObjectV1(tr), "bad-buildType", resolveddependencies.ResolveOptions{}) + if err == nil { + t.Error("getBuildDefinition(): expected error got nil") + } + if diff := cmp.Diff(slsa.ProvenanceBuildDefinition{}, got); diff != "" { + t.Errorf("getBuildDefinition(): -want +got: %s", diff) + } +} diff --git a/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters.go b/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters.go index 80ab28a493..95b9b8a900 100644 --- a/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters.go +++ b/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters.go @@ -17,6 +17,9 @@ limitations under the License. package internalparameters import ( + "fmt" + + buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_types" "github.com/tektoncd/chains/pkg/chains/objects" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" ) @@ -40,3 +43,19 @@ func TektonInternalParameters(tko objects.TektonObject) map[string]any { internalParams["annotations"] = tko.GetAnnotations() return internalParams } + +// GetInternalParamters returns the internal parameters for the given tekton object based on the build type. +func GetInternalParamters(obj objects.TektonObject, buildDefinitionType string) (map[string]any, error) { + var internalParameters map[string]any + + switch buildDefinitionType { + case buildtypes.SlsaBuildType: + internalParameters = SLSAInternalParameters(obj) + case buildtypes.TektonBuildType: + internalParameters = TektonInternalParameters(obj) + default: + return nil, fmt.Errorf("unsupported buildType %v", buildDefinitionType) + } + + return internalParameters, nil +} diff --git a/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters_test.go b/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters_test.go index 6dd6f2008b..36fd6c887d 100644 --- a/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters_test.go +++ b/pkg/chains/formats/slsa/internal/internal_parameters/internal_parameters_test.go @@ -20,41 +20,60 @@ import ( "testing" "github.com/google/go-cmp/cmp" + buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_types" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/config" ) -func TestTektonInternalParameters(t *testing.T) { - tr, err := objectloader.TaskRunV1Beta1FromFile("../../testdata/slsa-v2alpha2/taskrun1.json") - if err != nil { - t.Fatal(err) - } - tro := objects.NewTaskRunObjectV1Beta1(tr) - got := TektonInternalParameters(tro) - want := map[string]any{ - "labels": tro.GetLabels(), - "annotations": tro.GetAnnotations(), - "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, +func TestGetInternalParamters(t *testing.T) { + tests := []struct { + name string + shouldErr bool + buildDefinitionType string + expected map[string]any + }{ + { + name: "SLSA build type", + buildDefinitionType: buildtypes.SlsaBuildType, + expected: map[string]any{ + "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + }, + }, + { + name: "Tekton build type", + buildDefinitionType: buildtypes.TektonBuildType, + expected: map[string]any{ + "labels": map[string]string{"tekton.dev/pipelineTask": "build"}, + "annotations": map[string]string(nil), + "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + }, + }, + { + name: "Invalid build type", + buildDefinitionType: "invalid-type", + shouldErr: true, + }, } - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("TaskRun(): -want +got: %s", diff) - } -} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tr, err := objectloader.TaskRunV1Beta1FromFile("../../testdata/slsa-v2alpha2/taskrun1.json") + if err != nil { + t.Fatal(err) + } + tro := objects.NewTaskRunObjectV1Beta1(tr) -func TestSLSAInternalParameters(t *testing.T) { - tr, err := objectloader.TaskRunV1Beta1FromFile("../../testdata/slsa-v2alpha2/taskrun1.json") - if err != nil { - t.Fatal(err) - } - tro := objects.NewTaskRunObjectV1Beta1(tr) - got := SLSAInternalParameters(tro) - want := map[string]any{ - "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, - } + got, err := GetInternalParamters(tro, test.buildDefinitionType) + + didError := err != nil + if didError != test.shouldErr { + t.Fatalf("Unexpected error behavior, shouldErr: %v, didError: %v, error: %v", test.shouldErr, didError, err) + } - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("TaskRun(): -want +got: %s", diff) + if diff := cmp.Diff(test.expected, got); diff != "" { + t.Errorf("TaskRun(): -want +got: %s", diff) + } + }) } } diff --git a/pkg/chains/formats/slsa/internal/material/material.go b/pkg/chains/formats/slsa/internal/material/material.go index 6c29f4fe93..4cbff0b9d2 100644 --- a/pkg/chains/formats/slsa/internal/material/material.go +++ b/pkg/chains/formats/slsa/internal/material/material.go @@ -255,16 +255,44 @@ func FromTaskParamsAndResults(ctx context.Context, tro *objects.TaskRunObjectV1) }) } - sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, tro, artifacts.ArtifactsInputsResultName) + sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, tro.GetResults()) mats = artifact.AppendMaterials(mats, sms...) return mats } +// FromStepActionsResults extracts type hinted results from StepActions associated with the TaskRun and adds the url and digest to materials. +func FromStepActionsResults(ctx context.Context, tro *objects.TaskRunObjectV1) (mats []common.ProvenanceMaterial) { + for _, s := range tro.Status.Steps { + var sCommit, sURL string + for _, r := range s.Results { + if r.Name == attest.CommitParam { + sCommit = r.Value.StringVal + continue + } + + if r.Name == attest.URLParam { + sURL = r.Value.StringVal + } + } + + sURL = attest.SPDXGit(sURL, "") + if sCommit != "" && sURL != "" { + mats = artifact.AppendMaterials(mats, common.ProvenanceMaterial{ + URI: sURL, + Digest: map[string]string{"sha1": sCommit}, + }) + } + } + sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, tro.GetStepResults()) + mats = artifact.AppendMaterials(mats, sms...) + return +} + // FromPipelineParamsAndResults extracts type hinted params and results and adds the url and digest to materials. func FromPipelineParamsAndResults(ctx context.Context, pro *objects.PipelineRunObjectV1, slsaconfig *slsaconfig.SlsaConfig) []common.ProvenanceMaterial { mats := []common.ProvenanceMaterial{} - sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, pro, artifacts.ArtifactsInputsResultName) + sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, pro.GetResults()) mats = artifact.AppendMaterials(mats, sms...) var commit, url string diff --git a/pkg/chains/formats/slsa/internal/material/material_test.go b/pkg/chains/formats/slsa/internal/material/material_test.go index 9bd827aa8e..27ede6a389 100644 --- a/pkg/chains/formats/slsa/internal/material/material_test.go +++ b/pkg/chains/formats/slsa/internal/material/material_test.go @@ -598,6 +598,161 @@ func TestFromPipelineParamsAndResults(t *testing.T) { } } +func TestFromStepActionsResults(t *testing.T) { + tests := []struct { + name string + steps []v1.StepState + expected []common.ProvenanceMaterial + }{ + { + name: "no type-hint input", + steps: []v1.StepState{ + { + Results: []v1.TaskRunStepResult{ + {Name: "result1_ARTIFACT_URI", Value: *v1.NewStructuredValues("gcr.io/foo/bar1")}, + {Name: "result1_ARTIFACT_DIGEST", Value: *v1.NewStructuredValues(digest)}, + {Name: "result2_IMAGE_URL", Value: *v1.NewStructuredValues("gcr.io/foo/bar2")}, + {Name: "result2_IMAGE_DIGEST", Value: *v1.NewStructuredValues(digest)}, + }, + }, + { + Results: []v1.TaskRunStepResult{ + {Name: "result3_ARTIFACT_OUTPUTS", Value: *v1.NewObject(map[string]string{ + "uri": "gcr.io/foo/bar1", + "digest": digest, + })}, + }, + }, + }, + }, + { + name: "git result type-hint input", + steps: []v1.StepState{ + { + Results: []v1.TaskRunStepResult{ + {Name: "CHAINS-GIT_URL", Value: *v1.NewStructuredValues("https://github.com/org/repo1")}, + {Name: "CHAINS-GIT_COMMIT", Value: *v1.NewStructuredValues("a3efeffe520230f3608b8fc41f7807cbf19a472d")}, + }, + }, + { + Results: []v1.TaskRunStepResult{ + {Name: "CHAINS-GIT_URL", Value: *v1.NewStructuredValues("https://github.com/org/repo2")}, + {Name: "CHAINS-GIT_COMMIT", Value: *v1.NewStructuredValues("05669ed367ed21569f68edee8b93c64eda91e910")}, + }, + }, + }, + expected: []common.ProvenanceMaterial{ + { + URI: artifacts.GitSchemePrefix + "https://github.com/org/repo1.git", + Digest: common.DigestSet{ + "sha1": "a3efeffe520230f3608b8fc41f7807cbf19a472d", + }, + }, + { + URI: artifacts.GitSchemePrefix + "https://github.com/org/repo2.git", + Digest: common.DigestSet{ + "sha1": "05669ed367ed21569f68edee8b93c64eda91e910", + }, + }, + }, + }, + { + name: "object result type-hint input", + steps: []v1.StepState{ + { + Results: []v1.TaskRunStepResult{ + {Name: "res1_ARTIFACT_INPUTS", Value: *v1.NewObject(map[string]string{ + "uri": "https://github.com/tektoncd/pipeline", + "digest": "sha1:7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + })}, + }, + }, + { + Results: []v1.TaskRunStepResult{ + {Name: "res2_ARTIFACT_INPUTS", Value: *v1.NewObject(map[string]string{ + "uri": "https://github.com/org/repo2", + "digest": "sha1:05669ed367ed21569f68edee8b93c64eda91e910", + })}, + }, + }, + }, + expected: []common.ProvenanceMaterial{ + { + URI: "https://github.com/tektoncd/pipeline", + Digest: common.DigestSet{ + "sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + }, + }, + { + URI: "https://github.com/org/repo2", + Digest: common.DigestSet{ + "sha1": "05669ed367ed21569f68edee8b93c64eda91e910", + }, + }, + }, + }, + { + name: "no repeated inputs", + steps: []v1.StepState{ + { + Results: []v1.TaskRunStepResult{ + {Name: "CHAINS-GIT_URL", Value: *v1.NewStructuredValues("https://github.com/tektoncd/pipeline")}, + {Name: "CHAINS-GIT_COMMIT", Value: *v1.NewStructuredValues("7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601")}, + {Name: "res1_ARTIFACT_INPUTS", Value: *v1.NewObject(map[string]string{ + "uri": "https://github.com/tektoncd/pipeline", + "digest": "sha1:7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + })}, + }, + }, + { + Results: []v1.TaskRunStepResult{ + {Name: "CHAINS-GIT_URL", Value: *v1.NewStructuredValues("https://github.com/tektoncd/pipeline")}, + {Name: "CHAINS-GIT_COMMIT", Value: *v1.NewStructuredValues("7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601")}, + {Name: "res1_ARTIFACT_INPUTS", Value: *v1.NewObject(map[string]string{ + "uri": "https://github.com/tektoncd/pipeline", + "digest": "sha1:7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + })}, + }, + }, + }, + expected: []common.ProvenanceMaterial{ + { + URI: "https://github.com/tektoncd/pipeline", + Digest: common.DigestSet{ + "sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + }, + }, + { + URI: artifacts.GitSchemePrefix + "https://github.com/tektoncd/pipeline.git", + Digest: common.DigestSet{ + "sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + tr := objects.NewTaskRunObjectV1( + &v1.TaskRun{ + Status: v1.TaskRunStatus{ + TaskRunStatusFields: v1.TaskRunStatusFields{ + Steps: test.steps, + }, + }, + }, + ) + + got := FromStepActionsResults(ctx, tr) + if diff := cmp.Diff(test.expected, got, compare.MaterialsCompareOption()); diff != "" { + t.Errorf("FromStepActionsResults(): -want +got: %s", diff) + } + }) + } +} + //nolint:all func createProWithPipelineParamAndTaskResult() *objects.PipelineRunObjectV1 { pro := objects.NewPipelineRunObjectV1(&v1.PipelineRun{ diff --git a/pkg/chains/formats/slsa/internal/material/v1beta1/material.go b/pkg/chains/formats/slsa/internal/material/v1beta1/material.go index 3bcec5480b..ef49f2b73c 100644 --- a/pkg/chains/formats/slsa/internal/material/v1beta1/material.go +++ b/pkg/chains/formats/slsa/internal/material/v1beta1/material.go @@ -250,7 +250,7 @@ func FromTaskParamsAndResults(ctx context.Context, tro *objects.TaskRunObjectV1B }) } - sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, tro, artifacts.ArtifactsInputsResultName) + sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, tro.GetResults()) mats = artifact.AppendMaterials(mats, sms...) return mats @@ -259,7 +259,7 @@ func FromTaskParamsAndResults(ctx context.Context, tro *objects.TaskRunObjectV1B // FromPipelineParamsAndResults extracts type hinted params and results and adds the url and digest to materials. func FromPipelineParamsAndResults(ctx context.Context, pro *objects.PipelineRunObjectV1Beta1, slsaconfig *slsaconfig.SlsaConfig) []common.ProvenanceMaterial { mats := []common.ProvenanceMaterial{} - sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, pro, artifacts.ArtifactsInputsResultName) + sms := artifacts.RetrieveMaterialsFromStructuredResults(ctx, pro.GetResults()) mats = artifact.AppendMaterials(mats, sms...) var commit, url string diff --git a/pkg/chains/formats/slsa/internal/metadata/metadata.go b/pkg/chains/formats/slsa/internal/metadata/metadata.go new file mode 100644 index 0000000000..ab3d411d98 --- /dev/null +++ b/pkg/chains/formats/slsa/internal/metadata/metadata.go @@ -0,0 +1,28 @@ +/* +Copyright 2024 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 metadata + +import ( + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "github.com/tektoncd/chains/pkg/chains/objects" +) + +// GetBuildMetadata returns SLSA metadata. +func GetBuildMetadata(obj objects.TektonObject) slsa.BuildMetadata { + return slsa.BuildMetadata{ + InvocationID: string(obj.GetUID()), + StartedOn: obj.GetStartTime(), + FinishedOn: obj.GetCompletitionTime(), + } +} diff --git a/pkg/chains/formats/slsa/internal/metadata/metadata_test.go b/pkg/chains/formats/slsa/internal/metadata/metadata_test.go new file mode 100644 index 0000000000..2f0059f249 --- /dev/null +++ b/pkg/chains/formats/slsa/internal/metadata/metadata_test.go @@ -0,0 +1,86 @@ +/* +Copyright 2024 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 metadata + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "github.com/tektoncd/chains/pkg/chains/objects" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMetadata(t *testing.T) { + tr := &v1.TaskRun{ //nolint:staticcheck + ObjectMeta: metav1.ObjectMeta{ + Name: "my-taskrun", + Namespace: "my-namespace", + Annotations: map[string]string{ + "chains.tekton.dev/reproducible": "true", + }, + UID: "abhhf-12354-asjsdbjs23-3435353n", + }, + Status: v1.TaskRunStatus{ + TaskRunStatusFields: v1.TaskRunStatusFields{ + StartTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC)}, + CompletionTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC)}, + }, + }, + } + start := time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC) + end := time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC) + want := slsa.BuildMetadata{ + InvocationID: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: &start, + FinishedOn: &end, + } + got := GetBuildMetadata(objects.NewTaskRunObjectV1(tr)) + if d := cmp.Diff(want, got); d != "" { + t.Fatalf("metadata (-want, +got):\n%s", d) + } +} + +func TestMetadataInTimeZone(t *testing.T) { + tz := time.FixedZone("Test Time", int((12 * time.Hour).Seconds())) + tr := &v1.TaskRun{ //nolint:staticcheck + ObjectMeta: metav1.ObjectMeta{ + Name: "my-taskrun", + Namespace: "my-namespace", + Annotations: map[string]string{ + "chains.tekton.dev/reproducible": "true", + }, + UID: "abhhf-12354-asjsdbjs23-3435353n", + }, + Status: v1.TaskRunStatus{ + TaskRunStatusFields: v1.TaskRunStatusFields{ + StartTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 12, tz)}, + CompletionTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 24, tz)}, + }, + }, + } + start := time.Date(1995, time.December, 24, 6, 12, 12, 12, tz).UTC() + end := time.Date(1995, time.December, 24, 6, 12, 12, 24, tz).UTC() + want := slsa.BuildMetadata{ + InvocationID: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: &start, + FinishedOn: &end, + } + got := GetBuildMetadata(objects.NewTaskRunObjectV1(tr)) + if d := cmp.Diff(want, got); d != "" { + t.Fatalf("metadata (-want, +got):\n%s", d) + } +} diff --git a/pkg/chains/formats/slsa/internal/provenance/provenance.go b/pkg/chains/formats/slsa/internal/provenance/provenance.go new file mode 100644 index 0000000000..f514c03eed --- /dev/null +++ b/pkg/chains/formats/slsa/internal/provenance/provenance.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 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 provenance + +import ( + intoto "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/metadata" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + "github.com/tektoncd/chains/pkg/chains/objects" +) + +// GetSLSA1Statement returns a predicate in SLSA v1.0 format using the given data. +func GetSLSA1Statement(obj objects.TektonObject, sub []intoto.Subject, bd slsa.ProvenanceBuildDefinition, bp []slsa.ResourceDescriptor, slsaConfig *slsaconfig.SlsaConfig) intoto.ProvenanceStatementSLSA1 { + return intoto.ProvenanceStatementSLSA1{ + StatementHeader: intoto.StatementHeader{ + Type: intoto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: sub, + }, + Predicate: slsa.ProvenancePredicate{ + BuildDefinition: bd, + RunDetails: slsa.ProvenanceRunDetails{ + Builder: slsa.Builder{ + ID: slsaConfig.BuilderID, + }, + BuildMetadata: metadata.GetBuildMetadata(obj), + Byproducts: bp, + }, + }, + } +} diff --git a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go index 2cc1a8b60c..a619d9d299 100644 --- a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go +++ b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go @@ -51,6 +51,12 @@ const ( // and AddSLSATaskDescriptor type addTaskDescriptorContent func(*objects.TaskRunObjectV1) (*slsa.ResourceDescriptor, error) //nolint:staticcheck +// ResolveOptions represents the configuration to be use to resolve dependencies. +type ResolveOptions struct { + // Indicates if StepActions type-hinted results should be read to resolve dependecies. + WithStepActionsResults bool +} + // ConvertMaterialToResolvedDependency converts a SLSAv0.2 Material to a resolved dependency func ConvertMaterialsToResolvedDependencies(mats []common.ProvenanceMaterial, name string) []slsa.ResourceDescriptor { rds := []slsa.ResourceDescriptor{} @@ -183,7 +189,7 @@ func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObjectV } // taskDependencies gather all dependencies in a task and adds them to resolvedDependencies -func taskDependencies(ctx context.Context, tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { +func taskDependencies(ctx context.Context, opts ResolveOptions, tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { var resolvedDependencies []slsa.ResourceDescriptor var err error mats := []common.ProvenanceMaterial{} @@ -202,6 +208,11 @@ func taskDependencies(ctx context.Context, tro *objects.TaskRunObjectV1) ([]slsa mats = append(mats, sidecarMaterials...) resolvedDependencies = append(resolvedDependencies, ConvertMaterialsToResolvedDependencies(mats, "")...) + if opts.WithStepActionsResults { + mats = material.FromStepActionsResults(ctx, tro) + resolvedDependencies = append(resolvedDependencies, ConvertMaterialsToResolvedDependencies(mats, InputResultName)...) + } + mats = material.FromTaskParamsAndResults(ctx, tro) // convert materials to resolved dependencies resolvedDependencies = append(resolvedDependencies, ConvertMaterialsToResolvedDependencies(mats, InputResultName)...) @@ -240,7 +251,7 @@ func taskDependencies(ctx context.Context, tro *objects.TaskRunObjectV1) ([]slsa } // TaskRun constructs `predicate.resolvedDependencies` section by collecting all the artifacts that influence a taskrun such as source code repo and step&sidecar base images. -func TaskRun(ctx context.Context, tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { +func TaskRun(ctx context.Context, opts ResolveOptions, tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { var resolvedDependencies []slsa.ResourceDescriptor var err error @@ -254,7 +265,7 @@ func TaskRun(ctx context.Context, tro *objects.TaskRunObjectV1) ([]slsa.Resource resolvedDependencies = append(resolvedDependencies, rd) } - rds, err := taskDependencies(ctx, tro) + rds, err := taskDependencies(ctx, opts, tro) if err != nil { return nil, err } diff --git a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go index 8e9d0f88ab..a32ab0db9a 100644 --- a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go +++ b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go @@ -301,9 +301,10 @@ func tektonTaskRuns() map[string][]byte { func TestTaskRun(t *testing.T) { tests := []struct { - name string - obj objects.TektonObject //nolint:staticcheck - want []v1slsa.ResourceDescriptor + name string + obj objects.TektonObject //nolint:staticcheck + resolveOpts ResolveOptions + want []v1slsa.ResourceDescriptor }{ { name: "resolvedDependencies from pipeline resources", @@ -459,6 +460,12 @@ func TestTaskRun(t *testing.T) { Status: v1.TaskRunStatus{ TaskRunStatusFields: v1.TaskRunStatusFields{ Steps: []v1.StepState{{ + Results: []v1.TaskRunStepResult{ + {Name: "res1_ARTIFACT_INPUTS", Value: *v1.NewObject(map[string]string{ + "uri": "https://github.com/tektoncd/pipeline", + "digest": "sha1:7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + })}, + }, Name: "git-source-repo-jwqcl", ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, { @@ -493,7 +500,64 @@ func TestTaskRun(t *testing.T) { }, }, }, - }} + }, + { + name: "resolvedDependencies with nested results", + resolveOpts: ResolveOptions{ + WithStepActionsResults: true, + }, + obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ //nolint:staticcheck + Status: v1.TaskRunStatus{ + TaskRunStatusFields: v1.TaskRunStatusFields{ + Steps: []v1.StepState{{ + Results: []v1.TaskRunStepResult{ + {Name: "res1_ARTIFACT_INPUTS", Value: *v1.NewObject(map[string]string{ + "uri": "https://github.com/tektoncd/pipeline", + "digest": "sha1:7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + })}, + }, + Name: "git-source-repo-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, { + Name: "git-source-repo-repeat-again-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, { + Name: "build", + ImageID: "gcr.io/cloud-marketplace-containers/google/bazel@sha256:010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", + }}, + Sidecars: []v1.SidecarState{{ + Name: "sidecar-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init@sha256:a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", + }}, + }, + }, + }), + want: []v1slsa.ResourceDescriptor{ + { + URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Digest: common.DigestSet{ + "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, + }, { + URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Digest: common.DigestSet{ + "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", + }, + }, { + URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", + Digest: common.DigestSet{ + "sha256": "a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", + }, + }, { + Name: "inputs/result", + URI: "https://github.com/tektoncd/pipeline", + Digest: common.DigestSet{ + "sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + }, + }, + }, + }, + } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) @@ -517,7 +581,7 @@ func TestTaskRun(t *testing.T) { } } - rd, err := TaskRun(ctx, input) + rd, err := TaskRun(ctx, tc.resolveOpts, input) if err != nil { t.Fatalf("Did not expect an error but got %v", err) } diff --git a/pkg/chains/formats/slsa/internal/results/results.go b/pkg/chains/formats/slsa/internal/results/results.go new file mode 100644 index 0000000000..2fb901b411 --- /dev/null +++ b/pkg/chains/formats/slsa/internal/results/results.go @@ -0,0 +1,67 @@ +/* +Copyright 2024 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 results + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/tektoncd/chains/pkg/artifacts" + "github.com/tektoncd/chains/pkg/chains/objects" + + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" +) + +var imageResultsNamesSuffixs = []string{ + artifacts.OCIImageURLResultName, + artifacts.OCIImageDigestResultName, +} + +// GetResultsWithoutBuildArtifacts returns all the results without those that are build artifacts. +func GetResultsWithoutBuildArtifacts(results []objects.Result, resultTypePrefix string) ([]slsa.ResourceDescriptor, error) { + byProd := []slsa.ResourceDescriptor{} + for _, r := range results { + if isBuildArtifact, err := artifacts.IsBuildArtifact(r); err != nil || isBuildArtifact { + continue + } + + if isOCIImage(r.Name) { + continue + } + + content, err := json.Marshal(r.Value) + if err != nil { + return nil, err + } + + byProd = append(byProd, slsa.ResourceDescriptor{ + Name: fmt.Sprintf(resultTypePrefix, r.Name), + Content: content, + MediaType: "application/json", + }) + } + + return byProd, nil +} + +func isOCIImage(resName string) bool { + for _, suffix := range imageResultsNamesSuffixs { + if strings.HasSuffix(resName, suffix) { + return true + } + } + + return resName == artifacts.OCIImagesResultName +} diff --git a/pkg/chains/formats/slsa/internal/results/results_test.go b/pkg/chains/formats/slsa/internal/results/results_test.go new file mode 100644 index 0000000000..34f423fc46 --- /dev/null +++ b/pkg/chains/formats/slsa/internal/results/results_test.go @@ -0,0 +1,198 @@ +/* +Copyright 2024 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 results + +import ( + "encoding/json" + "testing" + + "github.com/google/go-cmp/cmp" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "github.com/tektoncd/chains/pkg/chains/objects" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" +) + +func TestGetResultsWithoutBuildArtifacts(t *testing.T) { + tests := []struct { + name string + prefix string + results []objects.Result + expected []slsa.ResourceDescriptor + }{ + { + name: "no results as input", + expected: []slsa.ResourceDescriptor{}, + }, + { + name: "results without build artifacts", + prefix: "taskRunResults/%s", + results: []objects.Result{ + { + Name: "result1", + Type: v1.ResultsTypeString, + Value: v1.ParamValue{ + Type: v1.ParamTypeString, + StringVal: "my-first-result", + }, + }, + { + Name: "res2-ARTIFACT_URI", + Type: v1.ResultsTypeString, + Value: v1.ParamValue{ + Type: v1.ParamTypeString, + StringVal: "gcr.io/my/image/fromstep2", + }, + }, + { + Name: "res2-ARTIFACT_DIGEST", + Type: v1.ResultsTypeString, + Value: v1.ParamValue{ + Type: v1.ParamTypeString, + StringVal: "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }, + }, + { + Name: "res3-ARTIFACT_OUTPUTS", + Type: v1.ResultsTypeObject, + Value: v1.ParamValue{ + Type: v1.ParamTypeObject, + ObjectVal: map[string]string{ + "uri": "oci://gcr.io/test1/test1", + "digest": "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + }, + }, + }, + { + Name: "res4-ARTIFACT_OUTPUTS", + Type: v1.ResultsTypeObject, + Value: v1.ParamValue{ + Type: v1.ParamTypeObject, + ObjectVal: map[string]string{ + "uri": "git+https://github.com/test", + "digest": "sha1:ab123", + "isBuildArtifact": "true", + }, + }, + }, + { + Name: "res5-ARTIFACT_OUTPUTS", + Type: v1.ResultsTypeObject, + Value: v1.ParamValue{ + Type: v1.ParamTypeObject, + ObjectVal: map[string]string{ + "uri": "oci://gcr.io/test2/test2", + "digest": "sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", + "isBuildArtifact": "false", + }, + }, + }, + { + Name: "res6-ARTIFACT_OUTPUTS", + Type: v1.ResultsTypeObject, + Value: v1.ParamValue{ + Type: v1.ParamTypeObject, + ObjectVal: map[string]string{ + "digest": "sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", + "isBuildArtifact": "true", + }, + }, + }, + { + Name: "res7-ARTIFACT_OUTPUTS", + Type: v1.ResultsTypeObject, + Value: v1.ParamValue{ + Type: v1.ParamTypeObject, + ObjectVal: map[string]string{ + "uri": "oci://gcr.io/test2/test2", + "digest": "sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", + "isBuildArtifact": "true", + }, + }, + }, + }, + expected: []slsa.ResourceDescriptor{ + { + Name: "taskRunResults/result1", + MediaType: "application/json", + Content: toJSONString(t, v1.ParamValue{ + Type: v1.ParamTypeString, + StringVal: "my-first-result", + }), + }, + { + Name: "taskRunResults/res2-ARTIFACT_URI", + MediaType: "application/json", + Content: toJSONString(t, v1.ParamValue{ + Type: v1.ParamTypeString, + StringVal: "gcr.io/my/image/fromstep2", + }), + }, + { + Name: "taskRunResults/res2-ARTIFACT_DIGEST", + MediaType: "application/json", + Content: toJSONString(t, v1.ParamValue{ + Type: v1.ParamTypeString, + StringVal: "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }), + }, + { + Name: "taskRunResults/res3-ARTIFACT_OUTPUTS", + MediaType: "application/json", + Content: toJSONString(t, v1.ParamValue{ + Type: v1.ParamTypeObject, + ObjectVal: map[string]string{ + "uri": "oci://gcr.io/test1/test1", + "digest": "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + }, + }), + }, + { + Name: "taskRunResults/res5-ARTIFACT_OUTPUTS", + MediaType: "application/json", + Content: toJSONString(t, v1.ParamValue{ + Type: v1.ParamTypeObject, + ObjectVal: map[string]string{ + "uri": "oci://gcr.io/test2/test2", + "digest": "sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", + "isBuildArtifact": "false", + }, + }), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := GetResultsWithoutBuildArtifacts(test.results, test.prefix) + + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if d := cmp.Diff(test.expected, got); d != "" { + t.Fatalf("metadata (-want, +got):\n%s", d) + } + }) + } +} + +func toJSONString(t *testing.T, val v1.ParamValue) []byte { + res, err := json.Marshal(val) + if err != nil { + t.Fatalf("error converting to json string: %v", err) + } + + return res +} diff --git a/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun-multiple-subjects.json b/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun-multiple-subjects.json new file mode 100644 index 0000000000..837cbce556 --- /dev/null +++ b/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun-multiple-subjects.json @@ -0,0 +1,72 @@ +{ + "spec": { + "params": [], + "taskRef": { + "name": "test-task", + "kind": "Task" + }, + "serviceAccountName": "default" + }, + "status": { + "conditions": [ + { + "type": "Succeeded", + "status": "True", + "lastTransitionTime": "2021-03-29T09:50:15Z", + "reason": "Succeeded", + "message": "All Steps have completed executing" + } + ], + "podName": "test-pod-name", + "steps": [ + { + "name": "step1", + "container": "step-step1", + "imageID": "docker-pullable://gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6" + } + ], + "results": [ + { + "name": "IMAGES", + "value": "gcr.io/myimage1@sha256:db546e77d11cf34199d965d28b1107f98bcbb7630182b7d847cc31d5d21b47b0,gcr.io/myimage3@sha256:8d14f5ded713f263742d371279586b264bde42ee8de97b808d1f5e205f376ade" + }, + { + "name": "result1_ARTIFACT_OUTPUTS", + "value": { + "uri": "gcr.io/foo/bar", + "digest": "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + "isBuildArtifact": "true" + } + }, + { + "name": "result2_ARTIFACT_OUTPUTS", + "value": { + "uri": "gcr.io/myimage2", + "digest": "sha256:9f036c6170dd7aba07e45cf2fe414c7ca792e5ede3bc3a78609e3aab4fa2ff2d", + "isBuildArtifact": "true" + } + } + ], + "taskSpec": { + "params": [], + "results": [ + { + "name": "file1_DIGEST", + "description": "Digest of a file to push." + }, + { + "name": "file1", + "description": "some assembled file" + }, + { + "name": "file2_DIGEST", + "description": "Digest of a file to push." + }, + { + "name": "file2", + "description": "some assembled file" + } + ] + } + } +} diff --git a/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun1.json b/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun1.json new file mode 100644 index 0000000000..4978b67c01 --- /dev/null +++ b/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun1.json @@ -0,0 +1,166 @@ +{ + "metadata": { + "name": "taskrun-build", + "labels": { + "tekton.dev/pipelineTask": "build" + }, + "uid": "abhhf-12354-asjsdbjs23-3435353n" + }, + "spec": { + "params": [ + { + "name": "IMAGE", + "value": "test.io/test/image" + }, + { + "name": "CHAINS-GIT_COMMIT", + "value": "taskrun" + }, + { + "name": "CHAINS-GIT_URL", + "value": "https://git.test.com" + } + ], + "taskRef": { + "name": "build", + "kind": "Task" + }, + "serviceAccountName": "default" + }, + "status": { + "startTime": "2021-03-29T09:50:00Z", + "completionTime": "2021-03-29T09:50:15Z", + "conditions": [ + { + "type": "Succeeded", + "status": "True", + "lastTransitionTime": "2021-03-29T09:50:15Z", + "reason": "Succeeded", + "message": "All Steps have completed executing" + } + ], + "podName": "test-pod-name", + "steps": [ + { + "name": "step1", + "container": "step-step1", + "imageID": "docker-pullable://gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + "results": [ + { + "name": "step1_result1", + "value": "result-value" + } + ] + }, + { + "name": "step2", + "container": "step-step2", + "imageID": "docker-pullable://gcr.io/test2/test2@sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", + "results": [ + { + "name": "step1_result1-ARTIFACT_OUTPUTS", + "value": { + "uri": "gcr.io/my/image/fromstep2", + "digest": "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7" + } + } + ] + }, + { + "name": "step3", + "container": "step-step3", + "imageID": "docker-pullable://gcr.io/test3/test3@sha256:f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478", + "results": [ + { + "name": "step3_result1-ARTIFACT_OUTPUTS", + "value": { + "uri": "gcr.io/my/image/fromstep3", + "digest": "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + "isBuildArtifact": "true" + } + } + ] + } + ], + "results": [ + { + "name": "IMAGE_DIGEST", + "value": "sha256:d31cc8328054de2bd93735f9cbf0ccfb6e0ee8f4c4225da7d8f8cb3900eaf466" + }, + { + "name": "IMAGE_URL", + "value": "gcr.io/my/image" + } + ], + "taskSpec": { + "params": [ + { + "name": "IMAGE", + "type": "string" + }, + { + "name": "filename", + "type": "string" + }, + { + "name": "DOCKERFILE", + "type": "string" + }, + { + "name": "CONTEXT", + "type": "string" + }, + { + "name": "EXTRA_ARGS", + "type": "string" + }, + { + "name": "BUILDER_IMAGE", + "type": "string" + }, { + "name": "CHAINS-GIT_COMMIT", + "type": "string", + "default": "task" + }, { + "name": "CHAINS-GIT_URL", + "type": "string", + "default": "https://defaultgit.test.com" + } + ], + "steps": [ + { + "name": "step1" + }, + { + "name": "step2" + }, + { + "name": "step3" + } + ], + "results": [ + { + "name": "IMAGE_DIGEST", + "description": "Digest of the image just built." + }, + { + "name": "filename_DIGEST", + "description": "Digest of the file just built." + } + ] + }, + "provenance": { + "refSource": { + "uri": "git+https://github.com/test", + "digest": { + "sha1": "ab123" + }, + "entryPoint": "build.yaml" + }, + "featureFlags": { + "EnableAPIFields": "beta", + "ResultExtractionMethod": "termination-message" + } + } + } +} diff --git a/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun2.json b/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun2.json new file mode 100644 index 0000000000..2c162a703e --- /dev/null +++ b/pkg/chains/formats/slsa/testdata/slsa-v2alpha4/taskrun2.json @@ -0,0 +1,115 @@ +{ + "metadata": { + "name": "git-clone", + "labels": { + "tekton.dev/pipelineTask": "git-clone" + }, + "uid": "abhhf-12354-asjsdbjs23-3435353n" + }, + "spec": { + "params": [ + { + "name": "url", + "value": "https://git.test.com" + }, + { + "name": "revision", + "value": "" + } + ], + "taskRef": { + "name": "git-clone", + "kind": "Task" + }, + "serviceAccountName": "default" + }, + "status": { + "startTime": "2021-03-29T09:50:00Z", + "completionTime": "2021-03-29T09:50:15Z", + "conditions": [ + { + "type": "Succeeded", + "status": "True", + "lastTransitionTime": "2021-03-29T09:50:15Z", + "reason": "Succeeded", + "message": "All Steps have completed executing" + } + ], + "podName": "test-pod-name", + "steps": [ + { + "name": "step1", + "container": "step-step1", + "imageID": "docker-pullable://gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + "results": [ + { + "name": "step1_result1-ARTIFACT_INPUTS", + "value": { + "uri": "https://github.com/tektoncd/pipeline", + "digest": "sha1:7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601" + } + } + ] + } + ], + "results": [ + { + "name": "some-uri_DIGEST", + "value": "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6" + }, + { + "name": "some-uri", + "value": "pkg:deb/debian/curl@7.50.3-1" + } + ], + "taskSpec": { + "steps": [ + { + "env": [ + { + "name": "HOME", + "value": "$(params.userHome)" + }, + { + "name": "PARAM_URL", + "value": "$(params.url)" + } + ], + "name": "step1", + "script": "git clone" + } + ], + "params": [ + { + "name": "CHAINS-GIT_COMMIT", + "type": "string", + "default": "sha:taskdefault" + }, + { + "name": "CHAINS-GIT_URL", + "type": "string", + "default": "https://git.test.com" + } + ], + "results": [ + { + "name": "some-uri_DIGEST", + "description": "Digest of a file to push." + }, + { + "name": "some-uri", + "description": "some calculated uri" + } + ] + }, + "provenance": { + "refSource": { + "uri": "git+https://github.com/catalog", + "digest": { + "sha1": "x123" + }, + "entryPoint": "git-clone.yaml" + } + } + } +} diff --git a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go index 793a732db7..f3d56f2772 100644 --- a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go @@ -18,12 +18,10 @@ import ( "encoding/json" "fmt" - intoto "github.com/in-toto/in-toto-golang/in_toto" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" - buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_types" - externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" - internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters" + builddefinition "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_definition" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/provenance" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/objects" @@ -38,44 +36,14 @@ func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObjectV1, slsa return nil, err } - bd, err := getBuildDefinition(ctx, slsaConfig.BuildType, tro) + bd, err := builddefinition.GetTaskRunBuildDefinition(ctx, tro, slsaConfig.BuildType, resolveddependencies.ResolveOptions{}) if err != nil { return nil, err } - att := intoto.ProvenanceStatementSLSA1{ - StatementHeader: intoto.StatementHeader{ - Type: intoto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: extract.SubjectDigests(ctx, tro, slsaConfig), - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: bd, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: slsaConfig.BuilderID, - }, - BuildMetadata: metadata(tro), - Byproducts: bp, - }, - }, - } - return att, nil -} + sub := extract.SubjectDigests(ctx, tro, slsaConfig) -func metadata(tro *objects.TaskRunObjectV1) slsa.BuildMetadata { - m := slsa.BuildMetadata{ - InvocationID: string(tro.ObjectMeta.UID), - } - if tro.Status.StartTime != nil { - utc := tro.Status.StartTime.Time.UTC() - m.StartedOn = &utc - } - if tro.Status.CompletionTime != nil { - utc := tro.Status.CompletionTime.Time.UTC() - m.FinishedOn = &utc - } - return m + return provenance.GetSLSA1Statement(tro, sub, bd, bp, slsaConfig), nil } // byproducts contains the taskRunResults @@ -95,39 +63,3 @@ func byproducts(tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) } return byProd, nil } - -// getBuildDefinition get the buildDefinition based on the configured buildType. This will default to the slsa buildType -func getBuildDefinition(ctx context.Context, buildType string, tro *objects.TaskRunObjectV1) (slsa.ProvenanceBuildDefinition, error) { - // if buildType is not set in the chains-config, default to slsa build type - buildDefinitionType := buildType - if buildType == "" { - buildDefinitionType = buildtypes.SlsaBuildType - } - - switch buildDefinitionType { - case buildtypes.SlsaBuildType: - rd, err := resolveddependencies.TaskRun(ctx, tro) - if err != nil { - return slsa.ProvenanceBuildDefinition{}, err - } - return slsa.ProvenanceBuildDefinition{ - BuildType: buildDefinitionType, - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.SLSAInternalParameters(tro), - ResolvedDependencies: rd, - }, nil - case buildtypes.TektonBuildType: - rd, err := resolveddependencies.TaskRun(ctx, tro) - if err != nil { - return slsa.ProvenanceBuildDefinition{}, err - } - return slsa.ProvenanceBuildDefinition{ - BuildType: buildDefinitionType, - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.TektonInternalParameters(tro), - ResolvedDependencies: rd, - }, nil - default: - return slsa.ProvenanceBuildDefinition{}, fmt.Errorf("unsupported buildType %v", buildType) - } -} diff --git a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go index 1606150399..6e3bd7940e 100644 --- a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go +++ b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go @@ -28,8 +28,6 @@ import ( slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" v1resourcedescriptor "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" - externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" - internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun" @@ -37,71 +35,9 @@ import ( "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/config" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logtesting "knative.dev/pkg/logging/testing" ) -func TestMetadata(t *testing.T) { - tr := &v1.TaskRun{ //nolint:staticcheck - ObjectMeta: metav1.ObjectMeta{ - Name: "my-taskrun", - Namespace: "my-namespace", - Annotations: map[string]string{ - "chains.tekton.dev/reproducible": "true", - }, - UID: "abhhf-12354-asjsdbjs23-3435353n", - }, - Status: v1.TaskRunStatus{ - TaskRunStatusFields: v1.TaskRunStatusFields{ - StartTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC)}, - CompletionTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC)}, - }, - }, - } - start := time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC) - end := time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC) - want := slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &start, - FinishedOn: &end, - } - got := metadata(objects.NewTaskRunObjectV1(tr)) - if d := cmp.Diff(want, got); d != "" { - t.Fatalf("metadata (-want, +got):\n%s", d) - } -} - -func TestMetadataInTimeZone(t *testing.T) { - tz := time.FixedZone("Test Time", int((12 * time.Hour).Seconds())) - tr := &v1.TaskRun{ //nolint:staticcheck - ObjectMeta: metav1.ObjectMeta{ - Name: "my-taskrun", - Namespace: "my-namespace", - Annotations: map[string]string{ - "chains.tekton.dev/reproducible": "true", - }, - UID: "abhhf-12354-asjsdbjs23-3435353n", - }, - Status: v1.TaskRunStatus{ - TaskRunStatusFields: v1.TaskRunStatusFields{ - StartTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 12, tz)}, - CompletionTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 24, tz)}, - }, - }, - } - start := time.Date(1995, time.December, 24, 6, 12, 12, 12, tz).UTC() - end := time.Date(1995, time.December, 24, 6, 12, 12, 24, tz).UTC() - want := slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &start, - FinishedOn: &end, - } - got := metadata(objects.NewTaskRunObjectV1(tr)) - if d := cmp.Diff(want, got); d != "" { - t.Fatalf("metadata (-want, +got):\n%s", d) - } -} - func TestByProducts(t *testing.T) { resultValue := v1.ResultValue{Type: "string", StringVal: "result-value"} tr := &v1.TaskRun{ //nolint:staticcheck @@ -239,94 +175,9 @@ func TestTaskRunGenerateAttestation(t *testing.T) { } func getResolvedDependencies(tro *objects.TaskRunObjectV1) []v1resourcedescriptor.ResourceDescriptor { - rd, err := resolveddependencies.TaskRun(context.Background(), tro) + rd, err := resolveddependencies.TaskRun(context.Background(), resolveddependencies.ResolveOptions{}, tro) if err != nil { return []v1resourcedescriptor.ResourceDescriptor{} } return rd } - -func TestGetBuildDefinition(t *testing.T) { - tr, err := objectloader.TaskRunFromFile("../../../testdata/slsa-v2alpha3/taskrun1.json") - if err != nil { - t.Fatal(err) - } - - tr.Annotations = map[string]string{ - "annotation1": "annotation1", - } - tr.Labels = map[string]string{ - "label1": "label1", - } - - tro := objects.NewTaskRunObjectV1(tr) - tests := []struct { - name string - buildType string - want slsa.ProvenanceBuildDefinition - err error - }{ - { - name: "test slsa build type", - buildType: "https://tekton.dev/chains/v2/slsa", - want: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.SLSAInternalParameters(tro), - ResolvedDependencies: getResolvedDependencies(tro), - }, - err: nil, - }, - { - name: "test default build type", - buildType: "", - want: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.SLSAInternalParameters(tro), - ResolvedDependencies: getResolvedDependencies(tro), - }, - err: nil, - }, - { - name: "test tekton build type", - buildType: "https://tekton.dev/chains/v2/slsa-tekton", - want: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa-tekton", - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.TektonInternalParameters(tro), - ResolvedDependencies: getResolvedDependencies(tro), - }, - err: nil, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - bd, err := getBuildDefinition(context.Background(), tc.buildType, tro) - if err != nil { - t.Fatalf("Did not expect an error but got %v", err) - } - - if diff := cmp.Diff(tc.want, bd); diff != "" { - t.Errorf("getBuildDefinition(): -want +got: %v", diff) - } - - }) - } -} - -func TestUnsupportedBuildType(t *testing.T) { - tr, err := objectloader.TaskRunFromFile("../../../testdata/slsa-v2alpha3/taskrun1.json") - if err != nil { - t.Fatal(err) - } - - got, err := getBuildDefinition(context.Background(), "bad-buildType", objects.NewTaskRunObjectV1(tr)) - if err == nil { - t.Error("getBuildDefinition(): expected error got nil") - } - if diff := cmp.Diff(slsa.ProvenanceBuildDefinition{}, got); diff != "" { - t.Errorf("getBuildDefinition(): -want +got: %s", diff) - } -} diff --git a/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go new file mode 100644 index 0000000000..dff00786aa --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go @@ -0,0 +1,69 @@ +/* +Copyright 2024 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 taskrun + +import ( + "context" + + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" + builddefinition "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_definition" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/provenance" + resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/results" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + "github.com/tektoncd/chains/pkg/chains/objects" +) + +const ( + taskRunResults = "taskRunResults/%s" + taskRunStepResults = "stepResults/%s" +) + +// GenerateAttestation returns the provenance for the given taskrun in SALSA 1.0 format. +func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObjectV1, slsaConfig *slsaconfig.SlsaConfig) (interface{}, error) { + bp, err := byproducts(tro) + if err != nil { + return nil, err + } + + resOpts := resolveddependencies.ResolveOptions{WithStepActionsResults: true} + bd, err := builddefinition.GetTaskRunBuildDefinition(ctx, tro, slsaConfig.BuildType, resOpts) + if err != nil { + return nil, err + } + + results := append(tro.GetResults(), tro.GetStepResults()...) + sub := extract.SubjectsFromBuildArtifact(ctx, results) + + return provenance.GetSLSA1Statement(tro, sub, bd, bp, slsaConfig), nil +} + +func byproducts(tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { + byProd := []slsa.ResourceDescriptor{} + + res, err := results.GetResultsWithoutBuildArtifacts(tro.GetResults(), taskRunResults) + if err != nil { + return nil, err + } + byProd = append(byProd, res...) + + res, err = results.GetResultsWithoutBuildArtifacts(tro.GetStepResults(), taskRunStepResults) + if err != nil { + return nil, err + } + byProd = append(byProd, res...) + + return byProd, nil +} diff --git a/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go new file mode 100644 index 0000000000..46bca6a05a --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go @@ -0,0 +1,169 @@ +/* +Copyright 2024 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 taskrun + +import ( + "encoding/json" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "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" + + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/internal/objectloader" + "github.com/tektoncd/pipeline/pkg/apis/config" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + logtesting "knative.dev/pkg/logging/testing" +) + +const jsonMediaType = "application/json" + +func TestByProducts(t *testing.T) { + resultValue := v1.ResultValue{Type: "string", StringVal: "result-value"} + tr := &v1.TaskRun{ //nolint:staticcheck + Status: v1.TaskRunStatus{ + TaskRunStatusFields: v1.TaskRunStatusFields{ + Results: []v1.TaskRunResult{ + { + Name: "result-name", + Value: resultValue, + }, + }, + }, + }, + } + + resultBytes, err := json.Marshal(resultValue) + if err != nil { + t.Fatalf("Could not marshal results: %s", err) + } + want := []slsa.ResourceDescriptor{ + { + Name: "taskRunResults/result-name", + Content: resultBytes, + MediaType: jsonMediaType, + }, + } + got, err := byproducts(objects.NewTaskRunObjectV1(tr)) + if err != nil { + t.Fatalf("Could not extract byproducts: %s", err) + } + if d := cmp.Diff(want, got); d != "" { + t.Fatalf("byproducts (-want, +got):\n%s", d) + } +} + +func TestTaskRunGenerateAttestation(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + tr, err := objectloader.TaskRunFromFile("../../../testdata/slsa-v2alpha4/taskrun1.json") + if err != nil { + t.Fatal(err) + } + e1BuildStart := time.Unix(1617011400, 0) + e1BuildFinished := time.Unix(1617011415, 0) + + want := in_toto.ProvenanceStatementSLSA1{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []in_toto.Subject{ + { + Name: "gcr.io/my/image/fromstep3", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }, + }, + { + Name: "gcr.io/my/image", + Digest: common.DigestSet{ + "sha256": "d31cc8328054de2bd93735f9cbf0ccfb6e0ee8f4c4225da7d8f8cb3900eaf466", + }, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + BuildDefinition: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: map[string]any{ + "runSpec": tr.Spec, + }, + InternalParameters: map[string]any{ + "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + }, + ResolvedDependencies: []slsa.ResourceDescriptor{ + { + URI: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "task", + }, + { + URI: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + { + URI: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + }, + { + URI: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + }, + {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "taskrun"}}, + }, + }, + RunDetails: slsa.ProvenanceRunDetails{ + Builder: slsa.Builder{ + ID: "test_builder-1", + }, + BuildMetadata: slsa.BuildMetadata{ + InvocationID: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: &e1BuildStart, + FinishedOn: &e1BuildFinished, + }, + Byproducts: []slsa.ResourceDescriptor{ + { + Name: "stepResults/step1_result1", + MediaType: "application/json", + Content: []uint8(`"result-value"`), + }, + { + Name: "stepResults/step1_result1-ARTIFACT_OUTPUTS", + MediaType: "application/json", + Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7","uri":"gcr.io/my/image/fromstep2"}`), + }, + }, + }, + }, + } + + got, err := GenerateAttestation(ctx, objects.NewTaskRunObjectV1(tr), &slsaconfig.SlsaConfig{ + BuilderID: "test_builder-1", + BuildType: "https://tekton.dev/chains/v2/slsa", + }) + + if err != nil { + t.Errorf("unwant error: %s", err.Error()) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("GenerateAttestation(): -want +got: %s", diff) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha4/slsav2.go b/pkg/chains/formats/slsa/v2alpha4/slsav2.go new file mode 100644 index 0000000000..6080df5119 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha4/slsav2.go @@ -0,0 +1,73 @@ +/* +Copyright 2024 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 v2alpha4 + +import ( + "context" + "fmt" + + "github.com/tektoncd/chains/pkg/chains/formats" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha4/internal/taskrun" + + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/config" +) + +const ( + payloadTypeSlsav2alpha4 = formats.PayloadTypeSlsav2alpha4 +) + +func init() { + formats.RegisterPayloader(payloadTypeSlsav2alpha4, NewFormatter) +} + +// Slsa is a v2alpha4 payloader implementation. +type Slsa struct { + slsaConfig *slsaconfig.SlsaConfig +} + +// NewFormatter returns a new v2alpha4 payloader. +func NewFormatter(cfg config.Config) (formats.Payloader, error) { + return &Slsa{ + slsaConfig: &slsaconfig.SlsaConfig{ + BuilderID: cfg.Builder.ID, + DeepInspectionEnabled: cfg.Artifacts.PipelineRuns.DeepInspectionEnabled, + BuildType: cfg.BuildDefinition.BuildType, + }, + }, nil +} + +// Wrap indicates if the resulting payload should be wrapped. +func (s *Slsa) Wrap() bool { + return true +} + +// CreatePayload returns the payload for the given object using the v2alpha4 formatter logic. +func (s *Slsa) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) { + switch v := obj.(type) { + case *objects.TaskRunObjectV1: + return taskrun.GenerateAttestation(ctx, v, s.slsaConfig) + default: + return nil, fmt.Errorf("intoto does not support type: %s", v) + } +} + +// Type returns the version of this payloader. +func (s *Slsa) Type() config.PayloadType { + return payloadTypeSlsav2alpha4 +} diff --git a/pkg/chains/formats/slsa/v2alpha4/slsav2_test.go b/pkg/chains/formats/slsa/v2alpha4/slsav2_test.go new file mode 100644 index 0000000000..a3df5df51a --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha4/slsav2_test.go @@ -0,0 +1,386 @@ +/* +Copyright 2024 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 v2alpha4 + +import ( + "encoding/json" + "testing" + "time" + + "github.com/tektoncd/chains/pkg/chains/formats" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/config" + "github.com/tektoncd/chains/pkg/internal/objectloader" + + "github.com/google/go-cmp/cmp" + "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" + pipelineConfig "github.com/tektoncd/pipeline/pkg/apis/config" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + logtesting "knative.dev/pkg/logging/testing" +) + +var ( + e1BuildStart = time.Unix(1617011400, 0) + e1BuildFinished = time.Unix(1617011415, 0) +) + +const jsonMediaType = "application/json" + +func TestNewFormatter(t *testing.T) { + t.Run("Ok", func(t *testing.T) { + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "testid", + }, + } + f, err := NewFormatter(cfg) + if err != nil { + t.Errorf("Error creating formatter: %s", err) + } + if f == nil { + t.Error("Failed to create formatter") + } + }) +} + +func TestCreatePayloadError(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "testid", + }, + } + f, _ := NewFormatter(cfg) + + t.Run("Invalid type", func(t *testing.T) { + p, err := f.CreatePayload(ctx, "not a task ref") + + if p != nil { + t.Errorf("Unexpected payload") + } + if err == nil { + t.Errorf("Expected error") + } else if err.Error() != "intoto does not support type: not a task ref" { + t.Errorf("wrong error returned: '%s'", err.Error()) + } + }) +} + +func TestCorrectPayloadType(t *testing.T) { + var i Slsa + if i.Type() != formats.PayloadTypeSlsav2alpha4 { + t.Errorf("Invalid type returned: %s", i.Type()) + } +} + +func TestTaskRunCreatePayload1(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + + tr, err := objectloader.TaskRunFromFile("../testdata/slsa-v2alpha4/taskrun1.json") + if err != nil { + t.Fatal(err) + } + + resultValue := v1.ParamValue{Type: "string", StringVal: "result-value"} + resultBytesStepResult, err := json.Marshal(resultValue) + if err != nil { + t.Fatalf("Could not marshal results: %s", err) + } + + resultValue = v1.ParamValue{Type: "object", ObjectVal: map[string]string{ + "uri": "gcr.io/my/image/fromstep2", + "digest": "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }} + resultBytesStepResultObj, err := json.Marshal(resultValue) + if err != nil { + t.Fatalf("Could not marshal results: %s", err) + } + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "test_builder-1", + }, + } + expected := in_toto.ProvenanceStatementSLSA1{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []in_toto.Subject{ + { + Name: "gcr.io/my/image/fromstep3", + Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, + }, + { + Name: "gcr.io/my/image", + Digest: common.DigestSet{"sha256": "d31cc8328054de2bd93735f9cbf0ccfb6e0ee8f4c4225da7d8f8cb3900eaf466"}, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + BuildDefinition: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: map[string]any{ + "runSpec": tr.Spec, + }, + InternalParameters: map[string]any{ + "tekton-pipelines-feature-flags": pipelineConfig.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + }, + ResolvedDependencies: []slsa.ResourceDescriptor{ + { + URI: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "task", + }, + { + URI: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + { + URI: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + }, + { + URI: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + }, + {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "taskrun"}}, + }, + }, + RunDetails: slsa.ProvenanceRunDetails{ + Builder: slsa.Builder{ + ID: "test_builder-1", + }, + BuildMetadata: slsa.BuildMetadata{ + InvocationID: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: &e1BuildStart, + FinishedOn: &e1BuildFinished, + }, + Byproducts: []slsa.ResourceDescriptor{ + { + Name: "stepResults/step1_result1", + Content: resultBytesStepResult, + MediaType: jsonMediaType, + }, + { + Name: "stepResults/step1_result1-ARTIFACT_OUTPUTS", + Content: resultBytesStepResultObj, + MediaType: jsonMediaType, + }, + }, + }, + }, + } + + i, _ := NewFormatter(cfg) + + got, err := i.CreatePayload(ctx, objects.NewTaskRunObjectV1(tr)) + + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if diff := cmp.Diff(expected, got); diff != "" { + t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) + } +} + +func TestTaskRunCreatePayload2(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + tr, err := objectloader.TaskRunFromFile("../testdata/slsa-v2alpha4/taskrun2.json") + if err != nil { + t.Fatal(err) + } + + resultValue := v1.ParamValue{Type: "string", StringVal: "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"} + resultBytesDigest, err := json.Marshal(resultValue) + if err != nil { + t.Fatalf("Could not marshal results: %s", err) + } + resultValue = v1.ParamValue{Type: "string", StringVal: "pkg:deb/debian/curl@7.50.3-1"} + resultBytesURI, err := json.Marshal(resultValue) + if err != nil { + t.Fatalf("Could not marshal results: %s", err) + } + resultValue = v1.ParamValue{Type: "object", ObjectVal: map[string]string{ + "uri": "https://github.com/tektoncd/pipeline", + "digest": "sha1:7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", + }} + resultBytesObj, err := json.Marshal(resultValue) + if err != nil { + t.Fatalf("Could not marshal results: %s", err) + } + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "test_builder-2", + }, + } + expected := in_toto.ProvenanceStatementSLSA1{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: nil, + }, + Predicate: slsa.ProvenancePredicate{ + BuildDefinition: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: map[string]any{ + "runSpec": tr.Spec, + }, + InternalParameters: map[string]any{}, + ResolvedDependencies: []slsa.ResourceDescriptor{ + { + URI: "git+https://github.com/catalog", + Digest: common.DigestSet{"sha1": "x123"}, + Name: "task", + }, + { + URI: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + { + Name: "inputs/result", + URI: "https://github.com/tektoncd/pipeline", + Digest: common.DigestSet{"sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601"}, + }, + { + Name: "inputs/result", + URI: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "sha:taskdefault"}, + }, + }, + }, + RunDetails: slsa.ProvenanceRunDetails{ + Builder: slsa.Builder{ + ID: "test_builder-2", + }, + BuildMetadata: slsa.BuildMetadata{ + InvocationID: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: &e1BuildStart, + FinishedOn: &e1BuildFinished, + }, + Byproducts: []slsa.ResourceDescriptor{ + { + Name: "taskRunResults/some-uri_DIGEST", + Content: resultBytesDigest, + MediaType: jsonMediaType, + }, + { + Name: "taskRunResults/some-uri", + Content: resultBytesURI, + MediaType: jsonMediaType, + }, + { + Name: "stepResults/step1_result1-ARTIFACT_INPUTS", + Content: resultBytesObj, + MediaType: jsonMediaType, + }, + }, + }, + }, + } + + i, _ := NewFormatter(cfg) + got, err := i.CreatePayload(ctx, objects.NewTaskRunObjectV1(tr)) + + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if diff := cmp.Diff(expected, got); diff != "" { + t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) + } +} + +func TestMultipleSubjects(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + + tr, err := objectloader.TaskRunFromFile("../testdata/slsa-v2alpha4/taskrun-multiple-subjects.json") + if err != nil { + t.Fatal(err) + } + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "test_builder-multiple", + }, + } + expected := in_toto.ProvenanceStatementSLSA1{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []in_toto.Subject{ + { + Name: "gcr.io/foo/bar", + Digest: common.DigestSet{ + "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + }, + }, + { + Name: "gcr.io/myimage2", + Digest: common.DigestSet{ + "sha256": "9f036c6170dd7aba07e45cf2fe414c7ca792e5ede3bc3a78609e3aab4fa2ff2d", + }, + }, + { + Name: "gcr.io/myimage1", + Digest: common.DigestSet{ + "sha256": "db546e77d11cf34199d965d28b1107f98bcbb7630182b7d847cc31d5d21b47b0", + }, + }, + { + Name: "gcr.io/myimage3", + Digest: common.DigestSet{ + "sha256": "8d14f5ded713f263742d371279586b264bde42ee8de97b808d1f5e205f376ade", + }, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + BuildDefinition: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: map[string]any{ + "runSpec": tr.Spec, + }, + InternalParameters: map[string]any{}, + ResolvedDependencies: []slsa.ResourceDescriptor{ + { + URI: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + }, + }, + RunDetails: slsa.ProvenanceRunDetails{ + Builder: slsa.Builder{ + ID: "test_builder-multiple", + }, + BuildMetadata: slsa.BuildMetadata{}, + Byproducts: []slsa.ResourceDescriptor{}, + }, + }, + } + + i, _ := NewFormatter(cfg) + got, err := i.CreatePayload(ctx, objects.NewTaskRunObjectV1(tr)) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if diff := cmp.Diff(expected, got); diff != "" { + t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) + } +} diff --git a/pkg/chains/objects/objects.go b/pkg/chains/objects/objects.go index 95319be9ae..f20bd93794 100644 --- a/pkg/chains/objects/objects.go +++ b/pkg/chains/objects/objects.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" @@ -70,6 +71,8 @@ type TektonObject interface { SupportsOCIArtifact() bool GetRemoteProvenance() *v1.Provenance IsRemote() bool + GetStartTime() *time.Time + GetCompletitionTime() *time.Time } func NewTektonObject(i interface{}) (TektonObject, error) { @@ -143,6 +146,20 @@ func (tro *TaskRunObjectV1) GetResults() []Result { return res } +// GetStepResults returns all the results from associated StepActions. +func (tro *TaskRunObjectV1) GetStepResults() []Result { + res := []Result{} + for _, s := range tro.Status.Steps { + for _, r := range s.Results { + res = append(res, Result{ + Name: r.Name, + Value: r.Value, + }) + } + } + return res +} + func (tro *TaskRunObjectV1) GetStepImages() []string { images := []string{} for _, stepState := range tro.Status.Steps { @@ -198,6 +215,26 @@ func (tro *TaskRunObjectV1) IsRemote() bool { return isRemoteTask } +// GetStartTime returns the time when the TaskRun started. +func (tro *TaskRunObjectV1) GetStartTime() *time.Time { + var utc *time.Time + if tro.Status.StartTime != nil { + val := tro.Status.StartTime.Time.UTC() + utc = &val + } + return utc +} + +// GetCompletitionTime returns the time when the TaskRun finished. +func (tro *TaskRunObjectV1) GetCompletitionTime() *time.Time { + var utc *time.Time + if tro.Status.CompletionTime != nil { + val := tro.Status.CompletionTime.Time.UTC() + utc = &val + } + return utc +} + // PipelineRunObjectV1 extends v1.PipelineRun with additional functions. type PipelineRunObjectV1 struct { // The base PipelineRun @@ -322,6 +359,26 @@ func (pro *PipelineRunObjectV1) IsRemote() bool { return isRemotePipeline } +// GetStartTime returns the time when the PipelineRun started. +func (pro *PipelineRunObjectV1) GetStartTime() *time.Time { + var utc *time.Time + if pro.Status.StartTime != nil { + val := pro.Status.StartTime.Time.UTC() + utc = &val + } + return utc +} + +// GetCompletitionTime returns the time when the PipelineRun finished. +func (pro *PipelineRunObjectV1) GetCompletitionTime() *time.Time { + var utc *time.Time + if pro.Status.CompletionTime != nil { + val := pro.Status.CompletionTime.Time.UTC() + utc = &val + } + return utc +} + // Get the imgPullSecrets from a pod template, if they exist func getPodPullSecrets(podTemplate *pod.Template) []string { imgPullSecrets := []string{} @@ -483,6 +540,26 @@ func (pro *PipelineRunObjectV1Beta1) IsRemote() bool { return isRemotePipeline } +// GetStartTime returns the time when the PipelineRun started. +func (pro *PipelineRunObjectV1Beta1) GetStartTime() *time.Time { + var utc *time.Time + if pro.Status.StartTime != nil { + val := pro.Status.StartTime.Time.UTC() + utc = &val + } + return utc +} + +// GetCompletitionTime returns the time when the PipelineRun finished. +func (pro *PipelineRunObjectV1Beta1) GetCompletitionTime() *time.Time { + var utc *time.Time + if pro.Status.CompletionTime != nil { + val := pro.Status.CompletionTime.Time.UTC() + utc = &val + } + return utc +} + // TaskRunObjectV1Beta1 extends v1beta1.TaskRun with additional functions. type TaskRunObjectV1Beta1 struct { *v1beta1.TaskRun @@ -624,3 +701,23 @@ func (tro *TaskRunObjectV1Beta1) IsRemote() bool { } return isRemoteTask } + +// GetStartTime returns the time when the TaskRun started. +func (tro *TaskRunObjectV1Beta1) GetStartTime() *time.Time { + var utc *time.Time + if tro.Status.StartTime != nil { + val := tro.Status.StartTime.Time.UTC() + utc = &val + } + return utc +} + +// GetCompletitionTime returns the time when the TaskRun finished. +func (tro *TaskRunObjectV1Beta1) GetCompletitionTime() *time.Time { + var utc *time.Time + if tro.Status.CompletionTime != nil { + val := tro.Status.CompletionTime.Time.UTC() + utc = &val + } + return utc +} diff --git a/pkg/chains/storage/oci/legacy.go b/pkg/chains/storage/oci/legacy.go index 64717d2772..4cb0d05875 100644 --- a/pkg/chains/storage/oci/legacy.go +++ b/pkg/chains/storage/oci/legacy.go @@ -271,7 +271,7 @@ func (b *Backend) RetrievePayloads(ctx context.Context, obj objects.TektonObject func (b *Backend) RetrieveArtifact(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) (map[string]oci.SignedImage, error) { // Given the TaskRun, retrieve the OCI images. - images := artifacts.ExtractOCIImagesFromResults(ctx, obj) + images := artifacts.ExtractOCIImagesFromResults(ctx, obj.GetResults()) m := make(map[string]oci.SignedImage) for _, image := range images { diff --git a/pkg/config/config.go b/pkg/config/config.go index 15dc3697da..5ec0e4233f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -266,7 +266,7 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { if err := cm.Parse(data, // Artifact-specific configs // TaskRuns - asString(taskrunFormatKey, &cfg.Artifacts.TaskRuns.Format, "in-toto", "slsa/v1", "slsa/v2alpha3"), + asString(taskrunFormatKey, &cfg.Artifacts.TaskRuns.Format, "in-toto", "slsa/v1", "slsa/v2alpha3", "slsa/v2alpha4"), asStringSet(taskrunStorageKey, &cfg.Artifacts.TaskRuns.StorageBackend, sets.New[string]("tekton", "oci", "gcs", "docdb", "grafeas", "kafka")), asString(taskrunSignerKey, &cfg.Artifacts.TaskRuns.Signer, "x509", "kms"), diff --git a/test/examples_test.go b/test/examples_test.go index 433fe01f1b..8dcb2e85ea 100644 --- a/test/examples_test.go +++ b/test/examples_test.go @@ -60,6 +60,7 @@ const ( type TestExample struct { name string cm map[string]string + pipelinesCm map[string]string getExampleObjects func(t *testing.T, ns string) map[string]objects.TektonObject payloadKey string signatureKey string @@ -119,6 +120,33 @@ func TestExamples(t *testing.T) { outputLocation: "slsa/v2alpha3", predicate: "slsav1.0", }, + { + name: "taskrun-examples-slsa-v2alpha4", + cm: map[string]string{ + "artifacts.taskrun.format": "slsa/v2alpha4", + "artifacts.oci.storage": "tekton", + }, + getExampleObjects: getTaskRunExamples, + payloadKey: "chains.tekton.dev/payload-taskrun-%s", + signatureKey: "chains.tekton.dev/signature-taskrun-%s", + outputLocation: "slsa/v2alpha4", + predicate: "slsav1.0", + }, + { + name: "taskrun-type-hinted-results-v2alpha4", + cm: map[string]string{ + "artifacts.taskrun.format": "slsa/v2alpha4", + "artifacts.oci.storage": "tekton", + }, + pipelinesCm: map[string]string{ + "enable-api-fields": "alpha", + }, + getExampleObjects: getTaskRunWithTypeHintedResultsExamples, + payloadKey: "chains.tekton.dev/payload-taskrun-%s", + signatureKey: "chains.tekton.dev/signature-taskrun-%s", + outputLocation: "slsa/v2alpha4", + predicate: "slsav1.0", + }, } for _, test := range tests { @@ -126,9 +154,16 @@ func TestExamples(t *testing.T) { ctx := context.Background() c, ns, cleanup := setup(ctx, t, setupOpts{}) t.Cleanup(cleanup) + cleanUpInTotoFormatter := setConfigMap(ctx, t, c, test.cm) + t.Cleanup(cleanUpInTotoFormatter) + + if len(test.pipelinesCm) > 0 { + resetPipelinesConfig := setupPipelinesFeatureFlags(ctx, t, c, test.pipelinesCm) + t.Cleanup(resetPipelinesConfig) + } + runInTotoFormatterTests(ctx, t, ns, c, test) - cleanUpInTotoFormatter() }) } } @@ -418,6 +453,13 @@ func getTaskRunExamples(t *testing.T, ns string) map[string]objects.TektonObject return examples } +func getTaskRunWithTypeHintedResultsExamples(t *testing.T, ns string) map[string]objects.TektonObject { + path := "../examples/v2alpha4/task-with-object-type-hinting.yaml" + trs := make(map[string]objects.TektonObject) + trs[path] = taskRunFromExample(t, ns, path) + return trs +} + func getPipelineRunExamples(t *testing.T, ns string) map[string]objects.TektonObject { examples := make(map[string]objects.TektonObject) for _, example := range getExamplePaths(t, pipelineRunExamplesPath) { diff --git a/test/test_utils.go b/test/test_utils.go index 77a14b1fb2..a798bf278f 100644 --- a/test/test_utils.go +++ b/test/test_utils.go @@ -172,7 +172,31 @@ func readObj(t *testing.T, bucket, name string, client *storage.Client) io.Reade func setConfigMap(ctx context.Context, t *testing.T, c *clients, data map[string]string) func() { // Change the config to be GCS storage with this bucket. // Note(rgreinho): This comment does not look right... - cm, err := c.KubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, "chains-config", metav1.GetOptions{}) + clean := updateConfigMap(ctx, t, c, data, namespace, "chains-config") + + err := restartChainsControllerPod(ctx, c.KubeClient, 300*time.Second) + if err != nil { + t.Fatalf("Failed to restart the pod: %v", err) + } + + return clean +} + +func setupPipelinesFeatureFlags(ctx context.Context, t *testing.T, c *clients, data map[string]string) func() { + pipelinesNs := "tekton-pipelines" + + clean := updateConfigMap(ctx, t, c, data, pipelinesNs, "feature-flags") + + err := restartControllerPod(ctx, c.KubeClient, 300*time.Second, pipelinesNs, "app.kubernetes.io/component=controller") + if err != nil { + t.Fatalf("Failed to restart the pod: %v", err) + } + + return clean +} + +func updateConfigMap(ctx context.Context, t *testing.T, c *clients, data map[string]string, ns, configMapName string) func() { + cm, err := c.KubeClient.CoreV1().ConfigMaps(ns).Get(ctx, configMapName, metav1.GetOptions{}) if err != nil { t.Fatal(err) } @@ -190,14 +214,10 @@ func setConfigMap(ctx context.Context, t *testing.T, c *clients, data map[string for k, v := range data { cm.Data[k] = v } - cm, err = c.KubeClient.CoreV1().ConfigMaps(namespace).Update(ctx, cm, metav1.UpdateOptions{}) + cm, err = c.KubeClient.CoreV1().ConfigMaps(ns).Update(ctx, cm, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } - err = restartChainsControllerPod(ctx, c.KubeClient, 300*time.Second) - if err != nil { - t.Fatalf("Failed to restart the pod: %v", err) - } return func() { for k := range data { @@ -206,7 +226,7 @@ func setConfigMap(ctx context.Context, t *testing.T, c *clients, data map[string for k, v := range oldData { cm.Data[k] = v } - if _, err := c.KubeClient.CoreV1().ConfigMaps(namespace).Update(ctx, cm, metav1.UpdateOptions{}); err != nil { + if _, err := c.KubeClient.CoreV1().ConfigMaps(ns).Update(ctx, cm, metav1.UpdateOptions{}); err != nil { t.Log(err) } } @@ -297,7 +317,11 @@ func verifySignature(ctx context.Context, t *testing.T, c *clients, obj objects. // restartChainsControllerPod restarts the pod running Chains // it then waits for a given timeout for the pod to resume running state func restartChainsControllerPod(ctx context.Context, c kubernetes.Interface, timeout time.Duration) error { - pods, err := c.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: "app.kubernetes.io/component=controller"}) + return restartControllerPod(ctx, c, timeout, namespace, "app.kubernetes.io/component=controller") +} + +func restartControllerPod(ctx context.Context, c kubernetes.Interface, timeout time.Duration, ns, labelSelector string) error { + pods, err := c.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: labelSelector}) if err != nil { return err } @@ -310,7 +334,7 @@ func restartChainsControllerPod(ctx context.Context, c kubernetes.Interface, tim } return wait.PollUntilContextTimeout(ctx, 2*time.Second, timeout, true, func(context.Context) (done bool, err error) { - pods, err := c.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "app.kubernetes.io/component=controller"}) + pods, err := c.CoreV1().Pods(ns).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) if err != nil { return false, err } diff --git a/test/testdata/slsa/v2alpha4/task-output-image.json b/test/testdata/slsa/v2alpha4/task-output-image.json new file mode 100644 index 0000000000..33deb9711d --- /dev/null +++ b/test/testdata/slsa/v2alpha4/task-output-image.json @@ -0,0 +1,62 @@ +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v1", + "subject": [ + { + "name": "gcr.io/foo/bar", + "digest": { + "sha256": "05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5" + } + } + ], + "predicate": { + "buildDefinition": { + "buildType": "https://tekton.dev/chains/v2/slsa", + "externalParameters": { + "runSpec": { + "serviceAccountName": "default", + "taskSpec": { + "steps": [ + { + "name": "create-image", + "image": "busybox", + "computeResources": {}, + "script": "#!/usr/bin/env sh\necho 'gcr.io/foo/bar' | tee $(results.IMAGE_URL.path)\necho 'sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5' | tee $(results.IMAGE_DIGEST.path)" + } + ], + "results": [ + { + "name": "IMAGE_URL", + "type": "string" + },{ + "name": "IMAGE_DIGEST", + "type": "string" + } + ] + }, + "timeout": "1h0m0s" + } + }, + "resolvedDependencies": [ + {{range .URIDigest}} + { + "uri": "{{.URI}}", + "digest": { + "sha256": "{{.Digest}}" + } + } + {{end}} + ] + }, + "runDetails": { + "builder": { + "id": "https://tekton.dev/chains/v2" + }, + "metadata": { + "invocationID": "{{.UID}}", + "startedOn": "{{index .BuildStartTimes 0}}", + "finishedOn": "{{index .BuildFinishedTimes 0}}" + } + } + } +} diff --git a/test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json b/test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json new file mode 100644 index 0000000000..0965545364 --- /dev/null +++ b/test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json @@ -0,0 +1,155 @@ +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v1", + "subject": [ + { + "name": "gcr.io/foo/img2", + "digest": { + "sha256": "05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5" + } + }, + { + "name": "gcr.io/foo/bar", + "digest": { + "sha256": "05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" + } + }, + { + "name": "gcr.io/test/img3", + "digest": { + "sha256": "2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b" + } + } + ], + "predicate": { + "buildDefinition": { + "buildType": "https://tekton.dev/chains/v2/slsa", + "externalParameters": { + "runSpec": { + "serviceAccountName": "default", + "taskSpec": { + "steps": [ + { + "name": "dummy-build", + "image": "bash:latest", + "computeResources": {}, + "script": "echo -n \"{\\\"uri\\\":\\\"gcr.io/foo/img1\\\", \\\"digest\\\":\\\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\\\"}\" \u003e $(results.first-ARTIFACT_OUTPUTS.path)\n\necho -n \"{\\\"uri\\\":\\\"gcr.io/foo/img2\\\", \\\"digest\\\":\\\"sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5\\\", \\\"isBuildArtifact\\\":\\\"true\\\"}\" \u003e $(results.second-ARTIFACT_OUTPUTS.path)\n\necho -n \"gcr.io/foo/bar\" | tee $(results.third-IMAGE_URL.path)\necho -n \"sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6\" | tee $(results.third-IMAGE_DIGEST.path)\n\necho -n \"gcr.io/test/img3@sha256:2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b\" | tee $(results.IMAGES.path)\n\necho -n \"{\\\"uri\\\":\\\"gcr.io/test/img4\\\", \\\"digest\\\":\\\"sha256:basd-sha\\\", \\\"isBuildArtifact\\\":\\\"true\\\"}\" \u003e $(results.fourth-ARTIFACT_OUTPUTS.path)" + } + ], + "results": [ + { + "name": "first-ARTIFACT_OUTPUTS", + "type": "object", + "properties": { + "digest": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "description": "The first artifact built" + }, + { + "name": "second-ARTIFACT_OUTPUTS", + "type": "object", + "properties": { + "digest": { + "type": "string" + }, + "isBuildArtifact": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "description": "The second artifact built" + }, + { + "name": "third-IMAGE_URL", + "type": "string" + }, + { + "name": "third-IMAGE_DIGEST", + "type": "string" + }, + { + "name": "IMAGES", + "type": "string" + }, + { + "name": "fourth-ARTIFACT_OUTPUTS", + "type": "object", + "properties": { + "digest": { + "type": "string" + }, + "isBuildArtifact": { + "type": "string" + }, + "uri": { + "type": "string" + } + } + } + ] + }, + "timeout": "1h0m0s" + } + }, + "internalParameters": { + "tekton-pipelines-feature-flags": { + "DisableAffinityAssistant": false, + "DisableCredsInit": false, + "RunningInEnvWithInjectedSidecars": true, + "RequireGitSSHSecretKnownHosts": false, + "EnableTektonOCIBundles": false, + "ScopeWhenExpressionsToTask": false, + "EnableAPIFields": "beta", + "SendCloudEventsForRuns": false, + "AwaitSidecarReadiness": true, + "EnforceNonfalsifiability": "none", + "EnableKeepPodOnCancel": false, + "VerificationNoMatchPolicy": "ignore", + "EnableProvenanceInStatus": true, + "ResultExtractionMethod": "termination-message", + "MaxResultSize": 4096, + "SetSecurityContext": false, + "Coschedule": "workspaces", + "EnableCELInWhenExpression": false, + "EnableStepActions": true, + "EnableParamEnum": false, + "EnableArtifacts": false + } + }, + "resolvedDependencies": [ + {{range .URIDigest}} + { + "uri": "{{.URI}}", + "digest": { + "sha256": "{{.Digest}}" + } + } + {{end}} + ] + }, + "runDetails": { + "builder": { + "id": "https://tekton.dev/chains/v2" + }, + "metadata": { + "invocationID": "{{.UID}}", + "startedOn": "{{index .BuildStartTimes 0}}", + "finishedOn": "{{index .BuildFinishedTimes 0}}" + }, + "byproducts": [ + { + "name": "taskRunResults/first-ARTIFACT_OUTPUTS", + "mediaType": "application/json", + "content": "eyJkaWdlc3QiOiJzaGEyNTY6NTg2Nzg5YWEwMzFmYWZjN2Q3OGE1MzkzY2RjNzcyZTBiNTUxMDdlYTU0YmI4YmNmM2YyY2RhYzZjNmRhNTFlZSIsInVyaSI6Imdjci5pby9mb28vaW1nMSJ9" + } + ] + } + } +} \ No newline at end of file From 1611c11a5206e32f4ea329ff1bee9513fb4321ab Mon Sep 17 00:00:00 2001 From: Renzo Rojas Silva Date: Mon, 20 May 2024 15:56:42 -0400 Subject: [PATCH 2/2] Upgrading SLSA and intoto libraries to not use deprecated structs and linter fixes. --- go.mod | 2 +- pkg/artifacts/signable_test.go | 2 +- pkg/chains/formats/slsa/extract/extract.go | 33 +- .../formats/slsa/extract/extract_test.go | 42 +- .../formats/slsa/extract/v1beta1/extract.go | 24 +- .../slsa/extract/v1beta1/extract_test.go | 33 +- .../formats/slsa/internal/artifact/append.go | 12 +- .../slsa/internal/artifact/append_test.go | 37 +- .../build_definition/build_definition.go | 40 +- .../build_definition/build_definition_test.go | 43 +- .../slsa/internal/compare/slsacompare.go | 11 +- .../internal/material/v1beta1/material.go | 6 +- .../slsa/internal/metadata/metadata.go | 26 +- .../slsa/internal/metadata/metadata_test.go | 28 +- .../slsa/internal/provenance/provenance.go | 56 +- .../resolved_dependencies.go | 67 +- .../resolved_dependencies_test.go | 185 ++-- .../formats/slsa/internal/results/results.go | 8 +- .../slsa/internal/results/results_test.go | 12 +- .../formats/slsa/v1/internal/protos/protos.go | 25 + pkg/chains/formats/slsa/v1/intotoite6_test.go | 912 ++++++++---------- .../slsa/v1/pipelinerun/pipelinerun.go | 41 +- .../slsa/v1/pipelinerun/provenance_test.go | 8 +- .../slsa/v1/taskrun/provenance_test.go | 7 +- pkg/chains/formats/slsa/v1/taskrun/taskrun.go | 42 +- .../internal/pipelinerun/pipelinerun.go | 117 ++- .../internal/pipelinerun/pipelinerun_test.go | 281 +++--- .../slsa/v2alpha3/internal/taskrun/taskrun.go | 12 +- .../v2alpha3/internal/taskrun/taskrun_test.go | 163 ++-- .../formats/slsa/v2alpha3/slsav2_test.go | 532 +++++----- .../slsa/v2alpha4/internal/taskrun/taskrun.go | 8 +- .../v2alpha4/internal/taskrun/taskrun_test.go | 175 ++-- pkg/chains/formats/slsa/v2alpha4/slsav2.go | 2 +- .../formats/slsa/v2alpha4/slsav2_test.go | 361 ++++--- pkg/chains/objects/objects.go | 2 +- pkg/chains/storage/grafeas/grafeas_test.go | 104 +- pkg/chains/storage/oci/attestation.go | 7 +- pkg/chains/storage/oci/legacy.go | 10 +- pkg/chains/storage/oci/oci_test.go | 27 +- pkg/chains/storage/pubsub/pubsub_test.go | 7 +- pkg/chains/storage/tekton/tekton.go | 12 +- test/e2e_test.go | 32 +- test/examples_test.go | 115 ++- test/test_utils.go | 7 + .../slsa/v1/pipeline-output-image.json | 4 +- test/testdata/slsa/v1/task-output-image.json | 4 +- test/testdata/slsa/v2/task-output-image.json | 4 +- .../slsa/v2alpha3/pipeline-output-image.json | 6 +- .../slsa/v2alpha3/task-output-image.json | 6 +- .../slsa/v2alpha4/task-output-image.json | 6 +- .../task-with-object-type-hinting.json | 6 +- .../go/predicates/provenance/v1/provenance.go | 138 +++ .../predicates/provenance/v1/provenance.pb.go | 577 +++++++++++ vendor/modules.txt | 1 + 54 files changed, 2769 insertions(+), 1659 deletions(-) create mode 100644 pkg/chains/formats/slsa/v1/internal/protos/protos.go create mode 100644 vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.go create mode 100644 vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.pb.go diff --git a/go.mod b/go.mod index 16e99b2858..289619feee 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/google/go-licenses v1.6.0 github.com/grafeas/grafeas v0.2.3 github.com/hashicorp/go-multierror v1.1.1 + github.com/in-toto/attestation v1.0.1 github.com/in-toto/in-toto-golang v0.9.1-0.20240317085821-8e2966059a09 github.com/opencontainers/go-digest v1.0.0 github.com/pkg/errors v0.9.1 @@ -254,7 +255,6 @@ require ( github.com/hashicorp/vault/api v1.12.2 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/imdario/mergo v0.3.16 // indirect - github.com/in-toto/attestation v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect diff --git a/pkg/artifacts/signable_test.go b/pkg/artifacts/signable_test.go index 98c303c0ac..8863984561 100644 --- a/pkg/artifacts/signable_test.go +++ b/pkg/artifacts/signable_test.go @@ -46,7 +46,6 @@ const ( var ignore = []cmp.Option{cmpopts.IgnoreUnexported(name.Registry{}, name.Repository{}, name.Digest{})} func TestOCIArtifact_ExtractObjects(t *testing.T) { - tests := []struct { name string obj objects.TektonObject @@ -808,6 +807,7 @@ func TestExtractBuildArtifactsFromResults(t *testing.T) { } func createDigest(t *testing.T, dgst string) name.Digest { + t.Helper() result, err := name.NewDigest(dgst) if err != nil { t.Fatal(err) diff --git a/pkg/chains/formats/slsa/extract/extract.go b/pkg/chains/formats/slsa/extract/extract.go index 510d13c06a..a47d558f7c 100644 --- a/pkg/chains/formats/slsa/extract/extract.go +++ b/pkg/chains/formats/slsa/extract/extract.go @@ -22,7 +22,7 @@ import ( "strings" "github.com/google/go-containerregistry/pkg/name" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" "github.com/tektoncd/chains/internal/backport" "github.com/tektoncd/chains/pkg/artifacts" @@ -44,8 +44,8 @@ import ( // - the `*_URL` or `*_URI` fields cannot be empty. // //nolint:all -func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject { - var subjects []intoto.Subject +func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []*intoto.ResourceDescriptor { + var subjects []*intoto.ResourceDescriptor switch obj.GetObject().(type) { case *v1.PipelineRun: @@ -61,7 +61,7 @@ func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *s return subjects } -func subjectsFromPipelineRun(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject { +func subjectsFromPipelineRun(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []*intoto.ResourceDescriptor { prSubjects := subjectsFromTektonObject(ctx, obj) // If deep inspection is not enabled, just return subjects observed on the pipelinerun level @@ -71,13 +71,14 @@ func subjectsFromPipelineRun(ctx context.Context, obj objects.TektonObject, slsa logger := logging.FromContext(ctx) // If deep inspection is enabled, collect subjects from child taskruns - var result []intoto.Subject + var result []*intoto.ResourceDescriptor pro := obj.(*objects.PipelineRunObjectV1) pSpec := pro.Status.PipelineSpec if pSpec != nil { - pipelineTasks := append(pSpec.Tasks, pSpec.Finally...) + pipelineTasks := pSpec.Tasks + pipelineTasks = append(pipelineTasks, pSpec.Finally...) for _, t := range pipelineTasks { tr := pro.GetTaskRunFromTask(t.Name) // Ignore Tasks that did not execute during the PipelineRun. @@ -97,14 +98,14 @@ func subjectsFromPipelineRun(ctx context.Context, obj objects.TektonObject, slsa return result } -func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []intoto.Subject { +func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []*intoto.ResourceDescriptor { logger := logging.FromContext(ctx) - var subjects []intoto.Subject + var subjects []*intoto.ResourceDescriptor imgs := artifacts.ExtractOCIImagesFromResults(ctx, obj.GetResults()) for _, i := range imgs { if d, ok := i.(name.Digest); ok { - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: d.Repository.Name(), Digest: common.DigestSet{ "sha256": strings.TrimPrefix(d.DigestStr(), "sha256:"), @@ -120,7 +121,7 @@ func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []i logger.Errorf("Digest %s should be in the format of: algorthm:abc", obj.Digest) continue } - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: obj.URI, Digest: common.DigestSet{ splits[0]: splits[1], @@ -133,7 +134,7 @@ func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []i splits := strings.Split(s.Digest, ":") alg := splits[0] digest := splits[1] - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: s.URI, Digest: common.DigestSet{ alg: digest, @@ -173,7 +174,7 @@ func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []i } } } - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: url, Digest: common.DigestSet{ "sha256": strings.TrimPrefix(digest, "sha256:"), @@ -211,8 +212,8 @@ func RetrieveAllArtifactURIs(ctx context.Context, obj objects.TektonObject, deep // with the fields `uri`, `digest`, and `isBuildArtifact` set to true. // - Use the IMAGES type-hint // - Use the *IMAGE_URL / *IMAGE_DIGEST type-hint suffix -func SubjectsFromBuildArtifact(ctx context.Context, results []objects.Result) []intoto.Subject { - var subjects []intoto.Subject +func SubjectsFromBuildArtifact(ctx context.Context, results []objects.Result) []*intoto.ResourceDescriptor { + var subjects []*intoto.ResourceDescriptor logger := logging.FromContext(ctx) buildArtifacts := artifacts.ExtractBuildArtifactsFromResults(ctx, results) for _, ba := range buildArtifacts { @@ -224,7 +225,7 @@ func SubjectsFromBuildArtifact(ctx context.Context, results []objects.Result) [] alg := splits[0] digest := splits[1] - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: ba.URI, Digest: common.DigestSet{ alg: digest, @@ -235,7 +236,7 @@ func SubjectsFromBuildArtifact(ctx context.Context, results []objects.Result) [] imgs := artifacts.ExtractOCIImagesFromResults(ctx, results) for _, i := range imgs { if d, ok := i.(name.Digest); ok { - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: d.Repository.Name(), Digest: common.DigestSet{ "sha256": strings.TrimPrefix(d.DigestStr(), "sha256:"), diff --git a/pkg/chains/formats/slsa/extract/extract_test.go b/pkg/chains/formats/slsa/extract/extract_test.go index 7a9b18828d..6467087466 100644 --- a/pkg/chains/formats/slsa/extract/extract_test.go +++ b/pkg/chains/formats/slsa/extract/extract_test.go @@ -23,12 +23,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" "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" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "google.golang.org/protobuf/testing/protocmp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logtesting "knative.dev/pkg/logging/testing" ) @@ -45,7 +46,7 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { name string // a map of url:digest pairs for type hinting results results map[string]string - wantSubjects []intoto.Subject + wantSubjects []*intoto.ResourceDescriptor wantFullURLs []string }{ { @@ -54,7 +55,7 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { artifactURL1: "sha256:" + artifactDigest1, artifactURL2: "sha256:" + artifactDigest2, }, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -110,7 +111,7 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { } for _, o := range runObjects { gotSubjects := extract.SubjectDigests(ctx, o, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}) - if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption()); diff != "" { + if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption(), protocmp.Transform()); diff != "" { t.Errorf("Wrong subjects extracted, diff=%s", diff) } @@ -119,7 +120,6 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { t.Errorf("Wrong URIs extracted, diff=%s", diff) } } - }) } } @@ -129,14 +129,14 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { name string pro objects.TektonObject deepInspectionEnabled bool - wantSubjects []intoto.Subject + wantSubjects []*intoto.ResourceDescriptor wantFullURLs []string }{ { name: "deep inspection disabled", pro: createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), deepInspectionEnabled: false, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -150,7 +150,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { name: "deep inspection enabled: no duplication", pro: createProWithTaskRunResults(nil, []artifact{{uri: artifactURL2, digest: "sha256:" + artifactDigest2}}), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL2, Digest: map[string]string{ @@ -167,7 +167,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL2, Digest: map[string]string{ @@ -193,7 +193,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL2, Digest: map[string]string{ @@ -208,12 +208,12 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { { name: "deep inspection enabled: pipelinerun and taskrun have duplicated results", pro: createProWithTaskRunResults( - createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}).(*objects.PipelineRunObjectV1), + createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), []artifact{ {uri: artifactURL1, digest: "sha256:" + artifactDigest1}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -228,12 +228,12 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { { name: "deep inspection enabled: pipelinerun and taskrun have different results", pro: createProWithTaskRunResults( - createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}).(*objects.PipelineRunObjectV1), + createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), []artifact{ {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -259,7 +259,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) gotSubjects := extract.SubjectDigests(ctx, tc.pro, &slsaconfig.SlsaConfig{DeepInspectionEnabled: tc.deepInspectionEnabled}) - if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption()); diff != "" { + if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption(), protocmp.Transform()); diff != "" { t.Errorf("Wrong subjects extracted, diff=%s, %s", diff, gotSubjects) } @@ -275,7 +275,7 @@ func TestSubjectsFromBuildArtifact(t *testing.T) { tests := []struct { name string results []objects.Result - expectedSubjects []intoto.Subject + expectedSubjects []*intoto.ResourceDescriptor }{ { name: "no type-hinted build artifacts", @@ -332,7 +332,7 @@ func TestSubjectsFromBuildArtifact(t *testing.T) { }), }, }, - expectedSubjects: []intoto.Subject{ + expectedSubjects: []*intoto.ResourceDescriptor{ { Name: "gcr.io/test/img4", Digest: map[string]string{ @@ -391,7 +391,7 @@ func TestSubjectsFromBuildArtifact(t *testing.T) { ), }, }, - expectedSubjects: []intoto.Subject{ + expectedSubjects: []*intoto.ResourceDescriptor{ { Name: "gcr.io/test/img1", Digest: map[string]string{ @@ -433,14 +433,14 @@ func TestSubjectsFromBuildArtifact(t *testing.T) { t.Run(test.name, func(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) got := extract.SubjectsFromBuildArtifact(ctx, test.results) - if diff := cmp.Diff(test.expectedSubjects, got); diff != "" { + if diff := cmp.Diff(test.expectedSubjects, got, protocmp.Transform()); diff != "" { t.Errorf("Wrong subjects from build artifacts, +got -want, diff=%s", diff) } }) } } -func createTaskRunObjectWithResults(results map[string]string) objects.TektonObject { +func createTaskRunObjectWithResults(results map[string]string) *objects.TaskRunObjectV1 { trResults := []v1.TaskRunResult{} prefix := 0 for url, digest := range results { @@ -462,7 +462,7 @@ func createTaskRunObjectWithResults(results map[string]string) objects.TektonObj ) } -func createProWithPipelineResults(results map[string]string) objects.TektonObject { +func createProWithPipelineResults(results map[string]string) *objects.PipelineRunObjectV1 { prResults := []v1.PipelineRunResult{} prefix := 0 for url, digest := range results { diff --git a/pkg/chains/formats/slsa/extract/v1beta1/extract.go b/pkg/chains/formats/slsa/extract/v1beta1/extract.go index 06f6899e53..043ec0deb1 100644 --- a/pkg/chains/formats/slsa/extract/v1beta1/extract.go +++ b/pkg/chains/formats/slsa/extract/v1beta1/extract.go @@ -22,7 +22,7 @@ import ( "strings" "github.com/google/go-containerregistry/pkg/name" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" "github.com/tektoncd/chains/internal/backport" "github.com/tektoncd/chains/pkg/artifacts" @@ -42,8 +42,8 @@ import ( // - the `*_URL` or `*_URI` fields cannot be empty. // //nolint:all -func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject { - var subjects []intoto.Subject +func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []*intoto.ResourceDescriptor { + var subjects []*intoto.ResourceDescriptor switch obj.GetObject().(type) { case *v1beta1.PipelineRun: @@ -55,7 +55,8 @@ func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *s return subjects } -func SubjectsFromPipelineRunV1Beta1(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject { +// SubjectsFromPipelineRunV1Beta1 returns software artifacts produced from the PipelineRun object. +func SubjectsFromPipelineRunV1Beta1(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []*intoto.ResourceDescriptor { prSubjects := SubjectsFromTektonObjectV1Beta1(ctx, obj) // If deep inspection is not enabled, just return subjects observed on the pipelinerun level @@ -65,7 +66,7 @@ func SubjectsFromPipelineRunV1Beta1(ctx context.Context, obj objects.TektonObjec logger := logging.FromContext(ctx) // If deep inspection is enabled, collect subjects from child taskruns - var result []intoto.Subject + var result []*intoto.ResourceDescriptor pro := obj.(*objects.PipelineRunObjectV1Beta1) @@ -90,14 +91,15 @@ func SubjectsFromPipelineRunV1Beta1(ctx context.Context, obj objects.TektonObjec return result } -func SubjectsFromTektonObjectV1Beta1(ctx context.Context, obj objects.TektonObject) []intoto.Subject { +// SubjectsFromTektonObjectV1Beta1 returns software artifacts produced from the Tekton object. +func SubjectsFromTektonObjectV1Beta1(ctx context.Context, obj objects.TektonObject) []*intoto.ResourceDescriptor { logger := logging.FromContext(ctx) - var subjects []intoto.Subject + var subjects []*intoto.ResourceDescriptor imgs := artifacts.ExtractOCIImagesFromResults(ctx, obj.GetResults()) for _, i := range imgs { if d, ok := i.(name.Digest); ok { - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: d.Repository.Name(), Digest: common.DigestSet{ "sha256": strings.TrimPrefix(d.DigestStr(), "sha256:"), @@ -113,7 +115,7 @@ func SubjectsFromTektonObjectV1Beta1(ctx context.Context, obj objects.TektonObje logger.Errorf("Digest %s should be in the format of: algorthm:abc", obj.Digest) continue } - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: obj.URI, Digest: common.DigestSet{ splits[0]: splits[1], @@ -126,7 +128,7 @@ func SubjectsFromTektonObjectV1Beta1(ctx context.Context, obj objects.TektonObje splits := strings.Split(s.Digest, ":") alg := splits[0] digest := splits[1] - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: s.URI, Digest: common.DigestSet{ alg: digest, @@ -164,7 +166,7 @@ func SubjectsFromTektonObjectV1Beta1(ctx context.Context, obj objects.TektonObje } } } - subjects = artifact.AppendSubjects(subjects, intoto.Subject{ + subjects = artifact.AppendSubjects(subjects, &intoto.ResourceDescriptor{ Name: url, Digest: common.DigestSet{ "sha256": strings.TrimPrefix(digest, "sha256:"), diff --git a/pkg/chains/formats/slsa/extract/v1beta1/extract_test.go b/pkg/chains/formats/slsa/extract/v1beta1/extract_test.go index 0e1cd2dd0c..9eb1a371b9 100644 --- a/pkg/chains/formats/slsa/extract/v1beta1/extract_test.go +++ b/pkg/chains/formats/slsa/extract/v1beta1/extract_test.go @@ -23,12 +23,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" extractv1beta1 "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract/v1beta1" "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/pipeline/pkg/apis/pipeline/v1beta1" + "google.golang.org/protobuf/testing/protocmp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logtesting "knative.dev/pkg/logging/testing" ) @@ -45,7 +46,7 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { name string // a map of url:digest pairs for type hinting results results map[string]string - wantSubjects []intoto.Subject + wantSubjects []*intoto.ResourceDescriptor wantFullURLs []string }{ { @@ -54,7 +55,7 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { artifactURL1: "sha256:" + artifactDigest1, artifactURL2: "sha256:" + artifactDigest2, }, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -110,7 +111,7 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { } for _, o := range runObjects { gotSubjects := extractv1beta1.SubjectDigests(ctx, o, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}) - if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption()); diff != "" { + if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption(), protocmp.Transform()); diff != "" { t.Errorf("Wrong subjects extracted, diff=%s", diff) } @@ -129,14 +130,14 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { name string pro objects.TektonObject deepInspectionEnabled bool - wantSubjects []intoto.Subject + wantSubjects []*intoto.ResourceDescriptor wantFullURLs []string }{ { name: "deep inspection disabled", pro: createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), deepInspectionEnabled: false, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -150,7 +151,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { name: "deep inspection enabled: no duplication", pro: createProWithTaskRunResults(nil, []artifact{{uri: artifactURL2, digest: "sha256:" + artifactDigest2}}), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL2, Digest: map[string]string{ @@ -167,7 +168,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL2, Digest: map[string]string{ @@ -193,7 +194,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL2, Digest: map[string]string{ @@ -208,12 +209,12 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { { name: "deep inspection enabled: pipelinerun and taskrun have duplicated results", pro: createProWithTaskRunResults( - createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}).(*objects.PipelineRunObjectV1Beta1), + createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), []artifact{ {uri: artifactURL1, digest: "sha256:" + artifactDigest1}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -228,12 +229,12 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { { name: "deep inspection enabled: pipelinerun and taskrun have different results", pro: createProWithTaskRunResults( - createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}).(*objects.PipelineRunObjectV1Beta1), + createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), []artifact{ {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, }), deepInspectionEnabled: true, - wantSubjects: []intoto.Subject{ + wantSubjects: []*intoto.ResourceDescriptor{ { Name: artifactURL1, Digest: map[string]string{ @@ -259,7 +260,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) gotSubjects := extractv1beta1.SubjectDigests(ctx, tc.pro, &slsaconfig.SlsaConfig{DeepInspectionEnabled: tc.deepInspectionEnabled}) - if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption()); diff != "" { + if diff := cmp.Diff(tc.wantSubjects, gotSubjects, compare.SubjectCompareOption(), protocmp.Transform()); diff != "" { t.Errorf("Wrong subjects extracted, diff=%s, %s", diff, gotSubjects) } @@ -271,7 +272,7 @@ func TestPipelineRunObserveModeForSubjects(t *testing.T) { } } -func createTaskRunObjectV1Beta1WithResults(results map[string]string) objects.TektonObject { +func createTaskRunObjectV1Beta1WithResults(results map[string]string) *objects.TaskRunObjectV1Beta1 { trResults := []v1beta1.TaskRunResult{} prefix := 0 for url, digest := range results { @@ -293,7 +294,7 @@ func createTaskRunObjectV1Beta1WithResults(results map[string]string) objects.Te ) } -func createProWithPipelineResults(results map[string]string) objects.TektonObject { +func createProWithPipelineResults(results map[string]string) *objects.PipelineRunObjectV1Beta1 { prResults := []v1beta1.PipelineRunResult{} prefix := 0 for url, digest := range results { diff --git a/pkg/chains/formats/slsa/internal/artifact/append.go b/pkg/chains/formats/slsa/internal/artifact/append.go index 5cab6e8429..dab976ef96 100644 --- a/pkg/chains/formats/slsa/internal/artifact/append.go +++ b/pkg/chains/formats/slsa/internal/artifact/append.go @@ -16,13 +16,13 @@ limitations under the License. package artifact import ( - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" ) // AppendSubjects adds new subject(s) to the original slice. // It merges the new item with an existing entry if they are duplicate instead of append. -func AppendSubjects(original []intoto.Subject, items ...intoto.Subject) []intoto.Subject { +func AppendSubjects(original []*intoto.ResourceDescriptor, items ...*intoto.ResourceDescriptor) []*intoto.ResourceDescriptor { var artifacts []artifact for _, s := range original { artifacts = append(artifacts, subjectToArtifact(s)) @@ -32,7 +32,7 @@ func AppendSubjects(original []intoto.Subject, items ...intoto.Subject) []intoto artifacts = addArtifact(artifacts, subjectToArtifact(s)) } - var result []intoto.Subject + var result []*intoto.ResourceDescriptor for _, a := range artifacts { result = append(result, artifactToSubject(a)) } @@ -99,15 +99,15 @@ func mergeMaps(m1 map[string]string, m2 map[string]string) { } } -func subjectToArtifact(s intoto.Subject) artifact { +func subjectToArtifact(s *intoto.ResourceDescriptor) artifact { return artifact{ name: s.Name, digestSet: s.Digest, } } -func artifactToSubject(a artifact) intoto.Subject { - return intoto.Subject{ +func artifactToSubject(a artifact) *intoto.ResourceDescriptor { + return &intoto.ResourceDescriptor{ Name: a.name, Digest: a.digestSet, } diff --git a/pkg/chains/formats/slsa/internal/artifact/append_test.go b/pkg/chains/formats/slsa/internal/artifact/append_test.go index 3ea0704744..e91cac650e 100644 --- a/pkg/chains/formats/slsa/internal/artifact/append_test.go +++ b/pkg/chains/formats/slsa/internal/artifact/append_test.go @@ -19,19 +19,20 @@ import ( "testing" "github.com/google/go-cmp/cmp" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" + "google.golang.org/protobuf/testing/protocmp" ) func TestAppendSubjects(t *testing.T) { tests := []struct { name string - original []intoto.Subject - toAdd []intoto.Subject - want []intoto.Subject + original []*intoto.ResourceDescriptor + toAdd []*intoto.ResourceDescriptor + want []*intoto.ResourceDescriptor }{{ name: "add a completely new subject", - original: []intoto.Subject{ + original: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -44,7 +45,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - toAdd: []intoto.Subject{ + toAdd: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", Digest: common.DigestSet{ @@ -52,7 +53,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - want: []intoto.Subject{ + want: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -72,7 +73,7 @@ func TestAppendSubjects(t *testing.T) { }, }, { name: "add a subject with same uri and digest", - original: []intoto.Subject{ + original: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -80,7 +81,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - toAdd: []intoto.Subject{ + toAdd: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -88,7 +89,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - want: []intoto.Subject{ + want: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -98,7 +99,7 @@ func TestAppendSubjects(t *testing.T) { }, }, { name: "add a subject with same uri but different digest", - original: []intoto.Subject{ + original: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -106,7 +107,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - toAdd: []intoto.Subject{ + toAdd: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -114,7 +115,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - want: []intoto.Subject{ + want: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -130,7 +131,7 @@ func TestAppendSubjects(t *testing.T) { }, { name: "add a subject with same uri, one common digest and one different digest", - original: []intoto.Subject{ + original: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -139,7 +140,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - toAdd: []intoto.Subject{ + toAdd: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -148,7 +149,7 @@ func TestAppendSubjects(t *testing.T) { }, }, }, - want: []intoto.Subject{ + want: []*intoto.ResourceDescriptor{ { Name: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ @@ -164,7 +165,7 @@ func TestAppendSubjects(t *testing.T) { t.Run(tc.name, func(t *testing.T) { got := AppendSubjects(tc.original, tc.toAdd...) - if diff := cmp.Diff(tc.want, got); diff != "" { + if diff := cmp.Diff(tc.want, got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("materials(): -want +got: %s", diff) } }) @@ -312,7 +313,7 @@ func TestAppendMaterials(t *testing.T) { t.Run(tc.name, func(t *testing.T) { got := AppendMaterials(tc.original, tc.toAdd...) - if diff := cmp.Diff(tc.want, got); diff != "" { + if diff := cmp.Diff(tc.want, got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("materials(): -want +got: %s", diff) } }) diff --git a/pkg/chains/formats/slsa/internal/build_definition/build_definition.go b/pkg/chains/formats/slsa/internal/build_definition/build_definition.go index 655bdbf701..891b3c2dbc 100644 --- a/pkg/chains/formats/slsa/internal/build_definition/build_definition.go +++ b/pkg/chains/formats/slsa/internal/build_definition/build_definition.go @@ -15,23 +15,30 @@ package builddefinition import ( "context" + "encoding/json" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_types" externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/objects" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" ) // GetTaskRunBuildDefinition returns the buildDefinition for the given TaskRun based on the configured buildType. This will default to the slsa buildType -func GetTaskRunBuildDefinition(ctx context.Context, tro *objects.TaskRunObjectV1, buildType string, resolveOpts resolveddependencies.ResolveOptions) (slsa.ProvenanceBuildDefinition, error) { +func GetTaskRunBuildDefinition(ctx context.Context, tro *objects.TaskRunObjectV1, buildType string, resolveOpts resolveddependencies.ResolveOptions) (slsa.BuildDefinition, error) { rd, err := resolveddependencies.TaskRun(ctx, resolveOpts, tro) if err != nil { - return slsa.ProvenanceBuildDefinition{}, err + return slsa.BuildDefinition{}, err } externalParams := externalparameters.TaskRun(tro) + structExternalParams, err := getStruct(externalParams) + if err != nil { + return slsa.BuildDefinition{}, err + } buildDefinitionType := buildType if buildDefinitionType == "" { @@ -40,13 +47,32 @@ func GetTaskRunBuildDefinition(ctx context.Context, tro *objects.TaskRunObjectV1 internalParams, err := internalparameters.GetInternalParamters(tro, buildDefinitionType) if err != nil { - return slsa.ProvenanceBuildDefinition{}, err + return slsa.BuildDefinition{}, err + } + structInternalParams, err := getStruct(internalParams) + if err != nil { + return slsa.BuildDefinition{}, err } - return slsa.ProvenanceBuildDefinition{ + return slsa.BuildDefinition{ BuildType: buildDefinitionType, - ExternalParameters: externalParams, - InternalParameters: internalParams, + ExternalParameters: structExternalParams, + InternalParameters: structInternalParams, ResolvedDependencies: rd, }, nil } + +func getStruct(data map[string]any) (*structpb.Struct, error) { + bytes, err := json.Marshal(data) + if err != nil { + return nil, err + } + + protoStruct := &structpb.Struct{} + err = protojson.Unmarshal(bytes, protoStruct) + if err != nil { + return nil, err + } + + return protoStruct, nil +} diff --git a/pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go b/pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go index ab91a0835c..963d6c07b8 100644 --- a/pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go +++ b/pkg/chains/formats/slsa/internal/build_definition/build_definition_test.go @@ -18,12 +18,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" ) func TestGetBuildDefinition(t *testing.T) { @@ -45,42 +47,43 @@ func TestGetBuildDefinition(t *testing.T) { tests := []struct { name string buildType string - want slsa.ProvenanceBuildDefinition + want slsa.BuildDefinition err error }{ { name: "test slsa build type", buildType: "https://tekton.dev/chains/v2/slsa", - want: slsa.ProvenanceBuildDefinition{ + want: slsa.BuildDefinition{ BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.SLSAInternalParameters(tro), + ExternalParameters: getProtoStruct(t, externalparameters.TaskRun(tro)), + InternalParameters: getProtoStruct(t, internalparameters.SLSAInternalParameters(tro)), }, err: nil, }, { name: "test default build type", buildType: "", - want: slsa.ProvenanceBuildDefinition{ + want: slsa.BuildDefinition{ BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.SLSAInternalParameters(tro), + ExternalParameters: getProtoStruct(t, externalparameters.TaskRun(tro)), + InternalParameters: getProtoStruct(t, internalparameters.SLSAInternalParameters(tro)), }, err: nil, }, { name: "test tekton build type", buildType: "https://tekton.dev/chains/v2/slsa-tekton", - want: slsa.ProvenanceBuildDefinition{ + want: slsa.BuildDefinition{ BuildType: "https://tekton.dev/chains/v2/slsa-tekton", - ExternalParameters: externalparameters.TaskRun(tro), - InternalParameters: internalparameters.TektonInternalParameters(tro), + ExternalParameters: getProtoStruct(t, externalparameters.TaskRun(tro)), + InternalParameters: getProtoStruct(t, internalparameters.TektonInternalParameters(tro)), }, err: nil, }, } - for _, tc := range tests { + for i := range tests { + tc := &tests[i] t.Run(tc.name, func(t *testing.T) { rd, err := resolveddependencies.TaskRun(ctx, resolveddependencies.ResolveOptions{}, tro) if err != nil { @@ -92,8 +95,8 @@ func TestGetBuildDefinition(t *testing.T) { if err != nil { t.Fatalf("Did not expect an error but got %v", err) } - - if diff := cmp.Diff(tc.want, bd); diff != "" { + bd.ProtoReflect() + if diff := cmp.Diff(&tc.want, &bd, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("getBuildDefinition(): -want +got: %v", diff) } }) @@ -110,7 +113,17 @@ func TestUnsupportedBuildType(t *testing.T) { if err == nil { t.Error("getBuildDefinition(): expected error got nil") } - if diff := cmp.Diff(slsa.ProvenanceBuildDefinition{}, got); diff != "" { + if diff := cmp.Diff(&slsa.BuildDefinition{}, &got, protocmp.Transform()); diff != "" { t.Errorf("getBuildDefinition(): -want +got: %s", diff) } } + +func getProtoStruct(t *testing.T, data map[string]any) *structpb.Struct { + t.Helper() + protoStruct, err := getStruct(data) + if err != nil { + t.Fatalf("error getting proto struct from data: %v", err) + } + + return protoStruct +} diff --git a/pkg/chains/formats/slsa/internal/compare/slsacompare.go b/pkg/chains/formats/slsa/internal/compare/slsacompare.go index 0b7b8c2d84..674112ea96 100644 --- a/pkg/chains/formats/slsa/internal/compare/slsacompare.go +++ b/pkg/chains/formats/slsa/internal/compare/slsacompare.go @@ -17,9 +17,8 @@ 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" + intoto "github.com/in-toto/attestation/go/v1" "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 @@ -28,12 +27,12 @@ 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 { + resourceDescriptorSort := func(x, y *intoto.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 + if x.Uri != y.Uri { + return x.Uri < y.Uri } return lessDigestSet(x.Digest, y.Digest) } @@ -47,7 +46,7 @@ func SLSAV1CompareOptions() []cmp.Option { // SubjectCompareOption returns the comparison option to sort and compare a // list of Subjects. func SubjectCompareOption() cmp.Option { - subjectSort := func(x, y in_toto.Subject) bool { + subjectSort := func(x, y *intoto.ResourceDescriptor) bool { if x.Name != y.Name { return x.Name < y.Name } diff --git a/pkg/chains/formats/slsa/internal/material/v1beta1/material.go b/pkg/chains/formats/slsa/internal/material/v1beta1/material.go index ef49f2b73c..650bb1d6dd 100644 --- a/pkg/chains/formats/slsa/internal/material/v1beta1/material.go +++ b/pkg/chains/formats/slsa/internal/material/v1beta1/material.go @@ -74,7 +74,8 @@ func PipelineMaterials(ctx context.Context, pro *objects.PipelineRunObjectV1Beta } pSpec := pro.Status.PipelineSpec if pSpec != nil { - pipelineTasks := append(pSpec.Tasks, pSpec.Finally...) + pipelineTasks := pSpec.Tasks + pipelineTasks = append(pipelineTasks, pSpec.Finally...) for _, t := range pipelineTasks { tr := pro.GetTaskRunFromTask(t.Name) // Ignore Tasks that did not execute during the PipelineRun. @@ -269,7 +270,8 @@ func FromPipelineParamsAndResults(ctx context.Context, pro *objects.PipelineRunO // search type hinting param/results from each individual taskruns if slsaconfig.DeepInspectionEnabled { logger := logging.FromContext(ctx) - pipelineTasks := append(pSpec.Tasks, pSpec.Finally...) + pipelineTasks := pSpec.Tasks + pipelineTasks = append(pipelineTasks, pSpec.Finally...) for _, t := range pipelineTasks { tr := pro.GetTaskRunFromTask(t.Name) // Ignore Tasks that did not execute during the PipelineRun. diff --git a/pkg/chains/formats/slsa/internal/metadata/metadata.go b/pkg/chains/formats/slsa/internal/metadata/metadata.go index ab3d411d98..7d98ad859e 100644 --- a/pkg/chains/formats/slsa/internal/metadata/metadata.go +++ b/pkg/chains/formats/slsa/internal/metadata/metadata.go @@ -14,15 +14,29 @@ limitations under the License. package metadata import ( - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" "github.com/tektoncd/chains/pkg/chains/objects" + "google.golang.org/protobuf/types/known/timestamppb" ) // GetBuildMetadata returns SLSA metadata. -func GetBuildMetadata(obj objects.TektonObject) slsa.BuildMetadata { - return slsa.BuildMetadata{ - InvocationID: string(obj.GetUID()), - StartedOn: obj.GetStartTime(), - FinishedOn: obj.GetCompletitionTime(), +func GetBuildMetadata(obj objects.TektonObject) *slsa.BuildMetadata { + var startedOn *timestamppb.Timestamp + var finishedOn *timestamppb.Timestamp + objStartTime := obj.GetStartTime() + objCompletitionTime := obj.GetCompletitionTime() + + if objStartTime != nil { + startedOn = timestamppb.New(*objStartTime) + } + + if objCompletitionTime != nil { + finishedOn = timestamppb.New(*objCompletitionTime) + } + + return &slsa.BuildMetadata{ + InvocationId: string(obj.GetUID()), + StartedOn: startedOn, + FinishedOn: finishedOn, } } diff --git a/pkg/chains/formats/slsa/internal/metadata/metadata_test.go b/pkg/chains/formats/slsa/internal/metadata/metadata_test.go index 2f0059f249..3f6cb3e7a9 100644 --- a/pkg/chains/formats/slsa/internal/metadata/metadata_test.go +++ b/pkg/chains/formats/slsa/internal/metadata/metadata_test.go @@ -18,14 +18,16 @@ import ( "time" "github.com/google/go-cmp/cmp" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" "github.com/tektoncd/chains/pkg/chains/objects" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestMetadata(t *testing.T) { - tr := &v1.TaskRun{ //nolint:staticcheck + tr := &v1.TaskRun{ ObjectMeta: metav1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -43,20 +45,20 @@ func TestMetadata(t *testing.T) { } start := time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC) end := time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC) - want := slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &start, - FinishedOn: &end, + want := &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(start), + FinishedOn: timestamppb.New(end), } got := GetBuildMetadata(objects.NewTaskRunObjectV1(tr)) - if d := cmp.Diff(want, got); d != "" { + if d := cmp.Diff(want, got, cmp.Options{protocmp.Transform()}); d != "" { t.Fatalf("metadata (-want, +got):\n%s", d) } } func TestMetadataInTimeZone(t *testing.T) { tz := time.FixedZone("Test Time", int((12 * time.Hour).Seconds())) - tr := &v1.TaskRun{ //nolint:staticcheck + tr := &v1.TaskRun{ ObjectMeta: metav1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -74,13 +76,13 @@ func TestMetadataInTimeZone(t *testing.T) { } start := time.Date(1995, time.December, 24, 6, 12, 12, 12, tz).UTC() end := time.Date(1995, time.December, 24, 6, 12, 12, 24, tz).UTC() - want := slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &start, - FinishedOn: &end, + want := &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(start), + FinishedOn: timestamppb.New(end), } got := GetBuildMetadata(objects.NewTaskRunObjectV1(tr)) - if d := cmp.Diff(want, got); d != "" { + if d := cmp.Diff(want, got, cmp.Options{protocmp.Transform()}); d != "" { t.Fatalf("metadata (-want, +got):\n%s", d) } } diff --git a/pkg/chains/formats/slsa/internal/provenance/provenance.go b/pkg/chains/formats/slsa/internal/provenance/provenance.go index f514c03eed..45f3070d7c 100644 --- a/pkg/chains/formats/slsa/internal/provenance/provenance.go +++ b/pkg/chains/formats/slsa/internal/provenance/provenance.go @@ -14,30 +14,52 @@ limitations under the License. package provenance import ( - intoto "github.com/in-toto/in-toto-golang/in_toto" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/metadata" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/objects" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" ) // GetSLSA1Statement returns a predicate in SLSA v1.0 format using the given data. -func GetSLSA1Statement(obj objects.TektonObject, sub []intoto.Subject, bd slsa.ProvenanceBuildDefinition, bp []slsa.ResourceDescriptor, slsaConfig *slsaconfig.SlsaConfig) intoto.ProvenanceStatementSLSA1 { - return intoto.ProvenanceStatementSLSA1{ - StatementHeader: intoto.StatementHeader{ - Type: intoto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: sub, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: bd, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: slsaConfig.BuilderID, - }, - BuildMetadata: metadata.GetBuildMetadata(obj), - Byproducts: bp, +func GetSLSA1Statement(obj objects.TektonObject, sub []*intoto.ResourceDescriptor, bd *slsa.BuildDefinition, bp []*intoto.ResourceDescriptor, slsaConfig *slsaconfig.SlsaConfig) (intoto.Statement, error) { + predicate := slsa.Provenance{ + BuildDefinition: bd, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: slsaConfig.BuilderID, }, + Metadata: metadata.GetBuildMetadata(obj), + Byproducts: bp, }, } + + predicateStruct, err := getProtoStruct(&predicate) + if err != nil { + return intoto.Statement{}, err + } + + return intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: sub, + Predicate: predicateStruct, + }, nil +} + +func getProtoStruct(predicate *slsa.Provenance) (*structpb.Struct, error) { + protoStruct := &structpb.Struct{} + predicateJSON, err := protojson.Marshal(predicate) + if err != nil { + return nil, err + } + + err = protojson.Unmarshal(predicateJSON, protoStruct) + if err != nil { + return nil, err + } + + return protoStruct, nil } diff --git a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go index a619d9d299..c76a818196 100644 --- a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go +++ b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies.go @@ -21,14 +21,15 @@ import ( "encoding/json" "fmt" + intoto "github.com/in-toto/attestation/go/v1" "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/material" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/objects" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "go.uber.org/zap" + "google.golang.org/protobuf/encoding/protojson" "knative.dev/pkg/logging" ) @@ -49,7 +50,7 @@ const ( // used to toggle the fields in see AddTektonTaskDescriptor // and AddSLSATaskDescriptor -type addTaskDescriptorContent func(*objects.TaskRunObjectV1) (*slsa.ResourceDescriptor, error) //nolint:staticcheck +type addTaskDescriptorContent func(*objects.TaskRunObjectV1) (*intoto.ResourceDescriptor, error) // ResolveOptions represents the configuration to be use to resolve dependencies. type ResolveOptions struct { @@ -58,37 +59,37 @@ type ResolveOptions struct { } // ConvertMaterialToResolvedDependency converts a SLSAv0.2 Material to a resolved dependency -func ConvertMaterialsToResolvedDependencies(mats []common.ProvenanceMaterial, name string) []slsa.ResourceDescriptor { - rds := []slsa.ResourceDescriptor{} +func ConvertMaterialsToResolvedDependencies(mats []common.ProvenanceMaterial, name string) []*intoto.ResourceDescriptor { + rds := []*intoto.ResourceDescriptor{} for _, mat := range mats { - rd := slsa.ResourceDescriptor{} - rd.URI = mat.URI + rd := intoto.ResourceDescriptor{} + rd.Uri = mat.URI rd.Digest = mat.Digest if len(name) > 0 { rd.Name = name } - rds = append(rds, rd) + rds = append(rds, &rd) } return rds } // RemoveDuplicateResolvedDependencies removes duplicate resolved dependencies from the slice of resolved dependencies. // Original order of resolved dependencies is retained. -func RemoveDuplicateResolvedDependencies(resolvedDependencies []slsa.ResourceDescriptor) ([]slsa.ResourceDescriptor, error) { - out := make([]slsa.ResourceDescriptor, 0, len(resolvedDependencies)) +func RemoveDuplicateResolvedDependencies(resolvedDependencies []*intoto.ResourceDescriptor) ([]*intoto.ResourceDescriptor, error) { + out := make([]*intoto.ResourceDescriptor, 0, len(resolvedDependencies)) // make map to store seen resolved dependencies seen := map[string]bool{} for _, resolvedDependency := range resolvedDependencies { // Since resolvedDependencies contain names, we want to ignore those while checking for duplicates. // Therefore, make a copy of the resolved dependency that only contains the uri and digest fields. - rDep := slsa.ResourceDescriptor{} - rDep.URI = resolvedDependency.URI + rDep := intoto.ResourceDescriptor{} + rDep.Uri = resolvedDependency.Uri rDep.Digest = resolvedDependency.Digest // pipelinTasks store content with the slsa-tekton buildType rDep.Content = resolvedDependency.Content // This allows us to ignore dependencies that have the same uri and digest. - rd, err := json.Marshal(rDep) + rd, err := protojson.Marshal(&rDep) if err != nil { return nil, err } @@ -110,8 +111,8 @@ func RemoveDuplicateResolvedDependencies(resolvedDependencies []slsa.ResourceDes // AddTektonTaskDescriptor returns the more verbose resolved dependency content. this adds the name, uri, digest // and content if possible. -func AddTektonTaskDescriptor(tr *objects.TaskRunObjectV1) (*slsa.ResourceDescriptor, error) { //nolint:staticcheck - rd := slsa.ResourceDescriptor{} +func AddTektonTaskDescriptor(tr *objects.TaskRunObjectV1) (*intoto.ResourceDescriptor, error) { + rd := intoto.ResourceDescriptor{} storedTr, err := json.Marshal(tr) if err != nil { return nil, err @@ -120,7 +121,7 @@ func AddTektonTaskDescriptor(tr *objects.TaskRunObjectV1) (*slsa.ResourceDescrip rd.Name = PipelineTaskConfigName rd.Content = storedTr if tr.Status.Provenance != nil && tr.Status.Provenance.RefSource != nil { - rd.URI = tr.Status.Provenance.RefSource.URI + rd.Uri = tr.Status.Provenance.RefSource.URI rd.Digest = tr.Status.Provenance.RefSource.Digest } @@ -129,11 +130,11 @@ func AddTektonTaskDescriptor(tr *objects.TaskRunObjectV1) (*slsa.ResourceDescrip // AddSLSATaskDescriptor resolves dependency content for the more generic slsa verifiers. just logs // the name, uri and digest. -func AddSLSATaskDescriptor(tr *objects.TaskRunObjectV1) (*slsa.ResourceDescriptor, error) { //nolint:staticcheck +func AddSLSATaskDescriptor(tr *objects.TaskRunObjectV1) (*intoto.ResourceDescriptor, error) { if tr.Status.Provenance != nil && tr.Status.Provenance.RefSource != nil { - return &slsa.ResourceDescriptor{ + return &intoto.ResourceDescriptor{ Name: PipelineTaskConfigName, - URI: tr.Status.Provenance.RefSource.URI, + Uri: tr.Status.Provenance.RefSource.URI, Digest: tr.Status.Provenance.RefSource.Digest, }, nil } @@ -142,9 +143,9 @@ func AddSLSATaskDescriptor(tr *objects.TaskRunObjectV1) (*slsa.ResourceDescripto // fromPipelineTask adds the resolved dependencies from pipeline tasks // such as pipeline task uri/digest for remote pipeline tasks and step and sidecar images. -func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObjectV1, addTasks addTaskDescriptorContent) ([]slsa.ResourceDescriptor, error) { +func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObjectV1, addTasks addTaskDescriptorContent) ([]*intoto.ResourceDescriptor, error) { pSpec := pro.Status.PipelineSpec - resolvedDependencies := []slsa.ResourceDescriptor{} + resolvedDependencies := []*intoto.ResourceDescriptor{} if pSpec != nil { pipelineTasks := pSpec.Tasks pipelineTasks = append(pipelineTasks, pSpec.Finally...) @@ -162,7 +163,7 @@ func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObjectV } if rd != nil { - resolvedDependencies = append(resolvedDependencies, *rd) + resolvedDependencies = append(resolvedDependencies, rd) } mats := []common.ProvenanceMaterial{} @@ -189,8 +190,8 @@ func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObjectV } // taskDependencies gather all dependencies in a task and adds them to resolvedDependencies -func taskDependencies(ctx context.Context, opts ResolveOptions, tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { - var resolvedDependencies []slsa.ResourceDescriptor +func taskDependencies(ctx context.Context, opts ResolveOptions, tro *objects.TaskRunObjectV1) ([]*intoto.ResourceDescriptor, error) { + var resolvedDependencies []*intoto.ResourceDescriptor var err error mats := []common.ProvenanceMaterial{} @@ -251,18 +252,18 @@ func taskDependencies(ctx context.Context, opts ResolveOptions, tro *objects.Tas } // TaskRun constructs `predicate.resolvedDependencies` section by collecting all the artifacts that influence a taskrun such as source code repo and step&sidecar base images. -func TaskRun(ctx context.Context, opts ResolveOptions, tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { - var resolvedDependencies []slsa.ResourceDescriptor +func TaskRun(ctx context.Context, opts ResolveOptions, tro *objects.TaskRunObjectV1) ([]*intoto.ResourceDescriptor, error) { + var resolvedDependencies []*intoto.ResourceDescriptor var err error // add top level task config if p := tro.Status.Provenance; p != nil && p.RefSource != nil { - rd := slsa.ResourceDescriptor{ + rd := intoto.ResourceDescriptor{ Name: TaskConfigName, - URI: p.RefSource.URI, + Uri: p.RefSource.URI, Digest: p.RefSource.Digest, } - resolvedDependencies = append(resolvedDependencies, rd) + resolvedDependencies = append(resolvedDependencies, &rd) } rds, err := taskDependencies(ctx, opts, tro) @@ -275,19 +276,19 @@ func TaskRun(ctx context.Context, opts ResolveOptions, tro *objects.TaskRunObjec } // PipelineRun constructs `predicate.resolvedDependencies` section by collecting all the artifacts that influence a pipeline run such as source code repo and step&sidecar base images. -func PipelineRun(ctx context.Context, pro *objects.PipelineRunObjectV1, slsaconfig *slsaconfig.SlsaConfig, addTasks addTaskDescriptorContent) ([]slsa.ResourceDescriptor, error) { +func PipelineRun(ctx context.Context, pro *objects.PipelineRunObjectV1, slsaconfig *slsaconfig.SlsaConfig, addTasks addTaskDescriptorContent) ([]*intoto.ResourceDescriptor, error) { var err error - var resolvedDependencies []slsa.ResourceDescriptor + var resolvedDependencies []*intoto.ResourceDescriptor logger := logging.FromContext(ctx) // add pipeline config to resolved dependencies if p := pro.Status.Provenance; p != nil && p.RefSource != nil { - rd := slsa.ResourceDescriptor{ + rd := intoto.ResourceDescriptor{ Name: PipelineConfigName, - URI: p.RefSource.URI, + Uri: p.RefSource.URI, Digest: p.RefSource.Digest, } - resolvedDependencies = append(resolvedDependencies, rd) + resolvedDependencies = append(resolvedDependencies, &rd) } // add resolved dependencies from pipeline tasks diff --git a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go index a32ab0db9a..e19f2eecfd 100644 --- a/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go +++ b/pkg/chains/formats/slsa/internal/resolved_dependencies/resolved_dependencies_test.go @@ -22,8 +22,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" - v1slsa "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/slsaconfig" @@ -32,6 +32,7 @@ import ( v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" + "google.golang.org/protobuf/testing/protocmp" logtesting "knative.dev/pkg/logging/testing" ) @@ -40,41 +41,41 @@ const digest = "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812ad func TestRemoveDuplicates(t *testing.T) { tests := []struct { name string - rds []v1slsa.ResourceDescriptor - want []v1slsa.ResourceDescriptor + rds []*intoto.ResourceDescriptor + want []*intoto.ResourceDescriptor }{{ name: "no duplicate resolvedDependencies", - rds: []v1slsa.ResourceDescriptor{ + rds: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Uri: "oci://gcr.io/cloud-marketplace-containers/google/bazel", Digest: common.DigestSet{ "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", Digest: common.DigestSet{ "sha256": "a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", }, }, }, - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Uri: "oci://gcr.io/cloud-marketplace-containers/google/bazel", Digest: common.DigestSet{ "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", Digest: common.DigestSet{ "sha256": "a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", }, @@ -82,22 +83,22 @@ func TestRemoveDuplicates(t *testing.T) { }, }, { name: "same uri and digest", - rds: []v1slsa.ResourceDescriptor{ + rds: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, }, - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, @@ -105,27 +106,27 @@ func TestRemoveDuplicates(t *testing.T) { }, }, { name: "same uri but different digest", - rds: []v1slsa.ResourceDescriptor{ + rds: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, }, }, - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, @@ -133,27 +134,27 @@ func TestRemoveDuplicates(t *testing.T) { }, }, { name: "same uri but different digest, swap order", - rds: []v1slsa.ResourceDescriptor{ + rds: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, }, - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, @@ -161,39 +162,39 @@ func TestRemoveDuplicates(t *testing.T) { }, }, { name: "task config must be present", - rds: []v1slsa.ResourceDescriptor{ + rds: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { Name: "task", - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, }, - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { Name: "task", - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, @@ -201,39 +202,39 @@ func TestRemoveDuplicates(t *testing.T) { }, }, { name: "pipeline config must be present", - rds: []v1slsa.ResourceDescriptor{ + rds: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { Name: "pipeline", - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, }, - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01248", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { Name: "pipeline", - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, @@ -246,7 +247,7 @@ func TestRemoveDuplicates(t *testing.T) { if err != nil { t.Fatalf("Did not expect an error but got %v", err) } - if diff := cmp.Diff(tc.want, rds); diff != "" { + if diff := cmp.Diff(tc.want, rds, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("resolvedDependencies(): -want +got: %s", diff) } }) @@ -302,9 +303,9 @@ func tektonTaskRuns() map[string][]byte { func TestTaskRun(t *testing.T) { tests := []struct { name string - obj objects.TektonObject //nolint:staticcheck + obj objects.TektonObject resolveOpts ResolveOptions - want []v1slsa.ResourceDescriptor + want []*intoto.ResourceDescriptor }{ { name: "resolvedDependencies from pipeline resources", @@ -355,17 +356,17 @@ func TestTaskRun(t *testing.T) { }, }, }), - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { Name: "inputs/result", - URI: "gcr.io/foo/bar", + Uri: "gcr.io/foo/bar", Digest: common.DigestSet{ "sha256": strings.TrimPrefix(digest, "sha256:"), }, }, { Name: "pipelineResource", - URI: "git+https://github.com/GoogleContainerTools/distroless.git", + Uri: "git+https://github.com/GoogleContainerTools/distroless.git", Digest: common.DigestSet{ "sha1": "50c56a48cfb3a5a80fa36ed91c739bdac8381cbe", }, @@ -374,7 +375,7 @@ func TestTaskRun(t *testing.T) { }, { name: "resolvedDependencies from remote task", - obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ //nolint:staticcheck + obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ Status: v1.TaskRunStatus{ TaskRunStatusFields: v1.TaskRunStatusFields{ Provenance: &v1.Provenance{ @@ -388,10 +389,10 @@ func TestTaskRun(t *testing.T) { }, }, }), - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { Name: "task", - URI: "git+github.com/something.git", + Uri: "git+github.com/something.git", Digest: common.DigestSet{ "sha1": "abcd1234", }, @@ -400,7 +401,7 @@ func TestTaskRun(t *testing.T) { }, { name: "git resolvedDependencies from taskrun params", - obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ //nolint:staticcheck + obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ Spec: v1.TaskRunSpec{ Params: []v1.Param{{ Name: "CHAINS-GIT_COMMIT", @@ -411,10 +412,10 @@ func TestTaskRun(t *testing.T) { }}, }, }), - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { Name: "inputs/result", - URI: "git+github.com/something.git", + Uri: "git+github.com/something.git", Digest: common.DigestSet{ "sha1": "my-commit", }, @@ -423,7 +424,7 @@ func TestTaskRun(t *testing.T) { }, { name: "resolvedDependencies from step images", - obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ //nolint:staticcheck + obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ Status: v1.TaskRunStatus{ TaskRunStatusFields: v1.TaskRunStatusFields{ Steps: []v1.StepState{{ @@ -439,15 +440,15 @@ func TestTaskRun(t *testing.T) { }, }, }), - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Uri: "oci://gcr.io/cloud-marketplace-containers/google/bazel", Digest: common.DigestSet{ "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", }, @@ -456,7 +457,7 @@ func TestTaskRun(t *testing.T) { }, { name: "resolvedDependencies from step and sidecar images", - obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ //nolint:staticcheck + obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ Status: v1.TaskRunStatus{ TaskRunStatusFields: v1.TaskRunStatusFields{ Steps: []v1.StepState{{ @@ -482,19 +483,19 @@ func TestTaskRun(t *testing.T) { }, }, }), - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Uri: "oci://gcr.io/cloud-marketplace-containers/google/bazel", Digest: common.DigestSet{ "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", Digest: common.DigestSet{ "sha256": "a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", }, @@ -506,7 +507,7 @@ func TestTaskRun(t *testing.T) { resolveOpts: ResolveOptions{ WithStepActionsResults: true, }, - obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ //nolint:staticcheck + obj: objects.NewTaskRunObjectV1(&v1.TaskRun{ Status: v1.TaskRunStatus{ TaskRunStatusFields: v1.TaskRunStatusFields{ Steps: []v1.StepState{{ @@ -532,25 +533,25 @@ func TestTaskRun(t *testing.T) { }, }, }), - want: []v1slsa.ResourceDescriptor{ + want: []*intoto.ResourceDescriptor{ { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", Digest: common.DigestSet{ "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", }, }, { - URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Uri: "oci://gcr.io/cloud-marketplace-containers/google/bazel", Digest: common.DigestSet{ "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", }, }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", + Uri: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", Digest: common.DigestSet{ "sha256": "a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", }, }, { Name: "inputs/result", - URI: "https://github.com/tektoncd/pipeline", + Uri: "https://github.com/tektoncd/pipeline", Digest: common.DigestSet{ "sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601", }, @@ -585,7 +586,7 @@ func TestTaskRun(t *testing.T) { if err != nil { t.Fatalf("Did not expect an error but got %v", err) } - if diff := cmp.Diff(tc.want, rd); diff != "" { + if diff := cmp.Diff(tc.want, rd, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("ResolvedDependencies(): -want +got: %s", diff) } }) @@ -597,52 +598,52 @@ func TestPipelineRun(t *testing.T) { tests := []struct { name string taskDescriptor addTaskDescriptorContent - want []v1slsa.ResourceDescriptor + want []*intoto.ResourceDescriptor }{ { name: "test slsa build type", taskDescriptor: AddSLSATaskDescriptor, - want: []v1slsa.ResourceDescriptor{ - {Name: "pipeline", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, - {Name: "pipelineTask", URI: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, + want: []*intoto.ResourceDescriptor{ + {Name: "pipeline", Uri: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, + {Name: "pipelineTask", Uri: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, { - URI: "oci://gcr.io/test1/test1", + Uri: "oci://gcr.io/test1/test1", Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - {Name: "pipelineTask", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, + {Name: "pipelineTask", Uri: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, { - URI: "oci://gcr.io/test2/test2", + Uri: "oci://gcr.io/test2/test2", Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, { - URI: "oci://gcr.io/test3/test3", + Uri: "oci://gcr.io/test3/test3", Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - {Name: "inputs/result", URI: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + {Name: "inputs/result", Uri: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, + {Name: "inputs/result", Uri: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, }, }, { name: "test tekton build type", taskDescriptor: AddTektonTaskDescriptor, - want: []v1slsa.ResourceDescriptor{ - {Name: "pipeline", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, - {Name: "pipelineTask", URI: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}, Content: taskRuns["git-clone"]}, + want: []*intoto.ResourceDescriptor{ + {Name: "pipeline", Uri: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, + {Name: "pipelineTask", Uri: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}, Content: taskRuns["git-clone"]}, { - URI: "oci://gcr.io/test1/test1", + Uri: "oci://gcr.io/test1/test1", Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - {Name: "pipelineTask", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}, Content: taskRuns["taskrun-build"]}, + {Name: "pipelineTask", Uri: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}, Content: taskRuns["taskrun-build"]}, { - URI: "oci://gcr.io/test2/test2", + Uri: "oci://gcr.io/test2/test2", Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, { - URI: "oci://gcr.io/test3/test3", + Uri: "oci://gcr.io/test3/test3", Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - {Name: "inputs/result", URI: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + {Name: "inputs/result", Uri: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, + {Name: "inputs/result", Uri: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, }, }, } @@ -655,7 +656,7 @@ func TestPipelineRun(t *testing.T) { if err != nil { t.Error(err) } - if d := cmp.Diff(tc.want, got); d != "" { + if d := cmp.Diff(tc.want, got, cmp.Options{protocmp.Transform()}); d != "" { t.Errorf("PipelineRunResolvedDependencies(): -want +got: %s", got) } }) diff --git a/pkg/chains/formats/slsa/internal/results/results.go b/pkg/chains/formats/slsa/internal/results/results.go index 2fb901b411..4861363307 100644 --- a/pkg/chains/formats/slsa/internal/results/results.go +++ b/pkg/chains/formats/slsa/internal/results/results.go @@ -21,7 +21,7 @@ import ( "github.com/tektoncd/chains/pkg/artifacts" "github.com/tektoncd/chains/pkg/chains/objects" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/v1" ) var imageResultsNamesSuffixs = []string{ @@ -30,8 +30,8 @@ var imageResultsNamesSuffixs = []string{ } // GetResultsWithoutBuildArtifacts returns all the results without those that are build artifacts. -func GetResultsWithoutBuildArtifacts(results []objects.Result, resultTypePrefix string) ([]slsa.ResourceDescriptor, error) { - byProd := []slsa.ResourceDescriptor{} +func GetResultsWithoutBuildArtifacts(results []objects.Result, resultTypePrefix string) ([]*slsa.ResourceDescriptor, error) { + byProd := []*slsa.ResourceDescriptor{} for _, r := range results { if isBuildArtifact, err := artifacts.IsBuildArtifact(r); err != nil || isBuildArtifact { continue @@ -46,7 +46,7 @@ func GetResultsWithoutBuildArtifacts(results []objects.Result, resultTypePrefix return nil, err } - byProd = append(byProd, slsa.ResourceDescriptor{ + byProd = append(byProd, &slsa.ResourceDescriptor{ Name: fmt.Sprintf(resultTypePrefix, r.Name), Content: content, MediaType: "application/json", diff --git a/pkg/chains/formats/slsa/internal/results/results_test.go b/pkg/chains/formats/slsa/internal/results/results_test.go index 34f423fc46..fb3392bcd5 100644 --- a/pkg/chains/formats/slsa/internal/results/results_test.go +++ b/pkg/chains/formats/slsa/internal/results/results_test.go @@ -18,9 +18,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/objects" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "google.golang.org/protobuf/testing/protocmp" ) func TestGetResultsWithoutBuildArtifacts(t *testing.T) { @@ -28,11 +29,11 @@ func TestGetResultsWithoutBuildArtifacts(t *testing.T) { name string prefix string results []objects.Result - expected []slsa.ResourceDescriptor + expected []*slsa.ResourceDescriptor }{ { name: "no results as input", - expected: []slsa.ResourceDescriptor{}, + expected: []*slsa.ResourceDescriptor{}, }, { name: "results without build artifacts", @@ -121,7 +122,7 @@ func TestGetResultsWithoutBuildArtifacts(t *testing.T) { }, }, }, - expected: []slsa.ResourceDescriptor{ + expected: []*slsa.ResourceDescriptor{ { Name: "taskRunResults/result1", MediaType: "application/json", @@ -181,7 +182,7 @@ func TestGetResultsWithoutBuildArtifacts(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } - if d := cmp.Diff(test.expected, got); d != "" { + if d := cmp.Diff(test.expected, got, cmp.Options{protocmp.Transform()}); d != "" { t.Fatalf("metadata (-want, +got):\n%s", d) } }) @@ -189,6 +190,7 @@ func TestGetResultsWithoutBuildArtifacts(t *testing.T) { } func toJSONString(t *testing.T, val v1.ParamValue) []byte { + t.Helper() res, err := json.Marshal(val) if err != nil { t.Fatalf("error converting to json string: %v", err) diff --git a/pkg/chains/formats/slsa/v1/internal/protos/protos.go b/pkg/chains/formats/slsa/v1/internal/protos/protos.go new file mode 100644 index 0000000000..14209b4307 --- /dev/null +++ b/pkg/chains/formats/slsa/v1/internal/protos/protos.go @@ -0,0 +1,25 @@ +package protos + +import ( + "encoding/json" + + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" +) + +// GetPredicateStruct returns a protobuf struct from the given SLSAv0.2 predicate. +func GetPredicateStruct(predicate *slsa.ProvenancePredicate) (*structpb.Struct, error) { + predicateJSON, err := json.Marshal(predicate) + if err != nil { + return nil, err + } + + predicateStruct := &structpb.Struct{} + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + return nil, err + } + + return predicateStruct, nil +} diff --git a/pkg/chains/formats/slsa/v1/intotoite6_test.go b/pkg/chains/formats/slsa/v1/intotoite6_test.go index 9e5605a2ee..504d7034cf 100644 --- a/pkg/chains/formats/slsa/v1/intotoite6_test.go +++ b/pkg/chains/formats/slsa/v1/intotoite6_test.go @@ -24,13 +24,16 @@ import ( "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/internal/protos" "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" "github.com/tektoncd/chains/pkg/config" "github.com/tektoncd/chains/pkg/internal/objectloader" + "google.golang.org/protobuf/testing/protocmp" "github.com/google/go-cmp/cmp" + intoto "github.com/in-toto/attestation/go/v1" "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/v0.2" @@ -38,8 +41,8 @@ import ( logtesting "knative.dev/pkg/logging/testing" ) -var e1BuildStart = time.Unix(1617011400, 0) -var e1BuildFinished = time.Unix(1617011415, 0) +var e1BuildStart = time.Unix(1617011400, 0).UTC() +var e1BuildFinished = time.Unix(1617011415, 0).UTC() func TestTaskRunCreatePayload1(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) @@ -54,84 +57,90 @@ func TestTaskRunCreatePayload1(t *testing.T) { ID: "test_builder-1", }, } - expected := in_toto.ProvenanceStatement{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ - { - Name: "gcr.io/my/image", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, - }, + + predicate := slsa.ProvenancePredicate{ + Metadata: &slsa.ProvenanceMetadata{ + BuildStartedOn: &e1BuildStart, + BuildFinishedOn: &e1BuildFinished, + }, + Materials: []common.ProvenanceMaterial{ + { + URI: artifacts.OCIScheme + "gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + { + URI: artifacts.OCIScheme + "gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + }, + { + URI: artifacts.OCIScheme + "gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, + {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "sha:taskrun"}}, }, - Predicate: slsa.ProvenancePredicate{ - Metadata: &slsa.ProvenanceMetadata{ - BuildStartedOn: &e1BuildStart, - BuildFinishedOn: &e1BuildFinished, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{ + URI: "github.com/test", + Digest: map[string]string{"sha1": "ab123"}, + EntryPoint: "build.yaml", }, - Materials: []common.ProvenanceMaterial{ + Parameters: map[string]v1beta1.ParamValue{ + "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, + "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskrun"}, + "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, + }, + Environment: map[string]map[string]string{ + "labels": {"tekton.dev/pipelineTask": "build"}, + }, + }, + Builder: common.ProvenanceBuilder{ + ID: "test_builder-1", + }, + BuildType: "tekton.dev/v1beta1/TaskRun", + BuildConfig: taskrun.BuildConfig{ + Steps: []attest.StepAttestation{ { - URI: artifacts.OCIScheme + "gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + Arguments: []string(nil), + Environment: map[string]interface{}{ + "container": string("step1"), + "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + }, }, { - URI: artifacts.OCIScheme + "gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + Arguments: []string(nil), + Environment: map[string]interface{}{ + "container": string("step2"), + "image": artifacts.OCIScheme + "gcr.io/test2/test2@sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", + }, }, { - URI: artifacts.OCIScheme + "gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "sha:taskrun"}}, - }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{ - URI: "github.com/test", - Digest: map[string]string{"sha1": "ab123"}, - EntryPoint: "build.yaml", - }, - Parameters: map[string]v1beta1.ParamValue{ - "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, - "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskrun"}, - "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, - }, - Environment: map[string]map[string]string{ - "labels": {"tekton.dev/pipelineTask": "build"}, + Arguments: []string(nil), + Environment: map[string]interface{}{ + "container": string("step3"), + "image": artifacts.OCIScheme + "gcr.io/test3/test3@sha256:f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478", + }, }, }, - Builder: common.ProvenanceBuilder{ - ID: "test_builder-1", - }, - BuildType: "tekton.dev/v1beta1/TaskRun", - BuildConfig: taskrun.BuildConfig{ - Steps: []attest.StepAttestation{ - { - Arguments: []string(nil), - Environment: map[string]interface{}{ - "container": string("step1"), - "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - }, - { - Arguments: []string(nil), - Environment: map[string]interface{}{ - "container": string("step2"), - "image": artifacts.OCIScheme + "gcr.io/test2/test2@sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", - }, - }, - { - Arguments: []string(nil), - Environment: map[string]interface{}{ - "container": string("step3"), - "image": artifacts.OCIScheme + "gcr.io/test3/test3@sha256:f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478", - }, - }, + }, + } + + predicateStruct, err := protos.GetPredicateStruct(&predicate) + if err != nil { + t.Fatalf("error creating proto struct from predicate: %v", err) + } + + expected := &intoto.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/my/image", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, }, }, + Predicate: predicateStruct, } i, _ := NewFormatter(cfg) @@ -141,7 +150,7 @@ func TestTaskRunCreatePayload1(t *testing.T) { t.Errorf("unexpected error: %s", err.Error()) } - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff) } } @@ -158,193 +167,71 @@ func TestPipelineRunCreatePayload(t *testing.T) { ID: "test_builder-1", }, } - expected := in_toto.ProvenanceStatement{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ - { - Name: "test.io/test/image", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, - }, + + predicate := &slsa.ProvenancePredicate{ + Metadata: &slsa.ProvenanceMetadata{ + BuildStartedOn: &e1BuildStart, + BuildFinishedOn: &e1BuildFinished, + Completeness: slsa.ProvenanceComplete{ + Parameters: false, + Environment: false, + Materials: false, }, + Reproducible: false, }, - Predicate: slsa.ProvenancePredicate{ - Metadata: &slsa.ProvenanceMetadata{ - BuildStartedOn: &e1BuildStart, - BuildFinishedOn: &e1BuildFinished, - Completeness: slsa.ProvenanceComplete{ - Parameters: false, - Environment: false, - Materials: false, - }, - Reproducible: false, + Materials: []common.ProvenanceMaterial{ + {URI: "github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, + { + URI: artifacts.OCIScheme + "gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - Materials: []common.ProvenanceMaterial{ - {URI: "github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, - { - URI: artifacts.OCIScheme + "gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - {URI: "github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, - { - URI: artifacts.OCIScheme + "gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: artifacts.OCIScheme + "gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - {URI: "github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, - {URI: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, - {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, - }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{ - URI: "github.com/test", - Digest: map[string]string{"sha1": "28b123"}, - EntryPoint: "pipeline.yaml", - }, - Parameters: map[string]v1beta1.ParamValue{ - "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, - }, + {URI: "github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, + { + URI: artifacts.OCIScheme + "gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - Builder: common.ProvenanceBuilder{ - ID: "test_builder-1", + { + URI: artifacts.OCIScheme + "gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - BuildType: "tekton.dev/v1beta1/PipelineRun", - BuildConfig: pipelinerun.BuildConfig{ - Tasks: []pipelinerun.TaskAttestation{ - { - Name: "git-clone", - After: nil, - Ref: v1beta1.TaskRef{ - Name: "git-clone", - Kind: "ClusterTask", - }, - StartedOn: e1BuildStart, - FinishedOn: e1BuildFinished, - Status: "Succeeded", - Steps: []attest.StepAttestation{ - { - EntryPoint: "git clone", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "container": "step1", - "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - Annotations: nil, - }, - }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{ - URI: "github.com/catalog", - Digest: common.DigestSet{"sha1": "x123"}, - EntryPoint: "git-clone.yaml", - }, - Parameters: map[string]v1beta1.ParamValue{ - "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskdefault"}, - "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, - "revision": {Type: "string", StringVal: ""}, - "url": {Type: "string", StringVal: "https://git.test.com"}, - }, - Environment: map[string]map[string]string{ - "labels": {"tekton.dev/pipelineTask": "git-clone"}, - }, - }, - Results: []v1beta1.TaskRunResult{ - { - Name: "some-uri_DIGEST", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - }, - { - Name: "some-uri", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "pkg:deb/debian/curl@7.50.3-1", - }, - }, - }, - }, - { - Name: "build", - After: []string{"git-clone"}, - Ref: v1beta1.TaskRef{ - Name: "build", - Kind: "ClusterTask", - }, - StartedOn: e1BuildStart, - FinishedOn: e1BuildFinished, - Status: "Succeeded", - Steps: []attest.StepAttestation{ - { - EntryPoint: "", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - "container": "step1", - }, - Annotations: nil, - }, - { - EntryPoint: "", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "image": artifacts.OCIScheme + "gcr.io/test2/test2@sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", - "container": "step2", - }, - Annotations: nil, - }, - { - EntryPoint: "", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "image": artifacts.OCIScheme + "gcr.io/test3/test3@sha256:f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478", - "container": "step3", - }, - Annotations: nil, - }, - }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{ - URI: "github.com/test", - Digest: map[string]string{"sha1": "ab123"}, - EntryPoint: "build.yaml", - }, - Parameters: map[string]v1beta1.ParamValue{ - "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskrun"}, - "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, - "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, - }, - Environment: map[string]map[string]string{ - "labels": {"tekton.dev/pipelineTask": "build"}, - }, - }, - Results: []v1beta1.TaskRunResult{ - { - Name: "IMAGE_DIGEST", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, - }, - { - Name: "IMAGE_URL", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "gcr.io/my/image", - }, - }, - }, - }, + {URI: "github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, + {URI: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, + {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + }, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{ + URI: "github.com/test", + Digest: map[string]string{"sha1": "28b123"}, + EntryPoint: "pipeline.yaml", + }, + Parameters: map[string]v1beta1.ParamValue{ + "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, + }, + }, + Builder: common.ProvenanceBuilder{ + ID: "test_builder-1", + }, + BuildType: "tekton.dev/v1beta1/PipelineRun", + BuildConfig: getBuildPipelineRun(), + } + + predicateStruct, err := protos.GetPredicateStruct(predicate) + if err != nil { + t.Fatalf("error getting predicate struct: %v", err) + } + + expected := &intoto.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []*intoto.ResourceDescriptor{ + { + Name: "test.io/test/image", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, }, }, + Predicate: predicateStruct, } tr1, err := objectloader.TaskRunV1Beta1FromFile("../testdata/pipeline-v1beta1/taskrun1.json") @@ -366,7 +253,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, compare.MaterialsCompareOption()); diff != "" { + if diff := cmp.Diff(expected, got, compare.MaterialsCompareOption(), protocmp.Transform()); diff != "" { t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff) } } @@ -382,187 +269,66 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) { ID: "test_builder-1", }, } - expected := in_toto.ProvenanceStatement{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ - { - Name: "test.io/test/image", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, - }, + + predicate := &slsa.ProvenancePredicate{ + Metadata: &slsa.ProvenanceMetadata{ + BuildStartedOn: &e1BuildStart, + BuildFinishedOn: &e1BuildFinished, + Completeness: slsa.ProvenanceComplete{ + Parameters: false, + Environment: false, + Materials: false, }, + Reproducible: false, }, - Predicate: slsa.ProvenancePredicate{ - Metadata: &slsa.ProvenanceMetadata{ - BuildStartedOn: &e1BuildStart, - BuildFinishedOn: &e1BuildFinished, - Completeness: slsa.ProvenanceComplete{ - Parameters: false, - Environment: false, - Materials: false, - }, - Reproducible: false, + Materials: []common.ProvenanceMaterial{ + { + URI: artifacts.OCIScheme + "gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - Materials: []common.ProvenanceMaterial{ - { - URI: artifacts.OCIScheme + "gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - {URI: "github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, - { - URI: artifacts.OCIScheme + "gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: artifacts.OCIScheme + "gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - {URI: "github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, - {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + {URI: "github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, + { + URI: artifacts.OCIScheme + "gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{}, - Parameters: map[string]v1beta1.ParamValue{ - "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, - }, + { + URI: artifacts.OCIScheme + "gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - Builder: common.ProvenanceBuilder{ - ID: "test_builder-1", + {URI: "github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, + {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + }, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{}, + Parameters: map[string]v1beta1.ParamValue{ + "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, }, - BuildType: "tekton.dev/v1beta1/PipelineRun", - BuildConfig: pipelinerun.BuildConfig{ - Tasks: []pipelinerun.TaskAttestation{ - { - Name: "git-clone", - After: nil, - Ref: v1beta1.TaskRef{ - Name: "git-clone", - Kind: "ClusterTask", - }, - StartedOn: e1BuildStart, - FinishedOn: e1BuildFinished, - Status: "Succeeded", - Steps: []attest.StepAttestation{ - { - EntryPoint: "git clone", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "container": "step1", - "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - Annotations: nil, - }, - }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{ - URI: "github.com/catalog", - Digest: common.DigestSet{"sha1": "x123"}, - EntryPoint: "git-clone.yaml", - }, - Parameters: map[string]v1beta1.ParamValue{ - "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskdefault"}, - "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, - "revision": {Type: "string", StringVal: ""}, - "url": {Type: "string", StringVal: "https://git.test.com"}, - }, - Environment: map[string]map[string]string{ - "labels": {"tekton.dev/pipelineTask": "git-clone"}, - }, - }, - Results: []v1beta1.TaskRunResult{ - { - Name: "some-uri_DIGEST", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - }, - { - Name: "some-uri", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "pkg:deb/debian/curl@7.50.3-1", - }, - }, - }, - }, - { - Name: "build", - After: []string{"git-clone"}, - Ref: v1beta1.TaskRef{ - Name: "build", - Kind: "ClusterTask", - }, - StartedOn: e1BuildStart, - FinishedOn: e1BuildFinished, - Status: "Succeeded", - Steps: []attest.StepAttestation{ - { - EntryPoint: "", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - "container": "step1", - }, - Annotations: nil, - }, - { - EntryPoint: "", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "image": artifacts.OCIScheme + "gcr.io/test2/test2@sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", - "container": "step2", - }, - Annotations: nil, - }, - { - EntryPoint: "", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "image": artifacts.OCIScheme + "gcr.io/test3/test3@sha256:f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478", - "container": "step3", - }, - Annotations: nil, - }, - }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{ - URI: "github.com/test", - Digest: map[string]string{"sha1": "ab123"}, - EntryPoint: "build.yaml", - }, - Parameters: map[string]v1beta1.ParamValue{ - "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskrun"}, - "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, - "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, - }, - Environment: map[string]map[string]string{ - "labels": {"tekton.dev/pipelineTask": "build"}, - }, - }, - Results: []v1beta1.TaskRunResult{ - { - Name: "IMAGE_DIGEST", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, - }, - { - Name: "IMAGE_URL", - Value: v1beta1.ParamValue{ - Type: v1beta1.ParamTypeString, - StringVal: "gcr.io/my/image", - }, - }, - }, - }, + }, + Builder: common.ProvenanceBuilder{ + ID: "test_builder-1", + }, + BuildType: "tekton.dev/v1beta1/PipelineRun", + BuildConfig: getBuildPipelineRun(), + } + + predicateStruct, err := protos.GetPredicateStruct(predicate) + if err != nil { + t.Fatalf("error getting predicate struct: %v", err) + } + + expected := &intoto.Statement{ + + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []*intoto.ResourceDescriptor{ + { + Name: "test.io/test/image", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, }, }, + Predicate: predicateStruct, } tr1, err := objectloader.TaskRunV1Beta1FromFile("../testdata/pipeline-v1beta1/taskrun1.json") @@ -583,7 +349,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, compare.MaterialsCompareOption()); diff != "" { + if diff := cmp.Diff(expected, got, compare.MaterialsCompareOption(), protocmp.Transform()); diff != "" { t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff) } } @@ -600,65 +366,71 @@ func TestTaskRunCreatePayload2(t *testing.T) { ID: "test_builder-2", }, } - expected := in_toto.ProvenanceStatement{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: nil, + + predicate := &slsa.ProvenancePredicate{ + Metadata: &slsa.ProvenanceMetadata{ + BuildStartedOn: &e1BuildStart, + BuildFinishedOn: &e1BuildFinished, + }, + Builder: common.ProvenanceBuilder{ + ID: "test_builder-2", }, - Predicate: slsa.ProvenancePredicate{ - Metadata: &slsa.ProvenanceMetadata{ - BuildStartedOn: &e1BuildStart, - BuildFinishedOn: &e1BuildFinished, + Materials: []common.ProvenanceMaterial{ + { + URI: artifacts.OCIScheme + "gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - Builder: common.ProvenanceBuilder{ - ID: "test_builder-2", + {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "sha:taskdefault"}}, + }, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{ + URI: "github.com/catalog", + Digest: common.DigestSet{"sha1": "x123"}, + EntryPoint: "git-clone.yaml", }, - Materials: []common.ProvenanceMaterial{ - { - URI: artifacts.OCIScheme + "gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - {URI: artifacts.GitSchemePrefix + "https://git.test.com.git", Digest: common.DigestSet{"sha1": "sha:taskdefault"}}, + Parameters: map[string]v1beta1.ParamValue{ + "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskdefault"}, + "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, + "revision": {Type: "string"}, + "url": {Type: "string", StringVal: "https://git.test.com"}, }, - Invocation: slsa.ProvenanceInvocation{ - ConfigSource: slsa.ConfigSource{ - URI: "github.com/catalog", - Digest: common.DigestSet{"sha1": "x123"}, - EntryPoint: "git-clone.yaml", - }, - Parameters: map[string]v1beta1.ParamValue{ - "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskdefault"}, - "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, - "revision": {Type: "string"}, - "url": {Type: "string", StringVal: "https://git.test.com"}, - }, - Environment: map[string]map[string]string{ - "labels": {"tekton.dev/pipelineTask": "git-clone"}, - }, + Environment: map[string]map[string]string{ + "labels": {"tekton.dev/pipelineTask": "git-clone"}, }, - BuildType: "tekton.dev/v1beta1/TaskRun", - BuildConfig: taskrun.BuildConfig{ - Steps: []attest.StepAttestation{ - { - EntryPoint: "git clone", - Arguments: []string(nil), - Environment: map[string]interface{}{ - "container": string("step1"), - "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, + }, + BuildType: "tekton.dev/v1beta1/TaskRun", + BuildConfig: taskrun.BuildConfig{ + Steps: []attest.StepAttestation{ + { + EntryPoint: "git clone", + Arguments: []string(nil), + Environment: map[string]interface{}{ + "container": string("step1"), + "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, }, }, }, } + + predicateStruct, err := protos.GetPredicateStruct(predicate) + if err != nil { + t.Fatalf("error getting predicate struct: %v", err) + } + + expected := &intoto.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: nil, + Predicate: predicateStruct, + } i, _ := NewFormatter(cfg) got, err := i.CreatePayload(ctx, objects.NewTaskRunObjectV1Beta1(tr)) if err != nil { t.Errorf("unexpected error: %s", err.Error()) } - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, protocmp.Transform()); diff != "" { t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff) } } @@ -676,51 +448,57 @@ func TestMultipleSubjects(t *testing.T) { ID: "test_builder-multiple", }, } - expected := in_toto.ProvenanceStatement{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + + predicate := &slsa.ProvenancePredicate{ + BuildType: "tekton.dev/v1beta1/TaskRun", + Metadata: &slsa.ProvenanceMetadata{}, + Builder: common.ProvenanceBuilder{ + ID: "test_builder-multiple", + }, + Materials: []common.ProvenanceMaterial{ + { + URI: artifacts.OCIScheme + "gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + }, + Invocation: slsa.ProvenanceInvocation{ + Parameters: map[string]v1beta1.ParamValue{}, + }, + BuildConfig: taskrun.BuildConfig{ + Steps: []attest.StepAttestation{ { - Name: "gcr.io/myimage1", - Digest: common.DigestSet{ - "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - }, { - Name: "gcr.io/myimage2", - Digest: common.DigestSet{ - "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", + Arguments: []string(nil), + Environment: map[string]interface{}{ + "container": string("step1"), + "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, }, }, }, - Predicate: slsa.ProvenancePredicate{ - BuildType: "tekton.dev/v1beta1/TaskRun", - Metadata: &slsa.ProvenanceMetadata{}, - Builder: common.ProvenanceBuilder{ - ID: "test_builder-multiple", - }, - Materials: []common.ProvenanceMaterial{ - { - URI: artifacts.OCIScheme + "gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + } + + predicateStruct, err := protos.GetPredicateStruct(predicate) + if err != nil { + t.Fatalf("error getting predicate struct: %v", err) + } + + expected := &intoto.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/myimage1", + Digest: common.DigestSet{ + "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, - }, - Invocation: slsa.ProvenanceInvocation{ - Parameters: map[string]v1beta1.ParamValue{}, - }, - BuildConfig: taskrun.BuildConfig{ - Steps: []attest.StepAttestation{ - { - Arguments: []string(nil), - Environment: map[string]interface{}{ - "container": string("step1"), - "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - }, + }, { + Name: "gcr.io/myimage2", + Digest: common.DigestSet{ + "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", }, }, }, + Predicate: predicateStruct, } i, _ := NewFormatter(cfg) @@ -728,7 +506,7 @@ func TestMultipleSubjects(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, protocmp.Transform()); diff != "" { t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff) } } @@ -783,3 +561,135 @@ func TestCorrectPayloadType(t *testing.T) { t.Errorf("Invalid type returned: %s", i.Type()) } } + +func getBuildPipelineRun() pipelinerun.BuildConfig { + return pipelinerun.BuildConfig{ + Tasks: []pipelinerun.TaskAttestation{ + { + Name: "git-clone", + After: nil, + Ref: v1beta1.TaskRef{ + Name: "git-clone", + Kind: "ClusterTask", + }, + StartedOn: e1BuildStart, + FinishedOn: e1BuildFinished, + Status: "Succeeded", + Steps: []attest.StepAttestation{ + { + EntryPoint: "git clone", + Arguments: []string(nil), + Environment: map[string]interface{}{ + "container": "step1", + "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + }, + Annotations: nil, + }, + }, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{ + URI: "github.com/catalog", + Digest: common.DigestSet{"sha1": "x123"}, + EntryPoint: "git-clone.yaml", + }, + Parameters: map[string]v1beta1.ParamValue{ + "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskdefault"}, + "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, + "revision": {Type: "string", StringVal: ""}, + "url": {Type: "string", StringVal: "https://git.test.com"}, + }, + Environment: map[string]map[string]string{ + "labels": {"tekton.dev/pipelineTask": "git-clone"}, + }, + }, + Results: []v1beta1.TaskRunResult{ + { + Name: "some-uri_DIGEST", + Value: v1beta1.ParamValue{ + Type: v1beta1.ParamTypeString, + StringVal: "sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + }, + }, + { + Name: "some-uri", + Value: v1beta1.ParamValue{ + Type: v1beta1.ParamTypeString, + StringVal: "pkg:deb/debian/curl@7.50.3-1", + }, + }, + }, + }, + { + Name: "build", + After: []string{"git-clone"}, + Ref: v1beta1.TaskRef{ + Name: "build", + Kind: "ClusterTask", + }, + StartedOn: e1BuildStart, + FinishedOn: e1BuildFinished, + Status: "Succeeded", + Steps: []attest.StepAttestation{ + { + EntryPoint: "", + Arguments: []string(nil), + Environment: map[string]interface{}{ + "image": artifacts.OCIScheme + "gcr.io/test1/test1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + "container": "step1", + }, + Annotations: nil, + }, + { + EntryPoint: "", + Arguments: []string(nil), + Environment: map[string]interface{}{ + "image": artifacts.OCIScheme + "gcr.io/test2/test2@sha256:4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac", + "container": "step2", + }, + Annotations: nil, + }, + { + EntryPoint: "", + Arguments: []string(nil), + Environment: map[string]interface{}{ + "image": artifacts.OCIScheme + "gcr.io/test3/test3@sha256:f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478", + "container": "step3", + }, + Annotations: nil, + }, + }, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{ + URI: "github.com/test", + Digest: map[string]string{"sha1": "ab123"}, + EntryPoint: "build.yaml", + }, + Parameters: map[string]v1beta1.ParamValue{ + "CHAINS-GIT_COMMIT": {Type: "string", StringVal: "sha:taskrun"}, + "CHAINS-GIT_URL": {Type: "string", StringVal: "https://git.test.com"}, + "IMAGE": {Type: "string", StringVal: "test.io/test/image"}, + }, + Environment: map[string]map[string]string{ + "labels": {"tekton.dev/pipelineTask": "build"}, + }, + }, + Results: []v1beta1.TaskRunResult{ + { + Name: "IMAGE_DIGEST", + Value: v1beta1.ParamValue{ + Type: v1beta1.ParamTypeString, + StringVal: "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }, + }, + { + Name: "IMAGE_URL", + Value: v1beta1.ParamValue{ + Type: v1beta1.ParamTypeString, + StringVal: "gcr.io/my/image", + }, + }, + }, + }, + }, + } +} diff --git a/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go b/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go index 6c552cc746..cf258c398c 100644 --- a/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go +++ b/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go @@ -17,13 +17,14 @@ import ( "context" "time" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" "github.com/tektoncd/chains/pkg/chains/formats/slsa/attest" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" materialv1beta1 "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/material/v1beta1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1/internal/protos" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" corev1 "k8s.io/api/core/v1" @@ -47,6 +48,8 @@ type TaskAttestation struct { Results []v1beta1.TaskRunResult `json:"results,omitempty"` } +const statementInTotoV01 = "https://in-toto.io/Statement/v0.1" + func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObjectV1Beta1, slsaConfig *slsaconfig.SlsaConfig) (interface{}, error) { subjects := extract.SubjectDigests(ctx, pro, slsaConfig) @@ -54,22 +57,28 @@ func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObjectV1Be if err != nil { return nil, err } - att := intoto.ProvenanceStatement{ - StatementHeader: intoto.StatementHeader{ - Type: intoto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: subjects, - }, - Predicate: slsa.ProvenancePredicate{ - Builder: common.ProvenanceBuilder{ - ID: slsaConfig.BuilderID, - }, - BuildType: pro.GetGVK(), - Invocation: invocation(pro), - BuildConfig: buildConfig(ctx, pro), - Metadata: metadata(pro), - Materials: mat, + + predicate := &slsa.ProvenancePredicate{ + Builder: common.ProvenanceBuilder{ + ID: slsaConfig.BuilderID, }, + BuildType: pro.GetGVK(), + Invocation: invocation(pro), + BuildConfig: buildConfig(ctx, pro), + Metadata: metadata(pro), + Materials: mat, + } + + predicateStruct, err := protos.GetPredicateStruct(predicate) + if err != nil { + return nil, err + } + + att := &intoto.Statement{ + Type: statementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: subjects, + Predicate: predicateStruct, } return att, nil } diff --git a/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go b/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go index 53489bd430..91ddb0bd41 100644 --- a/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go +++ b/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go @@ -20,7 +20,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-containerregistry/pkg/name" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" "github.com/tektoncd/chains/pkg/artifacts" @@ -31,6 +31,7 @@ import ( "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "google.golang.org/protobuf/testing/protocmp" "k8s.io/apimachinery/pkg/selection" logtesting "knative.dev/pkg/logging/testing" ) @@ -474,7 +475,7 @@ func TestMetadataInTimeZone(t *testing.T) { var ignore = []cmp.Option{cmpopts.IgnoreUnexported(name.Registry{}, name.Repository{}, name.Digest{})} func TestSubjectDigests(t *testing.T) { - wantSubjects := []intoto.Subject{ + wantSubjects := []*intoto.ResourceDescriptor{ { Name: "test.io/test/image", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, @@ -483,7 +484,8 @@ func TestSubjectDigests(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) gotSubjects := extract.SubjectDigests(ctx, pro, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}) - opts := append(ignore, compare.SubjectCompareOption()) + opts := ignore + opts = append(opts, compare.SubjectCompareOption(), protocmp.Transform()) if diff := cmp.Diff(gotSubjects, wantSubjects, opts...); diff != "" { t.Errorf("Differences in subjects: -want +got: %s", diff) } diff --git a/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go b/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go index f36e5ae360..979f342f4e 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go +++ b/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go @@ -22,11 +22,12 @@ import ( "testing" "time" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + "google.golang.org/protobuf/testing/protocmp" "github.com/google/go-cmp/cmp" - "github.com/in-toto/in-toto-golang/in_toto" "github.com/tektoncd/chains/internal/backport" "github.com/tektoncd/chains/pkg/artifacts" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" @@ -294,7 +295,7 @@ func TestGetSubjectDigests(t *testing.T) { }, } - want := []in_toto.Subject{ + want := []*intoto.ResourceDescriptor{ { Name: "com.google.guava:guava:1.0-jre.pom", Digest: common.DigestSet{ @@ -336,7 +337,7 @@ func TestGetSubjectDigests(t *testing.T) { tro := objects.NewTaskRunObjectV1Beta1(tr) got := extract.SubjectDigests(ctx, tro, nil) - if d := cmp.Diff(want, got, compare.SubjectCompareOption()); d != "" { + if d := cmp.Diff(want, got, compare.SubjectCompareOption(), protocmp.Transform()); d != "" { t.Errorf("Wrong subjects extracted, diff=%s", d) } } diff --git a/pkg/chains/formats/slsa/v1/taskrun/taskrun.go b/pkg/chains/formats/slsa/v1/taskrun/taskrun.go index 89b523e171..e1496bca93 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v1/taskrun/taskrun.go @@ -16,17 +16,20 @@ package taskrun import ( "context" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" "github.com/tektoncd/chains/pkg/chains/formats/slsa/attest" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" materialv1beta1 "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/material/v1beta1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1/internal/protos" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" ) +const statementInTotoV01 = "https://in-toto.io/Statement/v0.1" + func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObjectV1Beta1, slsaConfig *slsaconfig.SlsaConfig) (interface{}, error) { subjects := extract.SubjectDigests(ctx, tro, slsaConfig) @@ -34,24 +37,29 @@ func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObjectV1Beta1, if err != nil { return nil, err } - att := intoto.ProvenanceStatement{ - StatementHeader: intoto.StatementHeader{ - Type: intoto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: subjects, - }, - Predicate: slsa.ProvenancePredicate{ - Builder: common.ProvenanceBuilder{ - ID: slsaConfig.BuilderID, - }, - BuildType: tro.GetGVK(), - Invocation: invocation(tro), - BuildConfig: buildConfig(tro), - Metadata: Metadata(tro), - Materials: mat, + + predicate := &slsa.ProvenancePredicate{ + Builder: common.ProvenanceBuilder{ + ID: slsaConfig.BuilderID, }, + BuildType: tro.GetGVK(), + Invocation: invocation(tro), + BuildConfig: buildConfig(tro), + Metadata: Metadata(tro), + Materials: mat, + } + + predicateStruct, err := protos.GetPredicateStruct(predicate) + if err != nil { + return nil, err } - return att, nil + + return &intoto.Statement{ + Type: statementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: subjects, + Predicate: predicateStruct, + }, nil } // invocation describes the event that kicked off the build diff --git a/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun.go b/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun.go index 3c24410991..12f02567e5 100644 --- a/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun.go +++ b/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun.go @@ -18,8 +18,8 @@ import ( "encoding/json" "fmt" - intoto "github.com/in-toto/in-toto-golang/in_toto" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_types" externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" @@ -27,6 +27,9 @@ import ( resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/objects" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" ) const ( @@ -47,50 +50,61 @@ func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObjectV1, return nil, err } - att := intoto.ProvenanceStatementSLSA1{ - StatementHeader: intoto.StatementHeader{ - Type: intoto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: extract.SubjectDigests(ctx, pro, slsaconfig), - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: bd, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: slsaconfig.BuilderID, - }, - BuildMetadata: metadata(pro), - Byproducts: bp, + predicate := &slsa.Provenance{ + BuildDefinition: &bd, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: slsaconfig.BuilderID, }, + Metadata: metadata(pro), + Byproducts: bp, }, } + + predicateStruct := &structpb.Struct{} + predicateJSON, err := protojson.Marshal(predicate) + if err != nil { + return nil, err + } + + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + return nil, err + } + + att := &intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: extract.SubjectDigests(ctx, pro, slsaconfig), + Predicate: predicateStruct, + } return att, nil } -func metadata(pro *objects.PipelineRunObjectV1) slsa.BuildMetadata { - m := slsa.BuildMetadata{ - InvocationID: string(pro.ObjectMeta.UID), +func metadata(pro *objects.PipelineRunObjectV1) *slsa.BuildMetadata { + m := &slsa.BuildMetadata{ + InvocationId: string(pro.ObjectMeta.UID), } if pro.Status.StartTime != nil { utc := pro.Status.StartTime.Time.UTC() - m.StartedOn = &utc + m.StartedOn = timestamppb.New(utc) } if pro.Status.CompletionTime != nil { utc := pro.Status.CompletionTime.Time.UTC() - m.FinishedOn = &utc + m.FinishedOn = timestamppb.New(utc) } return m } // byproducts contains the pipelineRunResults -func byproducts(pro *objects.PipelineRunObjectV1) ([]slsa.ResourceDescriptor, error) { - byProd := []slsa.ResourceDescriptor{} +func byproducts(pro *objects.PipelineRunObjectV1) ([]*intoto.ResourceDescriptor, error) { + byProd := []*intoto.ResourceDescriptor{} for _, key := range pro.Status.Results { content, err := json.Marshal(key.Value) if err != nil { return nil, err } - bp := slsa.ResourceDescriptor{ + bp := &intoto.ResourceDescriptor{ Name: fmt.Sprintf(pipelineRunResults, key.Name), Content: content, MediaType: JsonMediaType, @@ -101,7 +115,7 @@ func byproducts(pro *objects.PipelineRunObjectV1) ([]slsa.ResourceDescriptor, er } // getBuildDefinition get the buildDefinition based on the configured buildType. This will default to the slsa buildType -func getBuildDefinition(ctx context.Context, slsaconfig *slsaconfig.SlsaConfig, pro *objects.PipelineRunObjectV1) (slsa.ProvenanceBuildDefinition, error) { +func getBuildDefinition(ctx context.Context, slsaconfig *slsaconfig.SlsaConfig, pro *objects.PipelineRunObjectV1) (slsa.BuildDefinition, error) { // if buildType is not set in the chains-config, default to slsa build type buildDefinitionType := slsaconfig.BuildType if slsaconfig.BuildType == "" { @@ -112,26 +126,61 @@ func getBuildDefinition(ctx context.Context, slsaconfig *slsaconfig.SlsaConfig, case buildtypes.SlsaBuildType: rd, err := resolveddependencies.PipelineRun(ctx, pro, slsaconfig, resolveddependencies.AddSLSATaskDescriptor) if err != nil { - return slsa.ProvenanceBuildDefinition{}, err + return slsa.BuildDefinition{}, err + } + extParamsStruct, err := getStruct(externalparameters.PipelineRun(pro)) + if err != nil { + return slsa.BuildDefinition{}, err } - return slsa.ProvenanceBuildDefinition{ + + intParamsStruct, err := getStruct(internalparameters.SLSAInternalParameters(pro)) + if err != nil { + return slsa.BuildDefinition{}, err + } + + return slsa.BuildDefinition{ BuildType: buildDefinitionType, - ExternalParameters: externalparameters.PipelineRun(pro), - InternalParameters: internalparameters.SLSAInternalParameters(pro), + ExternalParameters: extParamsStruct, + InternalParameters: intParamsStruct, ResolvedDependencies: rd, }, nil case buildtypes.TektonBuildType: rd, err := resolveddependencies.PipelineRun(ctx, pro, slsaconfig, resolveddependencies.AddTektonTaskDescriptor) if err != nil { - return slsa.ProvenanceBuildDefinition{}, err + return slsa.BuildDefinition{}, err } - return slsa.ProvenanceBuildDefinition{ + extParamsStruct, err := getStruct(externalparameters.PipelineRun(pro)) + if err != nil { + return slsa.BuildDefinition{}, err + } + + intParamsStruct, err := getStruct(internalparameters.TektonInternalParameters(pro)) + if err != nil { + return slsa.BuildDefinition{}, err + } + + return slsa.BuildDefinition{ BuildType: buildDefinitionType, - ExternalParameters: externalparameters.PipelineRun(pro), - InternalParameters: internalparameters.TektonInternalParameters(pro), + ExternalParameters: extParamsStruct, + InternalParameters: intParamsStruct, ResolvedDependencies: rd, }, nil default: - return slsa.ProvenanceBuildDefinition{}, fmt.Errorf("unsupported buildType %v", buildDefinitionType) + return slsa.BuildDefinition{}, fmt.Errorf("unsupported buildType %v", buildDefinitionType) + } +} + +func getStruct(data map[string]any) (*structpb.Struct, error) { + bytes, err := json.Marshal(data) + if err != nil { + return nil, err + } + + protoStruct := &structpb.Struct{} + err = protojson.Unmarshal(bytes, protoStruct) + if err != nil { + return nil, err } + + return protoStruct, nil } diff --git a/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun_test.go b/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun_test.go index 229caf69de..a363b1b86a 100644 --- a/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun_test.go +++ b/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun/pipelinerun_test.go @@ -23,11 +23,9 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "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" - v1resourcedescriptor "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" - "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters" internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" @@ -35,12 +33,16 @@ import ( "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logtesting "knative.dev/pkg/logging/testing" ) func TestMetadata(t *testing.T) { - pr := &v1.PipelineRun{ //nolint:staticcheck + pr := &v1.PipelineRun{ ObjectMeta: metav1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -58,20 +60,20 @@ func TestMetadata(t *testing.T) { } start := time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC) end := time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC) - want := slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &start, - FinishedOn: &end, + want := &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(start), + FinishedOn: timestamppb.New(end), } got := metadata(objects.NewPipelineRunObjectV1(pr)) - if d := cmp.Diff(want, got); d != "" { + if d := cmp.Diff(want, got, protocmp.Transform()); d != "" { t.Fatalf("metadata (-want, +got):\n%s", d) } } func TestMetadataInTimeZone(t *testing.T) { tz := time.FixedZone("Test Time", int((12 * time.Hour).Seconds())) - pr := &v1.PipelineRun{ //nolint:staticcheck + pr := &v1.PipelineRun{ ObjectMeta: metav1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -89,20 +91,20 @@ func TestMetadataInTimeZone(t *testing.T) { } start := time.Date(1995, time.December, 24, 6, 12, 12, 12, tz).UTC() end := time.Date(1995, time.December, 24, 6, 12, 12, 24, tz).UTC() - want := slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &start, - FinishedOn: &end, + want := &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(start), + FinishedOn: timestamppb.New(end), } got := metadata(objects.NewPipelineRunObjectV1(pr)) - if d := cmp.Diff(want, got); d != "" { + if d := cmp.Diff(want, got, protocmp.Transform()); d != "" { t.Fatalf("metadata (-want, +got):\n%s", d) } } func TestByProducts(t *testing.T) { resultValue := v1.ResultValue{Type: "string", StringVal: "result-value"} - pr := &v1.PipelineRun{ //nolint:staticcheck + pr := &v1.PipelineRun{ Status: v1.PipelineRunStatus{ PipelineRunStatusFields: v1.PipelineRunStatusFields{ Results: []v1.PipelineRunResult{ @@ -119,7 +121,7 @@ func TestByProducts(t *testing.T) { if err != nil { t.Fatalf("Could not marshal results: %s", err) } - want := []slsa.ResourceDescriptor{ + want := []*intoto.ResourceDescriptor{ { Name: "pipelineRunResults/result-name", Content: resultBytes, @@ -130,7 +132,7 @@ func TestByProducts(t *testing.T) { if err != nil { t.Fatalf("Could not extract byproducts: %s", err) } - if d := cmp.Diff(want, got); d != "" { + if d := cmp.Diff(want, got, protocmp.Transform()); d != "" { t.Fatalf("byproducts (-want, +got):\n%s", d) } } @@ -161,106 +163,123 @@ func TestGenerateAttestation(t *testing.T) { e1BuildStart := time.Unix(1617011400, 0) e1BuildFinished := time.Unix(1617011415, 0) - want := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + externalParams := map[string]any{ + "runSpec": pr.Spec, + } + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getProtoStruct(t, externalParams), + InternalParameters: getProtoStruct(t, map[string]any{}), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "test.io/test/image", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "28b123"}, + Name: "pipeline", }, - }, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": pr.Spec, + { + Uri: "git+https://github.com/catalog", + Digest: common.DigestSet{"sha1": "x123"}, + Name: "pipelineTask", }, - InternalParameters: map[string]any{}, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "28b123"}, - Name: "pipeline", - }, - { - URI: "git+https://github.com/catalog", - Digest: common.DigestSet{"sha1": "x123"}, - Name: "pipelineTask", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "ab123"}, - Name: "pipelineTask", - }, - { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: "oci://gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - { - URI: "abc", - Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, - Name: "inputs/result", - }, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + { + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-1", + { + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "pipelineTask", }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + { + Uri: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "pipelineRunResults/CHAINS-GIT_COMMIT", - Content: []uint8(`"abcd"`), - MediaType: JsonMediaType, - }, { - Name: "pipelineRunResults/CHAINS-GIT_URL", - Content: []uint8(`"https://git.test.com"`), - MediaType: JsonMediaType, - }, { - Name: "pipelineRunResults/IMAGE_URL", - Content: []uint8(`"test.io/test/image"`), - MediaType: JsonMediaType, - }, { - Name: "pipelineRunResults/IMAGE_DIGEST", - Content: []uint8(`"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"`), - MediaType: JsonMediaType, - }, { - Name: "pipelineRunResults/img-ARTIFACT_INPUTS", - Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7","uri":"abc"}`), - MediaType: JsonMediaType, - }, { - Name: "pipelineRunResults/img2-ARTIFACT_OUTPUTS", - Content: []uint8(`{"digest":"sha256:","uri":"def"}`), - MediaType: JsonMediaType, - }, { - Name: "pipelineRunResults/img_no_uri-ARTIFACT_OUTPUTS", - Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}`), - MediaType: JsonMediaType, - }, + { + Uri: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + }, + { + Uri: "abc", + Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, + Name: "inputs/result", + }, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "abcd"}, + }, + }, + }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-1", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "pipelineRunResults/CHAINS-GIT_COMMIT", + Content: []uint8(`"abcd"`), + MediaType: JsonMediaType, + }, { + Name: "pipelineRunResults/CHAINS-GIT_URL", + Content: []uint8(`"https://git.test.com"`), + MediaType: JsonMediaType, + }, { + Name: "pipelineRunResults/IMAGE_URL", + Content: []uint8(`"test.io/test/image"`), + MediaType: JsonMediaType, + }, { + Name: "pipelineRunResults/IMAGE_DIGEST", + Content: []uint8(`"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"`), + MediaType: JsonMediaType, + }, { + Name: "pipelineRunResults/img-ARTIFACT_INPUTS", + Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7","uri":"abc"}`), + MediaType: JsonMediaType, + }, { + Name: "pipelineRunResults/img2-ARTIFACT_OUTPUTS", + Content: []uint8(`{"digest":"sha256:","uri":"def"}`), + MediaType: JsonMediaType, + }, { + Name: "pipelineRunResults/img_no_uri-ARTIFACT_OUTPUTS", + Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}`), + MediaType: JsonMediaType, }, }, }, } + predicateJSON, err := protojson.Marshal(&slsaPredicate) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + predicateStruct := &structpb.Struct{} + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + want := intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "test.io/test/image", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }, + }, + }, + Predicate: predicateStruct, + } + got, err := GenerateAttestation(ctx, pr, &slsaconfig.SlsaConfig{ BuilderID: "test_builder-1", DeepInspectionEnabled: false, @@ -270,16 +289,17 @@ func TestGenerateAttestation(t *testing.T) { if err != nil { t.Errorf("unwant error: %s", err.Error()) } - if diff := cmp.Diff(want, got, compare.SLSAV1CompareOptions()...); diff != "" { + + if diff := cmp.Diff(&want, got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } -func getResolvedDependencies(addTasks func(*objects.TaskRunObjectV1) (*v1resourcedescriptor.ResourceDescriptor, error)) []v1resourcedescriptor.ResourceDescriptor { //nolint:staticcheck +func getResolvedDependencies(addTasks func(*objects.TaskRunObjectV1) (*intoto.ResourceDescriptor, error)) []*intoto.ResourceDescriptor { pr := createPro("../../../testdata/slsa-v2alpha3/pipelinerun1.json") rd, err := resolveddependencies.PipelineRun(context.Background(), pr, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}, addTasks) if err != nil { - return []v1resourcedescriptor.ResourceDescriptor{} + return nil } return rd } @@ -294,18 +314,18 @@ func TestGetBuildDefinition(t *testing.T) { } tests := []struct { name string - taskContent func(*objects.TaskRunObjectV1) (*v1resourcedescriptor.ResourceDescriptor, error) //nolint:staticcheck + taskContent func(*objects.TaskRunObjectV1) (*intoto.ResourceDescriptor, error) config *slsaconfig.SlsaConfig - want slsa.ProvenanceBuildDefinition + want slsa.BuildDefinition }{ { name: "test slsa build type", taskContent: resolveddependencies.AddSLSATaskDescriptor, config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa"}, - want: slsa.ProvenanceBuildDefinition{ + want: slsa.BuildDefinition{ BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalparameters.PipelineRun(pr), - InternalParameters: internalparameters.SLSAInternalParameters(pr), + ExternalParameters: getProtoStruct(t, externalparameters.PipelineRun(pr)), + InternalParameters: getProtoStruct(t, internalparameters.SLSAInternalParameters(pr)), ResolvedDependencies: getResolvedDependencies(resolveddependencies.AddSLSATaskDescriptor), }, }, @@ -313,10 +333,10 @@ func TestGetBuildDefinition(t *testing.T) { name: "test tekton build type", config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa-tekton"}, taskContent: resolveddependencies.AddSLSATaskDescriptor, - want: slsa.ProvenanceBuildDefinition{ + want: slsa.BuildDefinition{ BuildType: "https://tekton.dev/chains/v2/slsa-tekton", - ExternalParameters: externalparameters.PipelineRun(pr), - InternalParameters: internalparameters.TektonInternalParameters(pr), + ExternalParameters: getProtoStruct(t, externalparameters.PipelineRun(pr)), + InternalParameters: getProtoStruct(t, internalparameters.TektonInternalParameters(pr)), ResolvedDependencies: getResolvedDependencies(resolveddependencies.AddTektonTaskDescriptor), }, }, @@ -324,23 +344,24 @@ func TestGetBuildDefinition(t *testing.T) { name: "test default build type", config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa"}, taskContent: resolveddependencies.AddSLSATaskDescriptor, - want: slsa.ProvenanceBuildDefinition{ + want: slsa.BuildDefinition{ BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalparameters.PipelineRun(pr), - InternalParameters: internalparameters.SLSAInternalParameters(pr), + ExternalParameters: getProtoStruct(t, externalparameters.PipelineRun(pr)), + InternalParameters: getProtoStruct(t, internalparameters.SLSAInternalParameters(pr)), ResolvedDependencies: getResolvedDependencies(resolveddependencies.AddSLSATaskDescriptor), }, }, } - for _, tc := range tests { + for i := range tests { + tc := &tests[i] t.Run(tc.name, func(t *testing.T) { bd, err := getBuildDefinition(context.Background(), tc.config, pr) if err != nil { t.Fatalf("Did not expect an error but got %v", err) } - if diff := cmp.Diff(tc.want, bd); diff != "" { + if diff := cmp.Diff(&tc.want, &bd, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("getBuildDefinition(): -want +got: %v", diff) } }) @@ -354,7 +375,17 @@ func TestUnsupportedBuildType(t *testing.T) { if err == nil { t.Error("getBuildDefinition(): expected error got nil") } - if diff := cmp.Diff(slsa.ProvenanceBuildDefinition{}, got); diff != "" { + if diff := cmp.Diff(&slsa.BuildDefinition{}, &got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("getBuildDefinition(): -want +got: %s", diff) } } + +func getProtoStruct(t *testing.T, data map[string]any) *structpb.Struct { + t.Helper() + protoStruct, err := getStruct(data) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + return protoStruct +} diff --git a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go index f3d56f2772..246a1546f8 100644 --- a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun.go @@ -18,7 +18,7 @@ import ( "encoding/json" "fmt" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" builddefinition "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_definition" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/provenance" @@ -43,23 +43,23 @@ func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObjectV1, slsa sub := extract.SubjectDigests(ctx, tro, slsaConfig) - return provenance.GetSLSA1Statement(tro, sub, bd, bp, slsaConfig), nil + return provenance.GetSLSA1Statement(tro, sub, &bd, bp, slsaConfig) } // byproducts contains the taskRunResults -func byproducts(tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { - byProd := []slsa.ResourceDescriptor{} +func byproducts(tro *objects.TaskRunObjectV1) ([]*intoto.ResourceDescriptor, error) { + byProd := []*intoto.ResourceDescriptor{} for _, key := range tro.Status.Results { content, err := json.Marshal(key.Value) if err != nil { return nil, err } - bp := slsa.ResourceDescriptor{ + bp := intoto.ResourceDescriptor{ Name: fmt.Sprintf(taskRunResults, key.Name), Content: content, MediaType: "application/json", } - byProd = append(byProd, bp) + byProd = append(byProd, &bp) } return byProd, nil } diff --git a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go index 6e3bd7940e..b221a72409 100644 --- a/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go +++ b/pkg/chains/formats/slsa/v2alpha3/internal/taskrun/taskrun_test.go @@ -17,30 +17,31 @@ limitations under the License. package taskrun import ( - "context" "encoding/json" "testing" "time" "github.com/google/go-cmp/cmp" - "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" - v1resourcedescriptor "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" - resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha3/internal/pipelinerun" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/config" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" logtesting "knative.dev/pkg/logging/testing" ) func TestByProducts(t *testing.T) { resultValue := v1.ResultValue{Type: "string", StringVal: "result-value"} - tr := &v1.TaskRun{ //nolint:staticcheck + tr := &v1.TaskRun{ Status: v1.TaskRunStatus{ TaskRunStatusFields: v1.TaskRunStatusFields{ Results: []v1.TaskRunResult{ @@ -57,7 +58,7 @@ func TestByProducts(t *testing.T) { if err != nil { t.Fatalf("Could not marshal results: %s", err) } - want := []slsa.ResourceDescriptor{ + want := []*intoto.ResourceDescriptor{ { Name: "taskRunResults/result-name", Content: resultBytes, @@ -68,7 +69,7 @@ func TestByProducts(t *testing.T) { if err != nil { t.Fatalf("Could not extract byproducts: %s", err) } - if d := cmp.Diff(want, got); d != "" { + if d := cmp.Diff(want, got, cmp.Options{protocmp.Transform()}); d != "" { t.Fatalf("byproducts (-want, +got):\n%s", d) } } @@ -82,6 +83,13 @@ func TestTaskRunGenerateAttestation(t *testing.T) { e1BuildStart := time.Unix(1617011400, 0) e1BuildFinished := time.Unix(1617011415, 0) + externalParams := map[string]any{ + "runSpec": tr.Spec, + } + internalParams := map[string]any{ + "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + } + resultValue := v1.ResultValue{Type: "string", StringVal: "sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"} resultBytesDigest, err := json.Marshal(resultValue) if err != nil { @@ -93,72 +101,83 @@ func TestTaskRunGenerateAttestation(t *testing.T) { t.Fatalf("Could not marshal results: %s", err) } - want := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, externalParams), + InternalParameters: getStruct(t, internalParams), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "gcr.io/my/image", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "task", }, - }, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, + { + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - InternalParameters: map[string]any{ - "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + { + Uri: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "ab123"}, - Name: "task", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: "oci://gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "taskrun"}}, + { + Uri: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + }, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "taskrun"}, }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-1", + }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-1", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "taskRunResults/IMAGE_DIGEST", + Content: resultBytesDigest, + MediaType: pipelinerun.JsonMediaType, }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + { + Name: "taskRunResults/IMAGE_URL", + Content: resultBytesUri, + MediaType: pipelinerun.JsonMediaType, }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "taskRunResults/IMAGE_DIGEST", - Content: resultBytesDigest, - MediaType: pipelinerun.JsonMediaType, - }, - { - Name: "taskRunResults/IMAGE_URL", - Content: resultBytesUri, - MediaType: pipelinerun.JsonMediaType, - }, + }, + }, + } + + predicateJSON, err := protojson.Marshal(&slsaPredicate) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + predicateStruct := &structpb.Struct{} + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + want := intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/my/image", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, }, }, + Predicate: predicateStruct, } got, err := GenerateAttestation(ctx, objects.NewTaskRunObjectV1(tr), &slsaconfig.SlsaConfig{ @@ -169,15 +188,23 @@ func TestTaskRunGenerateAttestation(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, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } -func getResolvedDependencies(tro *objects.TaskRunObjectV1) []v1resourcedescriptor.ResourceDescriptor { - rd, err := resolveddependencies.TaskRun(context.Background(), resolveddependencies.ResolveOptions{}, tro) +func getStruct(t *testing.T, data map[string]any) *structpb.Struct { + t.Helper() + bytes, err := json.Marshal(data) if err != nil { - return []v1resourcedescriptor.ResourceDescriptor{} + t.Fatalf("error getting proto struct: %v", err) } - return rd + + protoStruct := &structpb.Struct{} + err = protojson.Unmarshal(bytes, protoStruct) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + return protoStruct } diff --git a/pkg/chains/formats/slsa/v2alpha3/slsav2_test.go b/pkg/chains/formats/slsa/v2alpha3/slsav2_test.go index 09f3f956a6..6d1de44767 100644 --- a/pkg/chains/formats/slsa/v2alpha3/slsav2_test.go +++ b/pkg/chains/formats/slsa/v2alpha3/slsav2_test.go @@ -22,16 +22,19 @@ 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/v2alpha3/internal/pipelinerun" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/config" "github.com/tektoncd/chains/pkg/internal/objectloader" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/google/go-cmp/cmp" - "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "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" pipelineConfig "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" logtesting "knative.dev/pkg/logging/testing" @@ -77,13 +80,10 @@ func TestCreatePayloadError(t *testing.T) { } if err == nil { t.Errorf("Expected error") - } else { - if err.Error() != "intoto does not support type: not a task ref" { - t.Errorf("wrong error returned: '%s'", err.Error()) - } + } else if err.Error() != "intoto does not support type: not a task ref" { + t.Errorf("wrong error returned: '%s'", err.Error()) } }) - } func TestCorrectPayloadType(t *testing.T) { @@ -117,72 +117,82 @@ func TestTaskRunCreatePayload1(t *testing.T) { ID: "test_builder-1", }, } - expected := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + + extParams := map[string]any{ + "runSpec": tr.Spec, + } + intParams := map[string]any{ + "tekton-pipelines-feature-flags": pipelineConfig.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + } + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, extParams), + InternalParameters: getStruct(t, intParams), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "gcr.io/my/image", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "task", }, - }, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, + { + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + { + Uri: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - InternalParameters: map[string]any{ - "tekton-pipelines-feature-flags": pipelineConfig.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + { + Uri: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "ab123"}, - Name: "task", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: "oci://gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "taskrun"}}, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "taskrun"}, }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-1", + }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-1", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "taskRunResults/IMAGE_DIGEST", + Content: resultBytesDigest, + MediaType: pipelinerun.JsonMediaType, }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + { + Name: "taskRunResults/IMAGE_URL", + Content: resultBytesUri, + MediaType: pipelinerun.JsonMediaType, }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "taskRunResults/IMAGE_DIGEST", - Content: resultBytesDigest, - MediaType: pipelinerun.JsonMediaType, - }, - { - Name: "taskRunResults/IMAGE_URL", - Content: resultBytesUri, - MediaType: pipelinerun.JsonMediaType, - }, + }, + }, + } + + predicateStruct := getPredicateStruct(t, &slsaPredicate) + + expected := &intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/my/image", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, }, }, + Predicate: predicateStruct, } i, _ := NewFormatter(cfg) @@ -192,7 +202,7 @@ func TestTaskRunCreatePayload1(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, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } } @@ -220,64 +230,73 @@ func TestTaskRunCreatePayload2(t *testing.T) { ID: "test_builder-2", }, } - expected := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: nil, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, + + extParams := map[string]any{ + "runSpec": tr.Spec, + } + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, extParams), + InternalParameters: getStruct(t, map[string]any{}), + ResolvedDependencies: []*intoto.ResourceDescriptor{ + { + Uri: "git+https://github.com/catalog", + Digest: common.DigestSet{"sha1": "x123"}, + Name: "task", + }, + { + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - InternalParameters: map[string]any{}, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/catalog", - Digest: common.DigestSet{"sha1": "x123"}, - Name: "task", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "sha:taskdefault"}}, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "sha:taskdefault"}, }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-2", - }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-2", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "taskRunResults/some-uri_DIGEST", + Content: resultBytesDigest, + MediaType: pipelinerun.JsonMediaType, }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "taskRunResults/some-uri_DIGEST", - Content: resultBytesDigest, - MediaType: pipelinerun.JsonMediaType, - }, - { - Name: "taskRunResults/some-uri", - Content: resultBytesUri, - MediaType: pipelinerun.JsonMediaType, - }, + { + Name: "taskRunResults/some-uri", + Content: resultBytesUri, + MediaType: pipelinerun.JsonMediaType, }, }, }, } + predicateStruct := getPredicateStruct(t, &slsaPredicate) + + expected := &intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: nil, + Predicate: predicateStruct, + } + i, _ := NewFormatter(cfg) got, err := i.CreatePayload(ctx, objects.NewTaskRunObjectV1(tr)) if err != nil { t.Errorf("unexpected error: %s", err.Error()) } - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } } @@ -303,52 +322,58 @@ func TestMultipleSubjects(t *testing.T) { ID: "test_builder-multiple", }, } - expected := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + + extParams := map[string]any{ + "runSpec": tr.Spec, + } + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, extParams), + InternalParameters: getStruct(t, map[string]any{}), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "gcr.io/myimage1", - Digest: common.DigestSet{ - "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - }, { - Name: "gcr.io/myimage2", - Digest: common.DigestSet{ - "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", - }, + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, }, }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, - }, - InternalParameters: map[string]any{}, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-multiple", + }, + Metadata: &slsa.BuildMetadata{}, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "taskRunResults/IMAGES", + Content: resultBytes, + MediaType: pipelinerun.JsonMediaType, }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-multiple", + }, + } + + predicateStruct := getPredicateStruct(t, &slsaPredicate) + + expected := &intoto.Statement{ + + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/myimage1", + Digest: common.DigestSet{ + "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, - BuildMetadata: slsa.BuildMetadata{}, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "taskRunResults/IMAGES", - Content: resultBytes, - MediaType: pipelinerun.JsonMediaType, - }, + }, { + Name: "gcr.io/myimage2", + Digest: common.DigestSet{ + "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", }, }, }, + Predicate: predicateStruct, } i, _ := NewFormatter(cfg) @@ -356,7 +381,7 @@ func TestMultipleSubjects(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, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } } @@ -390,104 +415,111 @@ func TestPipelineRunCreatePayload1(t *testing.T) { ID: "test_builder-1", }, } - expected := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, map[string]any{ + "runSpec": pr.Spec, + }), + InternalParameters: getStruct(t, map[string]any{}), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "test.io/test/image", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "28b123"}, + Name: "pipeline", }, - }, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": pr.Spec, + { + Uri: "git+https://github.com/catalog", + Digest: common.DigestSet{"sha1": "x123"}, + Name: "pipelineTask", }, - InternalParameters: map[string]any{}, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "28b123"}, - Name: "pipeline", - }, - { - URI: "git+https://github.com/catalog", - Digest: common.DigestSet{"sha1": "x123"}, - Name: "pipelineTask", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "ab123"}, - Name: "pipelineTask", - }, - { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: "oci://gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - { - URI: "abc", - Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, - Name: "inputs/result", - }, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + { + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-1", + { + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "pipelineTask", + }, + { + Uri: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + { + Uri: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "pipelineRunResults/CHAINS-GIT_COMMIT", - Content: []uint8(`"abcd"`), - MediaType: pipelinerun.JsonMediaType, - }, { - Name: "pipelineRunResults/CHAINS-GIT_URL", - Content: []uint8(`"https://git.test.com"`), - MediaType: pipelinerun.JsonMediaType, - }, { - Name: "pipelineRunResults/IMAGE_URL", - Content: []uint8(`"test.io/test/image"`), - MediaType: pipelinerun.JsonMediaType, - }, { - Name: "pipelineRunResults/IMAGE_DIGEST", - Content: []uint8(`"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"`), - MediaType: pipelinerun.JsonMediaType, - }, { - Name: "pipelineRunResults/img-ARTIFACT_INPUTS", - Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7","uri":"abc"}`), - MediaType: pipelinerun.JsonMediaType, - }, { - Name: "pipelineRunResults/img2-ARTIFACT_OUTPUTS", - Content: []uint8(`{"digest":"sha256:","uri":"def"}`), - MediaType: pipelinerun.JsonMediaType, - }, { - Name: "pipelineRunResults/img_no_uri-ARTIFACT_OUTPUTS", - Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}`), - MediaType: pipelinerun.JsonMediaType, - }, + { + Uri: "abc", + Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, + Name: "inputs/result", + }, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "abcd"}, }, }, }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-1", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "pipelineRunResults/CHAINS-GIT_COMMIT", + Content: []uint8(`"abcd"`), + MediaType: pipelinerun.JsonMediaType, + }, { + Name: "pipelineRunResults/CHAINS-GIT_URL", + Content: []uint8(`"https://git.test.com"`), + MediaType: pipelinerun.JsonMediaType, + }, { + Name: "pipelineRunResults/IMAGE_URL", + Content: []uint8(`"test.io/test/image"`), + MediaType: pipelinerun.JsonMediaType, + }, { + Name: "pipelineRunResults/IMAGE_DIGEST", + Content: []uint8(`"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"`), + MediaType: pipelinerun.JsonMediaType, + }, { + Name: "pipelineRunResults/img-ARTIFACT_INPUTS", + Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7","uri":"abc"}`), + MediaType: pipelinerun.JsonMediaType, + }, { + Name: "pipelineRunResults/img2-ARTIFACT_OUTPUTS", + Content: []uint8(`{"digest":"sha256:","uri":"def"}`), + MediaType: pipelinerun.JsonMediaType, + }, { + Name: "pipelineRunResults/img_no_uri-ARTIFACT_OUTPUTS", + Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}`), + MediaType: pipelinerun.JsonMediaType, + }, + }, + }, + } + + predicateStruct := getPredicateStruct(t, &slsaPredicate) + + expected := &intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "test.io/test/image", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }, + }, + }, + Predicate: predicateStruct, } i, _ := NewFormatter(cfg) @@ -497,7 +529,39 @@ func TestPipelineRunCreatePayload1(t *testing.T) { if err != nil { t.Errorf("unexpected error: %s", err.Error()) } - if diff := cmp.Diff(expected, got, compare.SLSAV1CompareOptions()...); diff != "" { + if diff := cmp.Diff(expected, got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } } + +func getStruct(t *testing.T, data map[string]any) *structpb.Struct { + t.Helper() + bytes, err := json.Marshal(data) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + protoStruct := &structpb.Struct{} + err = protojson.Unmarshal(bytes, protoStruct) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + return protoStruct +} + +func getPredicateStruct(t *testing.T, slsaPredicate *slsa.Provenance) *structpb.Struct { + t.Helper() + predicateJSON, err := protojson.Marshal(slsaPredicate) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + predicateStruct := &structpb.Struct{} + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + return predicateStruct +} diff --git a/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go index dff00786aa..8b41acb0be 100644 --- a/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun.go @@ -16,7 +16,7 @@ package taskrun import ( "context" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" builddefinition "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/build_definition" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/provenance" @@ -47,11 +47,11 @@ func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObjectV1, slsa results := append(tro.GetResults(), tro.GetStepResults()...) sub := extract.SubjectsFromBuildArtifact(ctx, results) - return provenance.GetSLSA1Statement(tro, sub, bd, bp, slsaConfig), nil + return provenance.GetSLSA1Statement(tro, sub, &bd, bp, slsaConfig) } -func byproducts(tro *objects.TaskRunObjectV1) ([]slsa.ResourceDescriptor, error) { - byProd := []slsa.ResourceDescriptor{} +func byproducts(tro *objects.TaskRunObjectV1) ([]*intoto.ResourceDescriptor, error) { + byProd := []*intoto.ResourceDescriptor{} res, err := results.GetResultsWithoutBuildArtifacts(tro.GetResults(), taskRunResults) if err != nil { diff --git a/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go index 46bca6a05a..9938287048 100644 --- a/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go +++ b/pkg/chains/formats/slsa/v2alpha4/internal/taskrun/taskrun_test.go @@ -22,9 +22,9 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "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/slsaconfig" @@ -32,6 +32,10 @@ import ( "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/config" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" logtesting "knative.dev/pkg/logging/testing" ) @@ -39,7 +43,7 @@ const jsonMediaType = "application/json" func TestByProducts(t *testing.T) { resultValue := v1.ResultValue{Type: "string", StringVal: "result-value"} - tr := &v1.TaskRun{ //nolint:staticcheck + tr := &v1.TaskRun{ Status: v1.TaskRunStatus{ TaskRunStatusFields: v1.TaskRunStatusFields{ Results: []v1.TaskRunResult{ @@ -56,7 +60,7 @@ func TestByProducts(t *testing.T) { if err != nil { t.Fatalf("Could not marshal results: %s", err) } - want := []slsa.ResourceDescriptor{ + want := []*intoto.ResourceDescriptor{ { Name: "taskRunResults/result-name", Content: resultBytes, @@ -67,7 +71,7 @@ func TestByProducts(t *testing.T) { if err != nil { t.Fatalf("Could not extract byproducts: %s", err) } - if d := cmp.Diff(want, got); d != "" { + if d := cmp.Diff(want, got, cmp.Options{protocmp.Transform()}); d != "" { t.Fatalf("byproducts (-want, +got):\n%s", d) } } @@ -81,78 +85,87 @@ func TestTaskRunGenerateAttestation(t *testing.T) { e1BuildStart := time.Unix(1617011400, 0) e1BuildFinished := time.Unix(1617011415, 0) - want := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + externalParams := map[string]any{ + "runSpec": tr.Spec, + } + internalParams := map[string]any{ + "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + } + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, externalParams), + InternalParameters: getStruct(t, internalParams), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "gcr.io/my/image/fromstep3", - Digest: common.DigestSet{ - "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", - }, + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "task", }, { - Name: "gcr.io/my/image", - Digest: common.DigestSet{ - "sha256": "d31cc8328054de2bd93735f9cbf0ccfb6e0ee8f4c4225da7d8f8cb3900eaf466", - }, + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - }, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, + { + Uri: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - InternalParameters: map[string]any{ - "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + { + Uri: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "ab123"}, - Name: "task", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: "oci://gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "taskrun"}}, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "taskrun"}, }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-1", + }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-1", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "stepResults/step1_result1", + MediaType: "application/json", + Content: []uint8(`"result-value"`), }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + { + Name: "stepResults/step1_result1-ARTIFACT_OUTPUTS", + MediaType: "application/json", + Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7","uri":"gcr.io/my/image/fromstep2"}`), }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "stepResults/step1_result1", - MediaType: "application/json", - Content: []uint8(`"result-value"`), - }, - { - Name: "stepResults/step1_result1-ARTIFACT_OUTPUTS", - MediaType: "application/json", - Content: []uint8(`{"digest":"sha256:827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7","uri":"gcr.io/my/image/fromstep2"}`), - }, + }, + }, + } + + predicateStruct := getPredicateStruct(t, &slsaPredicate) + + want := intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/my/image/fromstep3", + Digest: common.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }, + }, + { + Name: "gcr.io/my/image", + Digest: common.DigestSet{ + "sha256": "d31cc8328054de2bd93735f9cbf0ccfb6e0ee8f4c4225da7d8f8cb3900eaf466", }, }, }, + Predicate: predicateStruct, } got, err := GenerateAttestation(ctx, objects.NewTaskRunObjectV1(tr), &slsaconfig.SlsaConfig{ @@ -163,7 +176,39 @@ func TestTaskRunGenerateAttestation(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, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } + +func getStruct(t *testing.T, data map[string]any) *structpb.Struct { + t.Helper() + bytes, err := json.Marshal(data) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + protoStruct := &structpb.Struct{} + err = protojson.Unmarshal(bytes, protoStruct) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + return protoStruct +} + +func getPredicateStruct(t *testing.T, slsaPredicate *slsa.Provenance) *structpb.Struct { + t.Helper() + predicateJSON, err := protojson.Marshal(slsaPredicate) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + predicateStruct := &structpb.Struct{} + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + return predicateStruct +} diff --git a/pkg/chains/formats/slsa/v2alpha4/slsav2.go b/pkg/chains/formats/slsa/v2alpha4/slsav2.go index 6080df5119..3db3fbeeff 100644 --- a/pkg/chains/formats/slsa/v2alpha4/slsav2.go +++ b/pkg/chains/formats/slsa/v2alpha4/slsav2.go @@ -42,7 +42,7 @@ type Slsa struct { } // NewFormatter returns a new v2alpha4 payloader. -func NewFormatter(cfg config.Config) (formats.Payloader, error) { +func NewFormatter(cfg config.Config) (formats.Payloader, error) { //nolint:ireturn return &Slsa{ slsaConfig: &slsaconfig.SlsaConfig{ BuilderID: cfg.Builder.ID, diff --git a/pkg/chains/formats/slsa/v2alpha4/slsav2_test.go b/pkg/chains/formats/slsa/v2alpha4/slsav2_test.go index a3df5df51a..3c82def5a9 100644 --- a/pkg/chains/formats/slsa/v2alpha4/slsav2_test.go +++ b/pkg/chains/formats/slsa/v2alpha4/slsav2_test.go @@ -25,11 +25,15 @@ import ( "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/config" "github.com/tektoncd/chains/pkg/internal/objectloader" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/google/go-cmp/cmp" - "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" "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" pipelineConfig "github.com/tektoncd/pipeline/pkg/apis/config" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" logtesting "knative.dev/pkg/logging/testing" @@ -118,76 +122,81 @@ func TestTaskRunCreatePayload1(t *testing.T) { ID: "test_builder-1", }, } - expected := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, map[string]any{ + "runSpec": tr.Spec, + }), + InternalParameters: getStruct(t, map[string]any{ + "tekton-pipelines-feature-flags": pipelineConfig.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + }), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "gcr.io/my/image/fromstep3", - Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, + Uri: "git+https://github.com/test", + Digest: common.DigestSet{"sha1": "ab123"}, + Name: "task", }, { - Name: "gcr.io/my/image", - Digest: common.DigestSet{"sha256": "d31cc8328054de2bd93735f9cbf0ccfb6e0ee8f4c4225da7d8f8cb3900eaf466"}, + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, - }, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, + { + Uri: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, }, - InternalParameters: map[string]any{ - "tekton-pipelines-feature-flags": pipelineConfig.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + { + Uri: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, }, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/test", - Digest: common.DigestSet{"sha1": "ab123"}, - Name: "task", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, - }, - { - URI: "oci://gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, - }, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "taskrun"}}, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "taskrun"}, }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-1", - }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-1", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "stepResults/step1_result1", + Content: resultBytesStepResult, + MediaType: jsonMediaType, }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "stepResults/step1_result1", - Content: resultBytesStepResult, - MediaType: jsonMediaType, - }, - { - Name: "stepResults/step1_result1-ARTIFACT_OUTPUTS", - Content: resultBytesStepResultObj, - MediaType: jsonMediaType, - }, + { + Name: "stepResults/step1_result1-ARTIFACT_OUTPUTS", + Content: resultBytesStepResultObj, + MediaType: jsonMediaType, }, }, }, } + expected := &intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/my/image/fromstep3", + Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}, + }, + { + Name: "gcr.io/my/image", + Digest: common.DigestSet{"sha256": "d31cc8328054de2bd93735f9cbf0ccfb6e0ee8f4c4225da7d8f8cb3900eaf466"}, + }, + }, + Predicate: getPredicateStruct(t, &slsaPredicate), + } + i, _ := NewFormatter(cfg) got, err := i.CreatePayload(ctx, objects.NewTaskRunObjectV1(tr)) @@ -195,7 +204,7 @@ func TestTaskRunCreatePayload1(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, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } } @@ -231,78 +240,79 @@ func TestTaskRunCreatePayload2(t *testing.T) { ID: "test_builder-2", }, } - expected := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: nil, - }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, map[string]any{ + "runSpec": tr.Spec, + }), + InternalParameters: getStruct(t, map[string]any{}), + ResolvedDependencies: []*intoto.ResourceDescriptor{ + { + Uri: "git+https://github.com/catalog", + Digest: common.DigestSet{"sha1": "x123"}, + Name: "task", + }, + { + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + { + Name: "inputs/result", + Uri: "https://github.com/tektoncd/pipeline", + Digest: common.DigestSet{"sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601"}, }, - InternalParameters: map[string]any{}, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "git+https://github.com/catalog", - Digest: common.DigestSet{"sha1": "x123"}, - Name: "task", - }, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, - { - Name: "inputs/result", - URI: "https://github.com/tektoncd/pipeline", - Digest: common.DigestSet{"sha1": "7f2f46e1b97df36b2b82d1b1d87c81b8b3d21601"}, - }, - { - Name: "inputs/result", - URI: "git+https://git.test.com.git", - Digest: common.DigestSet{"sha1": "sha:taskdefault"}, - }, + { + Name: "inputs/result", + Uri: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "sha:taskdefault"}, }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-2", + }, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-2", + }, + Metadata: &slsa.BuildMetadata{ + InvocationId: "abhhf-12354-asjsdbjs23-3435353n", + StartedOn: timestamppb.New(e1BuildStart), + FinishedOn: timestamppb.New(e1BuildFinished), + }, + Byproducts: []*intoto.ResourceDescriptor{ + { + Name: "taskRunResults/some-uri_DIGEST", + Content: resultBytesDigest, + MediaType: jsonMediaType, }, - BuildMetadata: slsa.BuildMetadata{ - InvocationID: "abhhf-12354-asjsdbjs23-3435353n", - StartedOn: &e1BuildStart, - FinishedOn: &e1BuildFinished, + { + Name: "taskRunResults/some-uri", + Content: resultBytesURI, + MediaType: jsonMediaType, }, - Byproducts: []slsa.ResourceDescriptor{ - { - Name: "taskRunResults/some-uri_DIGEST", - Content: resultBytesDigest, - MediaType: jsonMediaType, - }, - { - Name: "taskRunResults/some-uri", - Content: resultBytesURI, - MediaType: jsonMediaType, - }, - { - Name: "stepResults/step1_result1-ARTIFACT_INPUTS", - Content: resultBytesObj, - MediaType: jsonMediaType, - }, + { + Name: "stepResults/step1_result1-ARTIFACT_INPUTS", + Content: resultBytesObj, + MediaType: jsonMediaType, }, }, }, } + expected := &intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: nil, + Predicate: getPredicateStruct(t, &slsaPredicate), + } + i, _ := NewFormatter(cfg) got, err := i.CreatePayload(ctx, objects.NewTaskRunObjectV1(tr)) if err != nil { t.Errorf("unexpected error: %s", err.Error()) } - if diff := cmp.Diff(expected, got); diff != "" { + if diff := cmp.Diff(expected, got, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } } @@ -320,59 +330,60 @@ func TestMultipleSubjects(t *testing.T) { ID: "test_builder-multiple", }, } - expected := in_toto.ProvenanceStatementSLSA1{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ - { - Name: "gcr.io/foo/bar", - Digest: common.DigestSet{ - "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", - }, - }, - { - Name: "gcr.io/myimage2", - Digest: common.DigestSet{ - "sha256": "9f036c6170dd7aba07e45cf2fe414c7ca792e5ede3bc3a78609e3aab4fa2ff2d", - }, - }, - { - Name: "gcr.io/myimage1", - Digest: common.DigestSet{ - "sha256": "db546e77d11cf34199d965d28b1107f98bcbb7630182b7d847cc31d5d21b47b0", - }, - }, + + slsaPredicate := slsa.Provenance{ + BuildDefinition: &slsa.BuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: getStruct(t, map[string]any{ + "runSpec": tr.Spec, + }), + InternalParameters: getStruct(t, map[string]any{}), + ResolvedDependencies: []*intoto.ResourceDescriptor{ { - Name: "gcr.io/myimage3", - Digest: common.DigestSet{ - "sha256": "8d14f5ded713f263742d371279586b264bde42ee8de97b808d1f5e205f376ade", - }, + Uri: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, }, }, }, - Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: map[string]any{ - "runSpec": tr.Spec, + RunDetails: &slsa.RunDetails{ + Builder: &slsa.Builder{ + Id: "test_builder-multiple", + }, + Metadata: &slsa.BuildMetadata{}, + Byproducts: []*intoto.ResourceDescriptor{}, + }, + } + + expected := &intoto.Statement{ + Type: intoto.StatementTypeUri, + PredicateType: "https://slsa.dev/provenance/v1", + Subject: []*intoto.ResourceDescriptor{ + { + Name: "gcr.io/foo/bar", + Digest: common.DigestSet{ + "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, - InternalParameters: map[string]any{}, - ResolvedDependencies: []slsa.ResourceDescriptor{ - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, - }, + }, + { + Name: "gcr.io/myimage2", + Digest: common.DigestSet{ + "sha256": "9f036c6170dd7aba07e45cf2fe414c7ca792e5ede3bc3a78609e3aab4fa2ff2d", + }, + }, + { + Name: "gcr.io/myimage1", + Digest: common.DigestSet{ + "sha256": "db546e77d11cf34199d965d28b1107f98bcbb7630182b7d847cc31d5d21b47b0", }, }, - RunDetails: slsa.ProvenanceRunDetails{ - Builder: slsa.Builder{ - ID: "test_builder-multiple", + { + Name: "gcr.io/myimage3", + Digest: common.DigestSet{ + "sha256": "8d14f5ded713f263742d371279586b264bde42ee8de97b808d1f5e205f376ade", }, - BuildMetadata: slsa.BuildMetadata{}, - Byproducts: []slsa.ResourceDescriptor{}, }, }, + Predicate: getPredicateStruct(t, &slsaPredicate), } i, _ := NewFormatter(cfg) @@ -380,7 +391,39 @@ func TestMultipleSubjects(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, cmp.Options{protocmp.Transform()}); diff != "" { t.Errorf("Slsa.CreatePayload(): -want +got: %s", diff) } } + +func getStruct(t *testing.T, data map[string]any) *structpb.Struct { + t.Helper() + bytes, err := json.Marshal(data) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + protoStruct := &structpb.Struct{} + err = protojson.Unmarshal(bytes, protoStruct) + if err != nil { + t.Fatalf("error getting proto struct: %v", err) + } + + return protoStruct +} + +func getPredicateStruct(t *testing.T, slsaPredicate *slsa.Provenance) *structpb.Struct { + t.Helper() + predicateJSON, err := protojson.Marshal(slsaPredicate) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + predicateStruct := &structpb.Struct{} + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + t.Fatalf("error getting SLSA predicate proto struct: %v", err) + } + + return predicateStruct +} diff --git a/pkg/chains/objects/objects.go b/pkg/chains/objects/objects.go index f20bd93794..0b6169d2b8 100644 --- a/pkg/chains/objects/objects.go +++ b/pkg/chains/objects/objects.go @@ -310,7 +310,7 @@ func (pro *PipelineRunObjectV1) AppendTaskRun(tr *v1.TaskRun) { } // Append TaskRuns to this PipelineRun -func (pro *PipelineRunObjectV1) GetTaskRuns() []*v1.TaskRun { //nolint:staticcheck +func (pro *PipelineRunObjectV1) GetTaskRuns() []*v1.TaskRun { return pro.taskRuns } diff --git a/pkg/chains/storage/grafeas/grafeas_test.go b/pkg/chains/storage/grafeas/grafeas_test.go index 7f9c976daf..57809aee31 100644 --- a/pkg/chains/storage/grafeas/grafeas_test.go +++ b/pkg/chains/storage/grafeas/grafeas_test.go @@ -23,7 +23,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - intoto "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" "github.com/tektoncd/chains/pkg/chains/formats" @@ -34,7 +34,9 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -83,14 +85,12 @@ var ( } // clone taskrun provenance - cloneTaskRunProvenance = intoto.ProvenanceStatement{ - Predicate: slsa.ProvenancePredicate{ - Materials: []common.ProvenanceMaterial{ - { - URI: repoURL, - Digest: common.DigestSet{ - "sha1": commitSHA, - }, + cloneTaskRunPredicate = slsa.ProvenancePredicate{ + Materials: []common.ProvenanceMaterial{ + { + URI: repoURL, + Digest: common.DigestSet{ + "sha1": commitSHA, }, }, }, @@ -119,20 +119,18 @@ var ( } // artifact built taskrun provenance - buildTaskRunProvenance = intoto.ProvenanceStatement{ - StatementHeader: intoto.StatementHeader{ - Subject: []intoto.Subject{ - { - Name: artifactURL1, - Digest: common.DigestSet{ - "sha256": artifactDigest1, - }, + buildTaskRunProvenance = intoto.Statement{ + Subject: []*intoto.ResourceDescriptor{ + { + Name: artifactURL1, + Digest: common.DigestSet{ + "sha256": artifactDigest1, }, - { - Name: artifactURL2, - Digest: common.DigestSet{ - "sha256": artifactDigest2, - }, + }, + { + Name: artifactURL2, + Digest: common.DigestSet{ + "sha256": artifactDigest2, }, }, }, @@ -162,11 +160,8 @@ var ( } // ci pipelinerun provenance - ciPipelineRunProvenance = intoto.ProvenanceStatement{ - StatementHeader: buildTaskRunProvenance.StatementHeader, - Predicate: slsa.ProvenancePredicate{ - Materials: cloneTaskRunProvenance.Predicate.Materials, - }, + ciPipelineRunPredicate = slsa.ProvenancePredicate{ + Materials: cloneTaskRunPredicate.Materials, } ) @@ -257,6 +252,15 @@ func TestBackend_ListOccurrences(t *testing.T) { // - if the StorePayload function can create correct occurrences and store them into grafeas server // - if the RetrievePayloads and RetrieveSignatures functions work properly to fetch correct payloads and signatures func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) { + cloneTaskRunProvenance := intoto.Statement{ + Predicate: getPredicateStruct(t, &cloneTaskRunPredicate), + } + + ciPipelineRunProvenance := intoto.Statement{ + Subject: buildTaskRunProvenance.Subject, + Predicate: getPredicateStruct(t, &ciPipelineRunPredicate), + } + tests := []testConfig{ { name: "intoto for clone taskrun, no error, no occurrences created because no artifacts were built.", @@ -264,7 +268,7 @@ func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) { runObject: &objects.TaskRunObjectV1Beta1{ TaskRun: cloneTaskRun, }, - payload: getRawPayload(t, cloneTaskRunProvenance), + payload: getRawPayload(t, &cloneTaskRunProvenance), signature: "clone taskrun signatures", opts: config.StorageOpts{PayloadFormat: formats.PayloadTypeSlsav1}, }, @@ -277,7 +281,7 @@ func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) { runObject: &objects.TaskRunObjectV1Beta1{ TaskRun: buildTaskRun, }, - payload: getRawPayload(t, buildTaskRunProvenance), + payload: getRawPayload(t, &buildTaskRunProvenance), signature: "build taskrun signature", opts: config.StorageOpts{PayloadFormat: formats.PayloadTypeSlsav1}, }, @@ -298,12 +302,12 @@ func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) { wantErr: false, }, { - name: "intoto for the ci pipeline, no error, 2 occurences should be created for the pipelinerun for the 2 artifact generated.", + name: "intoto for the ci pipeline, no error, 2 occurrences should be created for the pipelinerun for the 2 artifact generated.", args: args{ runObject: &objects.PipelineRunObjectV1Beta1{ PipelineRun: ciPipeline, }, - payload: getRawPayload(t, ciPipelineRunProvenance), + payload: getRawPayload(t, &ciPipelineRunProvenance), signature: "ci pipelinerun signature", opts: config.StorageOpts{PayloadFormat: formats.PayloadTypeSlsav1}, }, @@ -333,10 +337,9 @@ func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) { } defer conn.Close() - // collect all the occurences expected to be created in the server + // collect all the occurrences expected to be created in the server allOccurrencesInServer := []*pb.Occurrence{} for _, test := range tests { - // run the test t.Run(test.name, func(t *testing.T) { ctx := logging.WithLogger(ctx, logtesting.TestLogger(t)) @@ -387,6 +390,7 @@ func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) { // test attestation storage and retrieval func testStoreAndRetrieveHelper(ctx context.Context, t *testing.T, test testConfig, backend Backend) { + t.Helper() if err := backend.StorePayload(ctx, test.args.runObject, test.args.payload, test.args.signature, test.args.opts); (err != nil) != test.wantErr { t.Fatalf("Backend.StorePayload() failed. error:%v, wantErr:%v", err, test.wantErr) } @@ -444,6 +448,7 @@ func testStoreAndRetrieveHelper(ctx context.Context, t *testing.T, test testConf // ------------------ occurrences for taskruns and pipelineruns -------------- // BUILD Occurrence for the build taskrun that stores the slsa provenance func getTaskRunBuildOcc(t *testing.T, identifier string) *pb.Occurrence { + t.Helper() return &pb.Occurrence{ Name: identifier, ResourceUri: identifier, @@ -472,7 +477,7 @@ func getTaskRunBuildOcc(t *testing.T, identifier string) *pb.Occurrence { }, }, Envelope: &pb.Envelope{ - Payload: getRawPayload(t, buildTaskRunProvenance), + Payload: getRawPayload(t, &buildTaskRunProvenance), PayloadType: "application/vnd.in-toto+json", Signatures: []*pb.EnvelopeSignature{ {Sig: []byte("build taskrun signature")}, @@ -483,6 +488,7 @@ func getTaskRunBuildOcc(t *testing.T, identifier string) *pb.Occurrence { // ATTESTATION Occurrence for the build taskrun that stores the image attestation func getTaskRunAttestationOcc(t *testing.T, identifier string) *pb.Occurrence { + t.Helper() return &pb.Occurrence{ Name: identifier, ResourceUri: identifier, @@ -506,6 +512,12 @@ func getTaskRunAttestationOcc(t *testing.T, identifier string) *pb.Occurrence { } func getPipelineRunBuildOcc(t *testing.T, identifier string) *pb.Occurrence { + t.Helper() + ciPipelineRunProvenance := intoto.Statement{ + Subject: buildTaskRunProvenance.Subject, + Predicate: getPredicateStruct(t, &ciPipelineRunPredicate), + } + return &pb.Occurrence{ Name: identifier, ResourceUri: identifier, @@ -540,7 +552,7 @@ func getPipelineRunBuildOcc(t *testing.T, identifier string) *pb.Occurrence { }, }, Envelope: &pb.Envelope{ - Payload: getRawPayload(t, ciPipelineRunProvenance), + Payload: getRawPayload(t, &ciPipelineRunProvenance), PayloadType: "application/vnd.in-toto+json", Signatures: []*pb.EnvelopeSignature{ {Sig: []byte("ci pipelinerun signature")}, @@ -550,6 +562,7 @@ func getPipelineRunBuildOcc(t *testing.T, identifier string) *pb.Occurrence { } func getRawPayload(t *testing.T, in interface{}) []byte { + t.Helper() rawPayload, err := json.Marshal(in) if err != nil { t.Errorf("Unable to marshal the provenance: %v", in) @@ -559,7 +572,7 @@ func getRawPayload(t *testing.T, in interface{}) []byte { // set up the connection between grafeas server and client // and return the client object to the caller -func setupConnection() (*grpc.ClientConn, pb.GrafeasClient, error) { +func setupConnection() (*grpc.ClientConn, pb.GrafeasClient, error) { //nolint:ireturn serv := grpc.NewServer() pb.RegisterGrafeasServer(serv, &mockGrafeasServer{}) @@ -613,7 +626,7 @@ func (s *mockGrafeasServer) CreateOccurrence(ctx context.Context, req *pb.Create occ := req.GetOccurrence() noteName := req.GetOccurrence().NoteName resourceUri := req.GetOccurrence().ResourceUri - occ.Name = resourceUri // mock how the occurrence ID (name) is outputed. + occ.Name = resourceUri // mock how the occurrence ID (name) is outputted. if note, ok := s.entries[noteName]; ok { if _, ok := note.occurrences[resourceUri]; ok { @@ -700,7 +713,6 @@ func (s *mockGrafeasServer) getOccurrencesByFilter(filter string, occurrences [] // mock how uri filter works uris := parseURIFilterString(filter) - // result result result := []*pb.Occurrence{} for _, occ := range occurrences { @@ -735,3 +747,19 @@ func parseURIFilterString(filter string) []string { return results } + +func getPredicateStruct(t *testing.T, predicate *slsa.ProvenancePredicate) *structpb.Struct { + t.Helper() + predicateJSON, err := json.Marshal(predicate) + if err != nil { + t.Fatalf("error getting predicate struct: %v", err) + } + + predicateStruct := &structpb.Struct{} + err = protojson.Unmarshal(predicateJSON, predicateStruct) + if err != nil { + t.Fatalf("error getting predicate struct: %v", err) + } + + return predicateStruct +} diff --git a/pkg/chains/storage/oci/attestation.go b/pkg/chains/storage/oci/attestation.go index 0fd6709f33..5856c6d0fe 100644 --- a/pkg/chains/storage/oci/attestation.go +++ b/pkg/chains/storage/oci/attestation.go @@ -19,7 +19,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/pkg/errors" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" @@ -30,7 +30,7 @@ import ( ) var ( - _ api.Storer[name.Digest, in_toto.Statement] = &AttestationStorer{} + _ api.Storer[name.Digest, *intoto.Statement] = &AttestationStorer{} ) // AttestationStorer stores in-toto Attestation payloads in OCI registries. @@ -52,7 +52,8 @@ func NewAttestationStorer(opts ...AttestationStorerOption) (*AttestationStorer, return s, nil } -func (s *AttestationStorer) Store(ctx context.Context, req *api.StoreRequest[name.Digest, in_toto.Statement]) (*api.StoreResponse, error) { +// Store saves the given statement. +func (s *AttestationStorer) Store(ctx context.Context, req *api.StoreRequest[name.Digest, *intoto.Statement]) (*api.StoreResponse, error) { logger := logging.FromContext(ctx) repo := req.Artifact.Repository diff --git a/pkg/chains/storage/oci/legacy.go b/pkg/chains/storage/oci/legacy.go index 4cb0d05875..fdf355067f 100644 --- a/pkg/chains/storage/oci/legacy.go +++ b/pkg/chains/storage/oci/legacy.go @@ -26,7 +26,7 @@ import ( "knative.dev/pkg/logging" - "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/google/go-containerregistry/pkg/authn/k8schain" @@ -92,7 +92,7 @@ func (b *Backend) StorePayload(ctx context.Context, obj objects.TektonObject, ra } if _, ok := formats.IntotoAttestationSet[storageOpts.PayloadFormat]; ok { - attestation := in_toto.Statement{} + attestation := intoto.Statement{} if err := json.Unmarshal(rawPayload, &attestation); err != nil { return errors.Wrap(err, "unmarshal attestation") } @@ -106,7 +106,7 @@ func (b *Backend) StorePayload(ctx context.Context, obj objects.TektonObject, ra return nil } - return b.uploadAttestation(ctx, attestation, signature, storageOpts, auth) + return b.uploadAttestation(ctx, &attestation, signature, storageOpts, auth) } // Fallback in case unsupported payload format is used or the deprecated "tekton" format @@ -152,7 +152,7 @@ func (b *Backend) uploadSignature(ctx context.Context, format simple.SimpleConta return nil } -func (b *Backend) uploadAttestation(ctx context.Context, attestation in_toto.Statement, signature string, storageOpts config.StorageOpts, remoteOpts ...remote.Option) error { +func (b *Backend) uploadAttestation(ctx context.Context, attestation *intoto.Statement, signature string, storageOpts config.StorageOpts, remoteOpts ...remote.Option) error { logger := logging.FromContext(ctx) // upload an attestation for each subject logger.Info("Starting to upload attestations to OCI ...") @@ -176,7 +176,7 @@ func (b *Backend) uploadAttestation(ctx context.Context, attestation in_toto.Sta } // TODO: make these creation opts. store.remoteOpts = remoteOpts - if _, err := store.Store(ctx, &api.StoreRequest[name.Digest, in_toto.Statement]{ + if _, err := store.Store(ctx, &api.StoreRequest[name.Digest, *intoto.Statement]{ Object: nil, Artifact: ref, Payload: attestation, diff --git a/pkg/chains/storage/oci/oci_test.go b/pkg/chains/storage/oci/oci_test.go index e7e9b01150..5e142297c9 100644 --- a/pkg/chains/storage/oci/oci_test.go +++ b/pkg/chains/storage/oci/oci_test.go @@ -25,11 +25,14 @@ import ( "github.com/tektoncd/chains/pkg/chains/formats/simple" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/config" + "google.golang.org/protobuf/types/known/structpb" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/registry" "github.com/google/go-containerregistry/pkg/v1/remote" + intoto "github.com/in-toto/attestation/go/v1" "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/v0.2" "github.com/sigstore/sigstore/pkg/signature/payload" @@ -84,20 +87,18 @@ func TestBackend_StorePayload(t *testing.T) { }, } - intotoStatement := in_toto.ProvenanceStatement{ - StatementHeader: in_toto.StatementHeader{ - Type: in_toto.StatementInTotoV01, - PredicateType: slsa.PredicateSLSAProvenance, - Subject: []in_toto.Subject{ - { - Name: u.Host + "/task/" + tr.Name, - Digest: common.DigestSet{ - algo: hex, - }, + intotoStatement := &intoto.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []*intoto.ResourceDescriptor{ + { + Name: u.Host + "/task/" + tr.Name, + Digest: common.DigestSet{ + algo: hex, }, }, }, - Predicate: slsa.ProvenancePredicate{}, + Predicate: &structpb.Struct{}, } type fields struct { @@ -145,7 +146,7 @@ func TestBackend_StorePayload(t *testing.T) { object: objects.NewTaskRunObjectV1Beta1(tr), }, args: args{ - payload: in_toto.Statement{}, + payload: intoto.Statement{}, signature: "", storageOpts: config.StorageOpts{ PayloadFormat: formats.PayloadTypeSlsav1, @@ -210,7 +211,7 @@ func TestBackend_StorePayload(t *testing.T) { object: objects.NewPipelineRunObjectV1Beta1(pr), }, args: args{ - payload: in_toto.Statement{}, + payload: intoto.Statement{}, signature: "", storageOpts: config.StorageOpts{ PayloadFormat: formats.PayloadTypeSlsav1, diff --git a/pkg/chains/storage/pubsub/pubsub_test.go b/pkg/chains/storage/pubsub/pubsub_test.go index a7505674ba..9b4eab8741 100644 --- a/pkg/chains/storage/pubsub/pubsub_test.go +++ b/pkg/chains/storage/pubsub/pubsub_test.go @@ -19,7 +19,7 @@ import ( "log" "testing" - "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/formats" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/config" @@ -32,7 +32,10 @@ import ( func TestBackend_StorePayload(t *testing.T) { // pretty much anything that has no Subject - sampleIntotoStatementBytes, _ := json.Marshal(in_toto.Statement{}) + sampleIntotoStatementBytes, err := json.Marshal(intoto.Statement{}) + if err != nil { + t.Fatalf("error getting statement: %v", err) + } logger := logtesting.TestLogger(t) type fields struct { diff --git a/pkg/chains/storage/tekton/tekton.go b/pkg/chains/storage/tekton/tekton.go index 0849032382..f3a7b9c46d 100644 --- a/pkg/chains/storage/tekton/tekton.go +++ b/pkg/chains/storage/tekton/tekton.go @@ -18,7 +18,7 @@ import ( "encoding/base64" "fmt" - "github.com/in-toto/in-toto-golang/in_toto" + intoto "github.com/in-toto/attestation/go/v1" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/chains/signing" "github.com/tektoncd/chains/pkg/chains/storage/api" @@ -59,7 +59,7 @@ func (b *Backend) StorePayload(ctx context.Context, obj objects.TektonObject, ra client: b.pipelineclientset, key: opts.ShortKey, } - if _, err := store.Store(ctx, &api.StoreRequest[objects.TektonObject, *in_toto.Statement]{ + if _, err := store.Store(ctx, &api.StoreRequest[objects.TektonObject, *intoto.Statement]{ Object: obj, Artifact: obj, // We don't actually use payload - we store the raw bundle values directly. @@ -89,7 +89,7 @@ func (b *Backend) retrieveAnnotationValue(ctx context.Context, obj objects.Tekto var annotationValue string annotations, err := obj.GetLatestAnnotations(ctx, b.pipelineclientset) if err != nil { - return "", fmt.Errorf("error retrieving the annotation value for the key %q: %s", annotationKey, err) + return "", fmt.Errorf("error retrieving the annotation value for the key %q: %w", annotationKey, err) } val, ok := annotations[annotationKey] @@ -99,7 +99,7 @@ func (b *Backend) retrieveAnnotationValue(ctx context.Context, obj objects.Tekto if decode { decodedAnnotation, err := base64.StdEncoding.DecodeString(val) if err != nil { - return "", fmt.Errorf("error decoding the annotation value for the key %q: %s", annotationKey, err) + return "", fmt.Errorf("error decoding the annotation value for the key %q: %w", annotationKey, err) } annotationValue = string(decodedAnnotation) } else { @@ -153,11 +153,11 @@ type Storer struct { } var ( - _ api.Storer[objects.TektonObject, *in_toto.Statement] = &Storer{} + _ api.Storer[objects.TektonObject, *intoto.Statement] = &Storer{} ) // Store stores the statement in the TaskRun metadata as an annotation. -func (s *Storer) Store(ctx context.Context, req *api.StoreRequest[objects.TektonObject, *in_toto.Statement]) (*api.StoreResponse, error) { +func (s *Storer) Store(ctx context.Context, req *api.StoreRequest[objects.TektonObject, *intoto.Statement]) (*api.StoreResponse, error) { logger := logging.FromContext(ctx) obj := req.Object diff --git a/test/e2e_test.go b/test/e2e_test.go index 585c47ff95..4e1af98906 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -27,7 +27,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io" "os" "strings" "testing" @@ -37,6 +36,7 @@ import ( "cloud.google.com/go/storage" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + intoto "github.com/in-toto/attestation/go/v1" "github.com/in-toto/in-toto-golang/in_toto" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/sigstore/pkg/cryptoutils" @@ -49,6 +49,7 @@ import ( v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" + "google.golang.org/protobuf/encoding/protojson" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logtesting "knative.dev/pkg/logging/testing" ) @@ -396,6 +397,7 @@ func TestFulcio(t *testing.T) { } func base64Decode(t *testing.T, s string) []byte { + t.Helper() b, err := base64.StdEncoding.DecodeString(s) if err != nil { b, err = base64.URLEncoding.DecodeString(s) @@ -406,17 +408,6 @@ func base64Decode(t *testing.T, s string) []byte { return b } -// DSSE messages contain the signature and payload in one object, but our interface expects a signature and payload -// This means we need to use one field and ignore the other. The DSSE verifier upstream uses the signature field and ignores -// The message field, but we want the reverse here. -type reverseDSSEVerifier struct { - signature.Verifier -} - -func (w *reverseDSSEVerifier) VerifySignature(s io.Reader, m io.Reader, opts ...signature.VerifyOption) error { - return w.Verifier.VerifySignature(m, nil, opts...) -} - func TestOCIStorage(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) c, ns, cleanup := setup(ctx, t, setupOpts{registry: true}) @@ -691,14 +682,6 @@ func getTaskRunObjectV1(ns string) objects.TektonObject { return o } -func getTaskRunObjectV1WithParams(ns string, params []v1.Param) objects.TektonObject { - tr, _ := taskRunFromFile("testdata/type-hinting/taskrun.json") - o := objects.NewTaskRunObjectV1(tr) - o.Namespace = ns - o.Spec.Params = params - return o -} - var imagePipelineRun = v1.PipelineRun{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "image-pipelinerun", @@ -804,11 +787,16 @@ func TestProvenanceMaterials(t *testing.T) { if err != nil { t.Error(err) } - var actualProvenance in_toto.Statement + var actualProvenance intoto.Statement if err := json.Unmarshal(bodyBytes, &actualProvenance); err != nil { t.Error(err) } - predicateBytes, err := json.Marshal(actualProvenance.Predicate) + + predicateBytes, err := protojson.Marshal(actualProvenance.Predicate) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(bodyBytes, &actualProvenance); err != nil { t.Error(err) } diff --git a/test/examples_test.go b/test/examples_test.go index 8dcb2e85ea..334c07cad9 100644 --- a/test/examples_test.go +++ b/test/examples_test.go @@ -39,9 +39,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - intoto "github.com/in-toto/in-toto-golang/in_toto" - slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" - slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + + slsa1 "github.com/in-toto/attestation/go/predicates/provenance/v1" + intoto "github.com/in-toto/attestation/go/v1" + slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/tektoncd/chains/pkg/chains/objects" @@ -172,7 +175,7 @@ func runInTotoFormatterTests(ctx context.Context, t *testing.T, ns string, c *cl // TODO: Commenting this out for now. Causes race condition where tests write and revert the chains-config // and signing-secrets out of order // t.Parallel() - + t.Helper() for path, obj := range test.getExampleObjects(t, ns) { obj := obj t.Run(path, func(t *testing.T) { @@ -192,39 +195,57 @@ func runInTotoFormatterTests(ctx context.Context, t *testing.T, ns string, c *cl if test.predicate == "slsav1.0" { // make sure provenance is correct - var gotProvenance intoto.ProvenanceStatementSLSA1 + var gotProvenance intoto.Statement if err := json.Unmarshal(payload, &gotProvenance); err != nil { t.Fatal(err) } - expected := expectedProvenanceSLSA1(t, ctx, path, completed, test.outputLocation, ns, c) + expected := expectedProvenanceSLSA1(ctx, t, path, completed, test.outputLocation, ns, c) + + expPredicateStruct := expected.Predicate + expected.Predicate = nil + + gotPredicateStruct := gotProvenance.Predicate + gotProvenance.Predicate = nil opts := []cmp.Option{ // Annotations and labels may contain release specific information. Ignore // those to avoid brittle tests. - cmpopts.IgnoreFields(slsa1.ProvenanceBuildDefinition{}, "InternalParameters"), + cmpopts.IgnoreFields(slsa1.BuildDefinition{}, "InternalParameters"), cmpopts.IgnoreMapEntries(ignoreEnvironmentAnnotationsAndLabels), + protocmp.Transform(), } - if diff := cmp.Diff(expected, gotProvenance, opts...); diff != "" { + if diff := cmp.Diff(&expected, &gotProvenance, opts...); diff != "" { t.Errorf("provenance dont match: -want +got: %s", diff) } + + comparePredicates[slsa1.Provenance](t, expPredicateStruct, gotPredicateStruct, opts) } else { - var gotProvenance intoto.ProvenanceStatement + var gotProvenance intoto.Statement if err := json.Unmarshal(payload, &gotProvenance); err != nil { t.Fatal(err) } - expected := expectedProvenance(t, ctx, path, completed, test.outputLocation, ns, c) + expected := expectedProvenance(ctx, t, path, completed, test.outputLocation, ns, c) + + expPredicateStruct := expected.Predicate + expected.Predicate = nil + + gotPredicateStruct := gotProvenance.Predicate + gotProvenance.Predicate = nil opts := []cmp.Option{ // Annotations and labels may contain release specific information. Ignore // those to avoid brittle tests. - cmpopts.IgnoreFields(slsa.ProvenanceInvocation{}, "Environment"), + cmpopts.IgnoreFields(slsa02.ProvenanceInvocation{}, "Environment"), cmpopts.IgnoreMapEntries(ignoreEnvironmentAnnotationsAndLabels), + protocmp.Transform(), } - if diff := cmp.Diff(expected, gotProvenance, opts...); diff != "" { + if diff := cmp.Diff(&expected, &gotProvenance, opts...); diff != "" { t.Errorf("provenance dont match: -want +got: %s", diff) } + + comparePredicates[slsa02.ProvenancePredicate](t, expPredicateStruct, gotPredicateStruct, opts) } // verify signature @@ -271,32 +292,34 @@ func (v *verifier) Public() crypto.PublicKey { return v.pub } -func expectedProvenanceSLSA1(t *testing.T, ctx context.Context, example string, obj objects.TektonObject, outputLocation string, ns string, c *clients) intoto.ProvenanceStatementSLSA1 { +func expectedProvenanceSLSA1(ctx context.Context, t *testing.T, example string, obj objects.TektonObject, outputLocation string, ns string, c *clients) intoto.Statement { + t.Helper() switch obj.(type) { case *objects.TaskRunObjectV1: f := expectedTaskRunProvenanceFormat(t, example, obj, outputLocation) return expectedAttestationSLSA1(t, example, f, outputLocation) case *objects.PipelineRunObjectV1: - f := expectedPipelineRunProvenanceFormat(t, ctx, example, obj, outputLocation, ns, c) + f := expectedPipelineRunProvenanceFormat(ctx, t, obj, ns, c) return expectedAttestationSLSA1(t, example, f, outputLocation) default: t.Error("Unexpected type trying to get provenance") } - return intoto.ProvenanceStatementSLSA1{} + return intoto.Statement{} } -func expectedProvenance(t *testing.T, ctx context.Context, example string, obj objects.TektonObject, outputLocation string, ns string, c *clients) intoto.ProvenanceStatement { +func expectedProvenance(ctx context.Context, t *testing.T, example string, obj objects.TektonObject, outputLocation string, ns string, c *clients) intoto.Statement { + t.Helper() switch obj.(type) { case *objects.TaskRunObjectV1: f := expectedTaskRunProvenanceFormat(t, example, obj, outputLocation) return expectedAttestation(t, example, f, outputLocation) case *objects.PipelineRunObjectV1: - f := expectedPipelineRunProvenanceFormat(t, ctx, example, obj, outputLocation, ns, c) + f := expectedPipelineRunProvenanceFormat(ctx, t, obj, ns, c) return expectedAttestation(t, example, f, outputLocation) default: t.Error("Unexpected type trying to get provenance") } - return intoto.ProvenanceStatement{} + return intoto.Statement{} } type URIDigestPair struct { @@ -317,6 +340,7 @@ type Format struct { } func expectedTaskRunProvenanceFormat(t *testing.T, example string, obj objects.TektonObject, outputLocation string) Format { + t.Helper() tr := obj.GetObject().(*v1.TaskRun) name := tr.Name @@ -352,7 +376,8 @@ func expectedTaskRunProvenanceFormat(t *testing.T, example string, obj objects.T } } -func expectedPipelineRunProvenanceFormat(t *testing.T, ctx context.Context, example string, obj objects.TektonObject, outputLocation string, ns string, c *clients) Format { +func expectedPipelineRunProvenanceFormat(ctx context.Context, t *testing.T, obj objects.TektonObject, ns string, c *clients) Format { + t.Helper() pr := obj.GetObject().(*v1.PipelineRun) buildStartTimes := []string{} @@ -399,17 +424,20 @@ func expectedPipelineRunProvenanceFormat(t *testing.T, ctx context.Context, exam } } -func expectedAttestationSLSA1(t *testing.T, example string, f Format, outputLocation string) intoto.ProvenanceStatementSLSA1 { +func expectedAttestationSLSA1(t *testing.T, example string, f Format, outputLocation string) intoto.Statement { + t.Helper() b := readExpectedAttestationBytes(t, example, f, outputLocation) return readExpectedAttestationSLSA1(t, b) } -func expectedAttestation(t *testing.T, example string, f Format, outputLocation string) intoto.ProvenanceStatement { +func expectedAttestation(t *testing.T, example string, f Format, outputLocation string) intoto.Statement { + t.Helper() b := readExpectedAttestationBytes(t, example, f, outputLocation) return readExpectedAttestation(t, b) } func readExpectedAttestationBytes(t *testing.T, example string, f Format, outputLocation string) *bytes.Buffer { + t.Helper() path := filepath.Join("testdata", outputLocation, strings.Replace(filepath.Base(example), ".yaml", ".json", 1)) t.Logf("Reading expected provenance from %s", path) contents, err := ioutil.ReadFile(path) @@ -429,16 +457,18 @@ func readExpectedAttestationBytes(t *testing.T, example string, f Format, output return b } -func readExpectedAttestationSLSA1(t *testing.T, b *bytes.Buffer) intoto.ProvenanceStatementSLSA1 { - var expected intoto.ProvenanceStatementSLSA1 +func readExpectedAttestationSLSA1(t *testing.T, b *bytes.Buffer) intoto.Statement { + t.Helper() + var expected intoto.Statement if err := json.Unmarshal(b.Bytes(), &expected); err != nil { t.Fatal(err) } return expected } -func readExpectedAttestation(t *testing.T, b *bytes.Buffer) intoto.ProvenanceStatement { - var expected intoto.ProvenanceStatement +func readExpectedAttestation(t *testing.T, b *bytes.Buffer) intoto.Statement { + t.Helper() + var expected intoto.Statement if err := json.Unmarshal(b.Bytes(), &expected); err != nil { t.Fatal(err) } @@ -446,6 +476,7 @@ func readExpectedAttestation(t *testing.T, b *bytes.Buffer) intoto.ProvenanceSta } func getTaskRunExamples(t *testing.T, ns string) map[string]objects.TektonObject { + t.Helper() examples := make(map[string]objects.TektonObject) for _, example := range getExamplePaths(t, taskRunExamplesPath) { examples[example] = taskRunFromExample(t, ns, example) @@ -454,6 +485,7 @@ func getTaskRunExamples(t *testing.T, ns string) map[string]objects.TektonObject } func getTaskRunWithTypeHintedResultsExamples(t *testing.T, ns string) map[string]objects.TektonObject { + t.Helper() path := "../examples/v2alpha4/task-with-object-type-hinting.yaml" trs := make(map[string]objects.TektonObject) trs[path] = taskRunFromExample(t, ns, path) @@ -461,6 +493,7 @@ func getTaskRunWithTypeHintedResultsExamples(t *testing.T, ns string) map[string } func getPipelineRunExamples(t *testing.T, ns string) map[string]objects.TektonObject { + t.Helper() examples := make(map[string]objects.TektonObject) for _, example := range getExamplePaths(t, pipelineRunExamplesPath) { examples[example] = pipelineRunFromExample(t, ns, example) @@ -469,6 +502,7 @@ func getPipelineRunExamples(t *testing.T, ns string) map[string]objects.TektonOb } func getExamplePaths(t *testing.T, dir string) []string { + t.Helper() var examplePaths []string err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -491,6 +525,7 @@ func getExamplePaths(t *testing.T, dir string) []string { } func taskRunFromExample(t *testing.T, ns, example string) objects.TektonObject { + t.Helper() contents, err := ioutil.ReadFile(example) if err != nil { t.Fatal(err) @@ -504,6 +539,7 @@ func taskRunFromExample(t *testing.T, ns, example string) objects.TektonObject { } func pipelineRunFromExample(t *testing.T, ns, example string) objects.TektonObject { + t.Helper() contents, err := ioutil.ReadFile(example) if err != nil { t.Fatal(err) @@ -530,3 +566,32 @@ func ignoreEnvironmentAnnotationsAndLabels(key string, value any) bool { } return false } + +func comparePredicates[T any](t *testing.T, expPredicateStruct, gotPredicateStruct *structpb.Struct, opts []cmp.Option) { + t.Helper() + expJSON, err := expPredicateStruct.MarshalJSON() + if err != nil { + t.Fatalf("error getting predicate json: %v", err) + } + + gotJSON, err := gotPredicateStruct.MarshalJSON() + if err != nil { + t.Fatalf("error getting predicate json: %v", err) + } + + var expectedPredicate T + json.Unmarshal(expJSON, &expectedPredicate) + if err != nil { + t.Fatalf("error getting predicate original struct: %v", err) + } + + var gotPredicate T + json.Unmarshal(gotJSON, &gotPredicate) + if err != nil { + t.Fatalf("error getting predicate original struct: %v", err) + } + + if diff := cmp.Diff(&expectedPredicate, &gotPredicate, opts...); diff != "" { + t.Errorf("predicates dont match: -want +got: %s", diff) + } +} diff --git a/test/test_utils.go b/test/test_utils.go index a798bf278f..e46ca3cd35 100644 --- a/test/test_utils.go +++ b/test/test_utils.go @@ -130,6 +130,7 @@ var simpleTaskRun = v1.TaskRun{ func makeBucket(t *testing.T, client *storage.Client) (string, func()) { // Make a bucket + t.Helper() rand.Seed(time.Now().UnixNano()) testBucketName := fmt.Sprintf("tekton-chains-e2e-%d", rand.Intn(1000)) @@ -161,6 +162,7 @@ func makeBucket(t *testing.T, client *storage.Client) (string, func()) { } func readObj(t *testing.T, bucket, name string, client *storage.Client) io.Reader { + t.Helper() ctx := context.Background() reader, err := client.Bucket(bucket).Object(name).NewReader(ctx) if err != nil { @@ -170,6 +172,7 @@ func readObj(t *testing.T, bucket, name string, client *storage.Client) io.Reade } func setConfigMap(ctx context.Context, t *testing.T, c *clients, data map[string]string) func() { + t.Helper() // Change the config to be GCS storage with this bucket. // Note(rgreinho): This comment does not look right... clean := updateConfigMap(ctx, t, c, data, namespace, "chains-config") @@ -183,6 +186,7 @@ func setConfigMap(ctx context.Context, t *testing.T, c *clients, data map[string } func setupPipelinesFeatureFlags(ctx context.Context, t *testing.T, c *clients, data map[string]string) func() { + t.Helper() pipelinesNs := "tekton-pipelines" clean := updateConfigMap(ctx, t, c, data, pipelinesNs, "feature-flags") @@ -196,6 +200,7 @@ func setupPipelinesFeatureFlags(ctx context.Context, t *testing.T, c *clients, d } func updateConfigMap(ctx context.Context, t *testing.T, c *clients, data map[string]string, ns, configMapName string) func() { + t.Helper() cm, err := c.KubeClient.CoreV1().ConfigMaps(ns).Get(ctx, configMapName, metav1.GetOptions{}) if err != nil { t.Fatal(err) @@ -233,6 +238,7 @@ func updateConfigMap(ctx context.Context, t *testing.T, c *clients, data map[str } func printDebugging(t *testing.T, obj objects.TektonObject) { + t.Helper() kind := obj.GetObjectKind().GroupVersionKind().Kind t.Logf("============================== %s logs ==============================", obj.GetGVK()) @@ -249,6 +255,7 @@ func printDebugging(t *testing.T, obj objects.TektonObject) { } func verifySignature(ctx context.Context, t *testing.T, c *clients, obj objects.TektonObject) { + t.Helper() // Retrieve the configuration. chainsConfig, err := c.KubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, "chains-config", metav1.GetOptions{}) if err != nil { diff --git a/test/testdata/slsa/v1/pipeline-output-image.json b/test/testdata/slsa/v1/pipeline-output-image.json index 2c57b0090b..5a303c06c5 100644 --- a/test/testdata/slsa/v1/pipeline-output-image.json +++ b/test/testdata/slsa/v1/pipeline-output-image.json @@ -1,6 +1,6 @@ { - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v0.2", + "type": "https://in-toto.io/Statement/v0.1", + "predicate_type": "https://slsa.dev/provenance/v0.2", "subject": [ { "name": "gcr.io/foo/bar", diff --git a/test/testdata/slsa/v1/task-output-image.json b/test/testdata/slsa/v1/task-output-image.json index 6f50533549..c816782574 100644 --- a/test/testdata/slsa/v1/task-output-image.json +++ b/test/testdata/slsa/v1/task-output-image.json @@ -1,6 +1,6 @@ { - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v0.2", + "type": "https://in-toto.io/Statement/v0.1", + "predicate_type": "https://slsa.dev/provenance/v0.2", "subject": [ { "name": "gcr.io/foo/bar", diff --git a/test/testdata/slsa/v2/task-output-image.json b/test/testdata/slsa/v2/task-output-image.json index 46bcf76a57..aa4ebf9575 100644 --- a/test/testdata/slsa/v2/task-output-image.json +++ b/test/testdata/slsa/v2/task-output-image.json @@ -1,6 +1,6 @@ { - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v0.2", + "type": "https://in-toto.io/Statement/v0.1", + "predicate_type": "https://slsa.dev/provenance/v0.2", "subject": [ { "name": "gcr.io/foo/bar", diff --git a/test/testdata/slsa/v2alpha3/pipeline-output-image.json b/test/testdata/slsa/v2alpha3/pipeline-output-image.json index 168426c439..74ffc938d4 100644 --- a/test/testdata/slsa/v2alpha3/pipeline-output-image.json +++ b/test/testdata/slsa/v2alpha3/pipeline-output-image.json @@ -1,6 +1,6 @@ { - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v1", + "type": "https://in-toto.io/Statement/v1", + "predicate_type": "https://slsa.dev/provenance/v1", "subject": [ { "name": "gcr.io/foo/bar", @@ -105,7 +105,7 @@ "id": "https://tekton.dev/chains/v2" }, "metadata": { - "invocationID": "{{.UID}}", + "invocationId": "{{.UID}}", "startedOn": "{{.PipelineStartedOn}}", "finishedOn": "{{.PipelineFinishedOn}}" }, diff --git a/test/testdata/slsa/v2alpha3/task-output-image.json b/test/testdata/slsa/v2alpha3/task-output-image.json index 696ce8d528..c8e8956401 100644 --- a/test/testdata/slsa/v2alpha3/task-output-image.json +++ b/test/testdata/slsa/v2alpha3/task-output-image.json @@ -1,6 +1,6 @@ { - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v1", + "type": "https://in-toto.io/Statement/v1", + "predicate_type": "https://slsa.dev/provenance/v1", "subject": [ { "name": "gcr.io/foo/bar", @@ -53,7 +53,7 @@ "id": "https://tekton.dev/chains/v2" }, "metadata": { - "invocationID": "{{.UID}}", + "invocationId": "{{.UID}}", "startedOn": "{{index .BuildStartTimes 0}}", "finishedOn": "{{index .BuildFinishedTimes 0}}" }, diff --git a/test/testdata/slsa/v2alpha4/task-output-image.json b/test/testdata/slsa/v2alpha4/task-output-image.json index 33deb9711d..440ea595f3 100644 --- a/test/testdata/slsa/v2alpha4/task-output-image.json +++ b/test/testdata/slsa/v2alpha4/task-output-image.json @@ -1,6 +1,6 @@ { - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v1", + "type": "https://in-toto.io/Statement/v1", + "predicate_type": "https://slsa.dev/provenance/v1", "subject": [ { "name": "gcr.io/foo/bar", @@ -53,7 +53,7 @@ "id": "https://tekton.dev/chains/v2" }, "metadata": { - "invocationID": "{{.UID}}", + "invocationId": "{{.UID}}", "startedOn": "{{index .BuildStartTimes 0}}", "finishedOn": "{{index .BuildFinishedTimes 0}}" } diff --git a/test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json b/test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json index 0965545364..9f835644a3 100644 --- a/test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json +++ b/test/testdata/slsa/v2alpha4/task-with-object-type-hinting.json @@ -1,6 +1,6 @@ { - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v1", + "type": "https://in-toto.io/Statement/v1", + "predicate_type": "https://slsa.dev/provenance/v1", "subject": [ { "name": "gcr.io/foo/img2", @@ -139,7 +139,7 @@ "id": "https://tekton.dev/chains/v2" }, "metadata": { - "invocationID": "{{.UID}}", + "invocationId": "{{.UID}}", "startedOn": "{{index .BuildStartTimes 0}}", "finishedOn": "{{index .BuildFinishedTimes 0}}" }, diff --git a/vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.go b/vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.go new file mode 100644 index 0000000000..fede805045 --- /dev/null +++ b/vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.go @@ -0,0 +1,138 @@ +/* +Validator APIs for SLSA Provenance v1 protos. +*/ +package v1 + +import ( + "errors" + "fmt" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +// all of the following errors apply to SLSA Build L1 and above +var ( + ErrBuilderRequired = errors.New("runDetails.builder required") + ErrBuilderIdRequired = errors.New("runDetails.builder.id required") + ErrBuildDefinitionRequired = errors.New("buildDefinition required") + ErrBuildTypeRequired = errors.New("buildDefinition.buildType required") + ErrExternalParamsRequired = errors.New("buildDefinition.externalParameters required") + ErrRunDetailsRequired = errors.New("runDetails required") +) + +func (m *BuildMetadata) Validate() error { + // check valid timestamps + s := m.GetStartedOn() + if s != nil { + if err := s.CheckValid(); err != nil { + return fmt.Errorf("buildMetadata.startedOn error: %w", err) + } + } + + f := m.GetFinishedOn() + if f != nil { + if err := f.CheckValid(); err != nil { + return fmt.Errorf("buildMetadata.finishedOn error: %w", err) + } + } + + return nil +} + +func (b *Builder) Validate() error { + // the id field is required for SLSA Build L1 + if b.GetId() == "" { + return ErrBuilderIdRequired + } + + // check that all builderDependencies are valid RDs + builderDeps := b.GetBuilderDependencies() + for i, rd := range builderDeps { + if err := rd.Validate(); err != nil { + return fmt.Errorf("Invalid Builder.BuilderDependencies[%d]: %w", i, err) + } + } + + return nil +} + +func (b *BuildDefinition) Validate() error { + // the buildType field is required for SLSA Build L1 + if b.GetBuildType() == "" { + return ErrBuildTypeRequired + } + + // the externalParameters field is required for SLSA Build L1 + ext := b.GetExternalParameters() + if ext == nil || proto.Equal(ext, &structpb.Struct{}) { + return ErrExternalParamsRequired + } + + // check that all resolvedDependencies are valid RDs + resolvedDeps := b.GetResolvedDependencies() + for i, rd := range resolvedDeps { + if err := rd.Validate(); err != nil { + return fmt.Errorf("Invalid BuildDefinition.ResolvedDependencies[%d]: %w", i, err) + } + } + + return nil +} + +func (r *RunDetails) Validate() error { + // the builder field is required for SLSA Build L1 + builder := r.GetBuilder() + if builder == nil || proto.Equal(builder, &Builder{}) { + return ErrBuilderRequired + } + + // check the Builder + if err := builder.Validate(); err != nil { + return fmt.Errorf("runDetails.builder error: %w", err) + } + + // check the Metadata, if present + metadata := r.GetMetadata() + if metadata != nil && !proto.Equal(metadata, &BuildMetadata{}) { + if err := metadata.Validate(); err != nil { + return fmt.Errorf("Invalid RunDetails.Metadata: %w", err) + } + } + + // check that all byproducts are valid RDs + byproducts := r.GetByproducts() + for i, rd := range byproducts { + if err := rd.Validate(); err != nil { + return fmt.Errorf("Invalid RunDetails.Byproducts[%d]: %w", i, err) + } + } + + return nil +} + +func (p *Provenance) Validate() error { + // the buildDefinition field is required for SLSA Build L1 + buildDef := p.GetBuildDefinition() + if buildDef == nil || proto.Equal(buildDef, &BuildDefinition{}) { + return ErrBuildDefinitionRequired + } + + // check the BuildDefinition + if err := buildDef.Validate(); err != nil { + return fmt.Errorf("provenance.buildDefinition error: %w", err) + } + + // the runDetails field is required for SLSA Build L1 + runDetails := p.GetRunDetails() + if runDetails == nil || proto.Equal(runDetails, &RunDetails{}) { + return ErrRunDetailsRequired + } + + // check the RunDetails + if err := runDetails.Validate(); err != nil { + return fmt.Errorf("provenance.runDetails error: %w", err) + } + + return nil +} diff --git a/vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.pb.go b/vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.pb.go new file mode 100644 index 0000000000..fae536efd2 --- /dev/null +++ b/vendor/github.com/in-toto/attestation/go/predicates/provenance/v1/provenance.pb.go @@ -0,0 +1,577 @@ +// Keep in sync with schema at https://github.com/slsa-framework/slsa/blob/main/docs/provenance/schema/v1/provenance.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.24.4 +// source: in_toto_attestation/predicates/provenance/v1/provenance.proto + +package v1 + +import ( + v1 "github.com/in-toto/attestation/go/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Proto representation of predicate type https://slsa.dev/provenance/v1 +// Validation of all fields is left to the users of this proto. +type Provenance struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BuildDefinition *BuildDefinition `protobuf:"bytes,1,opt,name=build_definition,json=buildDefinition,proto3" json:"build_definition,omitempty"` + RunDetails *RunDetails `protobuf:"bytes,2,opt,name=run_details,json=runDetails,proto3" json:"run_details,omitempty"` +} + +func (x *Provenance) Reset() { + *x = Provenance{} + if protoimpl.UnsafeEnabled { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Provenance) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Provenance) ProtoMessage() {} + +func (x *Provenance) ProtoReflect() protoreflect.Message { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Provenance.ProtoReflect.Descriptor instead. +func (*Provenance) Descriptor() ([]byte, []int) { + return file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescGZIP(), []int{0} +} + +func (x *Provenance) GetBuildDefinition() *BuildDefinition { + if x != nil { + return x.BuildDefinition + } + return nil +} + +func (x *Provenance) GetRunDetails() *RunDetails { + if x != nil { + return x.RunDetails + } + return nil +} + +type BuildDefinition struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BuildType string `protobuf:"bytes,1,opt,name=build_type,json=buildType,proto3" json:"build_type,omitempty"` + ExternalParameters *structpb.Struct `protobuf:"bytes,2,opt,name=external_parameters,json=externalParameters,proto3" json:"external_parameters,omitempty"` + InternalParameters *structpb.Struct `protobuf:"bytes,3,opt,name=internal_parameters,json=internalParameters,proto3" json:"internal_parameters,omitempty"` + ResolvedDependencies []*v1.ResourceDescriptor `protobuf:"bytes,4,rep,name=resolved_dependencies,json=resolvedDependencies,proto3" json:"resolved_dependencies,omitempty"` +} + +func (x *BuildDefinition) Reset() { + *x = BuildDefinition{} + if protoimpl.UnsafeEnabled { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BuildDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BuildDefinition) ProtoMessage() {} + +func (x *BuildDefinition) ProtoReflect() protoreflect.Message { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BuildDefinition.ProtoReflect.Descriptor instead. +func (*BuildDefinition) Descriptor() ([]byte, []int) { + return file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescGZIP(), []int{1} +} + +func (x *BuildDefinition) GetBuildType() string { + if x != nil { + return x.BuildType + } + return "" +} + +func (x *BuildDefinition) GetExternalParameters() *structpb.Struct { + if x != nil { + return x.ExternalParameters + } + return nil +} + +func (x *BuildDefinition) GetInternalParameters() *structpb.Struct { + if x != nil { + return x.InternalParameters + } + return nil +} + +func (x *BuildDefinition) GetResolvedDependencies() []*v1.ResourceDescriptor { + if x != nil { + return x.ResolvedDependencies + } + return nil +} + +type RunDetails struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Builder *Builder `protobuf:"bytes,1,opt,name=builder,proto3" json:"builder,omitempty"` + Metadata *BuildMetadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` + Byproducts []*v1.ResourceDescriptor `protobuf:"bytes,3,rep,name=byproducts,proto3" json:"byproducts,omitempty"` +} + +func (x *RunDetails) Reset() { + *x = RunDetails{} + if protoimpl.UnsafeEnabled { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RunDetails) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunDetails) ProtoMessage() {} + +func (x *RunDetails) ProtoReflect() protoreflect.Message { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunDetails.ProtoReflect.Descriptor instead. +func (*RunDetails) Descriptor() ([]byte, []int) { + return file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescGZIP(), []int{2} +} + +func (x *RunDetails) GetBuilder() *Builder { + if x != nil { + return x.Builder + } + return nil +} + +func (x *RunDetails) GetMetadata() *BuildMetadata { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *RunDetails) GetByproducts() []*v1.ResourceDescriptor { + if x != nil { + return x.Byproducts + } + return nil +} + +type Builder struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Version map[string]string `protobuf:"bytes,2,rep,name=version,proto3" json:"version,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + BuilderDependencies []*v1.ResourceDescriptor `protobuf:"bytes,3,rep,name=builder_dependencies,json=builderDependencies,proto3" json:"builder_dependencies,omitempty"` +} + +func (x *Builder) Reset() { + *x = Builder{} + if protoimpl.UnsafeEnabled { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Builder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Builder) ProtoMessage() {} + +func (x *Builder) ProtoReflect() protoreflect.Message { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Builder.ProtoReflect.Descriptor instead. +func (*Builder) Descriptor() ([]byte, []int) { + return file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescGZIP(), []int{3} +} + +func (x *Builder) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Builder) GetVersion() map[string]string { + if x != nil { + return x.Version + } + return nil +} + +func (x *Builder) GetBuilderDependencies() []*v1.ResourceDescriptor { + if x != nil { + return x.BuilderDependencies + } + return nil +} + +type BuildMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InvocationId string `protobuf:"bytes,1,opt,name=invocation_id,json=invocationId,proto3" json:"invocation_id,omitempty"` + StartedOn *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=started_on,json=startedOn,proto3" json:"started_on,omitempty"` + FinishedOn *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=finished_on,json=finishedOn,proto3" json:"finished_on,omitempty"` +} + +func (x *BuildMetadata) Reset() { + *x = BuildMetadata{} + if protoimpl.UnsafeEnabled { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BuildMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BuildMetadata) ProtoMessage() {} + +func (x *BuildMetadata) ProtoReflect() protoreflect.Message { + mi := &file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BuildMetadata.ProtoReflect.Descriptor instead. +func (*BuildMetadata) Descriptor() ([]byte, []int) { + return file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescGZIP(), []int{4} +} + +func (x *BuildMetadata) GetInvocationId() string { + if x != nil { + return x.InvocationId + } + return "" +} + +func (x *BuildMetadata) GetStartedOn() *timestamppb.Timestamp { + if x != nil { + return x.StartedOn + } + return nil +} + +func (x *BuildMetadata) GetFinishedOn() *timestamppb.Timestamp { + if x != nil { + return x.FinishedOn + } + return nil +} + +var File_in_toto_attestation_predicates_provenance_v1_provenance_proto protoreflect.FileDescriptor + +var file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDesc = []byte{ + 0x0a, 0x3d, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, + 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, + 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x2c, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x30, 0x69, + 0x6e, 0x5f, 0x74, 0x6f, 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, + 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x68, 0x0a, + 0x10, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x74, + 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, + 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, + 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x66, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x66, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x69, + 0x6e, 0x5f, 0x74, 0x6f, 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0a, 0x72, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x22, 0xa5, 0x02, 0x0a, 0x0f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x66, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x48, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x12, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, + 0x48, 0x0a, 0x13, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x5f, 0x0a, 0x15, 0x72, 0x65, 0x73, + 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69, 0x6e, 0x5f, 0x74, 0x6f, + 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x6f, 0x72, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x44, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x22, 0x82, 0x02, 0x0a, 0x0a, 0x52, + 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x4f, 0x0a, 0x07, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x69, 0x6e, 0x5f, + 0x74, 0x6f, 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, + 0x72, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x57, 0x0a, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x69, + 0x6e, 0x5f, 0x74, 0x6f, 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x4a, 0x0a, 0x0a, 0x62, 0x79, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x74, + 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x52, 0x0a, 0x62, 0x79, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x22, + 0x92, 0x02, 0x0a, 0x07, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x5c, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x69, + 0x6e, 0x5f, 0x74, 0x6f, 0x74, 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x65, 0x72, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x5d, 0x0a, 0x14, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x74, + 0x6f, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x52, 0x13, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x44, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xac, 0x01, 0x0a, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, + 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x4f, 0x6e, 0x42, 0x73, 0x0a, 0x35, 0x69, 0x6f, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x69, 0x6e, 0x74, 0x6f, 0x74, 0x6f, 0x2e, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x5a, 0x3a, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x74, 0x6f, + 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, + 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x65, + 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescOnce sync.Once + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescData = file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDesc +) + +func file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescGZIP() []byte { + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescOnce.Do(func() { + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescData = protoimpl.X.CompressGZIP(file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescData) + }) + return file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDescData +} + +var file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_in_toto_attestation_predicates_provenance_v1_provenance_proto_goTypes = []interface{}{ + (*Provenance)(nil), // 0: in_toto_attestation.predicates.provenance.v1.Provenance + (*BuildDefinition)(nil), // 1: in_toto_attestation.predicates.provenance.v1.BuildDefinition + (*RunDetails)(nil), // 2: in_toto_attestation.predicates.provenance.v1.RunDetails + (*Builder)(nil), // 3: in_toto_attestation.predicates.provenance.v1.Builder + (*BuildMetadata)(nil), // 4: in_toto_attestation.predicates.provenance.v1.BuildMetadata + nil, // 5: in_toto_attestation.predicates.provenance.v1.Builder.VersionEntry + (*structpb.Struct)(nil), // 6: google.protobuf.Struct + (*v1.ResourceDescriptor)(nil), // 7: in_toto_attestation.v1.ResourceDescriptor + (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp +} +var file_in_toto_attestation_predicates_provenance_v1_provenance_proto_depIdxs = []int32{ + 1, // 0: in_toto_attestation.predicates.provenance.v1.Provenance.build_definition:type_name -> in_toto_attestation.predicates.provenance.v1.BuildDefinition + 2, // 1: in_toto_attestation.predicates.provenance.v1.Provenance.run_details:type_name -> in_toto_attestation.predicates.provenance.v1.RunDetails + 6, // 2: in_toto_attestation.predicates.provenance.v1.BuildDefinition.external_parameters:type_name -> google.protobuf.Struct + 6, // 3: in_toto_attestation.predicates.provenance.v1.BuildDefinition.internal_parameters:type_name -> google.protobuf.Struct + 7, // 4: in_toto_attestation.predicates.provenance.v1.BuildDefinition.resolved_dependencies:type_name -> in_toto_attestation.v1.ResourceDescriptor + 3, // 5: in_toto_attestation.predicates.provenance.v1.RunDetails.builder:type_name -> in_toto_attestation.predicates.provenance.v1.Builder + 4, // 6: in_toto_attestation.predicates.provenance.v1.RunDetails.metadata:type_name -> in_toto_attestation.predicates.provenance.v1.BuildMetadata + 7, // 7: in_toto_attestation.predicates.provenance.v1.RunDetails.byproducts:type_name -> in_toto_attestation.v1.ResourceDescriptor + 5, // 8: in_toto_attestation.predicates.provenance.v1.Builder.version:type_name -> in_toto_attestation.predicates.provenance.v1.Builder.VersionEntry + 7, // 9: in_toto_attestation.predicates.provenance.v1.Builder.builder_dependencies:type_name -> in_toto_attestation.v1.ResourceDescriptor + 8, // 10: in_toto_attestation.predicates.provenance.v1.BuildMetadata.started_on:type_name -> google.protobuf.Timestamp + 8, // 11: in_toto_attestation.predicates.provenance.v1.BuildMetadata.finished_on:type_name -> google.protobuf.Timestamp + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_in_toto_attestation_predicates_provenance_v1_provenance_proto_init() } +func file_in_toto_attestation_predicates_provenance_v1_provenance_proto_init() { + if File_in_toto_attestation_predicates_provenance_v1_provenance_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Provenance); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BuildDefinition); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RunDetails); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Builder); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BuildMetadata); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_in_toto_attestation_predicates_provenance_v1_provenance_proto_goTypes, + DependencyIndexes: file_in_toto_attestation_predicates_provenance_v1_provenance_proto_depIdxs, + MessageInfos: file_in_toto_attestation_predicates_provenance_v1_provenance_proto_msgTypes, + }.Build() + File_in_toto_attestation_predicates_provenance_v1_provenance_proto = out.File + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_rawDesc = nil + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_goTypes = nil + file_in_toto_attestation_predicates_provenance_v1_provenance_proto_depIdxs = nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7f1efa1922..d5a4bb1f46 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1114,6 +1114,7 @@ github.com/hexops/gotextdiff/span github.com/imdario/mergo # github.com/in-toto/attestation v1.0.1 ## explicit; go 1.20 +github.com/in-toto/attestation/go/predicates/provenance/v1 github.com/in-toto/attestation/go/v1 # github.com/in-toto/in-toto-golang v0.9.1-0.20240317085821-8e2966059a09 ## explicit; go 1.20