From 51b23bf09662ce407c69a3249bda27eeebb8eeaa 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 + .../build_definitions/build_definitions.go | 6 + .../internal/build_definitions/pipelinerun.go | 70 +++++++ .../build_definitions/pipelinerun_test.go | 188 +++++++++++++++++ .../resolved_dependencies.go | 76 +++++-- .../resolved_dependencies_test.go | 118 ++++++++--- .../internal/build_definitions/taskrun.go | 65 ++++++ .../build_definitions/taskrun_test.go | 178 ++++++++++++++++ .../internal/pipelinerun/pipelinerun.go | 86 ++++---- .../internal/pipelinerun/pipelinerun_test.go | 195 +++++++++--------- .../slsa/v2alpha2/internal/taskrun/taskrun.go | 82 ++++---- .../v2alpha2/internal/taskrun/taskrun_test.go | 181 ++++++++-------- pkg/chains/formats/slsa/v2alpha2/slsav2.go | 1 + pkg/config/config.go | 5 + 14 files changed, 937 insertions(+), 316 deletions(-) create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/build_definitions.go create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/pipelinerun.go create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/pipelinerun_test.go rename pkg/chains/formats/slsa/v2alpha2/internal/{resolved_dependencies => build_definitions}/resolved_dependencies.go (75%) rename pkg/chains/formats/slsa/v2alpha2/internal/{resolved_dependencies => build_definitions}/resolved_dependencies_test.go (81%) create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun.go create mode 100644 pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun_test.go diff --git a/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go b/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go index 64388806c9..f60c95f24c 100644 --- a/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go +++ b/pkg/chains/formats/slsa/internal/slsaconfig/slsaconfig.go @@ -18,4 +18,6 @@ package slsaconfig type SlsaConfig struct { // BuilderID is the URI of the trusted build platform. BuilderID string + // The buildType for the build definition + BuildType string } diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/build_definitions.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/build_definitions.go new file mode 100644 index 0000000000..7510b0a18a --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/build_definitions.go @@ -0,0 +1,6 @@ +package builddefinitions + +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/build_definitions/pipelinerun.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/pipelinerun.go new file mode 100644 index 0000000000..2512521ef2 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/pipelinerun.go @@ -0,0 +1,70 @@ +package builddefinitions + +import ( + "fmt" + + v1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" +) + +type PipelineBuildType struct { + BuildType string + Pro *objects.PipelineRunObject + InternalParameters func(*objects.PipelineRunObject) map[string]any + AddTaskDescriptorContent func(*v1beta1.TaskRun) (v1.ResourceDescriptor, error) +} + +// internalParameters adds the tekton feature flags that were enabled +// for the pipelinerun. +func (p PipelineBuildType) GetInternalParameters() map[string]any { + return p.InternalParameters(p.Pro) +} + +// externalParameters adds the pipeline run spec +func (p PipelineBuildType) GetExternalParameters() 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 p.Pro.Spec.PipelineRef != nil { + if p.Pro.Spec.PipelineRef.Resolver != "" && p.Pro.Spec.PipelineRef.Resolver != "Cluster" { + isRemotePipeline = true + } + } + + if p := 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"] = p.Pro.Spec + return externalParams +} + +func TektonPipelineInternalParameters(pro *objects.PipelineRunObject) map[string]any { + internalParams := make(map[string]any) + if pro.Status.Provenance != nil && pro.Status.Provenance.FeatureFlags != nil { + internalParams["tekton-pipelines-feature-flags"] = *pro.Status.Provenance.FeatureFlags + } + internalParams["labels"] = pro.ObjectMeta.Labels + internalParams["annotations"] = pro.ObjectMeta.Annotations + return internalParams +} + +func SLSAPipelineInternalParameters(pro *objects.PipelineRunObject) map[string]any { + internalParams := make(map[string]any) + if pro.Status.Provenance != nil && pro.Status.Provenance.FeatureFlags != nil { + internalParams["tekton-pipelines-feature-flags"] = *pro.Status.Provenance.FeatureFlags + } + return internalParams +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/pipelinerun_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/pipelinerun_test.go new file mode 100644 index 0000000000..9015af6e43 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/pipelinerun_test.go @@ -0,0 +1,188 @@ +package builddefinitions + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/config" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const pipelineFile = "pipeline.yaml" + +func TestPipelineInternalParameters(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, + }, + }, + }, + }, + ObjectMeta: v1.ObjectMeta{ + Annotations: map[string]string{ + "Annotation1": "Annotation1-value", + }, + Labels: map[string]string{ + "Label1": "Label1-value", + }, + }, + } + + tests := []struct { + name string + buildType PipelineBuildType + want map[string]any + }{ + { + name: "test slsa verfifier", + buildType: PipelineBuildType{ + BuildType: "slsa build", + Pro: objects.NewPipelineRunObject(pr), + InternalParameters: SLSAPipelineInternalParameters, + }, + 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, + }, + }, + }, + { + name: "test tekton verfifier", + buildType: PipelineBuildType{ + BuildType: "tekton build", + Pro: objects.NewPipelineRunObject(pr), + InternalParameters: TektonPipelineInternalParameters, + }, + 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, + }, + "annotations": map[string]string{ + "Annotation1": "Annotation1-value", + }, + "labels": map[string]string{ + "Label1": "Label1-value", + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := tc.buildType.GetInternalParameters() + if d := cmp.Diff(tc.want, got); d != "" { + t.Fatalf("internalParameters (-want, +got):\n%s", d) + } + }) + } +} + +func TestPipelineExternalParameters(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: pipelineFile, + }, + }, + }, + }, + } + + tests := []struct { + name string + buildType PipelineBuildType + want map[string]any + }{ + { + name: "test slsa verfifier", + buildType: PipelineBuildType{ + BuildType: "slsa build", + Pro: objects.NewPipelineRunObject(pr), + }, + want: map[string]any{ + "buildConfigSource": map[string]string{ + "path": pipelineFile, + "ref": "sha1:abc123", + "repository": "hello", + }, + "runSpec": pr.Spec, + }, + }, + { + name: "test tekton verfifier", + buildType: PipelineBuildType{ + BuildType: "tekton build", + Pro: objects.NewPipelineRunObject(pr), + }, + want: map[string]any{ + "buildConfigSource": map[string]string{ + "path": pipelineFile, + "ref": "sha1:abc123", + "repository": "hello", + }, + "runSpec": pr.Spec, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := tc.buildType.GetExternalParameters() + if d := cmp.Diff(tc.want, got); d != "" { + t.Fatalf("internalParameters (-want, +got):\n%s", d) + } + }) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/resolved_dependencies.go similarity index 75% rename from pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies.go rename to pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/resolved_dependencies.go index 19abb04bd9..07953495f0 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/resolved_dependencies.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resolveddependencies +package builddefinitions import ( "context" @@ -24,6 +24,7 @@ import ( v1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/material" "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "go.uber.org/zap" "knative.dev/pkg/logging" ) @@ -41,13 +42,52 @@ const ( pipelineResourceName = "pipelineResource" ) +// used to toggle the fields in resolvedDependencies. see AddTektonTaskDescriptor +// and AddSLSATaskDescriptor +type addTaskDescriptorContent func(*v1beta1.TaskRun) (v1.ResourceDescriptor, error) + +// the more verbose resolved dependency content. this adds the name, uri, digest +// and content if possible. +func AddTektonTaskDescriptor(tr *v1beta1.TaskRun) (v1.ResourceDescriptor, error) { + rd := v1.ResourceDescriptor{} + storedTr, err := json.Marshal(tr) + if err != nil { + return rd, err + } + logger := logging.FromContext(context.TODO()) + logger.Infof("logging taskRun %v", tr) + // add remote task configsource information in materials + 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 + rd.Content = storedTr + } else { + rd.Name = pipelineTaskConfigName + rd.Content = storedTr + } + return rd, nil +} + +// resolved dependency content for the more generic slsa verifiers. just logs +// the name, uri and digest. +func AddSLSATaskDescriptor(tr *v1beta1.TaskRun) (v1.ResourceDescriptor, error) { + 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 rd, 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) { +func (t TaskBuildType) ResolvedDependencies(ctx context.Context) ([]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 { + if p := t.Tro.Status.Provenance; p != nil && p.RefSource != nil { rd := v1.ResourceDescriptor{ Name: taskConfigName, URI: p.RefSource.URI, @@ -59,24 +99,24 @@ func TaskRun(ctx context.Context, tro *objects.TaskRunObject) ([]v1.ResourceDesc mats := []common.ProvenanceMaterial{} // add step and sidecar images - stepMaterials, err := material.FromStepImages(tro.Status.Steps) + stepMaterials, err := material.FromStepImages(t.Tro.Status.Steps) mats = append(mats, stepMaterials...) if err != nil { return nil, err } - sidecarMaterials, err := material.FromSidecarImages(tro.Status.Sidecars) + sidecarMaterials, err := material.FromSidecarImages(t.Tro.Status.Sidecars) if err != nil { return nil, err } mats = append(mats, sidecarMaterials...) resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, "")...) - mats = material.FromTaskParamsAndResults(ctx, tro) + mats = material.FromTaskParamsAndResults(ctx, t.Tro) // convert materials to resolved dependencies resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, inputResultName)...) // add task resources - mats = material.FromTaskResources(ctx, tro) + mats = material.FromTaskResources(ctx, t.Tro) // convert materials to resolved dependencies resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, pipelineResourceName)...) @@ -89,13 +129,13 @@ func TaskRun(ctx context.Context, tro *objects.TaskRunObject) ([]v1.ResourceDesc } // 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) ([]v1.ResourceDescriptor, error) { +func (p PipelineBuildType) ResolvedDependencies(ctx context.Context) ([]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 { + if p := p.Pro.Status.Provenance; p != nil && p.RefSource != nil { rd := v1.ResourceDescriptor{ Name: pipelineConfigName, URI: p.RefSource.URI, @@ -105,14 +145,14 @@ func PipelineRun(ctx context.Context, pro *objects.PipelineRunObject) ([]v1.Reso } // add resolved dependencies from pipeline tasks - rds, err := fromPipelineTask(logger, pro) + rds, err := fromPipelineTask(logger, p.Pro, p.AddTaskDescriptorContent) if err != nil { return nil, err } resolvedDependencies = append(resolvedDependencies, rds...) // add resolved dependencies from pipeline results - mats := material.FromPipelineParamsAndResults(ctx, pro) + mats := material.FromPipelineParamsAndResults(ctx, p.Pro) // convert materials to resolved dependencies resolvedDependencies = append(resolvedDependencies, convertMaterialsToResolvedDependencies(mats, inputResultName)...) @@ -175,7 +215,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 { @@ -187,15 +227,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{} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/resolved_dependencies_test.go similarity index 81% rename from pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go rename to pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/resolved_dependencies_test.go index fb0b949cab..28844de3a3 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies/resolved_dependencies_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/resolved_dependencies_test.go @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resolveddependencies +package builddefinitions import ( + "encoding/json" "strings" "testing" @@ -63,7 +64,33 @@ func createPro(path string) *objects.PipelineRunObject { return p } -func TestTaskRun(t *testing.T) { +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 TestTaskRunResolvedDependencies(t *testing.T) { tests := []struct { name string taskRun *v1beta1.TaskRun @@ -256,7 +283,8 @@ func TestTaskRun(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := logtesting.TestContextWithLogger(t) - rd, err := TaskRun(ctx, objects.NewTaskRunObject(tc.taskRun)) + bd := TaskBuildType{BuildType: "slsa-build", Tro: objects.NewTaskRunObject(tc.taskRun)} + rd, err := bd.ResolvedDependencies(ctx) if err != nil { t.Fatalf("Did not expect an error but got %v", err) } @@ -483,33 +511,72 @@ 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"}, - }, - {Name: "pipelineTask", URI: "git+https://github.com/test", Digest: common.DigestSet{"sha1": "ab123"}}, +func TestPipelineRunResolvedDependencies(t *testing.T) { + taskRuns := tektonTaskRuns() + tests := []struct { + name string + buildType PipelineBuildType + want []v1.ResourceDescriptor + }{ { - URI: "oci://gcr.io/test2/test2", - Digest: common.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + name: "test slsa build type", + buildType: PipelineBuildType{BuildType: "slsa-build", Pro: pro, AddTaskDescriptorContent: AddSLSATaskDescriptor}, + want: []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"}, + }, + {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", + buildType: PipelineBuildType{BuildType: "tekton-build", Pro: pro, AddTaskDescriptorContent: AddTektonTaskDescriptor}, + want: []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"}, 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) - 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 := tc.buildType.ResolvedDependencies(ctx) + if err != nil { + t.Error(err) + } + if d := cmp.Diff(tc.want, got); d != "" { + t.Errorf("PipelineRunResolvedDependencies(): -want +got: %s", got) + } + }) } } @@ -539,7 +606,8 @@ func TestPipelineRunStructuredResult(t *testing.T) { }, } ctx := logtesting.TestContextWithLogger(t) - got, err := PipelineRun(ctx, proStructuredResults) + bd := PipelineBuildType{BuildType: "slsa-build", Pro: proStructuredResults, AddTaskDescriptorContent: AddSLSATaskDescriptor} + got, err := bd.ResolvedDependencies(ctx) if err != nil { t.Errorf("error while extracting resolvedDependencies: %v", err) } diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun.go new file mode 100644 index 0000000000..2a7bd464e2 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun.go @@ -0,0 +1,65 @@ +package builddefinitions + +import ( + "fmt" + + "github.com/tektoncd/chains/pkg/chains/objects" +) + +type TaskBuildType struct { + BuildType string + Tro *objects.TaskRunObject + InternalParameters func(tro *objects.TaskRunObject) map[string]any +} + +// internalParameters adds the tekton feature flags that were enabled +// for the taskrun. +func SLSATaskInternalParameters(tro *objects.TaskRunObject) map[string]any { + internalParams := make(map[string]any) + if tro.Status.Provenance != nil && tro.Status.Provenance.FeatureFlags != nil { + internalParams["tekton-pipelines-feature-flags"] = *tro.Status.Provenance.FeatureFlags + } + return internalParams +} + +func TektonTaskInternalParameters(tro *objects.TaskRunObject) map[string]any { + internalParams := make(map[string]any) + if tro.Status.Provenance != nil && tro.Status.Provenance.FeatureFlags != nil { + internalParams["tekton-pipelines-feature-flags"] = *tro.Status.Provenance.FeatureFlags + } + internalParams["labels"] = tro.ObjectMeta.Labels + internalParams["annotations"] = tro.ObjectMeta.Annotations + return internalParams +} + +func (t TaskBuildType) GetInternalParameters() map[string]any { + return t.InternalParameters(t.Tro) +} + +// externalParameters adds the task run spec +func (t TaskBuildType) GetExternalParameters() 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 t.Tro.Spec.TaskRef != nil { + if t.Tro.Spec.TaskRef.Resolver != "" && t.Tro.Spec.TaskRef.Resolver != "Cluster" { + isRemoteTask = true + } + } + if t := 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"] = t.Tro.Spec + return externalParams +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun_test.go new file mode 100644 index 0000000000..a3c8fe1d48 --- /dev/null +++ b/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions/taskrun_test.go @@ -0,0 +1,178 @@ +package builddefinitions + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/config" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestTaskInternalParameters(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, + }, + }, + }, + }, + ObjectMeta: v1.ObjectMeta{ + Annotations: map[string]string{ + "Annotation1": "Annotation1-value", + }, + Labels: map[string]string{ + "Label1": "Label1-value", + }, + }, + } + tests := []struct { + name string + buildType TaskBuildType + want map[string]any + }{ + { + name: "test slsa verfifier", + buildType: TaskBuildType{ + BuildType: "slsa build", + Tro: objects.NewTaskRunObject(tr), + InternalParameters: SLSATaskInternalParameters, + }, + 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, + }, + }, + }, + { + name: "test tekton verfifier", + buildType: TaskBuildType{ + BuildType: "tekton build", + Tro: objects.NewTaskRunObject(tr), + InternalParameters: TektonTaskInternalParameters, + }, + 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, + }, + "annotations": map[string]string{ + "Annotation1": "Annotation1-value", + }, + "labels": map[string]string{ + "Label1": "Label1-value", + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := tc.buildType.GetInternalParameters() + if d := cmp.Diff(tc.want, got); d != "" { + t.Fatalf("internalParameters (-want, +got):\n%s", d) + } + }) + } +} + +func TestTaskExternalParameters(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", + }, + }, + }, + }, + } + tests := []struct { + name string + buildType TaskBuildType + want map[string]any + }{ + { + name: "test slsa verfifier", + buildType: TaskBuildType{ + BuildType: "slsa build", + Tro: objects.NewTaskRunObject(tr), + InternalParameters: SLSATaskInternalParameters, + }, + want: map[string]any{ + "buildConfigSource": map[string]string{"path": "task.yaml", "ref": "sha1:abc123", "repository": "hello"}, + "runSpec": tr.Spec, + }, + }, + { + name: "test tekton verfifier", + buildType: TaskBuildType{ + BuildType: "tekton build", + Tro: objects.NewTaskRunObject(tr), + InternalParameters: TektonTaskInternalParameters, + }, + want: map[string]any{ + "buildConfigSource": map[string]string{"path": "task.yaml", "ref": "sha1:abc123", "repository": "hello"}, + "runSpec": tr.Spec, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := tc.buildType.GetExternalParameters() + if d := cmp.Diff(tc.want, got); d != "" { + t.Fatalf("externalParameters (-want, +got):\n%s", d) + } + }) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go index cc33e3475c..2d9041405d 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun.go @@ -22,7 +22,7 @@ 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" - resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/resolved_dependencies" + builddefinitions "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions" "github.com/tektoncd/chains/pkg/chains/objects" ) @@ -34,14 +34,21 @@ 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) + bp, err := byproducts(pro) if err != nil { return nil, err } - bp, err := byproducts(pro) + + bd, err := getBuildDefinition(slsaconfig.BuildType, pro) + if err != nil { + return nil, err + } + + rd, err := bd.ResolvedDependencies(ctx) if err != nil { return nil, err } + att := intoto.ProvenanceStatementSLSA1{ StatementHeader: intoto.StatementHeader{ Type: intoto.StatementInTotoV01, @@ -50,9 +57,9 @@ func GenerateAttestation(ctx context.Context, pro *objects.PipelineRunObject, sl }, Predicate: slsa.ProvenancePredicate{ BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalParameters(pro), - InternalParameters: internalParameters(pro), + BuildType: bd.BuildType, + ExternalParameters: bd.GetExternalParameters(), + InternalParameters: bd.GetInternalParameters(), ResolvedDependencies: rd, }, RunDetails: slsa.ProvenanceRunDetails{ @@ -82,46 +89,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) - if pro.Status.Provenance != nil && pro.Status.Provenance.FeatureFlags != nil { - internalParams["tekton-pipelines-feature-flags"] = *pro.Status.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{} @@ -139,3 +106,30 @@ func byproducts(pro *objects.PipelineRunObject) ([]slsa.ResourceDescriptor, erro } return byProd, nil } + +func getBuildDefinition(buildType string, pro *objects.PipelineRunObject) (builddefinitions.PipelineBuildType, error) { + // if buildType is not set in the chains-config, default to slsa build type + buildDefinitionType := buildType + if buildType == "" { + buildDefinitionType = builddefinitions.SlsaBuildType + } + + switch buildDefinitionType { + case builddefinitions.SlsaBuildType: + return builddefinitions.PipelineBuildType{ + BuildType: buildDefinitionType, + Pro: pro, + InternalParameters: builddefinitions.SLSAPipelineInternalParameters, + AddTaskDescriptorContent: builddefinitions.AddSLSATaskDescriptor, + }, nil + case builddefinitions.TektonBuildType: + return builddefinitions.PipelineBuildType{ + BuildType: buildType, + Pro: pro, + InternalParameters: builddefinitions.TektonPipelineInternalParameters, + AddTaskDescriptorContent: builddefinitions.AddTektonTaskDescriptor, + }, nil + default: + return builddefinitions.PipelineBuildType{}, fmt.Errorf("unsupported buildType %v", buildType) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go b/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun/pipelinerun_test.go index 29e3e17f7d..28b6f6ee43 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" @@ -28,9 +29,9 @@ import ( "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/compare" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + builddefinitions "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions" "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" @@ -97,98 +98,6 @@ 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{ @@ -352,6 +261,7 @@ func TestGenerateAttestation(t *testing.T) { got, err := GenerateAttestation(ctx, pr, &slsaconfig.SlsaConfig{ BuilderID: "test_builder-1", + BuildType: "https://tekton.dev/chains/v2/slsa", }) if err != nil { @@ -361,3 +271,102 @@ func TestGenerateAttestation(t *testing.T) { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } + +func buildTypesEqual(x, y builddefinitions.PipelineBuildType) bool { + if x.BuildType != y.BuildType { + return false + } + + xInternal := x.GetExternalParameters() + yInternal := y.GetExternalParameters() + + if xInternal["labels"] != yInternal["labels"] { + return false + } + + if xInternal["annotations"] != yInternal["annotations"] { + return false + } + + // xDeps, _ := x.ResolvedDependencies() + // yDeps, _ := y.ResolvedDependencies() + + return true +} + +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 + buildType string + want builddefinitions.PipelineBuildType + }{ + { + name: "test slsa build type", + buildType: "https://tekton.dev/chains/v2/slsa", + want: builddefinitions.PipelineBuildType{ + BuildType: "https://tekton.dev/chains/v2/slsa", + Pro: pr, + InternalParameters: builddefinitions.SLSAPipelineInternalParameters, + AddTaskDescriptorContent: builddefinitions.AddSLSATaskDescriptor, + }, + }, + { + name: "test default build type", + buildType: "", + want: builddefinitions.PipelineBuildType{ + BuildType: "https://tekton.dev/chains/v2/slsa", + Pro: pr, + InternalParameters: builddefinitions.SLSAPipelineInternalParameters, + AddTaskDescriptorContent: builddefinitions.AddSLSATaskDescriptor, + }, + }, + { + name: "test tekton build type", + buildType: "https://tekton.dev/chains/v2/slsa-tekton", + want: builddefinitions.PipelineBuildType{ + BuildType: "https://tekton.dev/chains/v2/slsa-tekton", + Pro: pr, + InternalParameters: builddefinitions.TektonPipelineInternalParameters, + AddTaskDescriptorContent: builddefinitions.AddTektonTaskDescriptor, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.TODO() + bd, err := getBuildDefinition(tc.buildType, pr) + if err != nil { + t.Fatalf("Did not expect an error but got %v", err) + } + + if diff := cmp.Diff(tc.want.GetInternalParameters(), bd.GetInternalParameters()); diff != "" { + t.Errorf("getBuildDefinition - GetInternalParameters(): -want +got: %v", diff) + } + + if diff := cmp.Diff(tc.want.GetExternalParameters(), bd.GetExternalParameters()); diff != "" { + t.Errorf("getBuildDefinition - GetExternalParameters(): -want +got: %v", diff) + } + + wantDeps, err := tc.want.ResolvedDependencies(ctx) + if err != nil { + t.Fatalf("Did not expect an error but got %v", err) + } + gotDeps, err := bd.ResolvedDependencies(ctx) + if err != nil { + t.Fatalf("Did not expect an error but got %v", err) + } + if diff := cmp.Diff(wantDeps, gotDeps); diff != "" { + t.Errorf("getBuildDefinition - resolvedDependencies(): -want +got: %v", diff) + } + + }) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go index 7c82db346f..5769d827e1 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun.go @@ -22,8 +22,8 @@ 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" + builddefinitions "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions" "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" ) @@ -31,14 +31,20 @@ 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(slsaConfig.BuildType, tro) + if err != nil { + return nil, err + } + rd, err := bd.ResolvedDependencies(ctx) if err != nil { return nil, err } + att := intoto.ProvenanceStatementSLSA1{ StatementHeader: intoto.StatementHeader{ Type: intoto.StatementInTotoV01, @@ -47,9 +53,9 @@ func GenerateAttestation(ctx context.Context, tro *objects.TaskRunObject, slsaCo }, Predicate: slsa.ProvenancePredicate{ BuildDefinition: slsa.ProvenanceBuildDefinition{ - BuildType: "https://tekton.dev/chains/v2/slsa", - ExternalParameters: externalParameters(tro), - InternalParameters: internalParameters(tro), + BuildType: bd.BuildType, + ExternalParameters: bd.GetExternalParameters(), + InternalParameters: bd.GetInternalParameters(), ResolvedDependencies: rd, }, RunDetails: slsa.ProvenanceRunDetails{ @@ -79,44 +85,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) - if tro.Status.Provenance != nil && tro.Status.Provenance.FeatureFlags != nil { - internalParams["tekton-pipelines-feature-flags"] = *tro.Status.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 +102,29 @@ func byproducts(tro *objects.TaskRunObject) ([]slsa.ResourceDescriptor, error) { } return byProd, nil } + +func getBuildDefinition(buildType string, tro *objects.TaskRunObject) (builddefinitions.TaskBuildType, error) { + // if buildType is not set in the chains-config, default to slsa build type + buildDefinitionType := buildType + if buildType == "" { + buildDefinitionType = builddefinitions.SlsaBuildType + } + + switch buildDefinitionType { + // if buildType is not set in the chains-config, default to slsa build type + case builddefinitions.SlsaBuildType: + return builddefinitions.TaskBuildType{ + BuildType: buildDefinitionType, + Tro: tro, + InternalParameters: builddefinitions.SLSATaskInternalParameters, + }, nil + case builddefinitions.TektonBuildType: + return builddefinitions.TaskBuildType{ + BuildType: buildType, + Tro: tro, + InternalParameters: builddefinitions.TektonTaskInternalParameters, + }, nil + default: + return builddefinitions.TaskBuildType{}, 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..bfce081530 100644 --- a/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun_test.go +++ b/pkg/chains/formats/slsa/v2alpha2/internal/taskrun/taskrun_test.go @@ -27,6 +27,7 @@ import ( slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig" + builddefinitions "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/build_definitions" "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2alpha2/internal/pipelinerun" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/internal/objectloader" @@ -97,94 +98,6 @@ 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{ @@ -310,6 +223,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 +233,94 @@ func TestTaskRunGenerateAttestation(t *testing.T) { t.Errorf("GenerateAttestation(): -want +got: %s", diff) } } + +func equateErrorMessage(x, y error) bool { + if x == nil || y == nil { + return x == nil && y == nil + } + return x.Error() == y.Error() +} + +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 builddefinitions.TaskBuildType + err error + }{ + { + name: "test slsa build type", + buildType: "https://tekton.dev/chains/v2/slsa", + want: builddefinitions.TaskBuildType{ + BuildType: "https://tekton.dev/chains/v2/slsa", + Tro: tro, + InternalParameters: builddefinitions.SLSATaskInternalParameters, + }, + err: nil, + }, + { + name: "test default build type", + buildType: "", + want: builddefinitions.TaskBuildType{ + BuildType: "https://tekton.dev/chains/v2/slsa", + Tro: tro, + InternalParameters: builddefinitions.SLSATaskInternalParameters, + }, + err: nil, + }, + { + name: "test tekton build type", + buildType: "https://tekton.dev/chains/v2/slsa-tekton", + want: builddefinitions.TaskBuildType{ + BuildType: "https://tekton.dev/chains/v2/slsa-tekton", + Tro: tro, + InternalParameters: builddefinitions.TektonTaskInternalParameters, + }, + err: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + bd, err := getBuildDefinition(tc.buildType, tro) + if err != nil { + t.Fatalf("Did not expect an error but got %s", err) + } + + if diff := cmp.Diff(tc.want.GetInternalParameters(), bd.GetInternalParameters()); diff != "" { + t.Errorf("getBuildDefinition - GetInternalParameters(): -want +got: %s", diff) + } + + if diff := cmp.Diff(tc.want.GetExternalParameters(), bd.GetExternalParameters()); diff != "" { + t.Errorf("getBuildDefinition - GetExternalParameters(): -want +got: %s", diff) + } + + wantDeps, err := tc.want.ResolvedDependencies(ctx) + if err != nil { + t.Fatalf("Did not expect an error but got %s", err) + } + gotDeps, err := bd.ResolvedDependencies(ctx) + if err != nil { + t.Fatalf("Did not expect an error but got %s", err) + } + if diff := cmp.Diff(wantDeps, gotDeps); diff != "" { + t.Errorf("getBuildDefinition - resolvedDependencies(): -want +got: %s", diff) + } + + }) + } +} diff --git a/pkg/chains/formats/slsa/v2alpha2/slsav2.go b/pkg/chains/formats/slsa/v2alpha2/slsav2.go index a61b047353..4b7120f80f 100644 --- a/pkg/chains/formats/slsa/v2alpha2/slsav2.go +++ b/pkg/chains/formats/slsa/v2alpha2/slsav2.go @@ -44,6 +44,7 @@ func NewFormatter(cfg config.Config) (formats.Payloader, error) { return &Slsa{ slsaConfig: &slsaconfig.SlsaConfig{ BuilderID: cfg.Builder.ID, + BuildType: cfg.BuildType.ID, }, }, nil } diff --git a/pkg/config/config.go b/pkg/config/config.go index 855d51143d..8b9deb717a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -33,6 +33,7 @@ type Config struct { Signers SignerConfigs Builder BuilderConfig Transparency TransparencyConfig + BuildType BuildType } // ArtifactConfigs contains the configuration for how to sign/store/format the signatures for each artifact type @@ -69,6 +70,10 @@ type BuilderConfig struct { ID string } +type BuildType struct { + ID string +} + type X509Signer struct { FulcioEnabled bool FulcioAddr string