Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new v2alpha4 version for TaskRuns #1111

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
renzodavid9 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading