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..be72bd05a3 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,94 @@ 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 { +// +//nolint:all +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 +209,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 +217,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..5b37329faa 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,106 @@ 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) + + 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 +245,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 +266,46 @@ func createPipelineRunObjectWithResults(results map[string]string) objects.Tekto }, ) } + +type artifact struct { + uri string + digest string +} + +// create a child taskrun for each result +// +//nolint:all +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",