From 123637a08616ef2c669cd75e04651f111a46d30e Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Fri, 15 Sep 2023 17:03:11 -0400 Subject: [PATCH] Refactor StructuredSignable extraction This change makes the extraction of StructuredSignable objects more flexible so it can handle upcoming requirements such as SBOM support. Signed-off-by: Luiz Carvalho --- pkg/artifacts/signable.go | 92 ++++++++++------------------------ pkg/artifacts/signable_test.go | 2 +- pkg/artifacts/structured.go | 84 +++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 66 deletions(-) create mode 100644 pkg/artifacts/structured.go diff --git a/pkg/artifacts/signable.go b/pkg/artifacts/signable.go index 33594ade1f..1ac9492f99 100644 --- a/pkg/artifacts/signable.go +++ b/pkg/artifacts/signable.go @@ -148,14 +148,6 @@ type image struct { digest string } -// StructuredSignable contains info for signable targets to become either subjects or materials in intoto Statements. -// URI is the resource uri for the target needed iff the target is a material. -// Digest is the target's SHA digest. -type StructuredSignable struct { - URI string - Digest string -} - func (oa *OCIArtifact) ExtractObjects(ctx context.Context, obj objects.TektonObject) []interface{} { log := logging.FromContext(ctx) objs := []interface{}{} @@ -204,11 +196,13 @@ func (oa *OCIArtifact) ExtractObjects(ctx context.Context, obj objects.TektonObj func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) []interface{} { logger := logging.FromContext(ctx) objs := []interface{}{} - ss := extractTargetFromResults(ctx, obj, "IMAGE_URL", "IMAGE_DIGEST") - for _, s := range ss { - if s == nil || s.Digest == "" || s.URI == "" { - continue - } + + extractor := structuredSignableExtractor{ + uriSuffix: "IMAGE_URL", + digestSuffix: "IMAGE_DIGEST", + isValid: hasImageRequirements, + } + for _, s := range extractor.extract(ctx, obj) { dgst, err := name.NewDigest(fmt.Sprintf("%s@%s", s.URI, s.Digest)) if err != nil { logger.Errorf("error getting digest: %v", err) @@ -217,6 +211,7 @@ func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) objs = append(objs, dgst) } + // look for a comma separated list of images for _, key := range obj.GetResults() { if key.Name != "IMAGES" { @@ -242,24 +237,23 @@ func ExtractOCIImagesFromResults(ctx context.Context, obj objects.TektonObject) } // ExtractSignableTargetFromResults extracts signable targets that aim to generate intoto provenance as materials within TaskRun results and store them as StructuredSignable. -func ExtractSignableTargetFromResults(ctx context.Context, obj objects.TektonObject) []*StructuredSignable { +func ExtractSignableTargetFromResults(ctx context.Context, obj objects.TektonObject) []StructuredSignable { logger := logging.FromContext(ctx) - objs := []*StructuredSignable{} - ss := extractTargetFromResults(ctx, obj, "ARTIFACT_URI", "ARTIFACT_DIGEST") - // Only add it if we got both the signable URI and digest. - for _, s := range ss { - if s == nil || s.Digest == "" || s.URI == "" { - continue - } - if _, _, err := ParseDigest(s.Digest); err != nil { - logger.Errorf("error getting digest %s: %v", s.Digest, err) - continue - } - - objs = append(objs, s) + extractor := structuredSignableExtractor{ + uriSuffix: "ARTIFACT_URI", + digestSuffix: "ARTIFACT_DIGEST", + isValid: func(s StructuredSignable) bool { + if !hasImageRequirements(s) { + return false + } + if _, _, err := ParseDigest(s.Digest); err != nil { + logger.Errorf("error getting digest %s: %v", s.Digest, err) + return false + } + return true + }, } - - return objs + return extractor.extract(ctx, obj) } // FullRef returns the full reference of the signable artifact in the format of URI@DIGEST @@ -267,42 +261,6 @@ func (s *StructuredSignable) FullRef() string { return fmt.Sprintf("%s@%s", s.URI, s.Digest) } -func extractTargetFromResults(ctx context.Context, obj objects.TektonObject, identifierSuffix string, digestSuffix string) map[string]*StructuredSignable { - logger := logging.FromContext(ctx) - ss := map[string]*StructuredSignable{} - - for _, res := range obj.GetResults() { - if strings.HasSuffix(res.Name, identifierSuffix) { - if res.Value.StringVal == "" { - logger.Debugf("error getting string value for %s", res.Name) - continue - } - marker := strings.TrimSuffix(res.Name, identifierSuffix) - if v, ok := ss[marker]; ok { - v.URI = strings.TrimSpace(res.Value.StringVal) - - } else { - ss[marker] = &StructuredSignable{URI: strings.TrimSpace(res.Value.StringVal)} - } - // TODO: add logic for Intoto signable target as input. - } - if strings.HasSuffix(res.Name, digestSuffix) { - if res.Value.StringVal == "" { - logger.Debugf("error getting string value for %s", res.Name) - continue - } - marker := strings.TrimSuffix(res.Name, digestSuffix) - if v, ok := ss[marker]; ok { - v.Digest = strings.TrimSpace(res.Value.StringVal) - } else { - ss[marker] = &StructuredSignable{Digest: strings.TrimSpace(res.Value.StringVal)} - } - } - - } - return ss -} - // RetrieveMaterialsFromStructuredResults retrieves structured results from Tekton Object, and convert them into materials. func RetrieveMaterialsFromStructuredResults(ctx context.Context, obj objects.TektonObject, categoryMarker string) []common.ProvenanceMaterial { logger := logging.FromContext(ctx) @@ -437,3 +395,7 @@ func (oa *OCIArtifact) FullKey(obj interface{}) string { func (oa *OCIArtifact) Enabled(cfg config.Config) bool { return cfg.Artifacts.OCI.Enabled() } + +func hasImageRequirements(s StructuredSignable) bool { + return s.URI != "" && s.Digest != "" +} diff --git a/pkg/artifacts/signable_test.go b/pkg/artifacts/signable_test.go index 6f1be4a6ca..b3181020c3 100644 --- a/pkg/artifacts/signable_test.go +++ b/pkg/artifacts/signable_test.go @@ -370,7 +370,7 @@ func TestExtractSignableTargetFromResults(t *testing.T) { }, }, } - want := []*StructuredSignable{ + want := []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}, {URI: "com.google.guava:guava:31.0-jre-sources.jar", Digest: digest3}, diff --git a/pkg/artifacts/structured.go b/pkg/artifacts/structured.go new file mode 100644 index 0000000000..4e9efb1a9b --- /dev/null +++ b/pkg/artifacts/structured.go @@ -0,0 +1,84 @@ +/* +Copyright 2023 The Tekton Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package artifacts + +import ( + "context" + "strings" + + "github.com/tektoncd/chains/pkg/chains/objects" + "knative.dev/pkg/logging" +) + +// StructuredSignable contains info for signable targets to become either subjects or materials in +// intoto Statements. +// URI is the resource uri for the target needed iff the target is a material. +// Digest is the target's SHA digest. +type StructuredSignable struct { + URI string + Digest string +} + +type structuredSignableExtractor struct { + uriSuffix string + digestSuffix string + isValid func(StructuredSignable) bool +} + +func (b *structuredSignableExtractor) extract(ctx context.Context, obj objects.TektonObject) []StructuredSignable { + logger := logging.FromContext(ctx) + partials := map[string]StructuredSignable{} + + suffixes := map[string]func(StructuredSignable, string) StructuredSignable{ + b.uriSuffix: func(s StructuredSignable, value string) StructuredSignable { + s.URI = value + return s + }, + b.digestSuffix: func(s StructuredSignable, value string) StructuredSignable { + s.Digest = value + return s + }, + } + + for _, res := range obj.GetResults() { + for suffix, setFn := range suffixes { + if suffix == "" { + continue + } + if !strings.HasSuffix(res.Name, suffix) { + continue + } + value := strings.TrimSpace(res.Value.StringVal) + if value == "" { + logger.Debugf("error getting string value for %s", res.Name) + continue + } + marker := strings.TrimSuffix(res.Name, suffix) + if _, ok := partials[marker]; !ok { + partials[marker] = StructuredSignable{} + } + partials[marker] = setFn(partials[marker], value) + } + } + + var signables []StructuredSignable + for _, s := range partials { + if !b.isValid(s) { + continue + } + signables = append(signables, s) + } + + return signables +}