Skip to content

Commit

Permalink
Smarter Chains: check taskrun level results for Subjects (#866)
Browse files Browse the repository at this point in the history
Step 1/2 of #850

Prior, Chains only looks for pipeline results to understand what
artifacts were generated in a pipeline. That means pipeline authors need
to propagate child TaskRun results to pipeline level and name the pipeline
results in type hinting way even though the pulled tasks already produce
type hinting results.

Now, we introduced a new configmap field `artifacts.pipelinerun.enable-deep-inspection`
to allow Chains to inspect both pipeline results and child task results
to understand what artifacts were generated throughout a pipeline.

This way, pipeline authors no longer need to worry about the rules when
writting a pipeline as long as they pull in right tasks that produce type hinting results.
That said, users still have ability to propagate task results to pipeline
level if the tasks they referenced do not produce type hinting results.

Signed-off-by: Chuang Wang <chuangw@google.com>
  • Loading branch information
chuangw6 authored Aug 10, 2023
1 parent 985bcc3 commit f1e2dad
Show file tree
Hide file tree
Showing 24 changed files with 405 additions and 85 deletions.
6 changes: 4 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ Supported keys include:
| `artifacts.pipelinerun.format` | The format to store `PipelineRun` payloads in. | `in-toto`, `slsa/v1`| `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"` |

> NOTE: For grafeas storage backend, currently we only support Container Analysis. We will make grafeas server address configurabe within a short time.
> NOTE: `slsa/v1` is an alias of `in-toto` for backwards compatibility.
> NOTE:
> - 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.
### OCI Configuration

Expand Down
106 changes: 100 additions & 6 deletions pkg/chains/formats/slsa/extract/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
"github.com/tektoncd/chains/internal/backport"
"github.com/tektoncd/chains/pkg/artifacts"
"github.com/tektoncd/chains/pkg/chains/formats/slsa/internal/slsaconfig"
"github.com/tektoncd/chains/pkg/chains/objects"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"knative.dev/pkg/logging"
Expand All @@ -39,7 +40,102 @@ import (
// - have suffix `IMAGE_URL` & `IMAGE_DIGEST` or `ARTIFACT_URI` & `ARTIFACT_DIGEST` pair.
// - the `*_DIGEST` field must be in the format of "<algorithm>:<actual-sha>" where the algorithm must be "sha256" and actual sha must be valid per https://github.com/opencontainers/image-spec/blob/main/descriptor.md#sha-256.
// - the `*_URL` or `*_URI` fields cannot be empty.
func SubjectDigests(ctx context.Context, obj objects.TektonObject) []intoto.Subject {
//
//nolint:all
func SubjectDigests(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject {
var subjects []intoto.Subject

switch obj.GetObject().(type) {
case *v1beta1.PipelineRun:
subjects = subjectsFromPipelineRun(ctx, obj, slsaconfig)
case *v1beta1.TaskRun:
subjects = subjectsFromTektonObject(ctx, obj)
}

sort.Slice(subjects, func(i, j int) bool {
return subjects[i].Name <= subjects[j].Name
})

return subjects
}

func subjectsFromPipelineRun(ctx context.Context, obj objects.TektonObject, slsaconfig *slsaconfig.SlsaConfig) []intoto.Subject {
prSubjects := subjectsFromTektonObject(ctx, obj)

// If deep inspection is not enabled, just return subjects observed on the pipelinerun level
if !slsaconfig.DeepInspectionEnabled {
return prSubjects
}

logger := logging.FromContext(ctx)
// If deep inspection is enabled, collect subjects from child taskruns
var result []intoto.Subject

pro := obj.(*objects.PipelineRunObject)

pSpec := pro.Status.PipelineSpec
if pSpec != nil {
pipelineTasks := append(pSpec.Tasks, pSpec.Finally...)
for _, t := range pipelineTasks {
tr := pro.GetTaskRunFromTask(t.Name)
// Ignore Tasks that did not execute during the PipelineRun.
if tr == nil || tr.Status.CompletionTime == nil {
logger.Infof("taskrun status not found for task %s", t.Name)
continue
}

trSubjects := subjectsFromTektonObject(ctx, objects.NewTaskRunObject(tr))
for _, s := range trSubjects {
result = addSubject(result, s)
}
}
}

// also add subjects observed from pipelinerun level with duplication removed
for _, s := range prSubjects {
result = addSubject(result, s)
}

return result
}

// addSubject adds a new subject item to the original slice.
func addSubject(original []intoto.Subject, item intoto.Subject) []intoto.Subject {

for i, s := range original {
// if there is an equivalent entry in the original slice, merge item's DigestSet
// into the existing entry's DigestSet.
if subjectEqual(s, item) {
mergeMaps(original[i].Digest, item.Digest)
return original
}
}

original = append(original, item)
return original
}

// two subjects are equal if and only if they have same name and have at least
// one common algorithm and hex value.
func subjectEqual(x, y intoto.Subject) bool {
if x.Name != y.Name {
return false
}
for algo, hex := range x.Digest {
if y.Digest[algo] == hex {
return true
}
}
return false
}

func mergeMaps(m1 map[string]string, m2 map[string]string) {
for k, v := range m2 {
m1[k] = v
}
}

func subjectsFromTektonObject(ctx context.Context, obj objects.TektonObject) []intoto.Subject {
logger := logging.FromContext(ctx)
var subjects []intoto.Subject

Expand Down Expand Up @@ -121,19 +217,17 @@ func SubjectDigests(ctx context.Context, obj objects.TektonObject) []intoto.Subj
})
}
}
sort.Slice(subjects, func(i, j int) bool {
return subjects[i].Name <= subjects[j].Name
})

return subjects
}

// RetrieveAllArtifactURIs returns all the URIs of the software artifacts produced from the run object.
// - It first extracts intoto subjects from run object results and converts the subjects
// to a slice of string URIs in the format of "NAME" + "@" + "ALGORITHM" + ":" + "DIGEST".
// - If no subjects could be extracted from results, then an empty slice is returned.
func RetrieveAllArtifactURIs(ctx context.Context, obj objects.TektonObject) []string {
func RetrieveAllArtifactURIs(ctx context.Context, obj objects.TektonObject, deepInspectionEnabled bool) []string {
result := []string{}
subjects := SubjectDigests(ctx, obj)
subjects := SubjectDigests(ctx, obj, &slsaconfig.SlsaConfig{DeepInspectionEnabled: deepInspectionEnabled})

for _, s := range subjects {
for algo, digest := range s.Digest {
Expand Down
Loading

0 comments on commit f1e2dad

Please sign in to comment.