Skip to content

Commit

Permalink
Smarter Chains: check taskrun level results for Subjects
Browse files Browse the repository at this point in the history
Related feature 1 in tektoncd#850

Prior, Chains only looks for pipeline results to understand what
artifacts were generated in a pipeline. That means pipeline authors need
to name pipeline results in the type hinting way and propagate its value
with individual TaskRun results.

Now, Chains is able to dive into individual TaskRun 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.

Signed-off-by: Chuang Wang <[email protected]>
  • Loading branch information
chuangw6 committed Jul 18, 2023
1 parent 5230749 commit 53226e2
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 51 deletions.
61 changes: 57 additions & 4 deletions pkg/chains/formats/slsa/extract/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,67 @@ import (

// SubjectDigests returns software artifacts produced from the TaskRun/PipelineRun object
// in the form of standard subject field of intoto statement.
// The type hinting fields expected in results help identify the generated software artifacts.
// The type hinting fields expected in TaskRun results help identify the generated software artifacts in a TaskRun/PipelineRun.
// Valid type hinting fields must:
// - 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 {
var subjects []intoto.Subject

switch obj.GetObject().(type) {
case *v1beta1.PipelineRun:
subjects = subjectsFromPipelineRun(ctx, obj)
case *v1beta1.TaskRun:
subjects = subjectsFromTaskrun(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) []intoto.Subject {
// create a map to represent the subjects. Key is the Subject.Name; Value is the Subject.DigestSet
subjects := make(map[string](map[string]string))

pr := obj.(*objects.PipelineRunObject)

for _, tr := range pr.TaskRuns {
// collect subjects from individual TaskRuns
trSubjects := subjectsFromTaskrun(ctx, objects.NewTaskRunObject(tr))

for _, s := range trSubjects {
if v, ok := subjects[s.Name]; ok {
mergeMaps(subjects[s.Name], v)
} else {
subjects[s.Name] = make(map[string]string)
subjects[s.Name] = s.Digest
}
}
}

// convert to subject slice
var results []intoto.Subject
for k, v := range subjects {
results = append(results, intoto.Subject{
Name: k,
Digest: v,
})
}

return results
}

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

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

Expand Down Expand Up @@ -121,9 +176,7 @@ 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
}

Expand Down
27 changes: 13 additions & 14 deletions pkg/chains/formats/slsa/extract/extract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,23 +144,22 @@ func createTaskRunObjectWithResults(results map[string]string) objects.TektonObj
}

func createPipelineRunObjectWithResults(results map[string]string) objects.TektonObject {
prResults := []v1beta1.PipelineRunResult{}
pro := objects.NewPipelineRunObject(nil)
prefix := 0
for url, digest := range results {
prResults = append(prResults,
v1beta1.PipelineRunResult{Name: fmt.Sprintf("%v_IMAGE_DIGEST", prefix), Value: *v1beta1.NewStructuredValues(digest)},
v1beta1.PipelineRunResult{Name: fmt.Sprintf("%v_IMAGE_URL", prefix), Value: *v1beta1.NewStructuredValues(url)},
)
tr := &v1beta1.TaskRun{
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
TaskRunResults: []v1beta1.TaskRunResult{
{Name: fmt.Sprintf("%v_IMAGE_DIGEST", prefix), Value: *v1beta1.NewStructuredValues(digest)},
{Name: fmt.Sprintf("%v_IMAGE_URL", prefix), Value: *v1beta1.NewStructuredValues(url)},
},
},
},
}
pro.AppendTaskRun(tr)
prefix++
}

return objects.NewPipelineRunObject(
&v1beta1.PipelineRun{
Status: v1beta1.PipelineRunStatus{
PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{
PipelineResults: prResults,
},
},
},
)
return pro
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"taskResults": [
{
"name": "IMAGES",
"value": "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367"
"value": "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367"
}
],
"taskSpec": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"taskResults": [
{
"name": "IMAGES",
"value": "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367"
"value": "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367"
}
],
"taskSpec": {
Expand Down
8 changes: 4 additions & 4 deletions pkg/chains/formats/slsa/v1/intotoite6_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func TestPipelineRunCreatePayload(t *testing.T) {
PredicateType: slsa.PredicateSLSAProvenance,
Subject: []in_toto.Subject{
{
Name: "test.io/test/image",
Name: "gcr.io/my/image",
Digest: common.DigestSet{
"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7",
},
Expand Down Expand Up @@ -387,7 +387,7 @@ func TestPipelineRunCreatePayloadChildRefs(t *testing.T) {
PredicateType: slsa.PredicateSLSAProvenance,
Subject: []in_toto.Subject{
{
Name: "test.io/test/image",
Name: "gcr.io/my/image",
Digest: common.DigestSet{
"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7",
},
Expand Down Expand Up @@ -681,12 +681,12 @@ func TestMultipleSubjects(t *testing.T) {
PredicateType: slsa.PredicateSLSAProvenance,
Subject: []in_toto.Subject{
{
Name: "gcr.io/myimage",
Name: "gcr.io/myimage1",
Digest: common.DigestSet{
"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6",
},
}, {
Name: "gcr.io/myimage",
Name: "gcr.io/myimage2",
Digest: common.DigestSet{
"sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367",
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/chains/formats/slsa/v1/pipelinerun/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ var ignore = []cmp.Option{cmpopts.IgnoreUnexported(name.Registry{}, name.Reposit
func TestSubjectDigests(t *testing.T) {
wantSubjects := []intoto.Subject{
{
Name: "test.io/test/image",
Name: "gcr.io/my/image",
Digest: common.DigestSet{"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7"},
},
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/chains/formats/slsa/v2alpha1/slsav2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,12 @@ func TestMultipleSubjects(t *testing.T) {
PredicateType: slsa.PredicateSLSAProvenance,
Subject: []in_toto.Subject{
{
Name: "gcr.io/myimage",
Name: "gcr.io/myimage1",
Digest: common.DigestSet{
"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6",
},
}, {
Name: "gcr.io/myimage",
Name: "gcr.io/myimage2",
Digest: common.DigestSet{
"sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367",
},
Expand Down Expand Up @@ -352,7 +352,7 @@ func TestMultipleSubjects(t *testing.T) {
Name: "IMAGES",
Value: v1beta1.ParamValue{
Type: "string",
StringVal: "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367",
StringVal: "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367",
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func TestGenerateAttestation(t *testing.T) {
PredicateType: slsa.PredicateSLSAProvenance,
Subject: []in_toto.Subject{
{
Name: "test.io/test/image",
Name: "gcr.io/my/image",
Digest: common.DigestSet{
"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7",
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/chains/formats/slsa/v2alpha2/slsav2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func TestMultipleSubjects(t *testing.T) {

resultValue := v1beta1.ParamValue{
Type: "string",
StringVal: "gcr.io/myimage@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367",
StringVal: "gcr.io/myimage1@sha256:d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6,gcr.io/myimage2@sha256:daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367",
}
resultBytes, err := json.Marshal(resultValue)
if err != nil {
Expand All @@ -308,12 +308,12 @@ func TestMultipleSubjects(t *testing.T) {
PredicateType: slsa.PredicateSLSAProvenance,
Subject: []in_toto.Subject{
{
Name: "gcr.io/myimage",
Name: "gcr.io/myimage1",
Digest: common.DigestSet{
"sha256": "d4b63d3e24d6eef04a6dc0795cf8a73470688803d97c52cffa3c8d4efd3397b6",
},
}, {
Name: "gcr.io/myimage",
Name: "gcr.io/myimage2",
Digest: common.DigestSet{
"sha256": "daa1a56e13c85cf164e7d9e595006649e3a04c47fe4a8261320e18a0bf3b0367",
},
Expand Down Expand Up @@ -395,7 +395,7 @@ func TestPipelineRunCreatePayload1(t *testing.T) {
PredicateType: slsa.PredicateSLSAProvenance,
Subject: []in_toto.Subject{
{
Name: "test.io/test/image",
Name: "gcr.io/my/image",
Digest: common.DigestSet{
"sha256": "827521c857fdcd4374f4da5442fbae2edb01e7fbae285c3ec15673d4c1daecb7",
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/chains/objects/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ func (tro *TaskRunObject) GetPullSecrets() []string {
type PipelineRunObject struct {
// The base PipelineRun
*v1beta1.PipelineRun
// taskRuns that were apart of this PipelineRun
taskRuns []*v1beta1.TaskRun
// TaskRuns that were apart of this PipelineRun
TaskRuns []*v1beta1.TaskRun
}

var _ TektonObject = &PipelineRunObject{}
Expand Down Expand Up @@ -194,12 +194,12 @@ func (pro *PipelineRunObject) IsSuccessful() bool {

// Append TaskRuns to this PipelineRun
func (pro *PipelineRunObject) AppendTaskRun(tr *v1beta1.TaskRun) {
pro.taskRuns = append(pro.taskRuns, tr)
pro.TaskRuns = append(pro.TaskRuns, tr)
}

// Get the associated TaskRun via the Task name
func (pro *PipelineRunObject) GetTaskRunFromTask(taskName string) *v1beta1.TaskRun {
for _, tr := range pro.taskRuns {
for _, tr := range pro.TaskRuns {
val, ok := tr.Labels[PipelineTaskLabel]
if ok && val == taskName {
return tr
Expand Down
15 changes: 1 addition & 14 deletions pkg/chains/storage/grafeas/grafeas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,6 @@ var (
Name: "ci-pipeline",
UID: types.UID("uid-pipeline"),
},
Status: v1beta1.PipelineRunStatus{
PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{
PipelineResults: []v1beta1.PipelineRunResult{
// the results from task 1 - clone
{Name: "CHAINS-GIT_COMMIT", Value: *v1beta1.NewStructuredValues(commitSHA)},
{Name: "CHAINS-GIT_URL", Value: *v1beta1.NewStructuredValues(repoURL)},
// the results from task 2 - build
{Name: "IMAGE_DIGEST", Value: *v1beta1.NewStructuredValues("sha256:" + artifactDigest1)},
{Name: "IMAGE_URL", Value: *v1beta1.NewStructuredValues(artifactURL1)},
{Name: "x_ARTIFACT_DIGEST", Value: *v1beta1.NewStructuredValues("sha256:" + artifactDigest2)},
{Name: "x_ARTIFACT_URI", Value: *v1beta1.NewStructuredValues(artifactURL2)},
},
},
},
}

// ci pipelinerun provenance
Expand Down Expand Up @@ -302,6 +288,7 @@ func TestGrafeasBackend_StoreAndRetrieve(t *testing.T) {
args: args{
runObject: &objects.PipelineRunObject{
PipelineRun: ciPipeline,
TaskRuns: []*v1beta1.TaskRun{cloneTaskRun, buildTaskRun},
},
payload: getRawPayload(t, ciPipelineRunProvenance),
signature: "ci pipelinerun signature",
Expand Down

0 comments on commit 53226e2

Please sign in to comment.