From c7cb7b60a85c44c18e2bf60a6ca9011923fb0bf6 Mon Sep 17 00:00:00 2001 From: Chuang Wang Date: Tue, 18 Jul 2023 07:36:21 -0700 Subject: [PATCH] Smarter Chains: check taskrun level results for Subjects Step 1/2 of https://github.com/tektoncd/chains/issues/850 Prior, Chains only looks for pipeline results to understand what artifacts were generated in a pipeline. That means pipeline authors need to name pipeline results in the type hinting way and propagate its value with individual TaskRun results. Now, Chains is able to dive into individual TaskRun results to understand what artifacts were generated throughout a pipeline. This way, pipeline authors no longer need to worry about the rules when writting a pipeline as long as they pull in right tasks that produce type hinting results. That said, the old behaviour - observing pipeline level results is reserved by introducing a configmap field `artifacts.pipelinerun.observe-mode` which allows configuring how chains observes the outputs. Signed-off-by: Chuang Wang --- docs/config.md | 6 +- pkg/chains/formats/slsa/extract/extract.go | 96 ++++++++++- .../formats/slsa/extract/extract_test.go | 156 +++++++++++++++++- .../slsa/internal/slsaconfig/slsaconfig.go | 2 + .../testdata/taskrun-multiple-subjects.json | 2 +- .../v2alpha2/taskrun-multiple-subjects.json | 2 +- pkg/chains/formats/slsa/v1/intotoite6.go | 3 +- pkg/chains/formats/slsa/v1/intotoite6_test.go | 18 +- .../slsa/v1/pipelinerun/pipelinerun.go | 2 +- .../slsa/v1/pipelinerun/provenance_test.go | 3 +- .../slsa/v1/taskrun/provenance_test.go | 2 +- pkg/chains/formats/slsa/v1/taskrun/taskrun.go | 2 +- .../formats/slsa/v2alpha1/slsav2_test.go | 6 +- .../formats/slsa/v2alpha1/taskrun/taskrun.go | 2 +- .../slsa/v2alpha1/taskrun/taskrun_test.go | 2 +- .../internal/pipelinerun/pipelinerun.go | 2 +- .../internal/pipelinerun/pipelinerun_test.go | 5 +- .../slsa/v2alpha2/internal/taskrun/taskrun.go | 5 +- pkg/chains/formats/slsa/v2alpha2/slsav2.go | 3 +- .../formats/slsa/v2alpha2/slsav2_test.go | 13 +- pkg/chains/storage/grafeas/grafeas.go | 4 +- pkg/chains/storage/grafeas/grafeas_test.go | 9 +- pkg/config/config.go | 10 +- pkg/config/store_test.go | 16 +- 24 files changed, 321 insertions(+), 50 deletions(-) diff --git a/docs/config.md b/docs/config.md index 18f797641f..b65035fed7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -64,9 +64,11 @@ Supported keys include: | `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`| `in-toto` | | `artifacts.pipelinerun.storage` | The storage backend to store `PipelineRun` signatures in. Multiple backends can be specified with comma-separated list ("tekton,oci"). To disable the `PipelineRun` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `tekton` | | `artifacts.pipelinerun.signer` | The signature backend to sign `PipelineRun` payloads with. | `x509`, `kms` | `x509` | +| `artifacts.pipelinerun.observe-mode` | The way that Chains observes inputs & outputs of a PipelineRun. The default option `pr` configures Chains to only inspect Pipeline level params/results, whereas the option `tr` configures Chains to dive into child TaskRuns. | `pr`, `tr` | `pr` | -> NOTE: For grafeas storage backend, currently we only support Container Analysis. We will make grafeas server address configurabe within a short time. -> NOTE: `slsa/v1` is an alias of `in-toto` for backwards compatibility. +> NOTE: +> - For grafeas storage backend, currently we only support Container Analysis. We will make grafeas server address configurabe within a short time. +> - `slsa/v1` is an alias of `in-toto` for backwards compatibility. ### OCI Configuration diff --git a/pkg/chains/formats/slsa/extract/extract.go b/pkg/chains/formats/slsa/extract/extract.go index e01fefcb1e..ec8d59fbf4 100644 --- a/pkg/chains/formats/slsa/extract/extract.go +++ b/pkg/chains/formats/slsa/extract/extract.go @@ -27,6 +27,7 @@ import ( "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" "github.com/tektoncd/chains/internal/backport" "github.com/tektoncd/chains/pkg/artifacts" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "knative.dev/pkg/logging" @@ -39,7 +40,92 @@ import ( // - have suffix `IMAGE_URL` & `IMAGE_DIGEST` or `ARTIFACT_URI` & `ARTIFACT_DIGEST` pair. // - the `*_DIGEST` field must be in the format of ":" where the algorithm must be "sha256" and actual sha must be valid per https://github.com/opencontainers/image-spec/blob/main/descriptor.md#sha-256. // - the `*_URL` or `*_URI` fields cannot be empty. -func SubjectDigests(ctx context.Context, obj objects.TektonObject) []intoto.Subject { +func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject { + var subjects []intoto.Subject + + switch obj.GetObject().(type) { + case *v1beta1.PipelineRun: + subjects = subjectsFromPipelineRun(ctx, obj, slsaconfig) + case *v1beta1.TaskRun: + subjects = subjectsFromTektonObject(ctx, obj) + } + + sort.Slice(subjects, func(i, j int) bool { + return subjects[i].Name <= subjects[j].Name + }) + + return subjects +} + +func subjectsFromPipelineRun(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject { + logger := logging.FromContext(ctx) + // If the configured input/output observation mode is pipeline level, then + // call the generic function to parse the subject. + if slsaconfig.PrObserveMode == "pr" { + return subjectsFromTektonObject(ctx, obj) + } + + // If the configured input/output observation mode is task level, then dive into + // individual taskruns and collect subjects. + + var result []intoto.Subject + + pro := obj.(*objects.PipelineRunObject) + + pSpec := pro.Status.PipelineSpec + if pSpec != nil { + pipelineTasks := append(pSpec.Tasks, pSpec.Finally...) + for _, t := range pipelineTasks { + tr := pro.GetTaskRunFromTask(t.Name) + // Ignore Tasks that did not execute during the PipelineRun. + if tr == nil || tr.Status.CompletionTime == nil { + logger.Infof("taskrun status not found for task %s", t.Name) + continue + } + + trSubjects := subjectsFromTektonObject(ctx, objects.NewTaskRunObject(tr)) + for _, s := range trSubjects { + result = addSubject(result, s) + } + } + } + + return result +} + +// addSubject adds a new subject item to the original slice. +func addSubject(original []intoto.Subject, item intoto.Subject) []intoto.Subject { + + for i, s := range original { + // if there is an equivalent entry in the original slice, do nothing or replace + // the original entry with the item if the item has more rich digest set. + if subjectEqual(s, item) { + if len(s.Digest) < len(item.Digest) { + original[i] = item + } + return original + } + } + + original = append(original, item) + return original +} + +// two subjects are equal if and only if they have same name and have at least +// one common algorithm and hex value. +func subjectEqual(x, y intoto.Subject) bool { + if x.Name != y.Name { + return false + } + for algo, hex := range x.Digest { + if v, ok := y.Digest[algo]; ok && v == hex { + return true + } + } + return false +} + +func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []intoto.Subject { logger := logging.FromContext(ctx) var subjects []intoto.Subject @@ -121,9 +207,7 @@ func SubjectDigests(ctx context.Context, obj objects.TektonObject) []intoto.Subj }) } } - sort.Slice(subjects, func(i, j int) bool { - return subjects[i].Name <= subjects[j].Name - }) + return subjects } @@ -131,9 +215,9 @@ func SubjectDigests(ctx context.Context, obj objects.TektonObject) []intoto.Subj // - It first extracts intoto subjects from run object results and converts the subjects // to a slice of string URIs in the format of "NAME" + "@" + "ALGORITHM" + ":" + "DIGEST". // - If no subjects could be extracted from results, then an empty slice is returned. -func RetrieveAllArtifactURIs(ctx context.Context, obj objects.TektonObject) []string { +func RetrieveAllArtifactURIs(ctx context.Context, obj objects.TektonObject, observeMode string) []string { result := []string{} - subjects := SubjectDigests(ctx, obj) + subjects := SubjectDigests(ctx, obj, &slsaconfig.SlsaConfig{PrObserveMode: observeMode}) for _, s := range subjects { for algo, digest := range s.Digest { diff --git a/pkg/chains/formats/slsa/extract/extract_test.go b/pkg/chains/formats/slsa/extract/extract_test.go index 0b5a576a5e..c08dabe8b9 100644 --- a/pkg/chains/formats/slsa/extract/extract_test.go +++ b/pkg/chains/formats/slsa/extract/extract_test.go @@ -19,13 +19,16 @@ package extract_test import ( "fmt" "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" intoto "github.com/in-toto/in-toto-golang/in_toto" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" + "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" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logtesting "knative.dev/pkg/logging/testing" ) @@ -102,16 +105,15 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { // test both taskrun object and pipelinerun object runObjects := []objects.TektonObject{ createTaskRunObjectWithResults(tc.results), - createPipelineRunObjectWithResults(tc.results), + createPipelineRunObjectWithPipelineResults(tc.results), } - for _, o := range runObjects { - gotSubjects := extract.SubjectDigests(ctx, o) + gotSubjects := extract.SubjectDigests(ctx, o, &slsaconfig.SlsaConfig{PrObserveMode: "pr"}) if diff := cmp.Diff(tc.wantSubjects, gotSubjects, cmpopts.SortSlices(func(x, y intoto.Subject) bool { return x.Name < y.Name })); diff != "" { - t.Errorf("Wrong subjects extracted, diff=%s", diff) + t.Errorf("Wrong subjects extracted, diff=%s, %s", diff, gotSubjects) } - gotURIs := extract.RetrieveAllArtifactURIs(ctx, o) + gotURIs := extract.RetrieveAllArtifactURIs(ctx, o, "pr") if diff := cmp.Diff(tc.wantFullURLs, gotURIs, cmpopts.SortSlices(func(x, y string) bool { return x < y })); diff != "" { t.Errorf("Wrong URIs extracted, diff=%s", diff) } @@ -121,6 +123,107 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { } } +func TestPipelineRunObserveModeForSubjects(t *testing.T) { + var tests = []struct { + name string + pro objects.TektonObject + observeMode string + wantSubjects []intoto.Subject + wantFullURLs []string + }{ + { + name: "observe mode: pr", + pro: createPipelineRunObjectWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), + observeMode: "pr", + wantSubjects: []intoto.Subject{ + { + Name: artifactURL1, + Digest: map[string]string{ + "sha256": artifactDigest1, + }, + }, + }, + wantFullURLs: []string{fmt.Sprintf("%s@sha256:%s", artifactURL1, artifactDigest1)}, + }, + { + name: "observe mode: tr, no duplication", + pro: createPipelineRunObjectWithTaskRunResults([]artifact{{uri: artifactURL2, digest: "sha256:" + artifactDigest2}}), + observeMode: "tr", + wantSubjects: []intoto.Subject{ + { + Name: artifactURL2, + Digest: map[string]string{ + "sha256": artifactDigest2, + }, + }, + }, + wantFullURLs: []string{fmt.Sprintf("%s@sha256:%s", artifactURL2, artifactDigest2)}, + }, + { + name: "observe mode: tr - same uri with different sha256 digests", + pro: createPipelineRunObjectWithTaskRunResults([]artifact{ + {uri: artifactURL2, digest: "sha256:" + artifactDigest1}, + {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, + }), + observeMode: "tr", + wantSubjects: []intoto.Subject{ + { + Name: artifactURL2, + Digest: map[string]string{ + "sha256": artifactDigest2, + }, + }, + { + Name: artifactURL2, + Digest: map[string]string{ + "sha256": artifactDigest1, + }, + }, + }, + wantFullURLs: []string{ + fmt.Sprintf("%s@sha256:%s", artifactURL2, artifactDigest1), + fmt.Sprintf("%s@sha256:%s", artifactURL2, artifactDigest2), + }, + }, + { + name: "observe mode: tr - same uri with same sha256 digests", + pro: createPipelineRunObjectWithTaskRunResults([]artifact{ + {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, + {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, + }), + observeMode: "tr", + wantSubjects: []intoto.Subject{ + { + Name: artifactURL2, + Digest: map[string]string{ + "sha256": artifactDigest2, + }, + }, + }, + wantFullURLs: []string{ + fmt.Sprintf("%s@sha256:%s", artifactURL2, artifactDigest2), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + // test both taskrun object and pipelinerun object + + gotSubjects := extract.SubjectDigests(ctx, tc.pro, &slsaconfig.SlsaConfig{PrObserveMode: tc.observeMode}) + if diff := cmp.Diff(tc.wantSubjects, gotSubjects, cmpopts.SortSlices(func(x, y intoto.Subject) bool { return x.Name < y.Name })); diff != "" { + t.Errorf("Wrong subjects extracted, diff=%s, %s", diff, gotSubjects) + } + + gotURIs := extract.RetrieveAllArtifactURIs(ctx, tc.pro, tc.observeMode) + if diff := cmp.Diff(tc.wantFullURLs, gotURIs, cmpopts.SortSlices(func(x, y string) bool { return x < y })); diff != "" { + t.Errorf("Wrong URIs extracted, diff=%s", diff) + } + }) + } +} + func createTaskRunObjectWithResults(results map[string]string) objects.TektonObject { trResults := []v1beta1.TaskRunResult{} prefix := 0 @@ -143,7 +246,7 @@ func createTaskRunObjectWithResults(results map[string]string) objects.TektonObj ) } -func createPipelineRunObjectWithResults(results map[string]string) objects.TektonObject { +func createPipelineRunObjectWithPipelineResults(results map[string]string) objects.TektonObject { prResults := []v1beta1.PipelineRunResult{} prefix := 0 for url, digest := range results { @@ -164,3 +267,44 @@ func createPipelineRunObjectWithResults(results map[string]string) objects.Tekto }, ) } + +type artifact struct { + uri string + digest string +} + +// create a child taskrun for each result +func createPipelineRunObjectWithTaskRunResults(results []artifact) objects.TektonObject { + pro := objects.NewPipelineRunObject(&v1beta1.PipelineRun{ + Status: v1beta1.PipelineRunStatus{ + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + PipelineSpec: &v1beta1.PipelineSpec{}, + }, + }, + }) + + // create child taskruns with results and pipelinetask + prefix := 0 + for _, r := range results { + // simulate child taskruns + pipelineTaskName := fmt.Sprintf("task-%d", prefix) + tr := &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{objects.PipelineTaskLabel: pipelineTaskName}}, + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + CompletionTime: &metav1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC)}, + TaskRunResults: []v1beta1.TaskRunResult{ + {Name: fmt.Sprintf("%v_IMAGE_DIGEST", prefix), Value: *v1beta1.NewStructuredValues(r.digest)}, + {Name: fmt.Sprintf("%v_IMAGE_URL", prefix), Value: *v1beta1.NewStructuredValues(r.uri)}, + }, + }, + }, + } + + pro.AppendTaskRun(tr) + pro.Status.PipelineSpec.Tasks = append(pro.Status.PipelineSpec.Tasks, v1beta1.PipelineTask{Name: pipelineTaskName}) + prefix++ + } + + return pro +} diff --git a/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go b/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go index 64388806c9..727ff6d004 100644 --- a/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go +++ b/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go @@ -18,4 +18,6 @@ package slsaconfig type SlsaConfig struct { // BuilderID is the URI of the trusted build platform. BuilderID string + // PrObserveMode configures whether to observe the pipeline level or task level inputs/outputs for a given pipelinerun. + PrObserveMode string } diff --git a/pkg/chains/formats/slsa/testdata/taskrun-multiple-subjects.json b/pkg/chains/formats/slsa/testdata/taskrun-multiple-subjects.json index d7716372c8..32ddbc30e5 100644 --- a/pkg/chains/formats/slsa/testdata/taskrun-multiple-subjects.json +++ b/pkg/chains/formats/slsa/testdata/taskrun-multiple-subjects.json @@ -28,7 +28,7 @@ "taskResults": [ { "name": "IMAGES", - "value": "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367" + "value": "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367" } ], "taskSpec": { diff --git a/pkg/chains/formats/slsa/testdata/v2alpha2/taskrun-multiple-subjects.json b/pkg/chains/formats/slsa/testdata/v2alpha2/taskrun-multiple-subjects.json index d7716372c8..32ddbc30e5 100644 --- a/pkg/chains/formats/slsa/testdata/v2alpha2/taskrun-multiple-subjects.json +++ b/pkg/chains/formats/slsa/testdata/v2alpha2/taskrun-multiple-subjects.json @@ -28,7 +28,7 @@ "taskResults": [ { "name": "IMAGES", - "value": "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367" + "value": "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367" } ], "taskSpec": { diff --git a/pkg/chains/formats/slsa/v1/intotoite6.go b/pkg/chains/formats/slsa/v1/intotoite6.go index d4ba4cae15..f1ba4b40d2 100644 --- a/pkg/chains/formats/slsa/v1/intotoite6.go +++ b/pkg/chains/formats/slsa/v1/intotoite6.go @@ -45,7 +45,8 @@ type InTotoIte6 struct { func NewFormatter(cfg config.Config) (formats.Payloader, error) { return &InTotoIte6{ slsaConfig: &slsaconfig.SlsaConfig{ - BuilderID: cfg.Builder.ID, + BuilderID: cfg.Builder.ID, + PrObserveMode: cfg.Artifacts.PipelineRuns.ObserveMode, }, }, nil } diff --git a/pkg/chains/formats/slsa/v1/intotoite6_test.go b/pkg/chains/formats/slsa/v1/intotoite6_test.go index 01e422053a..487d82b041 100644 --- a/pkg/chains/formats/slsa/v1/intotoite6_test.go +++ b/pkg/chains/formats/slsa/v1/intotoite6_test.go @@ -157,6 +157,11 @@ func TestPipelineRunCreatePayload(t *testing.T) { Builder: config.BuilderConfig{ ID: "test_builder-1", }, + Artifacts: config.ArtifactConfigs{ + PipelineRuns: config.Artifact{ + ObserveMode: "tr", + }, + }, } expected := in_toto.ProvenanceStatement{ StatementHeader: in_toto.StatementHeader{ @@ -164,7 +169,7 @@ func TestPipelineRunCreatePayload(t *testing.T) { PredicateType: slsa.PredicateSLSAProvenance, Subject: []in_toto.Subject{ { - Name: "test.io/test/image", + Name: "gcr.io/my/image", Digest: common.DigestSet{ "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, @@ -381,6 +386,11 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) { Builder: config.BuilderConfig{ ID: "test_builder-1", }, + Artifacts: config.ArtifactConfigs{ + PipelineRuns: config.Artifact{ + ObserveMode: "tr", + }, + }, } expected := in_toto.ProvenanceStatement{ StatementHeader: in_toto.StatementHeader{ @@ -388,7 +398,7 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) { PredicateType: slsa.PredicateSLSAProvenance, Subject: []in_toto.Subject{ { - Name: "test.io/test/image", + Name: "gcr.io/my/image", Digest: common.DigestSet{ "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, @@ -682,12 +692,12 @@ func TestMultipleSubjects(t *testing.T) { PredicateType: slsa.PredicateSLSAProvenance, Subject: []in_toto.Subject{ { - Name: "gcr.io/myimage", + Name: "gcr.io/myimage1", Digest: common.DigestSet{ "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, }, { - Name: "gcr.io/myimage", + Name: "gcr.io/myimage2", Digest: common.DigestSet{ "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", }, diff --git a/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go b/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go index c1781fb209..984450e8f9 100644 --- a/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go +++ b/pkg/chains/formats/slsa/v1/pipelinerun/pipelinerun.go @@ -48,7 +48,7 @@ type TaskAttestation struct { } func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObject, slsaConfig *slsaconfig.SlsaConfig) (interface{}, error) { - subjects := extract.SubjectDigests(ctx, pro) + subjects := extract.SubjectDigests(ctx, pro, slsaConfig) mat, err := material.PipelineMaterials(ctx, pro) if err != nil { diff --git a/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go b/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go index 1f1ee34c6c..867f7082e8 100644 --- a/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go +++ b/pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go @@ -26,6 +26,7 @@ import ( "github.com/tektoncd/chains/pkg/artifacts" "github.com/tektoncd/chains/pkg/chains/formats/slsa/attest" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" + "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/pipeline/v1beta1" @@ -480,7 +481,7 @@ func TestSubjectDigests(t *testing.T) { } ctx := logtesting.TestContextWithLogger(t) - gotSubjects := extract.SubjectDigests(ctx, pro) + gotSubjects := extract.SubjectDigests(ctx, pro, &slsaconfig.SlsaConfig{PrObserveMode: "pr"}) opts := append(ignore, cmpopts.SortSlices(func(x, y intoto.Subject) bool { return x.Name < y.Name })) 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 0b0d47ab06..8d87becb0f 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go +++ b/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go @@ -333,7 +333,7 @@ func TestGetSubjectDigests(t *testing.T) { } ctx := logtesting.TestContextWithLogger(t) tro := objects.NewTaskRunObject(tr) - got := extract.SubjectDigests(ctx, tro) + got := extract.SubjectDigests(ctx, tro, nil) if !reflect.DeepEqual(expected, got) { if d := cmp.Diff(expected, got); d != "" { t.Log(d) diff --git a/pkg/chains/formats/slsa/v1/taskrun/taskrun.go b/pkg/chains/formats/slsa/v1/taskrun/taskrun.go index 99fb9773d1..b5e4509142 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v1/taskrun/taskrun.go @@ -28,7 +28,7 @@ import ( ) func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObject, slsaConfig *slsaconfig.SlsaConfig) (interface{}, error) { - subjects := extract.SubjectDigests(ctx, tro) + subjects := extract.SubjectDigests(ctx, tro, slsaConfig) mat, err := material.TaskMaterials(ctx, tro) if err != nil { diff --git a/pkg/chains/formats/slsa/v2alpha1/slsav2_test.go b/pkg/chains/formats/slsa/v2alpha1/slsav2_test.go index ad8090f678..c06e126ca7 100644 --- a/pkg/chains/formats/slsa/v2alpha1/slsav2_test.go +++ b/pkg/chains/formats/slsa/v2alpha1/slsav2_test.go @@ -292,12 +292,12 @@ func TestMultipleSubjects(t *testing.T) { PredicateType: slsa.PredicateSLSAProvenance, Subject: []in_toto.Subject{ { - Name: "gcr.io/myimage", + Name: "gcr.io/myimage1", Digest: common.DigestSet{ "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, }, { - Name: "gcr.io/myimage", + Name: "gcr.io/myimage2", Digest: common.DigestSet{ "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", }, @@ -352,7 +352,7 @@ func TestMultipleSubjects(t *testing.T) { Name: "IMAGES", Value: v1beta1.ParamValue{ Type: "string", - StringVal: "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", + StringVal: "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", }, }, }, diff --git a/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun.go index f7ca35cc3a..f9c01dbc1a 100644 --- a/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun.go @@ -38,7 +38,7 @@ type BuildConfig struct { } func GenerateAttestation(ctx context.Context, builderID string, payloadType config.PayloadType, tro *objects.TaskRunObject) (interface{}, error) { - subjects := extract.SubjectDigests(ctx, tro) + subjects := extract.SubjectDigests(ctx, tro, nil) mat, err := material.TaskMaterials(ctx, tro) if err != nil { return nil, err diff --git a/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun_test.go b/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun_test.go index 9e515bff15..ccfcbb5070 100644 --- a/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun_test.go +++ b/pkg/chains/formats/slsa/v2alpha1/taskrun/taskrun_test.go @@ -358,7 +358,7 @@ func TestGetSubjectDigests(t *testing.T) { } tro := objects.NewTaskRunObject(tr) ctx := logtesting.TestContextWithLogger(t) - got := extract.SubjectDigests(ctx, tro) + got := extract.SubjectDigests(ctx, tro, nil) if !reflect.DeepEqual(expected, got) { if d := cmp.Diff(expected, got); d != "" { t.Log(d) diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go index cc33e3475c..f9f334a44e 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go @@ -46,7 +46,7 @@ func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObject, sl StatementHeader: intoto.StatementHeader{ Type: intoto.StatementInTotoV01, PredicateType: slsa.PredicateSLSAProvenance, - Subject: extract.SubjectDigests(ctx, pro), + Subject: extract.SubjectDigests(ctx, pro, slsaconfig), }, Predicate: slsa.ProvenancePredicate{ BuildDefinition: slsa.ProvenanceBuildDefinition{ diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go index 29e3e17f7d..019a54a3cc 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go @@ -256,7 +256,7 @@ func TestGenerateAttestation(t *testing.T) { PredicateType: slsa.PredicateSLSAProvenance, Subject: []in_toto.Subject{ { - Name: "test.io/test/image", + Name: "gcr.io/my/image", Digest: common.DigestSet{ "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, @@ -351,7 +351,8 @@ func TestGenerateAttestation(t *testing.T) { } got, err := GenerateAttestation(ctx, pr, &slsaconfig.SlsaConfig{ - BuilderID: "test_builder-1", + BuilderID: "test_builder-1", + PrObserveMode: "tr", }) if err != nil { diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go index 7c82db346f..fc1b88a512 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go @@ -22,7 +22,6 @@ import ( slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" - "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/objects" ) @@ -43,7 +42,7 @@ func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObject, slsaCo StatementHeader: intoto.StatementHeader{ Type: intoto.StatementInTotoV01, PredicateType: slsa.PredicateSLSAProvenance, - Subject: extract.SubjectDigests(ctx, tro), + Subject: extract.SubjectDigests(ctx, tro, slsaConfig), }, Predicate: slsa.ProvenancePredicate{ BuildDefinition: slsa.ProvenanceBuildDefinition{ @@ -128,7 +127,7 @@ func byproducts(tro *objects.TaskRunObject) ([]slsa.ResourceDescriptor, error) { bp := slsa.ResourceDescriptor{ Name: fmt.Sprintf(taskRunResults, key.Name), Content: content, - MediaType: pipelinerun.JsonMediaType, + MediaType: "application/json", } byProd = append(byProd, bp) } diff --git a/pkg/chains/formats/slsa/v2alpha2/slsav2.go b/pkg/chains/formats/slsa/v2alpha2/slsav2.go index a61b047353..5e7276f651 100644 --- a/pkg/chains/formats/slsa/v2alpha2/slsav2.go +++ b/pkg/chains/formats/slsa/v2alpha2/slsav2.go @@ -43,7 +43,8 @@ type Slsa struct { func NewFormatter(cfg config.Config) (formats.Payloader, error) { return &Slsa{ slsaConfig: &slsaconfig.SlsaConfig{ - BuilderID: cfg.Builder.ID, + BuilderID: cfg.Builder.ID, + PrObserveMode: cfg.Artifacts.PipelineRuns.ObserveMode, }, }, nil } diff --git a/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go b/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go index 32dd4d9940..a976fb9fb1 100644 --- a/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go @@ -292,7 +292,7 @@ func TestMultipleSubjects(t *testing.T) { resultValue := v1beta1.ParamValue{ Type: "string", - StringVal: "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", + StringVal: "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", } resultBytes, err := json.Marshal(resultValue) if err != nil { @@ -309,12 +309,12 @@ func TestMultipleSubjects(t *testing.T) { PredicateType: slsa.PredicateSLSAProvenance, Subject: []in_toto.Subject{ { - Name: "gcr.io/myimage", + Name: "gcr.io/myimage1", Digest: common.DigestSet{ "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", }, }, { - Name: "gcr.io/myimage", + Name: "gcr.io/myimage2", Digest: common.DigestSet{ "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", }, @@ -389,6 +389,11 @@ func TestPipelineRunCreatePayload1(t *testing.T) { Builder: config.BuilderConfig{ ID: "test_builder-1", }, + Artifacts: config.ArtifactConfigs{ + PipelineRuns: config.Artifact{ + ObserveMode: "tr", + }, + }, } expected := in_toto.ProvenanceStatementSLSA1{ StatementHeader: in_toto.StatementHeader{ @@ -396,7 +401,7 @@ func TestPipelineRunCreatePayload1(t *testing.T) { PredicateType: slsa.PredicateSLSAProvenance, Subject: []in_toto.Subject{ { - Name: "test.io/test/image", + Name: "gcr.io/my/image", Digest: common.DigestSet{ "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", }, diff --git a/pkg/chains/storage/grafeas/grafeas.go b/pkg/chains/storage/grafeas/grafeas.go index fdaa1997c3..084d1d42b0 100644 --- a/pkg/chains/storage/grafeas/grafeas.go +++ b/pkg/chains/storage/grafeas/grafeas.go @@ -253,7 +253,7 @@ func (b *Backend) createOccurrence(ctx context.Context, obj objects.TektonObject } // create Occurrence_Build for TaskRun - allURIs := extract.RetrieveAllArtifactURIs(ctx, obj) + allURIs := extract.RetrieveAllArtifactURIs(ctx, obj, b.cfg.Artifacts.PipelineRuns.ObserveMode) for _, uri := range allURIs { occ, err := b.createBuildOccurrence(ctx, obj, payload, signature, uri) if err != nil { @@ -364,7 +364,7 @@ func (b *Backend) getBuildNotePath(obj objects.TektonObject) string { func (b *Backend) getAllOccurrences(ctx context.Context, obj objects.TektonObject, opts config.StorageOpts) ([]*pb.Occurrence, error) { result := []*pb.Occurrence{} // step 1: get all resource URIs created under the taskrun - uriFilters := extract.RetrieveAllArtifactURIs(ctx, obj) + uriFilters := extract.RetrieveAllArtifactURIs(ctx, obj, b.cfg.Artifacts.PipelineRuns.ObserveMode) // step 2: find all build occurrences if _, ok := formats.IntotoAttestationSet[opts.PayloadFormat]; ok { diff --git a/pkg/chains/storage/grafeas/grafeas_test.go b/pkg/chains/storage/grafeas/grafeas_test.go index 0c4d28e5a1..5ae0c4dd66 100644 --- a/pkg/chains/storage/grafeas/grafeas_test.go +++ b/pkg/chains/storage/grafeas/grafeas_test.go @@ -349,6 +349,11 @@ func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) { NoteID: NoteID, }, }, + Artifacts: config.ArtifactConfigs{ + PipelineRuns: config.Artifact{ + ObserveMode: "pr", + }, + }, }, } // test if the attestation of the taskrun/oci artifact can be successfully stored into grafeas server @@ -398,7 +403,7 @@ func testStoreAndRetrieveHelper(ctx context.Context, t *testing.T, test testConf expectSignature[test.args.opts.FullKey] = []string{test.args.signature} } if _, ok := formats.IntotoAttestationSet[test.args.opts.PayloadFormat]; ok { - allURIs := extract.RetrieveAllArtifactURIs(ctx, test.args.runObject) + allURIs := extract.RetrieveAllArtifactURIs(ctx, test.args.runObject, "pr") for _, u := range allURIs { expectSignature[u] = []string{test.args.signature} } @@ -420,7 +425,7 @@ func testStoreAndRetrieveHelper(ctx context.Context, t *testing.T, test testConf expectPayload[test.args.opts.FullKey] = string(test.args.payload) } if _, ok := formats.IntotoAttestationSet[test.args.opts.PayloadFormat]; ok { - allURIs := extract.RetrieveAllArtifactURIs(ctx, test.args.runObject) + allURIs := extract.RetrieveAllArtifactURIs(ctx, test.args.runObject, "pr") for _, u := range allURIs { expectPayload[u] = string(test.args.payload) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 855d51143d..d912c702fa 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -47,6 +47,7 @@ type Artifact struct { Format string StorageBackend sets.Set[string] Signer string + ObserveMode string } // StorageConfigs contains the configuration to instantiate different storage providers @@ -150,9 +151,10 @@ const ( taskrunStorageKey = "artifacts.taskrun.storage" taskrunSignerKey = "artifacts.taskrun.signer" - pipelinerunFormatKey = "artifacts.pipelinerun.format" - pipelinerunStorageKey = "artifacts.pipelinerun.storage" - pipelinerunSignerKey = "artifacts.pipelinerun.signer" + pipelinerunFormatKey = "artifacts.pipelinerun.format" + pipelinerunStorageKey = "artifacts.pipelinerun.storage" + pipelinerunSignerKey = "artifacts.pipelinerun.signer" + pipelinerunObserveModeKey = "artifacts.pipelinerun.observe-mode" ociFormatKey = "artifacts.oci.format" ociStorageKey = "artifacts.oci.storage" @@ -217,6 +219,7 @@ func defaultConfig() *Config { Format: "in-toto", StorageBackend: sets.New[string]("tekton"), Signer: "x509", + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -260,6 +263,7 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { asString(pipelinerunFormatKey, &cfg.Artifacts.PipelineRuns.Format, "in-toto", "slsa/v1", "slsa/v2alpha2"), asStringSet(pipelinerunStorageKey, &cfg.Artifacts.PipelineRuns.StorageBackend, sets.New[string]("tekton", "oci", "docdb", "grafeas")), asString(pipelinerunSignerKey, &cfg.Artifacts.PipelineRuns.Signer, "x509", "kms"), + asString(pipelinerunObserveModeKey, &cfg.Artifacts.PipelineRuns.ObserveMode, "pr", "tr"), // OCI asString(ociFormatKey, &cfg.Artifacts.OCI.Format, "simplesigning"), diff --git a/pkg/config/store_test.go b/pkg/config/store_test.go index cb79249322..6c040a8e08 100644 --- a/pkg/config/store_test.go +++ b/pkg/config/store_test.go @@ -106,6 +106,7 @@ var defaultArtifacts = ArtifactConfigs{ Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -196,6 +197,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -225,6 +227,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton", "docdb"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -254,6 +257,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -283,6 +287,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -312,6 +317,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -344,6 +350,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -376,6 +383,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -405,6 +413,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning", @@ -437,8 +446,9 @@ func TestParse(t *testing.T) { { name: "extra", data: map[string]string{ - taskrunSignerKey: "x509", - "other-key": "foo", + taskrunSignerKey: "x509", + "other-key": "foo", + pipelinerunObserveModeKey: "tr", }, taskrunEnabled: true, ociEnbaled: true, @@ -454,6 +464,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "tr", }, OCI: Artifact{ Format: "simplesigning", @@ -486,6 +497,7 @@ func TestParse(t *testing.T) { Format: "in-toto", Signer: "x509", StorageBackend: sets.New[string]("tekton"), + ObserveMode: "pr", }, OCI: Artifact{ Format: "simplesigning",