Skip to content

Commit

Permalink
TEP 109 implementation: add function to extract structured signable t…
Browse files Browse the repository at this point in the history
…argets
  • Loading branch information
ywluogg committed Oct 18, 2022
1 parent 43615bd commit 85ce20b
Show file tree
Hide file tree
Showing 11 changed files with 506 additions and 59 deletions.
57 changes: 55 additions & 2 deletions docs/intoto.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ subject `pkg:/docker/test/foo@sha256:abcd?repository_url=gcr.io`
is created. Note that image references are represented using [Package
URL](https://github.com/package-url/purl-spec) format.

## Limitations
### Limitations
This is an MVP implementation of the in-toto attestation
format. More work would be required to properly capture the
`Entrypoint` field in the provenance predicate, now the `TaskRef`'s name
is used. Also metadata related to hermeticity/reproducibility are
currently not populated.

## Examples
### Examples

Example `TaskRun` attestation:

Expand Down Expand Up @@ -401,3 +401,56 @@ Example `PipelineRun` attestation:
}
```

### Structured Result Type Hinting

**_A new feature will be implemented to have better support for artifact provenance retrieval. More details can be found in [Tekton Pipelines](https://github.com/tektoncd/pipeline/issues/5455)._**

The feature requires **Tekton Pipeline v0.38** or later.

To capture artifacts created by a task in a structured manner, Tekton Chains integrated with structured results and retrieve artifacts' provenances.
The result should be in the following format in a Task:
``` yaml
results:
- name: {artifact_name}-ARTIFACT_INPUTS
description: Digest of the image just built.
type: object
properties:
uri:
type: string
digest:
type:string
- name: {artifact_name}-ARTIFACT_OUTPUTS
description: Digest of the image just built.
type: object
properties:
uri:
type: string
digest:
type:string
```
Suffix `-ARTIFACT_INPUTS` will retrieve the artifact provenance and put them in [Intoto Materials](https://github.com/in-toto/attestation/blob/v0.1.0/spec/predicates/provenance.md#fields), and `-ARTIFACT_OUTPUTS` will retrieve the artifact provenance and put them in [Intoto Subjects](https://github.com/in-toto/attestation/tree/v0.1.0/spec#statement).

`uri` is the unique identifier for this artifact, and `digest` needs to be a string on the format `alg:digest`.

An example structured result in a TaskRun:
``` yaml
results:
- name: img_1-ARTIFACT_INPUTS
value:
uri: gcr.io/foo/bar
digest: sha123@89dedecaca1b85346600c7db9939a4fe090a42ef
- name: mvn1_pkg-ARTIFACT_OUTPUTS
value:
uri: maven-test-0.0.1.jar
digest: sha256@89dedecaca1b85346600c7db9939a4fe090a42ee
- name: mvn1_pom-ARTIFACT_OUTPUTS
value:
uri: maven-test-0.0.1.pom
digest: sha256@89dedecaca1b85346600c7db9939a4fe090a42eg
- name: mvn1_src-ARTIFACT_OUTPUTS
value:
uri: maven-test-0.0.1-sources.jar
digest: sha256@89dedecaca1b85346600c7db9939a4fe090a42ez
```

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
github.com/sigstore/rekor v0.12.1-0.20220915152154-4bb6f441c1b2
github.com/sigstore/sigstore v1.4.2
github.com/spiffe/go-spiffe/v2 v2.1.1
github.com/stretchr/testify v1.8.0
github.com/tektoncd/pipeline v0.40.1
github.com/tektoncd/plumbing v0.0.0-20220817140952-3da8ce01aeeb
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399
Expand Down Expand Up @@ -368,7 +369,6 @@ require (
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/sylvia7788/contextcheck v1.0.6 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
Expand Down
88 changes: 88 additions & 0 deletions pkg/artifacts/signable.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"strings"

"github.com/google/go-containerregistry/pkg/name"
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/opencontainers/go-digest"
"github.com/tektoncd/chains/pkg/chains/formats"
"github.com/tektoncd/chains/pkg/chains/objects"
Expand All @@ -28,6 +29,11 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
)

const (
ArtifactsInputsResultName = "ARTIFACT_INPUTS"
ArtifactsOutputsResultName = "ARTIFACT_OUTPUTS"
)

type Signable interface {
ExtractObjects(obj objects.TektonObject) []interface{}
StorageBackend(cfg config.Config) sets.String
Expand Down Expand Up @@ -257,6 +263,88 @@ func extractTargetFromResults(obj objects.TektonObject, identifierSuffix string,
return ss
}

// RetrieveMaterialsFromStructuredResults retrieves structured results from Tekton Object, and convert them into materials.
func RetrieveMaterialsFromStructuredResults(obj objects.TektonObject, categoryMarker string, logger *zap.SugaredLogger) []slsa.ProvenanceMaterial {
// Retrieve structured provenance for inputs.
mats := []slsa.ProvenanceMaterial{}
ssts := ExtractStructuredTargetFromResults(obj, ArtifactsInputsResultName, logger)
for _, s := range ssts {
if err := checkDigest(s.Digest); err != nil {
logger.Debugf("Digest for %s not in the right format: %s, %v", s.URI, s.Digest, err)
continue
}
splits := strings.Split(s.Digest, ":")
alg := splits[0]
digest := splits[1]
mats = append(mats, slsa.ProvenanceMaterial{
URI: s.URI,
Digest: map[string]string{alg: digest},
})
}
return mats
}

// 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(obj objects.TektonObject, categoryMarker string, logger *zap.SugaredLogger) []*StructuredSignable {
objs := []*StructuredSignable{}
if categoryMarker != ArtifactsInputsResultName && categoryMarker != ArtifactsOutputsResultName {
return objs
}

// TODO(#592): support structured results using Run
results := []objects.Result{}
tr, isTaskRun := obj.GetObject().(*v1beta1.TaskRun)
if isTaskRun {
for _, res := range tr.Status.TaskRunResults {
results = append(results, objects.Result{
Name: res.Name,
Value: res.Value,
})
}

} else {
pr := obj.GetObject().(*v1beta1.PipelineRun)
for _, res := range pr.Status.PipelineResults {
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 {
logger.Debugf("ExtractStructuredTargetFromResults: %v", err)
}
if valid {
objs = append(objs, &StructuredSignable{URI: res.Value.ObjectVal["uri"], Digest: res.Value.ObjectVal["digest"]})
}
}
}
return objs
}

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)
}
if res.Value.ObjectVal["uri"] == "" {
return false, fmt.Errorf("%s should have uri field: %v", res.Name, res.Value.ObjectVal)
}
if res.Value.ObjectVal["digest"] == "" {
return false, fmt.Errorf("%s should have digest field: %v", res.Name, res.Value.ObjectVal)
}
if err := checkDigest(res.Value.ObjectVal["digest"]); err != nil {
return false, fmt.Errorf("error getting digest %s: %v", res.Value.ObjectVal["digest"], err)
}
return true, nil
}

func checkDigest(dig string) error {
prefix := digest.Canonical.String() + ":"
if !strings.HasPrefix(dig, prefix) {
Expand Down
Loading

0 comments on commit 85ce20b

Please sign in to comment.