Skip to content

Commit

Permalink
Add new v2alpha4 version for TaskRuns (#1111)
Browse files Browse the repository at this point in the history
* Add new v2alpha4 version for TaskRuns

This new version will now process the information from any associated StepAction from the executed TaskRun.

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

* Upgrading SLSA and intoto libraries to not use deprecated structs and linter fixes.
  • Loading branch information
renzodavid9 authored May 21, 2024
1 parent b1773f8 commit 8e9373e
Show file tree
Hide file tree
Showing 69 changed files with 5,424 additions and 1,650 deletions.
3 changes: 2 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ Supported keys include:

| Key | Description | Supported Values | Default |
| :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | :-------- |
| `artifacts.taskrun.format` | The format to store `TaskRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3` | `in-toto` |
| `artifacts.taskrun.format` | The format to store `TaskRun` payloads in. | `in-toto`, `slsa/v1`, `slsa/v2alpha3`, `slsa/v2alpha4` | `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` |

> NOTE:
>
> - `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). Recommended format for new chains users who want the slsav1.0 spec.
### PipelineRun Configuration

Expand Down
101 changes: 100 additions & 1 deletion docs/slsa-provenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The following shows the mapping between slsa version and formatter name.

| SLSA Version | Formatter Name |
| ------------ | ---------------------- |
| v1.0 | `slsa/v2alpha3` |
| v1.0 | `slsa/v2alpha3` and `slsa/v2alpha4` |
| v0.2 | `slsa/v1` or `in-toto` |

To configure Task-level provenance version
Expand Down Expand Up @@ -319,6 +319,105 @@ spec:
</details>
## `v2alpha4` formatter
Starting with version `v2alpha4`, the type-hinted object results value now can include a new boolean flag called `isBuildArtifact`. When set to `true`, this flag indicates the output artifact should be considered as `subject` in the executed TaskRun/PipelineRun.
The `isBuildArtifact` can be set in results whose type-hint uses the `*ARTIFACT_OUTPUTS` format. Results using the `IMAGES` and `*IMAGE_URL` / `*IMAGE_DIGEST` type-hint format will still be considered as `subject` automatically; all other results will be clasified as `byProduct`
For instance, in the following TaskRun:
```yaml
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
name: image-build
spec:
taskSpec:
results:
- name: first-ARTIFACT_OUTPUTS
description: The first artifact built
type: object
properties:
uri: {}
digest: {}
- name: second-ARTIFACT_OUTPUTS
description: The second artifact built
type: object
properties:
uri: {}
digest: {}
isBuildArtifact: {}
- name: third-IMAGE_URL
type: string
- name: third-IMAGE_DIGEST
type: string
- name: IMAGES
type: string
steps:
- name: dummy-build
image: bash:latest
script: |
echo -n "{\"uri\":\"gcr.io/foo/img1\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\"}" > $(results.first-ARTIFACT_OUTPUTS.path)
echo -n "{\"uri\":\"gcr.io/foo/img2\", \"digest\":\"sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5\", \"isBuildArtifact\":\"true\"}" > $(results.second-ARTIFACT_OUTPUTS.path)
echo -n "gcr.io/foo/bar" | tee $(results.third-IMAGE_URL.path)
echo -n "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" | tee $(results.third-IMAGE_DIGEST.path)
echo -n "gcr.io/test/img3@sha256:2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b, gcr.io/test/img4@sha256:ef334b5d9704da9b325ed6d4e3e5327863847e2da6d43f81831fd1decbdb2213" | tee $(results.IMAGES.path)
```
`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:
```yaml
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
name: img-builder
spec:
image: busybox:glibc
results:
- name: first-ARTIFACT_OUTPUTS
description: The first artifact built
type: object
properties:
uri: {}
digest: {}
isBuildArtifact: {}
- name: second-IMAGE_URL
type: string
- name: second-IMAGE_DIGEST
type: string
script: |
echo -n "{\"uri\":\"gcr.io/foo/img1\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\", \"isBuildArtifact\": \"true\" }" > $(step.results.first-ARTIFACT_OUTPUTS.path)
echo -n "gcr.io/foo/bar" > $(step.results.second-IMAGE_URL.path)
echo -n "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" > $(step.results.second-IMAGE_DIGEST.path)
---
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
name: taskrun
spec:
taskSpec:
steps:
- name: action-runner
ref:
name: img-builder
```
Chains Will read `first-ARTIFACT_OUTPUTS` and `second-IMAGE_URL/second-IMAGE_DIGEST` from the StepAction and clasify them as a `subject`.
## Besides inputs/outputs
Tekton Chains is also able to capture the feature flags being used for Tekton Pipelines controller and the origin of the build configuration file with immutable references such as task.yaml and pipeline.yaml. However, those fields in Tekton Pipelines are gated by a dedicated feature flag. Therefore, the feature flag needs to be enabled to let Tekton Pipelines controller to populate these fields.
Expand Down
52 changes: 52 additions & 0 deletions examples/v2alpha4/task-with-object-type-hinting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
name: image-build
spec:
taskSpec:
results:
- name: first-ARTIFACT_OUTPUTS
description: The first artifact built
type: object
properties:
uri: {}
digest: {}

- name: second-ARTIFACT_OUTPUTS
description: The second artifact built
type: object
properties:
uri: {}
digest: {}
isBuildArtifact: {}

- name: third-IMAGE_URL
type: string
- name: third-IMAGE_DIGEST
type: string

- name: IMAGES
type: string

- name: fourth-ARTIFACT_OUTPUTS
type: object
properties:
uri: {}
digest: {}
isBuildArtifact: {}


steps:
- name: dummy-build
image: bash:latest
script: |
echo -n "{\"uri\":\"gcr.io/foo/img1\", \"digest\":\"sha256:586789aa031fafc7d78a5393cdc772e0b55107ea54bb8bcf3f2cdac6c6da51ee\"}" > $(results.first-ARTIFACT_OUTPUTS.path)
echo -n "{\"uri\":\"gcr.io/foo/img2\", \"digest\":\"sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5\", \"isBuildArtifact\":\"true\"}" > $(results.second-ARTIFACT_OUTPUTS.path)
echo -n "gcr.io/foo/bar" | tee $(results.third-IMAGE_URL.path)
echo -n "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b6" | tee $(results.third-IMAGE_DIGEST.path)
echo -n "gcr.io/test/img3@sha256:2996854378975c2f8011ddf0526975d1aaf1790b404da7aad4bf25293055bc8b" | tee $(results.IMAGES.path)
echo -n "{\"uri\":\"gcr.io/test/img4\", \"digest\":\"sha256:basd-sha\", \"isBuildArtifact\":\"true\"}" > $(results.fourth-ARTIFACT_OUTPUTS.path)
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/google/go-licenses v1.6.0
github.com/grafeas/grafeas v0.2.3
github.com/hashicorp/go-multierror v1.1.1
github.com/in-toto/attestation v1.0.1
github.com/in-toto/in-toto-golang v0.9.1-0.20240317085821-8e2966059a09
github.com/opencontainers/go-digest v1.0.0
github.com/pkg/errors v0.9.1
Expand Down Expand Up @@ -254,7 +255,6 @@ require (
github.com/hashicorp/vault/api v1.12.2 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/in-toto/attestation v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
Expand Down
70 changes: 57 additions & 13 deletions pkg/artifacts/signable.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ const (
ArtifactsOutputsResultName = "ARTIFACT_OUTPUTS"
OCIScheme = "oci://"
GitSchemePrefix = "git+"
isBuildArtifactField = "isBuildArtifact"
OCIImageURLResultName = "IMAGE_URL"
OCIImageDigestResultName = "IMAGE_DIGEST"
OCIImagesResultName = "IMAGES"
)

var (
Expand Down Expand Up @@ -191,22 +195,23 @@ func (oa *OCIArtifact) ExtractObjects(ctx context.Context, obj objects.TektonObj
}

// Now check TaskResults
resultImages := ExtractOCIImagesFromResults(ctx, obj)
resultImages := ExtractOCIImagesFromResults(ctx, obj.GetResults())
objs = append(objs, resultImages...)

return objs
}

func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) []interface{} {
// ExtractOCIImagesFromResults returns all the results marked as OCIImage type-hint result.
func ExtractOCIImagesFromResults(ctx context.Context, results []objects.Result) []interface{} {
logger := logging.FromContext(ctx)
objs := []interface{}{}

extractor := structuredSignableExtractor{
uriSuffix: "IMAGE_URL",
digestSuffix: "IMAGE_DIGEST",
uriSuffix: OCIImageURLResultName,
digestSuffix: OCIImageDigestResultName,
isValid: hasImageRequirements,
}
for _, s := range extractor.extract(ctx, obj) {
for _, s := range extractor.extract(ctx, results) {
dgst, err := name.NewDigest(fmt.Sprintf("%s@%s", s.URI, s.Digest))
if err != nil {
logger.Errorf("error getting digest: %v", err)
Expand All @@ -216,8 +221,8 @@ func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject)
}

// look for a comma separated list of images
for _, key := range obj.GetResults() {
if key.Name != "IMAGES" {
for _, key := range results {
if key.Name != OCIImagesResultName {
continue
}
imgs := strings.FieldsFunc(key.Value.StringVal, split)
Expand Down Expand Up @@ -256,20 +261,20 @@ func ExtractSignableTargetFromResults(ctx context.Context, obj objects.TektonObj
return true
},
}
return extractor.extract(ctx, obj)
return extractor.extract(ctx, obj.GetResults())
}

// FullRef returns the full reference of the signable artifact in the format of URI@DIGEST
func (s *StructuredSignable) FullRef() string {
return fmt.Sprintf("%s@%s", s.URI, s.Digest)
}

// RetrieveMaterialsFromStructuredResults retrieves structured results from Tekton Object, and convert them into materials.
func RetrieveMaterialsFromStructuredResults(ctx context.Context, obj objects.TektonObject, categoryMarker string) []common.ProvenanceMaterial {
// RetrieveMaterialsFromStructuredResults retrieves structured results from Object Results, and convert them into materials.
func RetrieveMaterialsFromStructuredResults(ctx context.Context, objResults []objects.Result) []common.ProvenanceMaterial {
logger := logging.FromContext(ctx)
// Retrieve structured provenance for inputs.
mats := []common.ProvenanceMaterial{}
ssts := ExtractStructuredTargetFromResults(ctx, obj, ArtifactsInputsResultName)
ssts := ExtractStructuredTargetFromResults(ctx, objResults, ArtifactsInputsResultName)
for _, s := range ssts {
alg, digest, err := ParseDigest(s.Digest)
if err != nil {
Expand All @@ -286,7 +291,7 @@ func RetrieveMaterialsFromStructuredResults(ctx context.Context, obj objects.Tek

// ExtractStructuredTargetFromResults extracts structured signable targets aim to generate intoto provenance as materials within TaskRun results and store them as StructuredSignable.
// categoryMarker categorizes signable targets into inputs and outputs.
func ExtractStructuredTargetFromResults(ctx context.Context, obj objects.TektonObject, categoryMarker string) []*StructuredSignable {
func ExtractStructuredTargetFromResults(ctx context.Context, objResults []objects.Result, categoryMarker string) []*StructuredSignable {
logger := logging.FromContext(ctx)
objs := []*StructuredSignable{}
if categoryMarker != ArtifactsInputsResultName && categoryMarker != ArtifactsOutputsResultName {
Expand All @@ -295,7 +300,7 @@ func ExtractStructuredTargetFromResults(ctx context.Context, obj objects.TektonO

// TODO(#592): support structured results using Run
results := []objects.Result{}
for _, res := range obj.GetResults() {
for _, res := range objResults {
results = append(results, objects.Result{
Name: res.Name,
Value: res.Value,
Expand All @@ -316,13 +321,52 @@ func ExtractStructuredTargetFromResults(ctx context.Context, obj objects.TektonO
return objs
}

// ExtractBuildArtifactsFromResults extracts all the structured signable targets from the given results, only processing the ones marked as build artifacts.
func ExtractBuildArtifactsFromResults(ctx context.Context, results []objects.Result) (objs []*StructuredSignable) {
logger := logging.FromContext(ctx)

for _, res := range results {
valid, err := IsBuildArtifact(res)
if err != nil {
logger.Debugf("ExtractBuildArtifactsFromResults failed validatin artifact %v, ignoring artifact, err: %v", res.Name, err)
continue
}
if valid {
logger.Debugf("Extracted Build artifact data from Result %s, %s", res.Value.ObjectVal["uri"], res.Value.ObjectVal["digest"])
objs = append(objs, &StructuredSignable{URI: res.Value.ObjectVal["uri"], Digest: res.Value.ObjectVal["digest"]})
}
}
return
}

// IsBuildArtifact indicates if a given result was marked as a Build Artifact.
func IsBuildArtifact(res objects.Result) (bool, error) {
if !strings.HasSuffix(res.Name, ArtifactsOutputsResultName) {
return false, nil
}

if res.Value.ObjectVal == nil {
return false, fmt.Errorf("%s should be an object: %v", res.Name, res.Value.ObjectVal)
}

if res.Value.ObjectVal[isBuildArtifactField] != "true" {
return false, nil
}

return isValidArtifactOutput(res)
}

func isStructuredResult(res objects.Result, categoryMarker string) (bool, error) {
if !strings.HasSuffix(res.Name, categoryMarker) {
return false, nil
}
if res.Value.ObjectVal == nil {
return false, fmt.Errorf("%s should be an object: %v", res.Name, res.Value.ObjectVal)
}
return isValidArtifactOutput(res)
}

func isValidArtifactOutput(res objects.Result) (bool, error) {
if res.Value.ObjectVal["uri"] == "" {
return false, fmt.Errorf("%s should have uri field: %v", res.Name, res.Value.ObjectVal)
}
Expand Down
Loading

0 comments on commit 8e9373e

Please sign in to comment.