Skip to content

Commit

Permalink
PipelineRuns with v2alpha4 to process StepActions (#1118)
Browse files Browse the repository at this point in the history
* Add new v2alpha4 version for PipelineRuns
This new version will now process the information from any associated StepAction from the executed PipelineRun when `artifacts.pipelinerun.enable-deep-inspection` is set to `true`.

Also, the way chains read results from PipelineRuns to populate the `subjects` field is changing: now the user has to explicitly mark a result as a subject using an object type-hinted tag (*ARTIFACT_OUTPUTS) + the new `isBuildArtifact` property in the value.

Refactors to share logic between v2alph3 and v2alpha4.

* Fix issue when reading *IMAGE_URL / *IMAGE_DIGEST type hint results when two or more tasks/steps are using the same prefix.
  • Loading branch information
renzodavid9 authored May 22, 2024
1 parent 8e9373e commit 9a67b0f
Show file tree
Hide file tree
Showing 28 changed files with 1,910 additions and 354 deletions.
3 changes: 2 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Supported keys include:

| Key | Description | Supported Values | Default |
| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------- | :-------- |
| `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3` | `in-toto` |
| `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3`, `slsa/v2alpha4` | `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` |
| `artifacts.pipelinerun.enable-deep-inspection` | This boolean option will configure whether Chains should inspect child taskruns in order to capture inputs/outputs within a pipelinerun. `"false"` means that Chains only checks pipeline level results, whereas `"true"` means Chains inspects both pipeline level and task level results. | `"true"`, `"false"` | `"false"` |
Expand All @@ -45,6 +45,7 @@ Supported keys include:
> - For grafeas storage backend, currently we only support Container Analysis. We will make grafeas server address configurabe within a short time.
> - `slsa/v1` is an alias of `in-toto` for backwards compatibility.
> - `slsa/v2alpha3` corresponds to the slsav1.0 spec. and uses latest [`v1` Tekton Objects](https://tekton.dev/docs/pipelines/pipeline-api/#tekton.dev/v1). Recommended format for new chains users who want the slsav1.0 spec.
> - `slsa/v2alpha4` corresponds to the slsav1.0 spec. and uses latest [`v1` Tekton Objects](https://tekton.dev/docs/pipelines/pipeline-api/#tekton.dev/v1). It reads type-hinted results from [StepActions](https://tekton.dev/docs/pipelines/pipeline-api/#tekton.dev/v1alpha1.StepAction) when `artifacts.pipelinerun.enable-deep-inspection` is set to `true`. Recommended format for new chains users who want the slsav1.0 spec.

### OCI Configuration
Expand Down
2 changes: 1 addition & 1 deletion docs/slsa-provenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ spec:
`second-ARTIFACT_OUTPUTS`, `third-IMAGE_URL`/`third-IMAGE_DIGEST`, and `IMAGES` will be considered as `subject`. `first-ARTIFACT_OUTPUTS` doesn't specify `isBuildArtifact: true` so it is not count as `subject`.
Chains' `v2alpha4` formatter now automatically reads type-hinted results from StepActions associated to the executed TaskRun; users no longer need to manually surface these results from the StepActions when the appropriate type hints are in place. For instance, with the following TaskRun:
Chains' `v2alpha4` formatter now automatically reads type-hinted results from StepActions associated to the executed TaskRun/PipelineRun; users no longer need to manually surface these results from the StepActions when the appropriate type hints are in place. PipelineRuns require `artifacts.pipelinerun.enable-deep-inspection: true` for this functionality to work. For instance, with the following TaskRun:
```yaml
apiVersion: tekton.dev/v1alpha1
Expand Down
35 changes: 35 additions & 0 deletions examples/v2alpha4/pipeline-with-object-type-hinting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: pipeline-test-run
spec:
pipelineSpec:
results:
- name: output1-ARTIFACT_OUTPUTS
value: $(tasks.t1.results.output1)
- name: output2-ARTIFACT_OUTPUTS
value: $(tasks.t1.results.output2)
tasks:
- name: t1
taskSpec:
results:
- name: output1
type: object
properties:
uri: {}
digest: {}
isBuildArtifact: {}

- name: output2
type: object
properties:
uri: {}
digest: {}

steps:
- name: step1
image: busybox:glibc
script: |
echo -n "Hello!"
echo -n "{\"uri\":\"gcr.io/foo/img1\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\", \"isBuildArtifact\": \"true\" }" > $(results.output1.path)
echo -n "{\"uri\":\"gcr.io/foo/img2\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\"}" > $(results.output2.path)
7 changes: 0 additions & 7 deletions pkg/artifacts/signable.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,7 @@ func ExtractStructuredTargetFromResults(ctx context.Context, objResults []object
}

// TODO(#592): support structured results using Run
results := []objects.Result{}
for _, res := range objResults {
results = append(results, objects.Result{
Name: res.Name,
Value: res.Value,
})
}
for _, res := range results {
if strings.HasSuffix(res.Name, categoryMarker) {
valid, err := isStructuredResult(res, categoryMarker)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters"
internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters"
resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies"
"github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig"
"github.com/tektoncd/chains/pkg/chains/objects"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -62,6 +63,46 @@ func GetTaskRunBuildDefinition(ctx context.Context, tro *objects.TaskRunObjectV1
}, nil
}

// GetPipelineRunBuildDefinition returns the buildDefinition for the given PipelineRun based on the configured buildType. This will default to the slsa buildType
func GetPipelineRunBuildDefinition(ctx context.Context, pro *objects.PipelineRunObjectV1, slsaconfig *slsaconfig.SlsaConfig, resolveOpts resolveddependencies.ResolveOptions) (slsa.BuildDefinition, error) {
buildDefinitionType := slsaconfig.BuildType
if slsaconfig.BuildType == "" {
buildDefinitionType = buildtypes.SlsaBuildType
}

td, err := resolveddependencies.GetTaskDescriptor(buildDefinitionType)
if err != nil {
return slsa.BuildDefinition{}, err
}

rd, err := resolveddependencies.PipelineRun(ctx, pro, slsaconfig, resolveOpts, td)
if err != nil {
return slsa.BuildDefinition{}, err
}

externalParams := externalparameters.PipelineRun(pro)
structExternalParams, err := getStruct(externalParams)
if err != nil {
return slsa.BuildDefinition{}, err
}

internalParams, err := internalparameters.GetInternalParamters(pro, buildDefinitionType)
if err != nil {
return slsa.BuildDefinition{}, err
}
structInternalParams, err := getStruct(internalParams)
if err != nil {
return slsa.BuildDefinition{}, err
}

return slsa.BuildDefinition{
BuildType: buildDefinitionType,
ExternalParameters: structExternalParams,
InternalParameters: structInternalParams,
ResolvedDependencies: rd,
}, nil
}

func getStruct(data map[string]any) (*structpb.Struct, error) {
bytes, err := json.Marshal(data)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ import (

"github.com/google/go-cmp/cmp"
slsa "github.com/in-toto/attestation/go/predicates/provenance/v1"
intoto "github.com/in-toto/attestation/go/v1"
externalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/external_parameters"
internalparameters "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/internal_parameters"
resolveddependencies "github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/resolved_dependencies"
"github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig"
"github.com/tektoncd/chains/pkg/chains/objects"
"github.com/tektoncd/chains/pkg/internal/objectloader"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
)

func TestGetBuildDefinition(t *testing.T) {
func TestGetTaskRunBuildDefinition(t *testing.T) {
tr, err := objectloader.TaskRunFromFile("../../testdata/slsa-v2alpha4/taskrun1.json")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -103,7 +105,7 @@ func TestGetBuildDefinition(t *testing.T) {
}
}

func TestUnsupportedBuildType(t *testing.T) {
func TestTaskRunUnsupportedBuildType(t *testing.T) {
tr, err := objectloader.TaskRunFromFile("../../testdata/slsa-v2alpha4/taskrun1.json")
if err != nil {
t.Fatal(err)
Expand All @@ -127,3 +129,102 @@ func getProtoStruct(t *testing.T, data map[string]any) *structpb.Struct {

return protoStruct
}

func TestGetPipelineRunBuildDefinition(t *testing.T) {
pr := createPro("../../testdata/slsa-v2alpha3/pipelinerun1.json")
pr.Annotations = map[string]string{
"annotation1": "annotation1",
}
pr.Labels = map[string]string{
"label1": "label1",
}
tests := []struct {
name string
config *slsaconfig.SlsaConfig
want slsa.BuildDefinition
}{
{
name: "test slsa build type",
config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa"},
want: slsa.BuildDefinition{
BuildType: "https://tekton.dev/chains/v2/slsa",
ExternalParameters: getProtoStruct(t, externalparameters.PipelineRun(pr)),
InternalParameters: getProtoStruct(t, internalparameters.SLSAInternalParameters(pr)),
ResolvedDependencies: getResolvedDependencies(pr, resolveddependencies.AddSLSATaskDescriptor),
},
},
{
name: "test tekton build type",
config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa-tekton"},
want: slsa.BuildDefinition{
BuildType: "https://tekton.dev/chains/v2/slsa-tekton",
ExternalParameters: getProtoStruct(t, externalparameters.PipelineRun(pr)),
InternalParameters: getProtoStruct(t, internalparameters.TektonInternalParameters(pr)),
ResolvedDependencies: getResolvedDependencies(pr, resolveddependencies.AddTektonTaskDescriptor),
},
},
{
name: "test default build type",
config: &slsaconfig.SlsaConfig{BuildType: "https://tekton.dev/chains/v2/slsa"},
want: slsa.BuildDefinition{
BuildType: "https://tekton.dev/chains/v2/slsa",
ExternalParameters: getProtoStruct(t, externalparameters.PipelineRun(pr)),
InternalParameters: getProtoStruct(t, internalparameters.SLSAInternalParameters(pr)),
ResolvedDependencies: getResolvedDependencies(pr, resolveddependencies.AddSLSATaskDescriptor),
},
},
}

for i := range tests {
tc := &tests[i]
t.Run(tc.name, func(t *testing.T) {
bd, err := GetPipelineRunBuildDefinition(context.TODO(), pr, tc.config, resolveddependencies.ResolveOptions{})
if err != nil {
t.Fatalf("Did not expect an error but got %v", err)
}

if diff := cmp.Diff(&tc.want, &bd, protocmp.Transform()); diff != "" {
t.Errorf("getBuildDefinition(): -want +got: %v", diff)
}
})
}
}

func createPro(path string) *objects.PipelineRunObjectV1 {
pr, err := objectloader.PipelineRunFromFile(path)
if err != nil {
panic(err)
}
tr1, err := objectloader.TaskRunFromFile("../../testdata/slsa-v2alpha3/taskrun1.json")
if err != nil {
panic(err)
}
tr2, err := objectloader.TaskRunFromFile("../../testdata/slsa-v2alpha3/taskrun2.json")
if err != nil {
panic(err)
}
p := objects.NewPipelineRunObjectV1(pr)
p.AppendTaskRun(tr1)
p.AppendTaskRun(tr2)
return p
}

func getResolvedDependencies(pr *objects.PipelineRunObjectV1, addTasks func(*objects.TaskRunObjectV1) (*intoto.ResourceDescriptor, error)) []*intoto.ResourceDescriptor {
rd, err := resolveddependencies.PipelineRun(context.Background(), pr, &slsaconfig.SlsaConfig{}, resolveddependencies.ResolveOptions{}, addTasks)
if err != nil {
return []*intoto.ResourceDescriptor{}
}
return rd
}

func TestPipelineRunUnsupportedBuildType(t *testing.T) {
pr := createPro("../../testdata/slsa-v2alpha3/pipelinerun1.json")

got, err := GetPipelineRunBuildDefinition(context.Background(), pr, &slsaconfig.SlsaConfig{BuildType: "bad-buildtype"}, resolveddependencies.ResolveOptions{})
if err == nil {
t.Error("getBuildDefinition(): expected error got nil")
}
if diff := cmp.Diff(&slsa.BuildDefinition{}, &got, protocmp.Transform()); diff != "" {
t.Errorf("getBuildDefinition(): -want +got: %s", diff)
}
}
Loading

0 comments on commit 9a67b0f

Please sign in to comment.