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..81bc2d7c82 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(*v1beta1.TaskRun) (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..4323b326ac 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 @@ -25,6 +25,7 @@ import ( "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/material" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "go.uber.org/zap" "knative.dev/pkg/logging" ) @@ -42,87 +43,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{} +// used to toggle the fields in resolvedDependencies. see AddTektonTaskDescriptor +// and AddSLSATaskDescriptor +type addTaskDescriptorContent func(*objects.TaskRunObject) (v1.ResourceDescriptor, error) //nolint:staticcheck - // 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) +// 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 rd, 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)...) - - // 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) - if err != nil { - return nil, err + // add remote task configsource information in materials + 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 } - 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 - } - 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 *v1beta1.TaskRun) (v1.ResourceDescriptor, error) { //nolint:staticcheck + rd := v1.ResourceDescriptor{} + if tr.Status.Provenance != nil && tr.Status.Provenance.RefSource != nil { + rd.Name = pipelineTaskConfigName + rd.URI = tr.Status.Provenance.RefSource.URI + rd.Digest = tr.Status.Provenance.RefSource.Digest } - return resolvedDependencies, nil + return rd, nil } // convertMaterialToResolvedDependency converts a SLSAv0.2 Material to a resolved dependency @@ -176,7 +130,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,15 +142,11 @@ 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 { + continue } + resolvedDependencies = append(resolvedDependencies, rd) mats := []common.ProvenanceMaterial{} @@ -220,3 +170,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..b08104f601 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/tekton-slsa"), ); err != nil { return nil, fmt.Errorf("failed to parse data: %w", err) }