Skip to content

Commit

Permalink
Basic ite6 (#5)
Browse files Browse the repository at this point in the history
* temp commit with more logging to get understanding of the resource structure

* fixed unit tests

* fixing unit tests

* Store entire taskrun as recipe.arguments.

* Store spec as well, not just status

* removed temp logger

* capture subjects if result name is *-DIGEST and materials

* Resolve automerge conflict.

* Added script to pull an attestation
  • Loading branch information
Fredrik Skogman committed May 17, 2021
1 parent a478aa7 commit cddd232
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 56 deletions.
6 changes: 1 addition & 5 deletions pkg/chains/formats/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,9 @@ limitations under the License.

package formats

import (
"go.uber.org/zap"
)

// Payloader is an interface to generate a chains Payload from a TaskRun
type Payloader interface {
CreatePayload(l *zap.SugaredLogger, obj interface{}) (interface{}, error)
CreatePayload(obj interface{}) (interface{}, error)
Type() PayloadType
}

Expand Down
110 changes: 67 additions & 43 deletions pkg/chains/formats/intotoite6.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package formats

import (
"fmt"

"go.uber.org/zap"
"strings"

"github.com/in-toto/in-toto-golang/in_toto"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
Expand All @@ -12,7 +11,12 @@ import (
type InTotoIte6 struct {
}

func (i *InTotoIte6) CreatePayload(l *zap.SugaredLogger, obj interface{}) (interface{}, error) {
type artifactResult struct {
ParamName string
ResultName string
}

func (i *InTotoIte6) CreatePayload(obj interface{}) (interface{}, error) {
var tr *v1beta1.TaskRun

switch v := obj.(type) {
Expand All @@ -25,65 +29,85 @@ func (i *InTotoIte6) CreatePayload(l *zap.SugaredLogger, obj interface{}) (inter

// Here we translate a Tekton TaskRun into an InToto ite6 attestation.
// At a high leevel, the mapping looks roughly like:
// Input Resource Results -> Materials
// Output Resource Results -> Subjects
// The entire TaskRun body -> Recipe.Arguments
// Results with name *-DIGEST -> Subject
// Step containers -> Materials

att := in_toto.Provenance{
Attestation: in_toto.Attestation{
AttestationType: in_toto.ProvenanceTypeV1,
},
}

// Populate materials with resource inputs.

results := []artifactResult{}
// Scan for digests
for _, r := range tr.Status.TaskSpec.Results {
if strings.HasSuffix(r.Name, "-DIGEST") {
// 7 chars in -DIGEST
results = append(results, artifactResult {
ParamName: r.Name[:len(r.Name)-7],
ResultName: r.Name,
})
}
}

att.Subject = in_toto.ArtifactCollection{}
att.Materials = in_toto.ArtifactCollection{}
if tr.Spec.Resources != nil {
for _, r := range tr.Spec.Resources.Inputs {
l.Infof("ITE6: resource input: %s", r.Name)
for _, rr := range tr.Status.ResourcesResult {
if r.Name == rr.ResourceName {
// if _, ok := l.Materials[rr.ResourceName]; !ok {
// l.Materials[rr.ResourceName] = map[string]string{}
// }
// m := l.Materials[rr.ResourceName].(map[string]string)
// m[rr.Key] = rr.Value
l.Infof("ITE6: match")
for _, ar := range results {
// get subject
sub := ""
for _, p := range tr.Spec.Params {
if ar.ParamName == p.Name {
if p.Value.Type != v1beta1.ParamTypeString {
continue
}
sub = p.Value.StringVal
break
}
}

// Dummy to just loop over the status results
for _, rr := range tr.Status.ResourcesResult {
l.Infof("ITE6: resource result %s", rr.ResourceName)
l.Infof("ITE6: resource result key %s", rr.Key)
l.Infof("ITE6: resource result value %s", rr.Value)
l.Infof("ITE6: resource result type %s", rr.ResultType)
if sub == "" {
// Parameter was not specifed for this task run
continue
}
for _, trr := range tr.Status.TaskRunResults {
if trr.Name == ar.ResultName {
// Value should be on format:
// hashalg:hash
d := strings.Split(trr.Value, ":")
if len(d) != 2 {
continue
}

// Populate products with resource outputs.
for _, r := range tr.Spec.Resources.Outputs {
l.Infof("ITE6: resource output: %s", r.Name)
for _, rr := range tr.Status.ResourcesResult {
if r.Name == rr.ResourceName {
// if _, ok := l.Products[rr.ResourceName]; !ok {
// l.Products[rr.ResourceName] = map[string]string{}
// }
// m := l.Products[rr.ResourceName].(map[string]string)
// m[rr.Key] = rr.Value
l.Infof("ITE: 6 match")
att.Subject[sub] = in_toto.ArtifactDigest{
d[0]: d[1],
}
}
}
} else {
l.Infof("ITE6: No resources found")
}

l.Infof("ITE6: Store TaskRun body")
m := map[string]interface{}{}
m["status"] = tr.Status
m["spec"] = tr.Spec
att.Recipe.Arguments = m
// Add all found step containers as materials
for _, trs := range tr.Status.Steps {
// image id formats: name@alg:digest
// schema://name@alg:hash
d := strings.Split(trs.ImageID, "//")
if len(d) == 2 {
// Get away with schema
d[0] = d[1]
}
d = strings.Split(d[0], "@")
if len(d) != 2 {
continue
}
// d[0]: image name
// d[1]: alg:hash
h := strings.Split(d[1], ":")
if len(h) != 2 {
continue
}
att.Materials[d[0]] = in_toto.ArtifactDigest{
h[0]: h[1],
}
}

return att, nil
}
Expand Down
128 changes: 128 additions & 0 deletions pkg/chains/formats/intotoite6_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package formats

import (
"encoding/json"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
)

var testData1 = `
{
"spec": {
"params": [
{
"name": "IMAGE",
"value": "test.io/test/image"
}
],
"serviceAccountName": "default"
},
"status": {
"conditions": [
{
"type": "Succeeded",
"status": "True",
"lastTransitionTime": "2021-03-29T09:50:15Z",
"reason": "Succeeded",
"message": "All Steps have completed executing"
}
],
"podName": "go-build-pipeline-run-tcjlv-docker-dfhs4-pod-tkkkv",
"steps": [
{
"name": "step1",
"container": "step-step1",
"imageID": "docker-pullable://gcr.io/test1/test1@sha256:hash1"
},
{
"name": "step2",
"container": "step-step2",
"imageID": "docker-pullable://gcr.io/test2/test2@sha256:hash2"
},
{
"name": "step3",
"container": "step-step3",
"imageID": "docker-pullable://gcr.io/test3/test3@sha256:hash3"
}
],
"taskResults": [
{
"name": "IMAGE-DIGEST",
"value": "sha256:hash4"
}
],
"taskSpec": {
"params": [
{
"name": "IMAGE",
"type": "string"
},
{
"name": "DOCKERFILE",
"type": "string"
},
{
"name": "CONTEXT",
"type": "string"
},
{
"name": "EXTRA_ARGS",
"type": "string"
},
{
"name": "BUILDER_IMAGE",
"type": "string"
}
],
"results": [
{
"name": "IMAGE-DIGEST",
"description": "Digest of the image just built."
}
]
}
}
}
`

var expected = in_toto.Provenance{
Attestation: in_toto.Attestation{
AttestationType: in_toto.ProvenanceTypeV1,
Subject: in_toto.ArtifactCollection{
"test.io/test/image": in_toto.ArtifactDigest{
"sha256": "hash4",
},
},
Materials: in_toto.ArtifactCollection{
"gcr.io/test1/test1": in_toto.ArtifactDigest{
"sha256": "hash1",
},
"gcr.io/test2/test2": in_toto.ArtifactDigest{
"sha256": "hash2",
},
"gcr.io/test3/test3": in_toto.ArtifactDigest{
"sha256": "hash3",
},
},
},
}

func TestInTotoIte6_CreatePayload1(t *testing.T) {
var tr v1beta1.TaskRun

err := json.Unmarshal([]byte(testData1), &tr)
if err != nil {
t.Errorf("json.Unmarshal() error = %v", err)
return
}

i := &InTotoIte6{}
got, err := i.CreatePayload(&tr)

if diff := cmp.Diff(got, expected); diff != "" {
t.Errorf("InTotoIte6.CreatePayload(): -want +got: %s", diff)
}
}
4 changes: 1 addition & 3 deletions pkg/chains/formats/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
"fmt"
"path"

"go.uber.org/zap"

"github.com/google/go-containerregistry/pkg/name"
)

Expand All @@ -28,7 +26,7 @@ type SimpleSigning struct {
}

// CreatePayload implements the Payloader interface.
func (i *SimpleSigning) CreatePayload(l *zap.SugaredLogger, obj interface{}) (interface{}, error) {
func (i *SimpleSigning) CreatePayload(obj interface{}) (interface{}, error) {
switch v := obj.(type) {
case name.Digest:
format := NewSimpleStruct()
Expand Down
2 changes: 1 addition & 1 deletion pkg/chains/formats/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestSimpleSigning_CreatePayload(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := &SimpleSigning{}
got, err := i.CreatePayload(nil, tt.obj)
got, err := i.CreatePayload(tt.obj)
if (err != nil) != tt.wantErr {
t.Errorf("SimpleSigning.CreatePayload() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
4 changes: 1 addition & 3 deletions pkg/chains/formats/tekton.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ package formats
import (
"fmt"

"go.uber.org/zap"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
)

Expand All @@ -26,7 +24,7 @@ type Tekton struct {
}

// CreatePayload implements the Payloader interface.
func (i *Tekton) CreatePayload(l *zap.SugaredLogger, obj interface{}) (interface{}, error) {
func (i *Tekton) CreatePayload(obj interface{}) (interface{}, error) {

switch v := obj.(type) {
case *v1beta1.TaskRun:
Expand Down
2 changes: 1 addition & 1 deletion pkg/chains/formats/tekton_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestTekton_CreatePayload(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := &Tekton{}
got, err := i.CreatePayload(nil, tt.tr)
got, err := i.CreatePayload(tt.tr)
if err != nil {
t.Errorf("Tekton.CreatePayload() error = %v", err)
return
Expand Down

0 comments on commit cddd232

Please sign in to comment.