diff --git a/docs/config.md b/docs/config.md index 18f797641f..5417889720 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.enable-deep-inspection` | This boolean option will configure whether Chains should inspect child taskruns in order to capture inputs/outputs within a pipelinerun. `"false"` means that Chains only checks pipeline level results, whereas `true` means Chains inspects both pipeline level and task level results. | `"true"`, `"false"` | `"false"` | -> 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..7e74aef2d5 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,102 @@ 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 { + prSubjects := subjectsFromTektonObject(ctx, obj) + + // If deep inspection is not enabled, just return subjects observed on the pipelinerun level + if !slsaconfig.DeepInspectionEnabled { + return prSubjects + } + + logger := logging.FromContext(ctx) + // If deep inspection is enabled, collect subjects from child taskruns + 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) + } + } + } + + // also add subjects observed from pipelinerun level with duplication removed + for _, s := range prSubjects { + 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, merge item's DigestSet + // into the existing entry's DigestSet. + if subjectEqual(s, item) { + mergeMaps(original[i].Digest, item.Digest) + 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 y.Digest[algo] == hex { + return true + } + } + return false +} + +func mergeMaps(m1 map[string]string, m2 map[string]string) { + for k, v := range m2 { + m1[k] = v + } +} + +func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []intoto.Subject { logger := logging.FromContext(ctx) var subjects []intoto.Subject @@ -121,9 +217,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 +225,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, deepInspectionEnabled bool) []string { result := []string{} - subjects := SubjectDigests(ctx, obj) + subjects := SubjectDigests(ctx, obj, &slsaconfig.SlsaConfig{DeepInspectionEnabled: deepInspectionEnabled}) 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..979e893566 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), + createProWithPipelineResults(tc.results), } - for _, o := range runObjects { - gotSubjects := extract.SubjectDigests(ctx, o) + gotSubjects := extract.SubjectDigests(ctx, o, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}) 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, false) 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,153 @@ func TestSubjectDigestsAndRetrieveAllArtifactURIs(t *testing.T) { } } +func TestPipelineRunObserveModeForSubjects(t *testing.T) { + var tests = []struct { + name string + pro objects.TektonObject + deepInspectionEnabled bool + wantSubjects []intoto.Subject + wantFullURLs []string + }{ + { + name: "deep inspection disabled", + pro: createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}), + deepInspectionEnabled: false, + wantSubjects: []intoto.Subject{ + { + Name: artifactURL1, + Digest: map[string]string{ + "sha256": artifactDigest1, + }, + }, + }, + wantFullURLs: []string{fmt.Sprintf("%s@sha256:%s", artifactURL1, artifactDigest1)}, + }, + { + name: "deep inspection enabled: no duplication", + pro: createProWithTaskRunResults(nil, []artifact{{uri: artifactURL2, digest: "sha256:" + artifactDigest2}}), + deepInspectionEnabled: true, + wantSubjects: []intoto.Subject{ + { + Name: artifactURL2, + Digest: map[string]string{ + "sha256": artifactDigest2, + }, + }, + }, + wantFullURLs: []string{fmt.Sprintf("%s@sha256:%s", artifactURL2, artifactDigest2)}, + }, + { + name: "deep inspection enabled: 2 tasks have same uri with different sha256 digests", + pro: createProWithTaskRunResults(nil, []artifact{ + {uri: artifactURL2, digest: "sha256:" + artifactDigest1}, + {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, + }), + deepInspectionEnabled: true, + 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: 2 taskruns have same uri with same sha256 digests", + pro: createProWithTaskRunResults(nil, []artifact{ + {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, + {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, + }), + deepInspectionEnabled: true, + wantSubjects: []intoto.Subject{ + { + Name: artifactURL2, + Digest: map[string]string{ + "sha256": artifactDigest2, + }, + }, + }, + wantFullURLs: []string{ + fmt.Sprintf("%s@sha256:%s", artifactURL2, artifactDigest2), + }, + }, + { + name: "observe mode: pipelinerun and taskrun have duplicated results", + pro: createProWithTaskRunResults( + createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}).(*objects.PipelineRunObject), + []artifact{ + {uri: artifactURL1, digest: "sha256:" + artifactDigest1}, + }), + deepInspectionEnabled: true, + wantSubjects: []intoto.Subject{ + { + Name: artifactURL1, + Digest: map[string]string{ + "sha256": artifactDigest1, + }, + }, + }, + wantFullURLs: []string{ + fmt.Sprintf("%s@sha256:%s", artifactURL1, artifactDigest1), + }, + }, + { + name: "observe mode: pipelinerun and taskrun have different results", + pro: createProWithTaskRunResults( + createProWithPipelineResults(map[string]string{artifactURL1: "sha256:" + artifactDigest1}).(*objects.PipelineRunObject), + []artifact{ + {uri: artifactURL2, digest: "sha256:" + artifactDigest2}, + }), + deepInspectionEnabled: true, + wantSubjects: []intoto.Subject{ + { + Name: artifactURL1, + Digest: map[string]string{ + "sha256": artifactDigest1, + }, + }, + { + Name: artifactURL2, + Digest: map[string]string{ + "sha256": artifactDigest2, + }, + }, + }, + wantFullURLs: []string{ + fmt.Sprintf("%s@sha256:%s", artifactURL1, artifactDigest1), + 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{DeepInspectionEnabled: tc.deepInspectionEnabled}) + 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.deepInspectionEnabled) + 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 +292,7 @@ func createTaskRunObjectWithResults(results map[string]string) objects.TektonObj ) } -func createPipelineRunObjectWithResults(results map[string]string) objects.TektonObject { +func createProWithPipelineResults(results map[string]string) objects.TektonObject { prResults := []v1beta1.PipelineRunResult{} prefix := 0 for url, digest := range results { @@ -164,3 +313,52 @@ 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 createProWithTaskRunResults(pro *objects.PipelineRunObject, results []artifact) objects.TektonObject { + if pro == nil { + pro = objects.NewPipelineRunObject(&v1beta1.PipelineRun{ + Status: v1beta1.PipelineRunStatus{ + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + PipelineSpec: &v1beta1.PipelineSpec{}, + }, + }, + }) + } + + if pro.Status.PipelineSpec == nil { + pro.Status.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..343cbf5bb7 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 + // DeepInspectionEnabled configures whether to dive into child taskruns in a pipelinerun + DeepInspectionEnabled bool } 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..4ab3c8d0bf 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, + DeepInspectionEnabled: cfg.Artifacts.PipelineRuns.DeepInspectionEnabled, }, }, nil } diff --git a/pkg/chains/formats/slsa/v1/intotoite6_test.go b/pkg/chains/formats/slsa/v1/intotoite6_test.go index 01e422053a..a61bf2489b 100644 --- a/pkg/chains/formats/slsa/v1/intotoite6_test.go +++ b/pkg/chains/formats/slsa/v1/intotoite6_test.go @@ -682,12 +682,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..1674e0dd5a 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{DeepInspectionEnabled: false}) 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..d13a5d30ef 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go @@ -351,7 +351,8 @@ func TestGenerateAttestation(t *testing.T) { } got, err := GenerateAttestation(ctx, pr, &slsaconfig.SlsaConfig{ - BuilderID: "test_builder-1", + BuilderID: "test_builder-1", + DeepInspectionEnabled: false, }) 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..688afa2ba0 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, + DeepInspectionEnabled: cfg.Artifacts.PipelineRuns.DeepInspectionEnabled, }, }, nil } diff --git a/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go b/pkg/chains/formats/slsa/v2alpha2/slsav2_test.go index 32dd4d9940..38c0107ca9 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", }, diff --git a/pkg/chains/storage/grafeas/grafeas.go b/pkg/chains/storage/grafeas/grafeas.go index fdaa1997c3..7f04f3e86e 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.DeepInspectionEnabled) 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.DeepInspectionEnabled) // 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..295ceacc50 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{ + DeepInspectionEnabled: false, + }, + }, }, } // 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, false) 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, false) for _, u := range allURIs { expectPayload[u] = string(test.args.payload) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 855d51143d..5e936adf8c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -44,9 +44,10 @@ type ArtifactConfigs struct { // Artifact contains the configuration for how to sign/store/format the signatures for a single artifact type Artifact struct { - Format string - StorageBackend sets.Set[string] - Signer string + Format string + StorageBackend sets.Set[string] + Signer string + DeepInspectionEnabled bool } // 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" + pipelinerunEnableDeepInspectionKey = "artifacts.pipelinerun.enable-deep-inspection" ociFormatKey = "artifacts.oci.format" ociStorageKey = "artifacts.oci.storage" @@ -214,9 +216,10 @@ func defaultConfig() *Config { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - StorageBackend: sets.New[string]("tekton"), - Signer: "x509", + Format: "in-toto", + StorageBackend: sets.New[string]("tekton"), + Signer: "x509", + DeepInspectionEnabled: false, }, 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"), + asBool(pipelinerunEnableDeepInspectionKey, &cfg.Artifacts.PipelineRuns.DeepInspectionEnabled), // OCI asString(ociFormatKey, &cfg.Artifacts.OCI.Format, "simplesigning"), diff --git a/pkg/config/store_test.go b/pkg/config/store_test.go index cb79249322..b10e3fedad 100644 --- a/pkg/config/store_test.go +++ b/pkg/config/store_test.go @@ -103,9 +103,10 @@ var defaultArtifacts = ArtifactConfigs{ Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -193,9 +194,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -222,9 +224,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton", "docdb"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton", "docdb"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -251,9 +254,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -280,9 +284,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -309,9 +314,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -341,9 +347,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -373,9 +380,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -402,9 +410,10 @@ func TestParse(t *testing.T) { StorageBackend: sets.New[string]("tekton"), }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, 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", + pipelinerunEnableDeepInspectionKey: "tr", }, taskrunEnabled: true, ociEnbaled: true, @@ -451,9 +461,10 @@ func TestParse(t *testing.T) { StorageBackend: sets.New[string]("tekton"), }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning", @@ -483,9 +494,10 @@ func TestParse(t *testing.T) { StorageBackend: sets.New[string]("tekton"), }, PipelineRuns: Artifact{ - Format: "in-toto", - Signer: "x509", - StorageBackend: sets.New[string]("tekton"), + Format: "in-toto", + Signer: "x509", + StorageBackend: sets.New[string]("tekton"), + DeepInspectionEnabled: false, }, OCI: Artifact{ Format: "simplesigning",