diff --git a/docs/config.md b/docs/config.md index 52fbe6491b..465c021069 100644 --- a/docs/config.md +++ b/docs/config.md @@ -51,7 +51,7 @@ Supported keys include: | Key | Description | Supported Values | Default | | :--- | :--- | :--- | :--- | -| `artifacts.taskrun.format` | The format to store `TaskRun` payloads in. | `in-toto`, `slsa/v1`| `in-toto` | +| `artifacts.taskrun.format` | The format to store `TaskRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2`| `in-toto` | | `artifacts.taskrun.storage` | The storage backend to store `TaskRun` signatures in. Multiple backends can be specified with comma-separated list ("tekton,oci"). To disable the `TaskRun` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `tekton` | | `artifacts.taskrun.signer` | The signature backend to sign `TaskRun` payloads with. | `x509`, `kms` | `x509` | @@ -61,7 +61,7 @@ Supported keys include: | Key | Description | Supported Values | Default | | :--- | :--- | :--- | :--- | -| `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`| `in-toto` | +| `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2`| `in-toto` | | `artifacts.pipelinerun.storage` | The storage backend to store `PipelineRun` signatures in. Multiple backends can be specified with comma-separated list ("tekton,oci"). To disable the `PipelineRun` artifact input an empty string (""). | `tekton`, `oci`, `gcs`, `docdb`, `grafeas` | `tekton` | | `artifacts.pipelinerun.signer` | The signature backend to sign `PipelineRun` payloads with. | `x509`, `kms` | `x509` | diff --git a/examples/taskruns/task-output-image.yaml b/examples/taskruns/task-output-image.yaml index 52c4190183..41189e556e 100644 --- a/examples/taskruns/task-output-image.yaml +++ b/examples/taskruns/task-output-image.yaml @@ -62,4 +62,4 @@ spec: type: image params: - name: url - value: gcr.io/foo/bar \ No newline at end of file + value: gcr.io/foo/bar diff --git a/pkg/chains/formats/all/all.go b/pkg/chains/formats/all/all.go index 61e0645ca8..2bfe3276da 100644 --- a/pkg/chains/formats/all/all.go +++ b/pkg/chains/formats/all/all.go @@ -17,4 +17,5 @@ package all import ( _ "github.com/tektoncd/chains/pkg/chains/formats/simple" _ "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1" + _ "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2" ) diff --git a/pkg/chains/formats/format.go b/pkg/chains/formats/format.go index 071b7e42fb..58e5d81555 100644 --- a/pkg/chains/formats/format.go +++ b/pkg/chains/formats/format.go @@ -32,6 +32,7 @@ const ( PayloadTypeSimpleSigning config.PayloadType = "simplesigning" PayloadTypeInTotoIte6 config.PayloadType = "in-toto" PayloadTypeSlsav1 config.PayloadType = "slsa/v1" + PayloadTypeSlsav2 config.PayloadType = "slsa/v2" ) var ( diff --git a/pkg/chains/formats/slsa/v1/taskrun/material.go b/pkg/chains/formats/slsa/v1/taskrun/material.go index dbfa0ad079..d54a266aa0 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/material.go +++ b/pkg/chains/formats/slsa/v1/taskrun/material.go @@ -78,8 +78,8 @@ func AddImageIDToMaterials(imageID string, mats *[]slsa.ProvenanceMaterial) erro return nil } -// materials constructs `predicate.materials` section by collecting all the artifacts that influence a taskrun such as source code repo and step&sidecar base images. -func materials(tro *objects.TaskRunObject, logger *zap.SugaredLogger) ([]slsa.ProvenanceMaterial, error) { +// Materials constructs `predicate.materials` section by collecting all the artifacts that influence a taskrun such as source code repo and step&sidecar base images. +func Materials(tro *objects.TaskRunObject, logger *zap.SugaredLogger) ([]slsa.ProvenanceMaterial, error) { var mats []slsa.ProvenanceMaterial // add step images diff --git a/pkg/chains/formats/slsa/v1/taskrun/material_test.go b/pkg/chains/formats/slsa/v1/taskrun/material_test.go index 8aec8ddec8..1629290a9e 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/material_test.go +++ b/pkg/chains/formats/slsa/v1/taskrun/material_test.go @@ -64,7 +64,7 @@ status: }, } - got, err := materials(objects.NewTaskRunObject(taskRun), logtesting.TestLogger(t)) + got, err := Materials(objects.NewTaskRunObject(taskRun), logtesting.TestLogger(t)) if err != nil { t.Fatalf("Did not expect an error but got %v", err) } @@ -236,7 +236,7 @@ func TestMaterials(t *testing.T) { }, }} for _, tc := range tests { - mat, err := materials(objects.NewTaskRunObject(tc.taskRun), logtesting.TestLogger(t)) + mat, err := Materials(objects.NewTaskRunObject(tc.taskRun), logtesting.TestLogger(t)) if err != nil { t.Fatalf("Did not expect an error but got %v", err) } @@ -310,7 +310,7 @@ func TestAddStepImagesToMaterials(t *testing.T) { } if tc.wantError == nil { if diff := cmp.Diff(tc.want, mat, test.OptSortMaterial); diff != "" { - t.Errorf("materials(): -want +got: %s", diff) + t.Errorf("Materials(): -want +got: %s", diff) } } } @@ -380,7 +380,7 @@ func TestAddSidecarImagesToMaterials(t *testing.T) { } if tc.wantError == nil { if diff := cmp.Diff(tc.want, mat, test.OptSortMaterial); diff != "" { - t.Errorf("materials(): -want +got: %s", diff) + t.Errorf("Materials(): -want +got: %s", diff) } } } @@ -418,7 +418,7 @@ func TestAddImageIDToMaterials(t *testing.T) { } if tc.wantError == nil { if diff := cmp.Diff(tc.want, mat, test.OptSortMaterial); diff != "" { - t.Errorf("materials(): -want +got: %s", diff) + t.Errorf("Materials(): -want +got: %s", diff) } } } diff --git a/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go b/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go index 6b5bafb2e8..12c4ef8360 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go +++ b/pkg/chains/formats/slsa/v1/taskrun/provenance_test.go @@ -66,7 +66,7 @@ func TestMetadata(t *testing.T) { BuildStartedOn: &start, BuildFinishedOn: &end, } - got := metadata(objects.NewTaskRunObject(tr)) + got := Metadata(objects.NewTaskRunObject(tr)) if !reflect.DeepEqual(expected, got) { t.Fatalf("expected %v got %v", expected, got) } @@ -95,7 +95,7 @@ func TestMetadataInTimeZone(t *testing.T) { BuildStartedOn: &start, BuildFinishedOn: &end, } - got := metadata(objects.NewTaskRunObject(tr)) + got := Metadata(objects.NewTaskRunObject(tr)) if !reflect.DeepEqual(expected, got) { t.Fatalf("expected %v got %v", expected, got) } diff --git a/pkg/chains/formats/slsa/v1/taskrun/taskrun.go b/pkg/chains/formats/slsa/v1/taskrun/taskrun.go index 8f2dcf860c..1072cfd12e 100644 --- a/pkg/chains/formats/slsa/v1/taskrun/taskrun.go +++ b/pkg/chains/formats/slsa/v1/taskrun/taskrun.go @@ -26,7 +26,7 @@ import ( func GenerateAttestation(builderID string, tro *objects.TaskRunObject, logger *zap.SugaredLogger) (interface{}, error) { subjects := extract.SubjectDigests(tro, logger) - mat, err := materials(tro, logger) + mat, err := Materials(tro, logger) if err != nil { return nil, err } @@ -43,7 +43,7 @@ func GenerateAttestation(builderID string, tro *objects.TaskRunObject, logger *z BuildType: tro.GetGVK(), Invocation: invocation(tro), BuildConfig: buildConfig(tro), - Metadata: metadata(tro), + Metadata: Metadata(tro), Materials: mat, }, } @@ -65,7 +65,9 @@ func invocation(tro *objects.TaskRunObject) slsa.ProvenanceInvocation { return attest.Invocation(source, tro.Spec.Params, paramSpecs) } -func metadata(tro *objects.TaskRunObject) *slsa.ProvenanceMetadata { +// Metadata adds taskrun's start time, completion time and reproducibility labels +// to the metadata section of the generated provenance. +func Metadata(tro *objects.TaskRunObject) *slsa.ProvenanceMetadata { m := &slsa.ProvenanceMetadata{} if tro.Status.StartTime != nil { utc := tro.Status.StartTime.Time.UTC() diff --git a/pkg/chains/formats/slsa/v2/intotoite6v2.go b/pkg/chains/formats/slsa/v2/intotoite6v2.go new file mode 100644 index 0000000000..8f5c6c79c3 --- /dev/null +++ b/pkg/chains/formats/slsa/v2/intotoite6v2.go @@ -0,0 +1,62 @@ +/* +Copyright 2021 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "context" + "fmt" + + "github.com/tektoncd/chains/pkg/chains/formats" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2/taskrun" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/config" +) + +const ( + PayloadTypeSlsav2 = formats.PayloadTypeSlsav2 +) + +func init() { + formats.RegisterPayloader(PayloadTypeSlsav2, NewFormatter) +} + +type InTotoIte6V2 struct { + builderID string +} + +func NewFormatter(cfg config.Config) (formats.Payloader, error) { + return &InTotoIte6V2{ + builderID: cfg.Builder.ID, + }, nil +} + +func (i *InTotoIte6V2) Wrap() bool { + return true +} + +func (i *InTotoIte6V2) CreatePayload(ctx context.Context, obj interface{}) (interface{}, error) { + switch v := obj.(type) { + case *objects.TaskRunObject: + return taskrun.GenerateAttestation(i.builderID, v, ctx) + default: + return nil, fmt.Errorf("intoto does not support type: %s", v) + } +} + +func (i *InTotoIte6V2) Type() config.PayloadType { + return formats.PayloadTypeSlsav2 +} diff --git a/pkg/chains/formats/slsa/v2/intotoite6v2_test.go b/pkg/chains/formats/slsa/v2/intotoite6v2_test.go new file mode 100644 index 0000000000..b054842460 --- /dev/null +++ b/pkg/chains/formats/slsa/v2/intotoite6v2_test.go @@ -0,0 +1,381 @@ +/* +Copyright 2021 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "testing" + "time" + + "github.com/tektoncd/chains/pkg/chains/formats" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/v2/taskrun" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/chains/pkg/config" + "github.com/tektoncd/chains/pkg/internal/objectloader" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/google/go-cmp/cmp" + "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + logtesting "knative.dev/pkg/logging/testing" +) + +var ( + e1BuildStart = time.Unix(1617011400, 0) + e1BuildFinished = time.Unix(1617011415, 0) +) + +func TestTaskRunCreatePayload1(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + + tr, err := objectloader.TaskRunFromFile("../testdata/taskrun1.json") + if err != nil { + t.Fatal(err) + } + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "test_builder-1", + }, + } + expected := in_toto.ProvenanceStatement{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []in_toto.Subject{ + { + Name: "gcr.io/my/image", + Digest: slsa.DigestSet{ + "sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7", + }, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + Metadata: &slsa.ProvenanceMetadata{ + BuildStartedOn: &e1BuildStart, + BuildFinishedOn: &e1BuildFinished, + }, + Materials: []slsa.ProvenanceMaterial{ + { + URI: "docker-pullable://gcr.io/test1/test1", + Digest: slsa.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + { + URI: "docker-pullable://gcr.io/test2/test2", + Digest: slsa.DigestSet{"sha256": "4d6dd704ef58cb214dd826519929e92a978a57cdee43693006139c0080fd6fac"}, + }, + { + URI: "docker-pullable://gcr.io/test3/test3", + Digest: slsa.DigestSet{"sha256": "f1a8b8549c179f41e27ff3db0fe1a1793e4b109da46586501a8343637b1d0478"}, + }, + {URI: "git+https://git.test.com.git", Digest: slsa.DigestSet{"sha1": "sha:taskrun"}}, + }, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{ + URI: "github.com/test", + Digest: map[string]string{"sha1": "ab123"}, + EntryPoint: "build.yaml", + }, + Parameters: map[string]any{ + "ComputeResources": (*corev1.ResourceRequirements)(nil), + "Debug": (*v1beta1.TaskRunDebug)(nil), + "Params": []v1beta1.Param{ + { + Name: "IMAGE", + Value: v1beta1.ParamValue{Type: "string", StringVal: "test.io/test/image"}, + }, + { + Name: "CHAINS-GIT_COMMIT", + Value: v1beta1.ParamValue{Type: "string", StringVal: "sha:taskrun"}, + }, + { + Name: "CHAINS-GIT_URL", + Value: v1beta1.ParamValue{Type: "string", StringVal: "https://git.test.com"}, + }, + }, + "PodTemplate": (*pod.Template)(nil), + "Resources": (*v1beta1.TaskRunResources)(nil), + "Retries": 0, + "ServiceAccountName": "default", + "SidecarOverrides": []v1beta1.TaskRunSidecarOverride(nil), + "Status": v1beta1.TaskRunSpecStatus(""), + "StatusMessage": v1beta1.TaskRunSpecStatusMessage(""), + "StepOverrides": []v1beta1.TaskRunStepOverride(nil), + "Timeout": (*metav1.Duration)(nil), + "Workspaces": []v1beta1.WorkspaceBinding(nil), + }, + }, + Builder: slsa.ProvenanceBuilder{ + ID: "test_builder-1", + }, + BuildType: "tekton.dev/v1beta1/TaskRun", + BuildConfig: taskrun.BuildConfig{ + TaskSpec: &v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{ + {Name: "IMAGE", Type: "string"}, {Name: "filename", Type: "string"}, + {Name: "DOCKERFILE", Type: "string"}, {Name: "CONTEXT", Type: "string"}, + {Name: "EXTRA_ARGS", Type: "string"}, {Name: "BUILDER_IMAGE", Type: "string"}, + {Name: "CHAINS-GIT_COMMIT", Type: "string", Default: &v1beta1.ParamValue{Type: "string", StringVal: "sha:task"}}, + {Name: "CHAINS-GIT_URL", Type: "string", Default: &v1beta1.ParamValue{Type: "string", StringVal: "https://defaultgit.test.com"}}, + }, + Steps: []v1beta1.Step{{Name: "step1"}, {Name: "step2"}, {Name: "step3"}}, + Results: []v1beta1.TaskResult{ + {Name: "IMAGE_DIGEST", Description: "Digest of the image just built."}, + {Name: "filename_DIGEST", Description: "Digest of the file just built."}, + }, + }, + }, + }, + } + i, _ := NewFormatter(cfg) + + got, err := i.CreatePayload(ctx, objects.NewTaskRunObject(tr)) + + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if diff := cmp.Diff(expected, got); diff != "" { + t.Errorf("InTotoIte6V2.CreatePayload(): -want +got: %s", diff) + } +} + +func TestTaskRunCreatePayload2(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + tr, err := objectloader.TaskRunFromFile("../testdata/taskrun2.json") + if err != nil { + t.Fatal(err) + } + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "test_builder-2", + }, + } + expected := in_toto.ProvenanceStatement{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: nil, + }, + Predicate: slsa.ProvenancePredicate{ + Metadata: &slsa.ProvenanceMetadata{ + BuildStartedOn: &e1BuildStart, + BuildFinishedOn: &e1BuildFinished, + }, + Builder: slsa.ProvenanceBuilder{ + ID: "test_builder-2", + }, + Materials: []slsa.ProvenanceMaterial{ + { + URI: "docker-pullable://gcr.io/test1/test1", + Digest: slsa.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + {URI: "git+https://git.test.com.git", Digest: slsa.DigestSet{"sha1": "sha:taskdefault"}}, + }, + Invocation: slsa.ProvenanceInvocation{ + ConfigSource: slsa.ConfigSource{ + URI: "github.com/catalog", + Digest: slsa.DigestSet{"sha1": "x123"}, + EntryPoint: "git-clone.yaml", + }, + Parameters: map[string]any{ + "ComputeResources": (*corev1.ResourceRequirements)(nil), + "Debug": (*v1beta1.TaskRunDebug)(nil), + "Params": []v1beta1.Param{ + { + Name: "url", + Value: v1beta1.ParamValue{Type: "string", StringVal: "https://git.test.com"}, + }, + {Name: "revision", Value: v1beta1.ParamValue{Type: "string"}}, + }, + "PodTemplate": (*pod.Template)(nil), + "Resources": (*v1beta1.TaskRunResources)(nil), + "Retries": 0, + "ServiceAccountName": "default", + "SidecarOverrides": []v1beta1.TaskRunSidecarOverride(nil), + "Status": v1beta1.TaskRunSpecStatus(""), + "StatusMessage": v1beta1.TaskRunSpecStatusMessage(""), + "StepOverrides": []v1beta1.TaskRunStepOverride(nil), + "Timeout": (*metav1.Duration)(nil), + "Workspaces": []v1beta1.WorkspaceBinding(nil), + }, + }, + BuildType: "tekton.dev/v1beta1/TaskRun", + BuildConfig: taskrun.BuildConfig{ + TaskSpec: &v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{ + {Name: "CHAINS-GIT_COMMIT", Type: "string", Default: &v1beta1.ParamValue{Type: "string", StringVal: "sha:taskdefault"}}, + {Name: "CHAINS-GIT_URL", Type: "string", Default: &v1beta1.ParamValue{Type: "string", StringVal: "https://git.test.com"}}, + }, + Steps: []v1beta1.Step{{Name: "step1", Env: []v1.EnvVar{{Name: "HOME", Value: "$(params.userHome)"}, {Name: "PARAM_URL", Value: "$(params.url)"}}, Script: "git clone"}}, + Results: []v1beta1.TaskResult{ + {Name: "some-uri_DIGEST", Description: "Digest of a file to push."}, + {Name: "some-uri", Description: "some calculated uri"}, + }, + }, + }, + }, + } + i, _ := NewFormatter(cfg) + got, err := i.CreatePayload(ctx, objects.NewTaskRunObject(tr)) + + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if diff := cmp.Diff(expected, got); diff != "" { + t.Errorf("InTotoIte6V2.CreatePayload(): -want +got: %s", diff) + } +} + +func TestMultipleSubjects(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + + tr, err := objectloader.TaskRunFromFile("../testdata/taskrun-multiple-subjects.json") + if err != nil { + t.Fatal(err) + } + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "test_builder-multiple", + }, + } + expected := in_toto.ProvenanceStatement{ + StatementHeader: in_toto.StatementHeader{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: []in_toto.Subject{ + { + Name: "gcr.io/myimage", + Digest: slsa.DigestSet{ + "sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6", + }, + }, { + Name: "gcr.io/myimage", + Digest: slsa.DigestSet{ + "sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367", + }, + }, + }, + }, + Predicate: slsa.ProvenancePredicate{ + BuildType: "tekton.dev/v1beta1/TaskRun", + Metadata: &slsa.ProvenanceMetadata{}, + Builder: slsa.ProvenanceBuilder{ + ID: "test_builder-multiple", + }, + Materials: []slsa.ProvenanceMaterial{ + { + URI: "docker-pullable://gcr.io/test1/test1", + Digest: slsa.DigestSet{"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6"}, + }, + }, + Invocation: slsa.ProvenanceInvocation{ + Parameters: map[string]any{ + "ComputeResources": (*corev1.ResourceRequirements)(nil), + "Debug": (*v1beta1.TaskRunDebug)(nil), + "Params": []v1beta1.Param{}, + "PodTemplate": (*pod.Template)(nil), + "Resources": (*v1beta1.TaskRunResources)(nil), + "Retries": 0, + "ServiceAccountName": "default", + "SidecarOverrides": []v1beta1.TaskRunSidecarOverride(nil), + "Status": v1beta1.TaskRunSpecStatus(""), + "StatusMessage": v1beta1.TaskRunSpecStatusMessage(""), + "StepOverrides": []v1beta1.TaskRunStepOverride(nil), + "Timeout": (*metav1.Duration)(nil), + "Workspaces": []v1beta1.WorkspaceBinding(nil), + }, + }, + BuildConfig: taskrun.BuildConfig{ + TaskSpec: &v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{}, + Results: []v1beta1.TaskResult{ + {Name: "file1_DIGEST", Description: "Digest of a file to push."}, + {Name: "file1", Description: "some assembled file"}, + {Name: "file2_DIGEST", Description: "Digest of a file to push."}, + {Name: "file2", Description: "some assembled file"}, + }, + }, + }, + }, + } + + i, _ := NewFormatter(cfg) + got, err := i.CreatePayload(ctx, objects.NewTaskRunObject(tr)) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if diff := cmp.Diff(expected, got); diff != "" { + t.Errorf("InTotoIte6V2.CreatePayload(): -want +got: %s", diff) + } +} + +func TestNewFormatter(t *testing.T) { + t.Run("Ok", func(t *testing.T) { + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "testid", + }, + } + f, err := NewFormatter(cfg) + if f == nil { + t.Error("Failed to create formatter") + } + if err != nil { + t.Errorf("Error creating formatter: %s", err) + } + }) +} + +func TestCreatePayloadError(t *testing.T) { + ctx := logtesting.TestContextWithLogger(t) + + cfg := config.Config{ + Builder: config.BuilderConfig{ + ID: "testid", + }, + } + f, _ := NewFormatter(cfg) + + t.Run("Invalid type", func(t *testing.T) { + p, err := f.CreatePayload(ctx, "not a task ref") + + if p != nil { + t.Errorf("Unexpected payload") + } + if err == nil { + t.Errorf("Expected error") + } else { + if err.Error() != "intoto does not support type: not a task ref" { + t.Errorf("wrong error returned: '%s'", err.Error()) + } + } + }) + +} + +func TestCorrectPayloadType(t *testing.T) { + var i InTotoIte6V2 + if i.Type() != formats.PayloadTypeSlsav2 { + t.Errorf("Invalid type returned: %s", i.Type()) + } +} diff --git a/pkg/chains/formats/slsa/v2/taskrun/provenance_test.go b/pkg/chains/formats/slsa/v2/taskrun/provenance_test.go new file mode 100644 index 0000000000..59594f0c39 --- /dev/null +++ b/pkg/chains/formats/slsa/v2/taskrun/provenance_test.go @@ -0,0 +1,344 @@ +/* +Copyright 2021 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskrun + +import ( + "reflect" + "strings" + "testing" + "time" + + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + + "github.com/ghodss/yaml" + "github.com/google/go-cmp/cmp" + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/tektoncd/chains/pkg/artifacts" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" + slsav1 "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1/taskrun" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + logtesting "knative.dev/pkg/logging/testing" +) + +const ( + digest1 = "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5" + digest2 = "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" + digest3 = "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b7" + digest4 = "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b8" + digest5 = "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b9" +) + +func TestMetadata(t *testing.T) { + tr := &v1beta1.TaskRun{ + ObjectMeta: v1.ObjectMeta{ + Name: "my-taskrun", + Namespace: "my-namespace", + Annotations: map[string]string{ + "chains.tekton.dev/reproducible": "true", + }, + }, + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + StartTime: &v1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC)}, + CompletionTime: &v1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC)}, + }, + }, + } + start := time.Date(1995, time.December, 24, 6, 12, 12, 12, time.UTC) + end := time.Date(1995, time.December, 24, 6, 12, 12, 24, time.UTC) + expected := &slsa.ProvenanceMetadata{ + BuildStartedOn: &start, + BuildFinishedOn: &end, + } + got := slsav1.Metadata(objects.NewTaskRunObject(tr)) + if !reflect.DeepEqual(expected, got) { + t.Fatalf("expected %v got %v", expected, got) + } +} + +func TestMetadataInTimeZone(t *testing.T) { + tz := time.FixedZone("Test Time", int((12 * time.Hour).Seconds())) + tr := &v1beta1.TaskRun{ + ObjectMeta: v1.ObjectMeta{ + Name: "my-taskrun", + Namespace: "my-namespace", + Annotations: map[string]string{ + "chains.tekton.dev/reproducible": "true", + }, + }, + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + StartTime: &v1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 12, tz)}, + CompletionTime: &v1.Time{Time: time.Date(1995, time.December, 24, 6, 12, 12, 24, tz)}, + }, + }, + } + start := time.Date(1995, time.December, 24, 6, 12, 12, 12, tz).UTC() + end := time.Date(1995, time.December, 24, 6, 12, 12, 24, tz).UTC() + expected := &slsa.ProvenanceMetadata{ + BuildStartedOn: &start, + BuildFinishedOn: &end, + } + got := slsav1.Metadata(objects.NewTaskRunObject(tr)) + if !reflect.DeepEqual(expected, got) { + t.Fatalf("expected %v got %v", expected, got) + } +} + +func TestInvocation(t *testing.T) { + taskrun := `apiVersion: tekton.dev/v1beta1 +kind: TaskRun +metadata: + uid: my-uid +spec: + params: + - name: my-param + value: string-param + - name: my-array-param + value: + - "my" + - "array" + - name: my-empty-string-param + value: "" + - name: my-empty-array-param + value: [] +status: + taskSpec: + params: + - name: my-param + default: ignored + - name: my-array-param + type: array + default: + - "also" + - "ignored" + - name: my-default-param + default: string-default-param + - name: my-default-array-param + type: array + default: + - "array" + - "default" + - "param" + - name: my-empty-string-param + default: "ignored" + - name: my-empty-array-param + type: array + default: + - "also" + - "ignored" + - name: my-default-empty-string-param + default: "" + - name: my-default-empty-array-param + type: array + default: [] +` + + var taskRun *v1beta1.TaskRun + if err := yaml.Unmarshal([]byte(taskrun), &taskRun); err != nil { + t.Fatal(err) + } + + expected := slsa.ProvenanceInvocation{ + Parameters: map[string]any{ + "Params": []v1beta1.Param{ + { + Name: "my-param", + Value: v1beta1.ParamValue{Type: "string", StringVal: "string-param"}, + }, + { + Name: "my-array-param", + Value: v1beta1.ParamValue{Type: "array", ArrayVal: []string{"my", "array"}}, + }, + {Name: "my-empty-string-param", Value: v1beta1.ParamValue{Type: "string"}}, + { + Name: "my-empty-array-param", + Value: v1beta1.ParamValue{Type: "array", ArrayVal: []string{}}, + }, + }, + "ComputeResources": (*corev1.ResourceRequirements)(nil), + "Debug": (*v1beta1.TaskRunDebug)(nil), + "PodTemplate": (*pod.Template)(nil), + "Resources": (*v1beta1.TaskRunResources)(nil), + "Retries": 0, + "ServiceAccountName": "", + "SidecarOverrides": []v1beta1.TaskRunSidecarOverride(nil), + "Status": v1beta1.TaskRunSpecStatus(""), + "StatusMessage": v1beta1.TaskRunSpecStatusMessage(""), + "StepOverrides": []v1beta1.TaskRunStepOverride(nil), + "Timeout": (*metav1.Duration)(nil), + "Workspaces": []v1beta1.WorkspaceBinding(nil), + }, + } + got := invocation(objects.NewTaskRunObject(taskRun)) + if !reflect.DeepEqual(expected, got) { + if d := cmp.Diff(expected, got); d != "" { + t.Log(d) + } + t.Fatalf("expected \n%v\n got \n%v\n", expected, got) + } +} + +func TestGetSubjectDigests(t *testing.T) { + tr := &v1beta1.TaskRun{ + Spec: v1beta1.TaskRunSpec{ + Resources: &v1beta1.TaskRunResources{ + Outputs: []v1beta1.TaskResourceBinding{ + { + PipelineResourceBinding: v1beta1.PipelineResourceBinding{ + Name: "nil-check", + }, + }, { + PipelineResourceBinding: v1beta1.PipelineResourceBinding{ + Name: "built-image", + ResourceSpec: &v1alpha1.PipelineResourceSpec{ + Type: v1alpha1.PipelineResourceTypeImage, + }, + }, + }, + }, + }, + }, + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + TaskRunResults: []v1beta1.TaskRunResult{ + { + Name: "IMAGE_URL", + Value: *v1beta1.NewArrayOrString("registry/myimage"), + }, + { + Name: "IMAGE_DIGEST", + Value: *v1beta1.NewArrayOrString(digest1), + }, + { + Name: "mvn1_ARTIFACT_URI", + Value: *v1beta1.NewArrayOrString("maven-test-0.1.1.jar"), + }, + { + Name: "mvn1_ARTIFACT_DIGEST", + Value: *v1beta1.NewArrayOrString(digest3), + }, + { + Name: "mvn1_pom_ARTIFACT_URI", + Value: *v1beta1.NewArrayOrString("maven-test-0.1.1.pom"), + }, + { + Name: "mvn1_pom_ARTIFACT_DIGEST", + Value: *v1beta1.NewArrayOrString(digest4), + }, + { + Name: "mvn1_src_ARTIFACT_URI", + Value: *v1beta1.NewArrayOrString("maven-test-0.1.1-sources.jar"), + }, + { + Name: "mvn1_src_ARTIFACT_DIGEST", + Value: *v1beta1.NewArrayOrString(digest5), + }, + { + Name: "invalid_ARTIFACT_DIGEST", + Value: *v1beta1.NewArrayOrString(digest5), + }, + { + Name: "mvn1_pkg" + "-" + artifacts.ArtifactsOutputsResultName, + Value: *v1beta1.NewObject(map[string]string{ + "uri": "projects/test-project-1/locations/us-west4/repositories/test-repo/mavenArtifacts/com.google.guava:guava:31.0-jre", + "digest": digest1, + }), + }, + { + Name: "mvn1_pom_sha512" + "-" + artifacts.ArtifactsOutputsResultName, + Value: *v1beta1.NewObject(map[string]string{ + "uri": "com.google.guava:guava:1.0-jre.pom", + "digest": digest2, + }), + }, + { + Name: "img1_input" + "-" + artifacts.ArtifactsInputsResultName, + Value: *v1beta1.NewObject(map[string]string{ + "uri": "gcr.io/foo/bar", + "digest": digest3, + }), + }, + }, + ResourcesResult: []v1beta1.PipelineResourceResult{ + { + ResourceName: "built-image", + Key: "url", + Value: "registry/resource-image", + }, { + ResourceName: "built-image", + Key: "digest", + Value: digest2, + }, + }, + }, + }, + } + + expected := []in_toto.Subject{ + { + Name: "com.google.guava:guava:1.0-jre.pom", + Digest: slsa.DigestSet{ + "sha256": strings.TrimPrefix(digest2, "sha256:"), + }, + }, { + Name: "index.docker.io/registry/myimage", + Digest: slsa.DigestSet{ + "sha256": strings.TrimPrefix(digest1, "sha256:"), + }, + }, { + Name: "maven-test-0.1.1-sources.jar", + Digest: slsa.DigestSet{ + "sha256": strings.TrimPrefix(digest5, "sha256:"), + }, + }, { + Name: "maven-test-0.1.1.jar", + Digest: slsa.DigestSet{ + "sha256": strings.TrimPrefix(digest3, "sha256:"), + }, + }, { + Name: "maven-test-0.1.1.pom", + Digest: slsa.DigestSet{ + "sha256": strings.TrimPrefix(digest4, "sha256:"), + }, + }, { + Name: "projects/test-project-1/locations/us-west4/repositories/test-repo/mavenArtifacts/com.google.guava:guava:31.0-jre", + Digest: slsa.DigestSet{ + "sha256": strings.TrimPrefix(digest1, "sha256:"), + }, + }, { + Name: "registry/resource-image", + Digest: slsa.DigestSet{ + "sha256": strings.TrimPrefix(digest2, "sha256:"), + }, + }, + } + tro := objects.NewTaskRunObject(tr) + got := extract.SubjectDigests(tro, logtesting.TestLogger(t)) + if !reflect.DeepEqual(expected, got) { + if d := cmp.Diff(expected, got); d != "" { + t.Log(d) + } + t.Fatalf("expected \n%v\n got \n%v\n", expected, got) + } +} diff --git a/pkg/chains/formats/slsa/v2/taskrun/taskrun.go b/pkg/chains/formats/slsa/v2/taskrun/taskrun.go new file mode 100644 index 0000000000..cd560a6a89 --- /dev/null +++ b/pkg/chains/formats/slsa/v2/taskrun/taskrun.go @@ -0,0 +1,93 @@ +/* +Copyright 2022 The Tekton Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskrun + +import ( + "context" + "reflect" + + intoto "github.com/in-toto/in-toto-golang/in_toto" + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + "github.com/tektoncd/chains/pkg/chains/formats/slsa/extract" + slsav1 "github.com/tektoncd/chains/pkg/chains/formats/slsa/v1/taskrun" + "github.com/tektoncd/chains/pkg/chains/objects" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "k8s.io/apimachinery/pkg/util/sets" + "knative.dev/pkg/logging" +) + +// BuildConfig is the custom Chains format to fill out the +// "buildConfig" section of the slsa-provenance predicate +type BuildConfig struct { + TaskSpec *v1beta1.TaskSpec `json:"taskSpec"` +} + +func GenerateAttestation(builderID string, tro *objects.TaskRunObject, ctx context.Context) (interface{}, error) { + logger := logging.FromContext(ctx) + subjects := extract.SubjectDigests(tro, logger) + + mat, err := slsav1.Materials(tro, logger) + if err != nil { + return nil, err + } + att := intoto.ProvenanceStatement{ + StatementHeader: intoto.StatementHeader{ + Type: intoto.StatementInTotoV01, + PredicateType: slsa.PredicateSLSAProvenance, + Subject: subjects, + }, + Predicate: slsa.ProvenancePredicate{ + Builder: slsa.ProvenanceBuilder{ + ID: builderID, + }, + BuildType: tro.GetGVK(), + Invocation: invocation(tro), + BuildConfig: BuildConfig{TaskSpec: tro.Status.TaskSpec}, + Metadata: slsav1.Metadata(tro), + Materials: mat, + }, + } + return att, nil +} + +// invocation describes the event that kicked off the build +// we currently don't set ConfigSource because we don't know +// which material the Task definition came from +func invocation(tro *objects.TaskRunObject) slsa.ProvenanceInvocation { + i := slsa.ProvenanceInvocation{} + if p := tro.Status.Provenance; p != nil { + i.ConfigSource = slsa.ConfigSource{ + URI: p.ConfigSource.URI, + Digest: p.ConfigSource.Digest, + EntryPoint: p.ConfigSource.EntryPoint, + } + } + i.Parameters = invocationParams(tro) + return i +} + +// invocationParams adds all fields from the task run object except +// TaskRef or TaskSpec since they are in the ConfigSource or buildConfig. +func invocationParams(tro *objects.TaskRunObject) map[string]any { + var iParams map[string]any = make(map[string]any) + skipFields := sets.NewString("TaskRef", "TaskSpec") + v := reflect.ValueOf(tro.Spec) + for i := 0; i < v.NumField(); i++ { + fieldName := v.Type().Field(i).Name + if !skipFields.Has(v.Type().Field(i).Name) { + iParams[fieldName] = v.Field(i).Interface() + } + } + return iParams +} diff --git a/pkg/config/config.go b/pkg/config/config.go index c01638d6a0..bb40077269 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -250,12 +250,12 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { if err := cm.Parse(data, // Artifact-specific configs // TaskRuns - asString(taskrunFormatKey, &cfg.Artifacts.TaskRuns.Format, "in-toto", "slsa/v1"), + asString(taskrunFormatKey, &cfg.Artifacts.TaskRuns.Format, "in-toto", "slsa/v1", "slsa/v2"), asStringSet(taskrunStorageKey, &cfg.Artifacts.TaskRuns.StorageBackend, sets.NewString("tekton", "oci", "gcs", "docdb", "grafeas", "kafka")), asString(taskrunSignerKey, &cfg.Artifacts.TaskRuns.Signer, "x509", "kms"), // PipelineRuns - asString(pipelinerunFormatKey, &cfg.Artifacts.PipelineRuns.Format, "in-toto", "slsa/v1"), + asString(pipelinerunFormatKey, &cfg.Artifacts.PipelineRuns.Format, "in-toto", "slsa/v1", "slsa/v2"), asStringSet(pipelinerunStorageKey, &cfg.Artifacts.PipelineRuns.StorageBackend, sets.NewString("tekton", "oci", "grafeas")), asString(pipelinerunSignerKey, &cfg.Artifacts.PipelineRuns.Signer, "x509", "kms"), diff --git a/test/examples_test.go b/test/examples_test.go index 989f6c854b..f3baca7ba1 100644 --- a/test/examples_test.go +++ b/test/examples_test.go @@ -58,6 +58,7 @@ type TestExample struct { getExampleObjects func(t *testing.T, ns string) map[string]objects.TektonObject payloadKey string signatureKey string + outputLocation string } // TestExamples copies the format in the tektoncd/pipelines repo @@ -65,7 +66,7 @@ type TestExample struct { func TestExamples(t *testing.T) { tests := []TestExample{ { - name: "taskrun-examples", + name: "taskrun-examples-slsa-v1", cm: map[string]string{ "artifacts.taskrun.format": "slsa/v1", "artifacts.oci.storage": "tekton", @@ -73,6 +74,18 @@ func TestExamples(t *testing.T) { getExampleObjects: getTaskRunExamples, payloadKey: "chains.tekton.dev/payload-taskrun-%s", signatureKey: "chains.tekton.dev/signature-taskrun-%s", + outputLocation: "slsa/v1", + }, + { + name: "taskrun-examples-slsa-v2", + cm: map[string]string{ + "artifacts.taskrun.format": "slsa/v2", + "artifacts.oci.storage": "tekton", + }, + getExampleObjects: getTaskRunExamples, + payloadKey: "chains.tekton.dev/payload-taskrun-%s", + signatureKey: "chains.tekton.dev/signature-taskrun-%s", + outputLocation: "slsa/v2", }, { name: "pipelinerun-examples", @@ -83,6 +96,7 @@ func TestExamples(t *testing.T) { getExampleObjects: getPipelineRunExamples, payloadKey: "chains.tekton.dev/payload-pipelinerun-%s", signatureKey: "chains.tekton.dev/signature-pipelinerun-%s", + outputLocation: "slsa/v1", }, } @@ -127,7 +141,7 @@ func runInTotoFormatterTests(ctx context.Context, t *testing.T, ns string, c *cl if err := json.Unmarshal(payload, &gotProvenance); err != nil { t.Fatal(err) } - expected := expectedProvenance(t, path, completed) + expected := expectedProvenance(t, path, completed, test.outputLocation) if diff := cmp.Diff(expected, gotProvenance, OptSortMaterial); diff != "" { t.Errorf("provenance dont match: -want +got: %s", diff) @@ -177,12 +191,12 @@ func (v *verifier) Public() crypto.PublicKey { return v.pub } -func expectedProvenance(t *testing.T, example string, obj objects.TektonObject) intoto.ProvenanceStatement { +func expectedProvenance(t *testing.T, example string, obj objects.TektonObject, outputLocation string) intoto.ProvenanceStatement { switch obj.(type) { case *objects.TaskRunObject: - return expectedTaskRunProvenance(t, example, obj) + return expectedTaskRunProvenance(t, example, obj, outputLocation) case *objects.PipelineRunObject: - return expectedPipelineRunProvenance(t, example, obj) + return expectedPipelineRunProvenance(t, example, obj, outputLocation) default: t.Error("Unexpected type trying to get provenance") } @@ -205,7 +219,7 @@ type Format struct { URIDigest []URIDigestPair } -func expectedTaskRunProvenance(t *testing.T, example string, obj objects.TektonObject) intoto.ProvenanceStatement { +func expectedTaskRunProvenance(t *testing.T, example string, obj objects.TektonObject, outputLocation string) intoto.ProvenanceStatement { tr := obj.GetObject().(*v1beta1.TaskRun) name := tr.Name @@ -239,10 +253,10 @@ func expectedTaskRunProvenance(t *testing.T, example string, obj objects.TektonO URIDigest: uridigest, } - return readExpectedAttestation(t, example, f) + return readExpectedAttestation(t, example, f, outputLocation) } -func expectedPipelineRunProvenance(t *testing.T, example string, obj objects.TektonObject) intoto.ProvenanceStatement { +func expectedPipelineRunProvenance(t *testing.T, example string, obj objects.TektonObject, outputLocation string) intoto.ProvenanceStatement { pr := obj.GetObject().(*v1beta1.PipelineRun) buildStartTimes := []string{} @@ -284,11 +298,11 @@ func expectedPipelineRunProvenance(t *testing.T, example string, obj objects.Tek URIDigest: uridigest, } - return readExpectedAttestation(t, example, f) + return readExpectedAttestation(t, example, f, outputLocation) } -func readExpectedAttestation(t *testing.T, example string, f Format) intoto.ProvenanceStatement { - path := filepath.Join("testdata/intoto", strings.Replace(filepath.Base(example), ".yaml", ".json", 1)) +func readExpectedAttestation(t *testing.T, example string, f Format, outputLocation string) intoto.ProvenanceStatement { + path := filepath.Join("testdata", outputLocation, strings.Replace(filepath.Base(example), ".yaml", ".json", 1)) t.Logf("Reading expected provenance from %s", path) contents, err := ioutil.ReadFile(path) diff --git a/test/testdata/intoto/pipeline-output-image.json b/test/testdata/slsa/v1/pipeline-output-image.json similarity index 100% rename from test/testdata/intoto/pipeline-output-image.json rename to test/testdata/slsa/v1/pipeline-output-image.json diff --git a/test/testdata/intoto/task-output-image.json b/test/testdata/slsa/v1/task-output-image.json similarity index 100% rename from test/testdata/intoto/task-output-image.json rename to test/testdata/slsa/v1/task-output-image.json diff --git a/test/testdata/slsa/v2/task-output-image.json b/test/testdata/slsa/v2/task-output-image.json new file mode 100644 index 0000000000..158d792434 --- /dev/null +++ b/test/testdata/slsa/v2/task-output-image.json @@ -0,0 +1,111 @@ +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v0.2", + "subject": [ + { + "name": "gcr.io/foo/bar", + "digest": { + "sha256": "05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5" + } + } + ], + "predicate": { + "builder": { + "id": "https://tekton.dev/chains/v2" + }, + "buildType": "tekton.dev/v1beta1/TaskRun", + "invocation": { + "configSource": {}, + "parameters": { + "ComputeResources": null, + "Debug": null, + "Params": null, + "PodTemplate": null, + "Resources": { + "inputs": [{ + "name": "sourcerepo", + "resourceSpec": { + "params": [ + {"name": "revision", "value": "v0.32.0"}, + { + "name": "url", + "value": "https://github.com/GoogleContainerTools/skaffold" + } + ], + "type": "git" + } + }], + "outputs": [{ + "name": "builtImage", + "resourceSpec": { + "params": [{"name": "url", "value": "gcr.io/foo/bar"}], + "type": "image" + } + }] + }, + "Retries": 0, + "ServiceAccountName": "default", + "SidecarOverrides": null, + "Status": "", + "StatusMessage": "", + "StepOverrides": null, + "Timeout": "1h0m0s", + "Workspaces": null + } + }, + "buildConfig": { + "taskSpec": { + "resources": { + "inputs": [{ + "name": "sourcerepo", + "type": "git" + }], + "outputs": [{ + "name": "builtImage", + "type": "image", + "targetPath": "/workspace/sourcerepo" + }] + }, + "steps": [ + { + "name": "build-and-push", + "image": "busybox", + "resources": {}, + "script": "set -e\ncat \u003c\u003cEOF > $(inputs.resources.sourcerepo.path)/index.json\n{\n\"schemaVersion\": 2,\n\"manifests\": [\n {\n \"mediaType\": \"application/vnd.oci.image.index.v1+json\",\n \"size\": 314,\n \"digest\": \"sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5\"\n }\n]\n}\n" + },{ + "name": "echo", + "image": "busybox", + "resources": {}, + "script": "cat $(inputs.resources.sourcerepo.path)/index.json" + } + ] + } + }, + "metadata": { + "buildStartedOn": "{{index .BuildStartTimes 0}}", + "buildFinishedOn": "{{index .BuildFinishedTimes 0}}", + "completeness": { + "parameters": false, + "environment": false, + "materials": false + }, + "reproducible": false + }, + "materials": [ + {{range .URIDigest}} + { + "uri": "{{.URI}}", + "digest": { + "sha256": "{{.Digest}}" + } + }, + {{end}} + { + "uri": "git+https://github.com/GoogleContainerTools/skaffold@v0.32.0", + "digest": { + "sha1": "6ed7aad5e8a36052ee5f6079fc91368e362121f7" + } + } + ] + } +}