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