From 35cbe22b43237984663cb5cac5c3dda70357e014 Mon Sep 17 00:00:00 2001 From: Joe Stuart Date: Mon, 31 Jul 2023 16:00:07 -0500 Subject: [PATCH] provide support for multiple buildTypes. The initial types are to support more general slsa verifiers and provide more verbose output for tekton verifiers. This implementation will default to the slsa buildType. --- .../slsa/internal/slsaconfig/slsaconfig.go | 2 + .../internal/build_types/build_types.go | 6 + .../external_parameters.go | 44 ++ .../external_parameters_test.go | 97 ++++ .../internal_parameters.go | 26 + .../internal_parameters_test.go | 44 ++ .../internal/pipelinerun/pipelinerun.go | 93 ++-- .../internal/pipelinerun/pipelinerun_test.go | 190 ++++--- .../resolved_dependencies.go | 214 ++++--- .../resolved_dependencies_test.go | 521 ++++++++++-------- .../slsa/v2alpha2/internal/taskrun/taskrun.go | 91 ++- .../v2alpha2/internal/taskrun/taskrun_test.go | 193 ++++--- pkg/chains/formats/slsa/v2alpha2/slsav2.go | 1 + pkg/chains/objects/objects.go | 36 ++ pkg/chains/objects/objects_test.go | 65 +++ pkg/config/config.go | 24 +- pkg/config/store_test.go | 115 ++-- 17 files changed, 1125 insertions(+), 637 deletions(-) create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/build_types/build_types.go create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters.go create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters_test.go create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters.go create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters_test.go diff --git a/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go b/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go index 343cbf5bb7..124c9ed145 100644 --- a/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go +++ b/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go @@ -20,4 +20,6 @@ type SlsaConfig struct { BuilderID string // DeepInspectionEnabled configures whether to dive into child taskruns in a pipelinerun DeepInspectionEnabled bool + // The buildType for the build definition + BuildType string } diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/build_types/build_types.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_types/build_types.go new file mode 100644 index 0000000000..b5eaf96c5f --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_types/build_types.go @@ -0,0 +1,6 @@ +package buildtypes + +const ( + SlsaBuildType = "https://tekton.dev/chains/v2/slsa" + TektonBuildType = "https://tekton.dev/chains/v2/slsa-tekton" +) diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters.go b/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters.go new file mode 100644 index 0000000000..cb85e58d74 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters.go @@ -0,0 +1,44 @@ +package externalparameters + +import ( + "fmt" + + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" +) + +func buildConfigSource(provenance *v1beta1.Provenance) map[string]string { + ref := "" + for alg, hex := range provenance.RefSource.Digest { + ref = fmt.Sprintf("%s:%s", alg, hex) + break + } + buildConfigSource := map[string]string{ + "ref": ref, + "repository": provenance.RefSource.URI, + "path": provenance.RefSource.EntryPoint, + } + return buildConfigSource +} + +// PipelineRun adds the pipeline run spec and provenance if available +func PipelineRun(pro *objects.PipelineRunObject) map[string]any { + externalParams := make(map[string]any) + + if provenance := pro.GetRemoteProvenance(); provenance != nil { + externalParams["buildConfigSource"] = buildConfigSource(provenance) + } + externalParams["runSpec"] = pro.Spec + return externalParams +} + +// TaskRun adds the task run spec and provenance if available +func TaskRun(tro *objects.TaskRunObject) map[string]any { + externalParams := make(map[string]any) + + if provenance := tro.GetRemoteProvenance(); provenance != nil { + externalParams["buildConfigSource"] = buildConfigSource(provenance) + } + externalParams["runSpec"] = tro.Spec + return externalParams +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters_test.go new file mode 100644 index 0000000000..c53aac1a9d --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters/external_parameters_test.go @@ -0,0 +1,97 @@ +package externalparameters + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/internal/objectloader" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" +) + +func TestBuildConfigSource(t *testing.T) { + provenance := &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + Digest: map[string]string{"alg1": "hex1", "alg2": "hex2"}, + URI: "https://tekton.com", + EntryPoint: "/path/to/entry", + }, + } + + want := map[string]string{ + "ref": "alg1:hex1", + "repository": "https://tekton.com", + "path": "/path/to/entry", + } + + got := buildConfigSource(provenance) + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("buildConfigSource(): -want +got: %s", diff) + } +} +func createPro(path string) *objects.PipelineRunObject { + pr, err := objectloader.PipelineRunFromFile(path) + if err != nil { + panic(err) + } + tr1, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun1.json") + if err != nil { + panic(err) + } + tr2, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun2.json") + if err != nil { + panic(err) + } + p := objects.NewPipelineRunObject(pr) + p.AppendTaskRun(tr1) + p.AppendTaskRun(tr2) + return p +} + +func TestPipelineRun(t *testing.T) { + pro := createPro("../../../testdata/v2alpha2/pipelinerun1.json") + + got := PipelineRun(pro) + + want := map[string]any{ + "runSpec": v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, + Params: v1beta1.Params{ + { + Name: "IMAGE", + Value: v1beta1.ParamValue{Type: "string", StringVal: "test.io/test/image"}, + }, + }, + ServiceAccountName: "pipeline", + }, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("PipelineRun(): -want +got: %s", diff) + } +} + +func TestTaskRun(t *testing.T) { + tr, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun1.json") + if err != nil { + t.Fatal(err) + } + got := TaskRun(objects.NewTaskRunObject(tr)) + + want := map[string]any{ + "runSpec": v1beta1.TaskRunSpec{ + Params: v1beta1.Params{ + {Name: "IMAGE", Value: v1beta1.ParamValue{Type: "string", StringVal: "test.io/test/image"}}, + {Name: "CHAINS-GIT_COMMIT", Value: v1beta1.ParamValue{Type: "string", StringVal: "taskrun"}}, + {Name: "CHAINS-GIT_URL", Value: v1beta1.ParamValue{Type: "string", StringVal: "https://git.test.com"}}, + }, + ServiceAccountName: "default", + TaskRef: &v1beta1.TaskRef{Name: "build", Kind: "Task"}, + }, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("TaskRun(): -want +got: %s", diff) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters.go b/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters.go new file mode 100644 index 0000000000..2db0ae5b11 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters.go @@ -0,0 +1,26 @@ +package internalparameters + +import ( + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" +) + +// SLSAInternalParameters provides the chains config as internalparameters +func SLSAInternalParameters(tko objects.TektonObject) map[string]any { + internalParams := make(map[string]any) + if provenance := tko.GetProvenance(); provenance != (*v1beta1.Provenance)(nil) && provenance.FeatureFlags != nil { + internalParams["tekton-pipelines-feature-flags"] = *provenance.FeatureFlags + } + return internalParams +} + +// TektonInternalParameters provides the chains config as well as annotations and labels +func TektonInternalParameters(tko objects.TektonObject) map[string]any { + internalParams := make(map[string]any) + if provenance := tko.GetProvenance(); provenance != (*v1beta1.Provenance)(nil) && provenance.FeatureFlags != nil { + internalParams["tekton-pipelines-feature-flags"] = *provenance.FeatureFlags + } + internalParams["labels"] = tko.GetLabels() + internalParams["annotations"] = tko.GetAnnotations() + return internalParams +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters_test.go new file mode 100644 index 0000000000..664a373714 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters/internal_parameters_test.go @@ -0,0 +1,44 @@ +package internalparameters + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/internal/objectloader" + "github.com/tektoncd/pipeline/pkg/apis/config" +) + +func TestTektonInternalParameters(t *testing.T) { + tr, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun1.json") + if err != nil { + t.Fatal(err) + } + tro := objects.NewTaskRunObject(tr) + got := TektonInternalParameters(tro) + want := map[string]any{ + "labels": tro.GetLabels(), + "annotations": tro.GetAnnotations(), + "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("TaskRun(): -want +got: %s", diff) + } +} + +func TestSLSAInternalParameters(t *testing.T) { + tr, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun1.json") + if err != nil { + t.Fatal(err) + } + tro := objects.NewTaskRunObject(tr) + got := SLSAInternalParameters(tro) + want := map[string]any{ + "tekton-pipelines-feature-flags": config.FeatureFlags{EnableAPIFields: "beta", ResultExtractionMethod: "termination-message"}, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("TaskRun(): -want +got: %s", diff) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go index add62022f8..cab493d5f6 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go @@ -22,6 +22,9 @@ 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" + buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/build_types" + externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters" + internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/objects" ) @@ -34,14 +37,16 @@ const ( // GenerateAttestation generates a provenance statement with SLSA v1.0 predicate for a pipeline run. func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObject, slsaconfig *slsaconfig.SlsaConfig) (interface{}, error) { - rd, err := resolveddependencies.PipelineRun(ctx, pro, slsaconfig) + bp, err := byproducts(pro) if err != nil { return nil, err } - bp, err := byproducts(pro) + + bd, err := getBuildDefinition(ctx, slsaconfig, pro) if err != nil { return nil, err } + att := intoto.ProvenanceStatementSLSA1{ StatementHeader: intoto.StatementHeader{ Type: intoto.StatementInTotoV01, @@ -49,12 +54,7 @@ func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObject, sl Subject: extract.SubjectDigests(ctx, pro, slsaconfig), }, Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalParameters(pro), - InternalParameters: internalParameters(pro), - ResolvedDependencies: rd, - }, + BuildDefinition: bd, RunDetails: slsa.ProvenanceRunDetails{ Builder: slsa.Builder{ ID: slsaconfig.BuilderID, @@ -82,47 +82,6 @@ func metadata(pro *objects.PipelineRunObject) slsa.BuildMetadata { return m } -// internalParameters adds the tekton feature flags that were enabled -// for the pipelinerun. -func internalParameters(pro *objects.PipelineRunObject) map[string]any { - internalParams := make(map[string]any) - provenance := pro.GetProvenance() - if provenance != nil && provenance.FeatureFlags != nil { - internalParams["tekton-pipelines-feature-flags"] = *provenance.FeatureFlags - } - return internalParams -} - -// externalParameters adds the pipeline run spec -func externalParameters(pro *objects.PipelineRunObject) map[string]any { - externalParams := make(map[string]any) - - // add the origin of top level pipeline config - // isRemotePipeline checks if the pipeline was fetched using a remote resolver - isRemotePipeline := false - if pro.Spec.PipelineRef != nil { - if pro.Spec.PipelineRef.Resolver != "" && pro.Spec.PipelineRef.Resolver != "Cluster" { - isRemotePipeline = true - } - } - - if p := pro.Status.Provenance; p != nil && p.RefSource != nil && isRemotePipeline { - ref := "" - for alg, hex := range p.RefSource.Digest { - ref = fmt.Sprintf("%s:%s", alg, hex) - break - } - buildConfigSource := map[string]string{ - "ref": ref, - "repository": p.RefSource.URI, - "path": p.RefSource.EntryPoint, - } - externalParams["buildConfigSource"] = buildConfigSource - } - externalParams["runSpec"] = pro.Spec - return externalParams -} - // byproducts contains the pipelineRunResults func byproducts(pro *objects.PipelineRunObject) ([]slsa.ResourceDescriptor, error) { byProd := []slsa.ResourceDescriptor{} @@ -140,3 +99,39 @@ func byproducts(pro *objects.PipelineRunObject) ([]slsa.ResourceDescriptor, erro } return byProd, nil } + +// getBuildDefinition get the buildDefinition based on the configured buildType. This will default to the slsa buildType +func getBuildDefinition(ctx context.Context, slsaconfig *slsaconfig.SlsaConfig, pro *objects.PipelineRunObject) (slsa.ProvenanceBuildDefinition, error) { + // if buildType is not set in the chains-config, default to slsa build type + buildDefinitionType := slsaconfig.BuildType + if slsaconfig.BuildType == "" { + buildDefinitionType = buildtypes.SlsaBuildType + } + + switch buildDefinitionType { + case buildtypes.SlsaBuildType: + rd, err := resolveddependencies.PipelineRun(ctx, pro, slsaconfig, resolveddependencies.AddSLSATaskDescriptor) + if err != nil { + return slsa.ProvenanceBuildDefinition{}, err + } + return slsa.ProvenanceBuildDefinition{ + BuildType: buildDefinitionType, + ExternalParameters: externalparameters.PipelineRun(pro), + InternalParameters: internalparameters.SLSAInternalParameters(pro), + ResolvedDependencies: rd, + }, nil + case buildtypes.TektonBuildType: + rd, err := resolveddependencies.PipelineRun(ctx, pro, slsaconfig, resolveddependencies.AddTektonTaskDescriptor) + if err != nil { + return slsa.ProvenanceBuildDefinition{}, err + } + return slsa.ProvenanceBuildDefinition{ + BuildType: buildDefinitionType, + ExternalParameters: externalparameters.PipelineRun(pro), + InternalParameters: internalparameters.TektonInternalParameters(pro), + ResolvedDependencies: rd, + }, nil + default: + return slsa.ProvenanceBuildDefinition{}, fmt.Errorf("unsupported buildType %v", buildDefinitionType) + } +} 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 d13a5d30ef..34a12edc48 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go @@ -17,6 +17,7 @@ limitations under the License. package pipelinerun import ( + "context" "encoding/json" "testing" "time" @@ -25,19 +26,21 @@ import ( "github.com/in-toto/in-toto-golang/in_toto" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" - + v1resourcedescriptor "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters" + internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters" + resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" - "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" logtesting "knative.dev/pkg/logging/testing" ) func TestMetadata(t *testing.T) { - pr := &v1beta1.PipelineRun{ + pr := &v1beta1.PipelineRun{ //nolint:staticcheck ObjectMeta: v1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -68,7 +71,7 @@ func TestMetadata(t *testing.T) { func TestMetadataInTimeZone(t *testing.T) { tz := time.FixedZone("Test Time", int((12 * time.Hour).Seconds())) - pr := &v1beta1.PipelineRun{ + pr := &v1beta1.PipelineRun{ //nolint:staticcheck ObjectMeta: v1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -97,101 +100,9 @@ func TestMetadataInTimeZone(t *testing.T) { } } -func TestExternalParameters(t *testing.T) { - pr := &v1beta1.PipelineRun{ - Spec: v1beta1.PipelineRunSpec{ - Params: v1beta1.Params{ - { - Name: "my-param", - Value: v1beta1.ResultValue{Type: "string", StringVal: "string-param"}, - }, - { - Name: "my-array-param", - Value: v1beta1.ResultValue{Type: "array", ArrayVal: []string{"my", "array"}}, - }, - { - Name: "my-empty-string-param", - Value: v1beta1.ResultValue{Type: "string"}, - }, - { - Name: "my-empty-array-param", - Value: v1beta1.ResultValue{Type: "array", ArrayVal: []string{}}, - }, - }, - PipelineRef: &v1beta1.PipelineRef{ - ResolverRef: v1beta1.ResolverRef{ - Resolver: "git", - }, - }, - }, - Status: v1beta1.PipelineRunStatus{ - PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ - Provenance: &v1beta1.Provenance{ - RefSource: &v1beta1.RefSource{ - URI: "hello", - Digest: map[string]string{ - "sha1": "abc123", - }, - EntryPoint: "pipeline.yaml", - }, - }, - }, - }, - } - - want := map[string]any{ - "buildConfigSource": map[string]string{ - "path": "pipeline.yaml", - "ref": "sha1:abc123", - "repository": "hello", - }, - "runSpec": pr.Spec, - } - got := externalParameters(objects.NewPipelineRunObject(pr)) - if d := cmp.Diff(want, got); d != "" { - t.Fatalf("externalParameters (-want, +got):\n%s", d) - } -} - -func TestInternalParameters(t *testing.T) { - pr := &v1beta1.PipelineRun{ - Status: v1beta1.PipelineRunStatus{ - PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ - Provenance: &v1beta1.Provenance{ - FeatureFlags: &config.FeatureFlags{ - RunningInEnvWithInjectedSidecars: true, - EnableAPIFields: "stable", - AwaitSidecarReadiness: true, - VerificationNoMatchPolicy: "skip", - EnableProvenanceInStatus: true, - ResultExtractionMethod: "termination-message", - MaxResultSize: 4096, - }, - }, - }, - }, - } - - want := map[string]any{ - "tekton-pipelines-feature-flags": config.FeatureFlags{ - RunningInEnvWithInjectedSidecars: true, - EnableAPIFields: "stable", - AwaitSidecarReadiness: true, - VerificationNoMatchPolicy: "skip", - EnableProvenanceInStatus: true, - ResultExtractionMethod: "termination-message", - MaxResultSize: 4096, - }, - } - got := internalParameters(objects.NewPipelineRunObject(pr)) - if d := cmp.Diff(want, got); d != "" { - t.Fatalf("internalParameters (-want, +got):\n%s", d) - } -} - func TestByProducts(t *testing.T) { resultValue := v1beta1.ResultValue{Type: "string", StringVal: "result-value"} - pr := &v1beta1.PipelineRun{ + pr := &v1beta1.PipelineRun{ //nolint:staticcheck Status: v1beta1.PipelineRunStatus{ PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ PipelineResults: []v1beta1.PipelineRunResult{ @@ -353,6 +264,7 @@ func TestGenerateAttestation(t *testing.T) { got, err := GenerateAttestation(ctx, pr, &slsaconfig.SlsaConfig{ BuilderID: "test_builder-1", DeepInspectionEnabled: false, + BuildType: "https://tekton.dev/chains/v2/slsa", }) if err != nil { @@ -362,3 +274,87 @@ func TestGenerateAttestation(t *testing.T) { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } + +func getResolvedDependencies(addTasks func(*objects.TaskRunObject) (*v1resourcedescriptor.ResourceDescriptor, error)) []v1resourcedescriptor.ResourceDescriptor { //nolint:staticcheck + pr := createPro("../../../testdata/v2alpha2/pipelinerun1.json") + rd, err := resolveddependencies.PipelineRun(context.Background(), pr, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}, addTasks) + if err != nil { + return []v1resourcedescriptor.ResourceDescriptor{} + } + return rd +} + +func TestGetBuildDefinition(t *testing.T) { + pr := createPro("../../../testdata/v2alpha2/pipelinerun1.json") + pr.Annotations = map[string]string{ + "annotation1": "annotation1", + } + pr.Labels = map[string]string{ + "label1": "label1", + } + tests := []struct { + name string + taskContent func(*objects.TaskRunObject) (*v1resourcedescriptor.ResourceDescriptor, error) //nolint:staticcheck + config *slsaconfig.SlsaConfig + want slsa.ProvenanceBuildDefinition + }{ + { + name: "test slsa build type", + taskContent: resolveddependencies.AddSLSATaskDescriptor, + config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa"}, + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: externalparameters.PipelineRun(pr), + InternalParameters: internalparameters.SLSAInternalParameters(pr), + ResolvedDependencies: getResolvedDependencies(resolveddependencies.AddSLSATaskDescriptor), + }, + }, + { + name: "test tekton build type", + config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa-tekton"}, + taskContent: resolveddependencies.AddSLSATaskDescriptor, + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa-tekton", + ExternalParameters: externalparameters.PipelineRun(pr), + InternalParameters: internalparameters.TektonInternalParameters(pr), + ResolvedDependencies: getResolvedDependencies(resolveddependencies.AddTektonTaskDescriptor), + }, + }, + { + name: "test default build type", + config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa"}, + taskContent: resolveddependencies.AddSLSATaskDescriptor, + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: externalparameters.PipelineRun(pr), + InternalParameters: internalparameters.SLSAInternalParameters(pr), + ResolvedDependencies: getResolvedDependencies(resolveddependencies.AddSLSATaskDescriptor), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bd, err := getBuildDefinition(context.Background(), tc.config, pr) + if err != nil { + t.Fatalf("Did not expect an error but got %v", err) + } + + if diff := cmp.Diff(tc.want, bd); diff != "" { + t.Errorf("getBuildDefinition(): -want +got: %v", diff) + } + }) + } +} + +func TestUnsupportedBuildType(t *testing.T) { + pr := createPro("../../../testdata/v2alpha2/pipelinerun1.json") + + got, err := getBuildDefinition(context.Background(), &slsaconfig.SlsaConfig{BuildType: "bad-buildtype"}, pr) + if err == nil { + t.Error("getBuildDefinition(): expected error got nil") + } + if diff := cmp.Diff(slsa.ProvenanceBuildDefinition{}, got); diff != "" { + t.Errorf("getBuildDefinition(): -want +got: %s", diff) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies.go b/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies.go index fcd6130b18..54fb4e1454 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies.go @@ -42,87 +42,40 @@ const ( pipelineResourceName = "pipelineResource" ) -// TaskRun constructs `predicate.resolvedDependencies` section by collecting all the artifacts that influence a taskrun such as source code repo and step&sidecar base images. -func TaskRun(ctx context.Context, tro *objects.TaskRunObject) ([]v1.ResourceDescriptor, error) { - var resolvedDependencies []v1.ResourceDescriptor - var err error - - // add top level task config - if p := tro.Status.Provenance; p != nil && p.RefSource != nil { - rd := v1.ResourceDescriptor{ - Name: taskConfigName, - URI: p.RefSource.URI, - Digest: p.RefSource.Digest, - } - resolvedDependencies = append(resolvedDependencies, rd) - } - - mats := []common.ProvenanceMaterial{} - - // add step and sidecar images - stepMaterials, err := material.FromStepImages(tro) - mats = append(mats, stepMaterials...) - if err != nil { - return nil, err - } - sidecarMaterials, err := material.FromSidecarImages(tro) - if err != nil { - return nil, err - } - mats = append(mats, sidecarMaterials...) - resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, "")...) - - mats = material.FromTaskParamsAndResults(ctx, tro) - // convert materials to resolved dependencies - resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, inputResultName)...) +// used to toggle the fields in resolvedDependencies. see AddTektonTaskDescriptor +// and AddSLSATaskDescriptor +type addTaskDescriptorContent func(*objects.TaskRunObject) (*v1.ResourceDescriptor, error) //nolint:staticcheck - // add task resources - mats = material.FromTaskResources(ctx, tro) - // convert materials to resolved dependencies - resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, pipelineResourceName)...) - - // remove duplicate resolved dependencies - resolvedDependencies, err = removeDuplicateResolvedDependencies(resolvedDependencies) +// the more verbose resolved dependency content. this adds the name, uri, digest +// and content if possible. +func AddTektonTaskDescriptor(tr *objects.TaskRunObject) (*v1.ResourceDescriptor, error) { //nolint:staticcheck + rd := v1.ResourceDescriptor{} + storedTr, err := json.Marshal(tr) if err != nil { return nil, err } - return resolvedDependencies, nil -} - -// PipelineRun constructs `predicate.resolvedDependencies` section by collecting all the artifacts that influence a pipeline run such as source code repo and step&sidecar base images. -func PipelineRun(ctx context.Context, pro *objects.PipelineRunObject, slsaconfig *slsaconfig.SlsaConfig) ([]v1.ResourceDescriptor, error) { - var err error - var resolvedDependencies []v1.ResourceDescriptor - logger := logging.FromContext(ctx) - - // add pipeline config to resolved dependencies - if p := pro.Status.Provenance; p != nil && p.RefSource != nil { - rd := v1.ResourceDescriptor{ - Name: pipelineConfigName, - URI: p.RefSource.URI, - Digest: p.RefSource.Digest, - } - resolvedDependencies = append(resolvedDependencies, rd) - } - // add resolved dependencies from pipeline tasks - rds, err := fromPipelineTask(logger, pro) - if err != nil { - return nil, err + rd.Name = pipelineTaskConfigName + rd.Content = storedTr + if tr.Status.Provenance != nil && tr.Status.Provenance.RefSource != nil { + rd.URI = tr.Status.Provenance.RefSource.URI + rd.Digest = tr.Status.Provenance.RefSource.Digest } - resolvedDependencies = append(resolvedDependencies, rds...) - // add resolved dependencies from pipeline results - mats := material.FromPipelineParamsAndResults(ctx, pro, slsaconfig) - // convert materials to resolved dependencies - resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, inputResultName)...) + return &rd, nil +} - // remove duplicate resolved dependencies - resolvedDependencies, err = removeDuplicateResolvedDependencies(resolvedDependencies) - if err != nil { - return nil, err +// resolved dependency content for the more generic slsa verifiers. just logs +// the name, uri and digest. +func AddSLSATaskDescriptor(tr *objects.TaskRunObject) (*v1.ResourceDescriptor, error) { //nolint:staticcheck + if tr.Status.Provenance != nil && tr.Status.Provenance.RefSource != nil { + return &v1.ResourceDescriptor{ + Name: pipelineTaskConfigName, + URI: tr.Status.Provenance.RefSource.URI, + Digest: tr.Status.Provenance.RefSource.Digest, + }, nil } - return resolvedDependencies, nil + return nil, nil } // convertMaterialToResolvedDependency converts a SLSAv0.2 Material to a resolved dependency @@ -153,6 +106,8 @@ func removeDuplicateResolvedDependencies(resolvedDependencies []v1.ResourceDescr rDep := v1.ResourceDescriptor{} rDep.URI = resolvedDependency.URI rDep.Digest = resolvedDependency.Digest + // pipelinTasks store content with the slsa-tekton buildType + rDep.Content = resolvedDependency.Content // This allows us to ignore dependencies that have the same uri and digest. rd, err := json.Marshal(rDep) if err != nil { @@ -176,7 +131,7 @@ func removeDuplicateResolvedDependencies(resolvedDependencies []v1.ResourceDescr // fromPipelineTask adds the resolved dependencies from pipeline tasks // such as pipeline task uri/digest for remote pipeline tasks and step and sidecar images. -func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObject) ([]v1.ResourceDescriptor, error) { +func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObject, addTasks addTaskDescriptorContent) ([]v1.ResourceDescriptor, error) { pSpec := pro.Status.PipelineSpec resolvedDependencies := []v1.ResourceDescriptor{} if pSpec != nil { @@ -188,14 +143,14 @@ func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObject) logger.Infof("taskrun status not found for task %s", t.Name) continue } - // add remote task configsource information in materials - if tr.Status.Provenance != nil && tr.Status.Provenance.RefSource != nil { - rd := v1.ResourceDescriptor{ - Name: pipelineTaskConfigName, - URI: tr.Status.Provenance.RefSource.URI, - Digest: tr.Status.Provenance.RefSource.Digest, - } - resolvedDependencies = append(resolvedDependencies, rd) + rd, err := addTasks(tr) + if err != nil { + logger.Errorf("error storing taskRun %s, error: %s", t.Name, err) + continue + } + + if rd != nil { + resolvedDependencies = append(resolvedDependencies, *rd) } mats := []common.ProvenanceMaterial{} @@ -220,3 +175,100 @@ func fromPipelineTask(logger *zap.SugaredLogger, pro *objects.PipelineRunObject) } return resolvedDependencies, nil } + +// taskDependencies gather all dependencies in a task and adds them to resolvedDependencies +func taskDependencies(ctx context.Context, tr *objects.TaskRunObject) ([]v1.ResourceDescriptor, error) { + var resolvedDependencies []v1.ResourceDescriptor + var err error + mats := []common.ProvenanceMaterial{} + + // add step and sidecar images + stepMaterials, err := material.FromStepImages(tr) + mats = append(mats, stepMaterials...) + if err != nil { + return nil, err + } + sidecarMaterials, err := material.FromSidecarImages(tr) + if err != nil { + return nil, err + } + mats = append(mats, sidecarMaterials...) + resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, "")...) + + mats = material.FromTaskParamsAndResults(ctx, tr) + // convert materials to resolved dependencies + resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, inputResultName)...) + + // add task resources + mats = material.FromTaskResources(ctx, tr) + // convert materials to resolved dependencies + resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, pipelineResourceName)...) + + // remove duplicate resolved dependencies + resolvedDependencies, err = removeDuplicateResolvedDependencies(resolvedDependencies) + if err != nil { + return nil, err + } + + return resolvedDependencies, nil +} + +// TaskRun constructs `predicate.resolvedDependencies` section by collecting all the artifacts that influence a taskrun such as source code repo and step&sidecar base images. +func TaskRun(ctx context.Context, tro *objects.TaskRunObject) ([]v1.ResourceDescriptor, error) { + var resolvedDependencies []v1.ResourceDescriptor + var err error + + // add top level task config + if p := tro.Status.Provenance; p != nil && p.RefSource != nil { + rd := v1.ResourceDescriptor{ + Name: taskConfigName, + URI: p.RefSource.URI, + Digest: p.RefSource.Digest, + } + resolvedDependencies = append(resolvedDependencies, rd) + } + + rds, err := taskDependencies(ctx, tro) + if err != nil { + return nil, err + } + resolvedDependencies = append(resolvedDependencies, rds...) + + return resolvedDependencies, nil +} + +// PipelineRun constructs `predicate.resolvedDependencies` section by collecting all the artifacts that influence a pipeline run such as source code repo and step&sidecar base images. +func PipelineRun(ctx context.Context, pro *objects.PipelineRunObject, slsaconfig *slsaconfig.SlsaConfig, addTasks addTaskDescriptorContent) ([]v1.ResourceDescriptor, error) { + var err error + var resolvedDependencies []v1.ResourceDescriptor + logger := logging.FromContext(ctx) + + // add pipeline config to resolved dependencies + if p := pro.Status.Provenance; p != nil && p.RefSource != nil { + rd := v1.ResourceDescriptor{ + Name: pipelineConfigName, + URI: p.RefSource.URI, + Digest: p.RefSource.Digest, + } + resolvedDependencies = append(resolvedDependencies, rd) + } + + // add resolved dependencies from pipeline tasks + rds, err := fromPipelineTask(logger, pro, addTasks) + if err != nil { + return nil, err + } + resolvedDependencies = append(resolvedDependencies, rds...) + + // add resolved dependencies from pipeline results + mats := material.FromPipelineParamsAndResults(ctx, pro, slsaconfig) + // convert materials to resolved dependencies + resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, inputResultName)...) + + // remove duplicate resolved dependencies + resolvedDependencies, err = removeDuplicateResolvedDependencies(resolvedDependencies) + if err != nil { + return nil, err + } + return resolvedDependencies, nil +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go index 046d2fc102..d8013bdf07 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go @@ -17,12 +17,14 @@ limitations under the License. package resolveddependencies import ( + "encoding/json" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" v1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + v1slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/internal/backport" "github.com/tektoncd/chains/pkg/artifacts" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" @@ -64,208 +66,30 @@ func createPro(path string) *objects.PipelineRunObject { return p } -func TestTaskRun(t *testing.T) { - tests := []struct { - name string - taskRun *v1beta1.TaskRun - want []v1.ResourceDescriptor - }{{ - name: "resolvedDependencies from pipeline resources", - taskRun: &v1beta1.TaskRun{ - Spec: v1beta1.TaskRunSpec{ - Resources: &v1beta1.TaskRunResources{ //nolint:all //incompatible with pipelines v0.45 - Inputs: []v1beta1.TaskResourceBinding{ //nolint:all //incompatible with pipelines v0.45 - { - PipelineResourceBinding: v1beta1.PipelineResourceBinding{ //nolint:all //incompatible with pipelines v0.45 - Name: "nil-resource-spec", - }, - }, { - PipelineResourceBinding: v1beta1.PipelineResourceBinding{ //nolint:all //incompatible with pipelines v0.45 - Name: "repo", - ResourceSpec: &v1alpha1.PipelineResourceSpec{ //nolint:all //incompatible with pipelines v0.45 - Params: []v1alpha1.ResourceParam{ //nolint:all //incompatible with pipelines v0.45 - {Name: "url", Value: "https://github.com/GoogleContainerTools/distroless"}, - }, - Type: backport.PipelineResourceTypeGit, - }, - }, - }, - }, - }, - }, - Status: v1beta1.TaskRunStatus{ - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - TaskRunResults: []v1beta1.TaskRunResult{ - { - Name: "img1_input" + "-" + artifacts.ArtifactsInputsResultName, - Value: *v1beta1.NewObject(map[string]string{ - "uri": "gcr.io/foo/bar", - "digest": digest, - }), - }, - }, - ResourcesResult: []v1beta1.PipelineResourceResult{ - { - ResourceName: "repo", - Key: "commit", - Value: "50c56a48cfb3a5a80fa36ed91c739bdac8381cbe", - }, { - ResourceName: "repo", - Key: "url", - Value: "https://github.com/GoogleContainerTools/distroless", - }, - }, - }, - }, - }, - want: []v1.ResourceDescriptor{ - { - Name: "inputs/result", - URI: "gcr.io/foo/bar", - Digest: common.DigestSet{ - "sha256": strings.TrimPrefix(digest, "sha256:"), - }, - }, - { - Name: "pipelineResource", - URI: "git+https://github.com/GoogleContainerTools/distroless.git", - Digest: common.DigestSet{ - "sha1": "50c56a48cfb3a5a80fa36ed91c739bdac8381cbe", - }, - }, - }, - }, { - name: "resolvedDependencies from remote task", - taskRun: &v1beta1.TaskRun{ - Status: v1beta1.TaskRunStatus{ - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - Provenance: &v1beta1.Provenance{ - RefSource: &v1beta1.RefSource{ - URI: "git+github.com/something.git", - Digest: map[string]string{ - "sha1": "abcd1234", - }, - }, - }, - }, - }, - }, - want: []v1.ResourceDescriptor{ - { - Name: "task", - URI: "git+github.com/something.git", - Digest: common.DigestSet{ - "sha1": "abcd1234", - }, - }, - }, - }, { - name: "git resolvedDependencies from taskrun params", - taskRun: &v1beta1.TaskRun{ - Spec: v1beta1.TaskRunSpec{ - Params: []v1beta1.Param{{ - Name: "CHAINS-GIT_COMMIT", - Value: *v1beta1.NewStructuredValues("my-commit"), - }, { - Name: "CHAINS-GIT_URL", - Value: *v1beta1.NewStructuredValues("github.com/something"), - }}, - }, - }, - want: []v1.ResourceDescriptor{ - { - Name: "inputs/result", - URI: "git+github.com/something.git", - Digest: common.DigestSet{ - "sha1": "my-commit", - }, - }, - }, - }, { - name: "resolvedDependencies from step images", - taskRun: &v1beta1.TaskRun{ - Status: v1beta1.TaskRunStatus{ - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - Steps: []v1beta1.StepState{{ - Name: "git-source-repo-jwqcl", - ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", - }, { - Name: "git-source-repo-repeat-again-jwqcl", - ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", - }, { - Name: "build", - ImageID: "gcr.io/cloud-marketplace-containers/google/bazel@sha256:010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", - }}, - }, - }, - }, - want: []v1.ResourceDescriptor{ - { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", - Digest: common.DigestSet{ - "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", - }, - }, - { - URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", - Digest: common.DigestSet{ - "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", - }, - }, - }, - }, { - name: "resolvedDependencies from step and sidecar images", - taskRun: &v1beta1.TaskRun{ - Status: v1beta1.TaskRunStatus{ - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - Steps: []v1beta1.StepState{{ - Name: "git-source-repo-jwqcl", - ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", - }, { - Name: "git-source-repo-repeat-again-jwqcl", - ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", - }, { - Name: "build", - ImageID: "gcr.io/cloud-marketplace-containers/google/bazel@sha256:010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", - }}, - Sidecars: []v1beta1.SidecarState{{ - Name: "sidecar-jwqcl", - ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init@sha256:a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", - }}, - }, - }, - }, - want: []v1.ResourceDescriptor{ - { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", - Digest: common.DigestSet{ - "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", - }, - }, { - URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", - Digest: common.DigestSet{ - "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", - }, - }, { - URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", - Digest: common.DigestSet{ - "sha256": "a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", - }, - }, - }, - }} - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := logtesting.TestContextWithLogger(t) - rd, err := TaskRun(ctx, objects.NewTaskRunObject(tc.taskRun)) - if err != nil { - t.Fatalf("Did not expect an error but got %v", err) - } - if diff := cmp.Diff(tc.want, rd); diff != "" { - t.Errorf("ResolvedDependencies(): -want +got: %s", diff) - } - }) +func tektonTaskRuns() map[string][]byte { + trs := make(map[string][]byte) + tr1, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun1.json") + if err != nil { + panic(err) } + tr2, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun2.json") + if err != nil { + panic(err) + } + + tr1Desc, err := json.Marshal(tr1) + if err != nil { + panic(err) + } + trs[tr1.Name] = tr1Desc + + tr2Desc, err := json.Marshal(tr2) + if err != nil { + panic(err) + } + trs[tr2.Name] = tr2Desc + + return trs } func TestRemoveDuplicates(t *testing.T) { @@ -484,38 +308,281 @@ func TestRemoveDuplicates(t *testing.T) { } } -func TestPipelineRun(t *testing.T) { - expected := []v1.ResourceDescriptor{ - {Name: "pipeline", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, - {Name: "pipelineTask", URI: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, - { - URI: "oci://gcr.io/test1/test1", - Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, +func TestTaskRun(t *testing.T) { + tests := []struct { + name string + taskRun *v1beta1.TaskRun //nolint:staticcheck + want []v1.ResourceDescriptor + }{{ + name: "resolvedDependencies from pipeline resources", + taskRun: &v1beta1.TaskRun{ //nolint:staticcheck + Spec: v1beta1.TaskRunSpec{ + Resources: &v1beta1.TaskRunResources{ //nolint:all //incompatible with pipelines v0.45 + Inputs: []v1beta1.TaskResourceBinding{ //nolint:all //incompatible with pipelines v0.45 + { + PipelineResourceBinding: v1beta1.PipelineResourceBinding{ //nolint:all //incompatible with pipelines v0.45 + Name: "nil-resource-spec", + }, + }, { + PipelineResourceBinding: v1beta1.PipelineResourceBinding{ //nolint:all //incompatible with pipelines v0.45 + Name: "repo", + ResourceSpec: &v1alpha1.PipelineResourceSpec{ //nolint:all //incompatible with pipelines v0.45 + Params: []v1alpha1.ResourceParam{ //nolint:all //incompatible with pipelines v0.45 + {Name: "url", Value: "https://github.com/GoogleContainerTools/distroless"}, + }, + Type: backport.PipelineResourceTypeGit, + }, + }, + }, + }, + }, + }, + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + TaskRunResults: []v1beta1.TaskRunResult{ + { + Name: "img1_input" + "-" + artifacts.ArtifactsInputsResultName, + Value: *v1beta1.NewObject(map[string]string{ + "uri": "gcr.io/foo/bar", + "digest": digest, + }), + }, + }, + ResourcesResult: []v1beta1.PipelineResourceResult{ + { + ResourceName: "repo", + Key: "commit", + Value: "50c56a48cfb3a5a80fa36ed91c739bdac8381cbe", + }, { + ResourceName: "repo", + Key: "url", + Value: "https://github.com/GoogleContainerTools/distroless", + }, + }, + }, + }, }, - {Name: "pipelineTask", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, + want: []v1.ResourceDescriptor{ + { + Name: "inputs/result", + URI: "gcr.io/foo/bar", + Digest: common.DigestSet{ + "sha256": strings.TrimPrefix(digest, "sha256:"), + }, + }, + { + Name: "pipelineResource", + URI: "git+https://github.com/GoogleContainerTools/distroless.git", + Digest: common.DigestSet{ + "sha1": "50c56a48cfb3a5a80fa36ed91c739bdac8381cbe", + }, + }, + }, + }, { + name: "resolvedDependencies from remote task", + taskRun: &v1beta1.TaskRun{ //nolint:staticcheck + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + Provenance: &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + URI: "git+github.com/something.git", + Digest: map[string]string{ + "sha1": "abcd1234", + }, + }, + }, + }, + }, + }, + want: []v1.ResourceDescriptor{ + { + Name: "task", + URI: "git+github.com/something.git", + Digest: common.DigestSet{ + "sha1": "abcd1234", + }, + }, + }, + }, { + name: "git resolvedDependencies from taskrun params", + taskRun: &v1beta1.TaskRun{ //nolint:staticcheck + Spec: v1beta1.TaskRunSpec{ + Params: []v1beta1.Param{{ + Name: "CHAINS-GIT_COMMIT", + Value: *v1beta1.NewStructuredValues("my-commit"), + }, { + Name: "CHAINS-GIT_URL", + Value: *v1beta1.NewStructuredValues("github.com/something"), + }}, + }, + }, + want: []v1.ResourceDescriptor{ + { + Name: "inputs/result", + URI: "git+github.com/something.git", + Digest: common.DigestSet{ + "sha1": "my-commit", + }, + }, + }, + }, { + name: "resolvedDependencies from step images", + taskRun: &v1beta1.TaskRun{ //nolint:staticcheck + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + Steps: []v1beta1.StepState{{ + Name: "git-source-repo-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, { + Name: "git-source-repo-repeat-again-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, { + Name: "build", + ImageID: "gcr.io/cloud-marketplace-containers/google/bazel@sha256:010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", + }}, + }, + }, + }, + want: []v1.ResourceDescriptor{ + { + URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Digest: common.DigestSet{ + "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, + }, + { + URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Digest: common.DigestSet{ + "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", + }, + }, + }, + }, { + name: "resolvedDependencies from step and sidecar images", + taskRun: &v1beta1.TaskRun{ //nolint:staticcheck + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + Steps: []v1beta1.StepState{{ + Name: "git-source-repo-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, { + Name: "git-source-repo-repeat-again-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, { + Name: "build", + ImageID: "gcr.io/cloud-marketplace-containers/google/bazel@sha256:010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", + }}, + Sidecars: []v1beta1.SidecarState{{ + Name: "sidecar-jwqcl", + ImageID: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init@sha256:a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", + }}, + }, + }, + }, + want: []v1.ResourceDescriptor{ + { + URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init", + Digest: common.DigestSet{ + "sha256": "b963f6e7a69617db57b685893256f978436277094c21d43b153994acd8a01247", + }, + }, { + URI: "oci://gcr.io/cloud-marketplace-containers/google/bazel", + Digest: common.DigestSet{ + "sha256": "010a1ecd1a8c3610f12039a25b823e3a17bd3e8ae455a53e340dcfdd37a49964", + }, + }, { + URI: "oci://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/sidecar-git-init", + Digest: common.DigestSet{ + "sha256": "a1234f6e7a69617db57b685893256f978436277094c21d43b153994acd8a09567", + }, + }, + }, + }} + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + rd, err := TaskRun(ctx, objects.NewTaskRunObject(tc.taskRun)) + if err != nil { + t.Fatalf("Did not expect an error but got %v", err) + } + if diff := cmp.Diff(tc.want, rd); diff != "" { + t.Errorf("ResolvedDependencies(): -want +got: %s", diff) + } + }) + } +} + +func TestPipelineRun(t *testing.T) { + taskRuns := tektonTaskRuns() + tests := []struct { + name string + taskDescriptor addTaskDescriptorContent + want []v1slsa.ResourceDescriptor + }{ { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + name: "test slsa build type", + taskDescriptor: AddSLSATaskDescriptor, + want: []v1slsa.ResourceDescriptor{ + {Name: "pipeline", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, + {Name: "pipelineTask", URI: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, + { + URI: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + {Name: "pipelineTask", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, + { + URI: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + }, + { + URI: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + }, + {Name: "inputs/result", URI: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, + {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + }, }, { - URI: "oci://gcr.io/test3/test3", - Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + name: "test tekton build type", + taskDescriptor: AddTektonTaskDescriptor, + want: []v1slsa.ResourceDescriptor{ + {Name: "pipeline", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, + {Name: "pipelineTask", URI: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}, Content: taskRuns["git-clone"]}, + { + URI: "oci://gcr.io/test1/test1", + Digest: common.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + {Name: "pipelineTask", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}, Content: taskRuns["taskrun-build"]}, + { + URI: "oci://gcr.io/test2/test2", + Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + }, + { + URI: "oci://gcr.io/test3/test3", + Digest: common.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + }, + {Name: "inputs/result", URI: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, + {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, + }, }, - {Name: "inputs/result", URI: "abc", Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"}}, - {Name: "inputs/result", URI: "git+https://git.test.com.git", Digest: common.DigestSet{"sha1": "abcd"}}, } + ctx := logtesting.TestContextWithLogger(t) - got, err := PipelineRun(ctx, pro, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}) - if err != nil { - t.Error(err) - } - if diff := cmp.Diff(expected, got, compare.SLSAV1CompareOptions()...); diff != "" { - t.Errorf("PipelineRunResolvedDependencies(): -want +got: %s", diff) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := PipelineRun(ctx, pro, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}, tc.taskDescriptor) + if err != nil { + t.Error(err) + } + if d := cmp.Diff(tc.want, got); d != "" { + t.Errorf("PipelineRunResolvedDependencies(): -want +got: %s", got) + } + }) } } func TestPipelineRunStructuredResult(t *testing.T) { - want := []v1.ResourceDescriptor{ + want := []v1slsa.ResourceDescriptor{ {Name: "pipeline", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "28b123"}}, {Name: "pipelineTask", URI: "git+https://github.com/catalog", Digest: common.DigestSet{"sha1": "x123"}}, { @@ -539,15 +606,13 @@ func TestPipelineRunStructuredResult(t *testing.T) { }, }, { - Name: "inputs/result", - URI: "git+https://git.test.com.git", - Digest: common.DigestSet{ - "sha1": "abcd", - }, + URI: "git+https://git.test.com.git", + Digest: common.DigestSet{"sha1": "abcd"}, + Name: "inputs/result", }, } ctx := logtesting.TestContextWithLogger(t) - got, err := PipelineRun(ctx, pro, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}) + got, err := PipelineRun(ctx, pro, &slsaconfig.SlsaConfig{DeepInspectionEnabled: false}, AddSLSATaskDescriptor) if err != nil { t.Errorf("error while extracting resolvedDependencies: %v", err) } diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go index 3d5249be3e..9f53d253f0 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go @@ -22,6 +22,9 @@ 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" + buildtypes "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/build_types" + externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters" + internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters" resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies" "github.com/tektoncd/chains/pkg/chains/objects" ) @@ -30,14 +33,16 @@ const taskRunResults = "taskRunResults/%s" // GenerateAttestation generates a provenance statement with SLSA v1.0 predicate for a task run. func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObject, slsaConfig *slsaconfig.SlsaConfig) (interface{}, error) { - rd, err := resolveddependencies.TaskRun(ctx, tro) + bp, err := byproducts(tro) if err != nil { return nil, err } - bp, err := byproducts(tro) + + bd, err := getBuildDefinition(ctx, slsaConfig.BuildType, tro) if err != nil { return nil, err } + att := intoto.ProvenanceStatementSLSA1{ StatementHeader: intoto.StatementHeader{ Type: intoto.StatementInTotoV01, @@ -45,12 +50,7 @@ func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObject, slsaCo Subject: extract.SubjectDigests(ctx, tro, slsaConfig), }, Predicate: slsa.ProvenancePredicate{ - BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalParameters(tro), - InternalParameters: internalParameters(tro), - ResolvedDependencies: rd, - }, + BuildDefinition: bd, RunDetails: slsa.ProvenanceRunDetails{ Builder: slsa.Builder{ ID: slsaConfig.BuilderID, @@ -78,45 +78,6 @@ func metadata(tro *objects.TaskRunObject) slsa.BuildMetadata { return m } -// internalParameters adds the tekton feature flags that were enabled -// for the taskrun. -func internalParameters(tro *objects.TaskRunObject) map[string]any { - internalParams := make(map[string]any) - provenance := tro.GetProvenance() - if provenance != nil && provenance.FeatureFlags != nil { - internalParams["tekton-pipelines-feature-flags"] = *provenance.FeatureFlags - } - return internalParams -} - -// externalParameters adds the task run spec -func externalParameters(tro *objects.TaskRunObject) map[string]any { - externalParams := make(map[string]any) - // add origin of the top level task config - // isRemoteTask checks if the task was fetched using a remote resolver - isRemoteTask := false - if tro.Spec.TaskRef != nil { - if tro.Spec.TaskRef.Resolver != "" && tro.Spec.TaskRef.Resolver != "Cluster" { - isRemoteTask = true - } - } - if t := tro.Status.Provenance; t != nil && t.RefSource != nil && isRemoteTask { - ref := "" - for alg, hex := range t.RefSource.Digest { - ref = fmt.Sprintf("%s:%s", alg, hex) - break - } - buildConfigSource := map[string]string{ - "ref": ref, - "repository": t.RefSource.URI, - "path": t.RefSource.EntryPoint, - } - externalParams["buildConfigSource"] = buildConfigSource - } - externalParams["runSpec"] = tro.Spec - return externalParams -} - // byproducts contains the taskRunResults func byproducts(tro *objects.TaskRunObject) ([]slsa.ResourceDescriptor, error) { byProd := []slsa.ResourceDescriptor{} @@ -134,3 +95,39 @@ func byproducts(tro *objects.TaskRunObject) ([]slsa.ResourceDescriptor, error) { } return byProd, nil } + +// getBuildDefinition get the buildDefinition based on the configured buildType. This will default to the slsa buildType +func getBuildDefinition(ctx context.Context, buildType string, tro *objects.TaskRunObject) (slsa.ProvenanceBuildDefinition, error) { + // if buildType is not set in the chains-config, default to slsa build type + buildDefinitionType := buildType + if buildType == "" { + buildDefinitionType = buildtypes.SlsaBuildType + } + + switch buildDefinitionType { + case buildtypes.SlsaBuildType: + rd, err := resolveddependencies.TaskRun(ctx, tro) + if err != nil { + return slsa.ProvenanceBuildDefinition{}, err + } + return slsa.ProvenanceBuildDefinition{ + BuildType: buildDefinitionType, + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.SLSAInternalParameters(tro), + ResolvedDependencies: rd, + }, nil + case buildtypes.TektonBuildType: + rd, err := resolveddependencies.TaskRun(ctx, tro) + if err != nil { + return slsa.ProvenanceBuildDefinition{}, err + } + return slsa.ProvenanceBuildDefinition{ + BuildType: buildDefinitionType, + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.TektonInternalParameters(tro), + ResolvedDependencies: rd, + }, nil + default: + return slsa.ProvenanceBuildDefinition{}, fmt.Errorf("unsupported buildType %v", buildType) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun_test.go index 2df3e1861e..731d74a1cd 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun_test.go @@ -17,6 +17,7 @@ limitations under the License. package taskrun import ( + "context" "encoding/json" "testing" "time" @@ -26,8 +27,12 @@ import ( "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + v1resourcedescriptor "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/external_parameters" + internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/internal_parameters" "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" "github.com/tektoncd/chains/pkg/internal/objectloader" "github.com/tektoncd/pipeline/pkg/apis/config" @@ -37,7 +42,7 @@ import ( ) func TestMetadata(t *testing.T) { - tr := &v1beta1.TaskRun{ + tr := &v1beta1.TaskRun{ //nolint:staticcheck ObjectMeta: v1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -68,7 +73,7 @@ func TestMetadata(t *testing.T) { func TestMetadataInTimeZone(t *testing.T) { tz := time.FixedZone("Test Time", int((12 * time.Hour).Seconds())) - tr := &v1beta1.TaskRun{ + tr := &v1beta1.TaskRun{ //nolint:staticcheck ObjectMeta: v1.ObjectMeta{ Name: "my-taskrun", Namespace: "my-namespace", @@ -97,97 +102,9 @@ func TestMetadataInTimeZone(t *testing.T) { } } -func TestExternalParameters(t *testing.T) { - tr := &v1beta1.TaskRun{ - Spec: v1beta1.TaskRunSpec{ - Params: v1beta1.Params{ - { - Name: "my-param", - Value: v1beta1.ResultValue{Type: "string", StringVal: "string-param"}, - }, - { - Name: "my-array-param", - Value: v1beta1.ResultValue{Type: "array", ArrayVal: []string{"my", "array"}}, - }, - { - Name: "my-empty-string-param", - Value: v1beta1.ResultValue{Type: "string"}, - }, - { - Name: "my-empty-array-param", - Value: v1beta1.ResultValue{Type: "array", ArrayVal: []string{}}, - }, - }, - TaskRef: &v1beta1.TaskRef{ - ResolverRef: v1beta1.ResolverRef{ - Resolver: "git", - }, - }, - }, - Status: v1beta1.TaskRunStatus{ - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - Provenance: &v1beta1.Provenance{ - RefSource: &v1beta1.RefSource{ - URI: "hello", - Digest: map[string]string{ - "sha1": "abc123", - }, - EntryPoint: "task.yaml", - }, - }, - }, - }, - } - - want := map[string]any{ - "buildConfigSource": map[string]string{"path": "task.yaml", "ref": "sha1:abc123", "repository": "hello"}, - "runSpec": tr.Spec, - } - got := externalParameters(objects.NewTaskRunObject(tr)) - if d := cmp.Diff(want, got); d != "" { - t.Fatalf("externalParameters (-want, +got):\n%s", d) - } -} - -func TestInternalParameters(t *testing.T) { - tr := &v1beta1.TaskRun{ - Status: v1beta1.TaskRunStatus{ - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - Provenance: &v1beta1.Provenance{ - FeatureFlags: &config.FeatureFlags{ - RunningInEnvWithInjectedSidecars: true, - EnableAPIFields: "stable", - AwaitSidecarReadiness: true, - VerificationNoMatchPolicy: "skip", - EnableProvenanceInStatus: true, - ResultExtractionMethod: "termination-message", - MaxResultSize: 4096, - }, - }, - }, - }, - } - - want := map[string]any{ - "tekton-pipelines-feature-flags": config.FeatureFlags{ - RunningInEnvWithInjectedSidecars: true, - EnableAPIFields: "stable", - AwaitSidecarReadiness: true, - VerificationNoMatchPolicy: "skip", - EnableProvenanceInStatus: true, - ResultExtractionMethod: "termination-message", - MaxResultSize: 4096, - }, - } - got := internalParameters(objects.NewTaskRunObject(tr)) - if d := cmp.Diff(want, got); d != "" { - t.Fatalf("internalParameters (-want, +got):\n%s", d) - } -} - func TestByProducts(t *testing.T) { resultValue := v1beta1.ResultValue{Type: "string", StringVal: "result-value"} - tr := &v1beta1.TaskRun{ + tr := &v1beta1.TaskRun{ //nolint:staticcheck Status: v1beta1.TaskRunStatus{ TaskRunStatusFields: v1beta1.TaskRunStatusFields{ TaskRunResults: []v1beta1.TaskRunResult{ @@ -310,6 +227,7 @@ func TestTaskRunGenerateAttestation(t *testing.T) { got, err := GenerateAttestation(ctx, objects.NewTaskRunObject(tr), &slsaconfig.SlsaConfig{ BuilderID: "test_builder-1", + BuildType: "https://tekton.dev/chains/v2/slsa", }) if err != nil { @@ -319,3 +237,96 @@ func TestTaskRunGenerateAttestation(t *testing.T) { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } + +func getResolvedDependencies(tro *objects.TaskRunObject) []v1resourcedescriptor.ResourceDescriptor { + rd, err := resolveddependencies.TaskRun(context.Background(), tro) + if err != nil { + return []v1resourcedescriptor.ResourceDescriptor{} + } + return rd +} + +func TestGetBuildDefinition(t *testing.T) { + tr, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun1.json") + if err != nil { + t.Fatal(err) + } + + tr.Annotations = map[string]string{ + "annotation1": "annotation1", + } + tr.Labels = map[string]string{ + "label1": "label1", + } + + tro := objects.NewTaskRunObject(tr) + tests := []struct { + name string + buildType string + want slsa.ProvenanceBuildDefinition + err error + }{ + { + name: "test slsa build type", + buildType: "https://tekton.dev/chains/v2/slsa", + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.SLSAInternalParameters(tro), + ResolvedDependencies: getResolvedDependencies(tro), + }, + err: nil, + }, + { + name: "test default build type", + buildType: "", + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa", + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.SLSAInternalParameters(tro), + ResolvedDependencies: getResolvedDependencies(tro), + }, + err: nil, + }, + { + name: "test tekton build type", + buildType: "https://tekton.dev/chains/v2/slsa-tekton", + want: slsa.ProvenanceBuildDefinition{ + BuildType: "https://tekton.dev/chains/v2/slsa-tekton", + ExternalParameters: externalparameters.TaskRun(tro), + InternalParameters: internalparameters.TektonInternalParameters(tro), + ResolvedDependencies: getResolvedDependencies(tro), + }, + err: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bd, err := getBuildDefinition(context.Background(), tc.buildType, tro) + if err != nil { + t.Fatalf("Did not expect an error but got %v", err) + } + + if diff := cmp.Diff(tc.want, bd); diff != "" { + t.Errorf("getBuildDefinition(): -want +got: %v", diff) + } + + }) + } +} + +func TestUnsupportedBuildType(t *testing.T) { + tr, err := objectloader.TaskRunFromFile("../../../testdata/v2alpha2/taskrun1.json") + if err != nil { + t.Fatal(err) + } + + got, err := getBuildDefinition(context.Background(), "bad-buildType", objects.NewTaskRunObject(tr)) + if err == nil { + t.Error("getBuildDefinition(): expected error got nil") + } + if diff := cmp.Diff(slsa.ProvenanceBuildDefinition{}, got); diff != "" { + t.Errorf("getBuildDefinition(): -want +got: %s", diff) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/slsav2.go b/pkg/chains/formats/slsa/v2alpha2/slsav2.go index 688afa2ba0..2368b459c5 100644 --- a/pkg/chains/formats/slsa/v2alpha2/slsav2.go +++ b/pkg/chains/formats/slsa/v2alpha2/slsav2.go @@ -45,6 +45,7 @@ func NewFormatter(cfg config.Config) (formats.Payloader, error) { slsaConfig: &slsaconfig.SlsaConfig{ BuilderID: cfg.Builder.ID, DeepInspectionEnabled: cfg.Artifacts.PipelineRuns.DeepInspectionEnabled, + BuildType: cfg.BuildDefinition.BuildType, }, }, nil } diff --git a/pkg/chains/objects/objects.go b/pkg/chains/objects/objects.go index a4132c3c8e..d89204af28 100644 --- a/pkg/chains/objects/objects.go +++ b/pkg/chains/objects/objects.go @@ -66,6 +66,8 @@ type TektonObject interface { SupportsTaskRunArtifact() bool SupportsPipelineRunArtifact() bool SupportsOCIArtifact() bool + GetRemoteProvenance() *v1beta1.Provenance + IsRemote() bool } func NewTektonObject(i interface{}) (TektonObject, error) { @@ -173,6 +175,23 @@ func (tro *TaskRunObject) SupportsOCIArtifact() bool { return true } +func (tro *TaskRunObject) GetRemoteProvenance() *v1beta1.Provenance { + if t := tro.Status.Provenance; t != nil && t.RefSource != nil && tro.IsRemote() { + return tro.Status.Provenance + } + return nil +} + +func (tro *TaskRunObject) IsRemote() bool { + isRemoteTask := false + if tro.Spec.TaskRef != nil { + if tro.Spec.TaskRef.Resolver != "" && tro.Spec.TaskRef.Resolver != "Cluster" { + isRemoteTask = true + } + } + return isRemoteTask +} + // PipelineRunObject extends v1beta1.PipelineRun with additional functions. type PipelineRunObject struct { // The base PipelineRun @@ -275,6 +294,23 @@ func (pro *PipelineRunObject) SupportsOCIArtifact() bool { return false } +func (pro *PipelineRunObject) GetRemoteProvenance() *v1beta1.Provenance { + if p := pro.Status.Provenance; p != nil && p.RefSource != nil && pro.IsRemote() { + return pro.Status.Provenance + } + return nil +} + +func (pro *PipelineRunObject) IsRemote() bool { + isRemotePipeline := false + if pro.Spec.PipelineRef != nil { + if pro.Spec.PipelineRef.Resolver != "" && pro.Spec.PipelineRef.Resolver != "Cluster" { + isRemotePipeline = true + } + } + return isRemotePipeline +} + // Get the imgPullSecrets from a pod template, if they exist func getPodPullSecrets(podTemplate *pod.Template) []string { imgPullSecrets := []string{} diff --git a/pkg/chains/objects/objects_test.go b/pkg/chains/objects/objects_test.go index 68de4b8cf5..61e9817f3d 100644 --- a/pkg/chains/objects/objects_test.go +++ b/pkg/chains/objects/objects_test.go @@ -367,3 +367,68 @@ func TestPipelineRun_GetTaskRunFromTask(t *testing.T) { tr := pro.GetTaskRunFromTask("foo-task") assert.Equal(t, "foo", tr.Name) } + +func TestProvenanceExists(t *testing.T) { + pro := NewPipelineRunObject(getPipelineRun()) + provenance := &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + URI: "tekton.com", + }, + } + pro.Status.Provenance = &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + URI: "tekton.com", + }, + } + assert.Equal(t, provenance, pro.GetProvenance()) +} + +func TestPipelineRunRemoteProvenance(t *testing.T) { + pro := NewPipelineRunObject(getPipelineRun()) + provenance := &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + URI: "tekton.com", + }, + } + pro.Status.Provenance = &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + URI: "tekton.com", + }, + } + assert.Equal(t, provenance, pro.GetProvenance()) +} + +func TestTaskRunRemoteProvenance(t *testing.T) { + tro := NewTaskRunObject(getTaskRun()) + provenance := &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + URI: "tekton.com", + }, + } + tro.Status.Provenance = &v1beta1.Provenance{ + RefSource: &v1beta1.RefSource{ + URI: "tekton.com", + }, + } + assert.Equal(t, provenance, tro.GetProvenance()) +} + +func TestPipelineRunIsRemote(t *testing.T) { + pro := NewPipelineRunObject(getPipelineRun()) + pro.Spec.PipelineRef = &v1beta1.PipelineRef{ + ResolverRef: v1beta1.ResolverRef{ + Resolver: "Bundle", + }, + } + assert.Equal(t, true, pro.IsRemote()) +} + +func TestTaskRunIsRemote(t *testing.T) { + tro := NewTaskRunObject(getTaskRun()) + tro.Spec.TaskRef = &v1beta1.TaskRef{ + ResolverRef: v1beta1.ResolverRef{ + Resolver: "Bundle", + }, + } + assert.Equal(t, true, tro.IsRemote()) +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 5e936adf8c..15c49e878e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -28,11 +28,12 @@ import ( ) type Config struct { - Artifacts ArtifactConfigs - Storage StorageConfigs - Signers SignerConfigs - Builder BuilderConfig - Transparency TransparencyConfig + Artifacts ArtifactConfigs + Storage StorageConfigs + Signers SignerConfigs + Builder BuilderConfig + Transparency TransparencyConfig + BuildDefinition BuildDefinitionConfig } // ArtifactConfigs contains the configuration for how to sign/store/format the signatures for each artifact type @@ -70,6 +71,10 @@ type BuilderConfig struct { ID string } +type BuildDefinitionConfig struct { + BuildType string +} + type X509Signer struct { FulcioEnabled bool FulcioAddr string @@ -200,6 +205,9 @@ const ( transparencyEnabledKey = "transparency.enabled" transparencyURLKey = "transparency.url" + // Build type + buildTypeKey = "builddefinition.buildtype" + ChainsConfig = "chains-config" ) @@ -245,6 +253,9 @@ func defaultConfig() *Config { Builder: BuilderConfig{ ID: "https://tekton.dev/chains/v2", }, + BuildDefinition: BuildDefinitionConfig{ + BuildType: "https://tekton.dev/chains/v2/slsa", + }, } } @@ -308,6 +319,9 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { // Build config asString(builderIDKey, &cfg.Builder.ID), + + // Build type + asString(buildTypeKey, &cfg.BuildDefinition.BuildType, "https://tekton.dev/chains/v2/slsa", "https://tekton.dev/chains/v2/slsa-tekton"), ); err != nil { return nil, fmt.Errorf("failed to parse data: %w", err) } diff --git a/pkg/config/store_test.go b/pkg/config/store_test.go index b10e3fedad..88895d80a2 100644 --- a/pkg/config/store_test.go +++ b/pkg/config/store_test.go @@ -125,6 +125,10 @@ var defaultTransparency = TransparencyConfig{ URL: "https://rekor.sigstore.dev", } +var defaultBuildDefinition = BuildDefinitionConfig{ + BuildType: "https://tekton.dev/chains/v2/slsa", +} + func TestParse(t *testing.T) { tests := []struct { name string @@ -139,11 +143,12 @@ func TestParse(t *testing.T) { taskrunEnabled: true, ociEnbaled: true, want: Config{ - Builder: defaultBuilder, - Artifacts: defaultArtifacts, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Builder: defaultBuilder, + Artifacts: defaultArtifacts, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { name: "builder configuration", @@ -156,10 +161,11 @@ func TestParse(t *testing.T) { Builder: BuilderConfig{ "builder-id-test", }, - Artifacts: defaultArtifacts, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Artifacts: defaultArtifacts, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { name: "storage configuration", @@ -177,7 +183,8 @@ func TestParse(t *testing.T) { NoteHint: "a test message", }, }, - Transparency: defaultTransparency, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -205,9 +212,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -235,9 +243,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -265,9 +274,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -295,9 +305,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -325,9 +336,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -358,9 +370,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -391,9 +404,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -421,9 +435,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -441,6 +456,7 @@ func TestParse(t *testing.T) { VerifyAnnotation: true, URL: "https://rekor.sigstore.dev", }, + BuildDefinition: defaultBuildDefinition, }, }, { @@ -472,9 +488,10 @@ func TestParse(t *testing.T) { Signer: "x509", }, }, - Signers: defaultSigners, - Storage: defaultStorage, - Transparency: defaultTransparency, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { name: "fulcio", @@ -513,8 +530,9 @@ func TestParse(t *testing.T) { TUFMirrorURL: "https://tuf-repo-cdn.sigstore.dev", }, }, - Storage: defaultStorage, - Transparency: defaultTransparency, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: defaultBuildDefinition, }, }, { name: "rekor - true", @@ -538,6 +556,7 @@ func TestParse(t *testing.T) { Enabled: true, URL: "https://rekor.sigstore.dev", }, + BuildDefinition: defaultBuildDefinition, }, }, { name: "rekor - manual", @@ -562,6 +581,24 @@ func TestParse(t *testing.T) { VerifyAnnotation: true, URL: "https://rekor.sigstore.dev", }, + BuildDefinition: defaultBuildDefinition, + }, + }, { + name: "buildDefinition - slsa-tekton", + data: map[string]string{ + "builddefinition.buildtype": "https://tekton.dev/chains/v2/slsa-tekton", + }, + taskrunEnabled: true, + ociEnbaled: true, + want: Config{ + Builder: defaultBuilder, + Artifacts: defaultArtifacts, + Signers: defaultSigners, + Storage: defaultStorage, + Transparency: defaultTransparency, + BuildDefinition: BuildDefinitionConfig{ + BuildType: "https://tekton.dev/chains/v2/slsa-tekton", + }, }, }, }