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 3, 2022
1 parent bc296d1 commit e024af0
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 51 deletions.
72 changes: 72 additions & 0 deletions pkg/artifacts/signable.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ import (
"github.com/tektoncd/chains/pkg/chains/objects"
"github.com/tektoncd/chains/pkg/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/list"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/util/sets"
"knative.dev/pkg/apis"
)

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

type Signable interface {
Expand All @@ -38,6 +45,18 @@ type Signable interface {
Enabled(cfg config.Config) bool
}

type ProvenanceSchema struct {
// Properties is the JSON Schema properties to support key-value pairs parameter.
Properties map[string]v1beta1.PropertySpec
}

var structuredSignableSchema = ProvenanceSchema{
Properties: map[string]v1beta1.PropertySpec{
"uri": {Type: v1beta1.ParamTypeString},
"digest": {Type: v1beta1.ParamTypeString},
},
}

type TaskRunArtifact struct {
Logger *zap.SugaredLogger
}
Expand Down Expand Up @@ -257,6 +276,59 @@ func extractTargetFromResults(obj objects.TektonObject, identifierSuffix string,
return ss
}

// ExtractStructuredTargetFromResults extracts structured signable targets aim to generate intoto provenance as materials within TaskRun results and store them as StructuredSignable.
func ExtractStructuredTargetFromResults(obj objects.TektonObject, categoryMarker string, logger *zap.SugaredLogger) []*StructuredSignable {
objs := []*StructuredSignable{}
for _, res := range obj.GetResults() {
if strings.HasSuffix(res.Name, categoryMarker) {
if validateObjectKeys(structuredSignableSchema.Properties, &res.Value) != nil {
logger.Errorf("%s should follow this schema: %v", res.Name, structuredSignableSchema)
continue
}
if err := checkDigest(res.Value.ObjectVal["digest"]); err != nil {
logger.Errorf("error getting digest %s: %v", res.Value.ObjectVal["digest"], err)
continue
}
if res.Value.ObjectVal["uri"] == "" {
logger.Errorf("URI cannot be empty for %s", res.Name)
continue
}
objs = append(objs, &StructuredSignable{URI: res.Value.ObjectVal["uri"], Digest: res.Value.ObjectVal["digest"]})
}
}
return objs
}

// validateObjectKeys validates if object keys defined in properties are all provided in its value provider iff the provider is not nil.
func validateObjectKeys(properties map[string]v1beta1.PropertySpec, propertiesProvider *v1beta1.ArrayOrString) (errs *apis.FieldError) {
if propertiesProvider == nil || propertiesProvider.ObjectVal == nil {
return nil
}

neededKeys := []string{}
providedKeys := []string{}

// collect all needed keys
for key := range properties {
neededKeys = append(neededKeys, key)
}

// collect all provided keys
for key := range propertiesProvider.ObjectVal {
providedKeys = append(providedKeys, key)
}

missings := list.DiffLeft(neededKeys, providedKeys)
if len(missings) != 0 {
return &apis.FieldError{
Message: fmt.Sprintf("Required key(s) %s are missing in the value provider.", missings),
Paths: []string{"properties", "default"},
}
}

return nil
}

func checkDigest(dig string) error {
prefix := digest.Canonical.String() + ":"
if !strings.HasPrefix(dig, prefix) {
Expand Down
66 changes: 66 additions & 0 deletions pkg/artifacts/signable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,72 @@ func TestExtractSignableTargetFromResults(t *testing.T) {
}
}

func TestExtractStructuredTargetFromResults(t *testing.T) {
tr := &v1beta1.TaskRun{
Status: v1beta1.TaskRunStatus{
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
TaskRunResults: []v1beta1.TaskRunResult{
{
Name: "mvn1_pkg" + "_" + ArtifactsOutputsResultName,
Value: *v1beta1.NewObject(map[string]string{
"uri": "projects/test-project/locations/us-west4/repositories/test-repo/mavenArtifacts/com.google.guava:guava:31.0-jre",
"digest": digest1,
"signable_type": "",
}),
},
{
Name: "mvn1_pom_sha512" + "_" + ArtifactsOutputsResultName,
Value: *v1beta1.NewObject(map[string]string{
"uri": "com.google.guava:guava:31.0-jre.pom",
"digest": digest2,
"signable_type": "",
}),
},
{
Name: "img1_input" + "_" + ArtifactsInputsResultName,
Value: *v1beta1.NewObject(map[string]string{
"uri": "gcr.io/foo/bar",
"digest": digest3,
}),
},
{
Name: "img2_input_no_digest" + "_" + ArtifactsInputsResultName,
Value: *v1beta1.NewObject(map[string]string{
"uri": "gcr.io/foo/foo",
"digest": "",
}),
},
},
},
},
}
wantInputs := []*StructuredSignable{
{URI: "gcr.io/foo/bar", Digest: digest3},
}

wantOutputs := []*StructuredSignable{
{URI: "projects/test-project/locations/us-west4/repositories/test-repo/mavenArtifacts/com.google.guava:guava:31.0-jre", Digest: digest1},
{URI: "com.google.guava:guava:31.0-jre.pom", Digest: digest2},
}
gotInputs := ExtractStructuredTargetFromResults(objects.NewTaskRunObject(tr), ArtifactsInputsResultName, logtesting.TestLogger(t))
gotOutputs := ExtractStructuredTargetFromResults(objects.NewTaskRunObject(tr), ArtifactsOutputsResultName, logtesting.TestLogger(t))
gotInputs = sortArtifacts(gotInputs)
gotOutputs = sortArtifacts(gotOutputs)
if !cmp.Equal(gotInputs, wantInputs, ignore...) {
t.Fatalf("Inputs not the same %s", cmp.Diff(wantInputs, gotInputs, ignore...))
}
if !cmp.Equal(gotOutputs, wantOutputs, ignore...) {
t.Fatalf("Outputs not the same %s", cmp.Diff(wantOutputs, gotOutputs, ignore...))
}
}

func sortArtifacts(artifacts []*StructuredSignable) []*StructuredSignable {
sort.Slice(artifacts, func(i, j int) bool {
return artifacts[i].Digest < artifacts[j].Digest
})
return artifacts
}

func createDigest(t *testing.T, dgst string) name.Digest {
result, err := name.NewDigest(dgst)
if err != nil {
Expand Down
96 changes: 96 additions & 0 deletions pkg/chains/formats/intotoite6/extract/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ func SubjectDigests(obj objects.TektonObject, logger *zap.SugaredLogger) []intot
})
}

ssts := artifacts.ExtractStructuredTargetFromResults(obj, artifacts.ArtifactsOutputsResultName, logger)
for _, s := range ssts {
splits := strings.Split(s.Digest, ":")
alg := splits[0]
digest := splits[1]
subjects = append(subjects, intoto.Subject{
Name: s.URI,
Digest: slsa.DigestSet{
alg: digest,
},
})
}

// Check if object is a Taskrun, if so search for images used in PipelineResources
// Otherwise object is a PipelineRun, where Pipelineresources are not relevant.
// PipelineResources have been deprecated so their support has been left out of
Expand Down Expand Up @@ -105,3 +118,86 @@ func SubjectDigests(obj objects.TektonObject, logger *zap.SugaredLogger) []intot
})
return subjects
}

// // GetSubjectDigests extracts OCI images from the TaskRun based on standard hinting set up
// // It also goes through looking for any PipelineResources of Image type
// func GetSubjectDigests(tr *v1beta1.TaskRun, logger *zap.SugaredLogger) []intoto.Subject {
// var subjects []intoto.Subject

// imgs := artifacts.ExtractOCIImagesFromResults(tr, logger)
// for _, i := range imgs {
// if d, ok := i.(name.Digest); ok {
// subjects = append(subjects, intoto.Subject{
// Name: d.Repository.Name(),
// Digest: slsa.DigestSet{
// "sha256": strings.TrimPrefix(d.DigestStr(), "sha256:"),
// },
// })
// }
// }

// sts := artifacts.ExtractSignableTargetFromResults(tr, logger)
// for _, obj := range sts {
// splits := strings.Split(obj.Digest, ":")
// if len(splits) != 2 {
// logger.Errorf("Digest %s should be in the format of: algorthm:abc", obj.Digest)
// continue
// }
// subjects = append(subjects, intoto.Subject{
// Name: obj.URI,
// Digest: slsa.DigestSet{
// splits[0]: splits[1],
// },
// })
// }

// ssts := artifacts.ExtractStructuredTargetFromResults(tr, artifacts.ArtifactsOutputsResultName, logger)
// for _, s := range ssts {
// splits := strings.Split(s.Digest, ":")
// alg := splits[0]
// digest := splits[1]
// subjects = append(subjects, intoto.Subject{
// Name: s.URI,
// Digest: slsa.DigestSet{
// alg: digest,
// },
// })
// }

// if tr.Spec.Resources == nil {
// return subjects
// }

// // go through resourcesResult
// for _, output := range tr.Spec.Resources.Outputs {
// name := output.Name
// if output.PipelineResourceBinding.ResourceSpec == nil {
// continue
// }
// // similarly, we could do this for other pipeline resources or whatever thing replaces them
// if output.PipelineResourceBinding.ResourceSpec.Type == v1alpha1.PipelineResourceTypeImage {
// // get the url and digest, and save as a subject
// var url, digest string
// for _, s := range tr.Status.ResourcesResult {
// if s.ResourceName == name {
// if s.Key == "url" {
// url = s.Value
// }
// if s.Key == "digest" {
// digest = s.Value
// }
// }
// }
// subjects = append(subjects, intoto.Subject{
// Name: url,
// Digest: slsa.DigestSet{
// "sha256": strings.TrimPrefix(digest, "sha256:"),
// },
// })
// }
// }
// sort.Slice(subjects, func(i, j int) bool {
// return subjects[i].Name <= subjects[j].Name
// })
// return subjects
// }
Loading

0 comments on commit e024af0

Please sign in to comment.