From 19291d900e1613c4b344053dd41e3ddbb2b64cd2 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Tue, 10 Jan 2023 12:34:21 +0000 Subject: [PATCH 1/2] inspect: move attestation loading to struct methods This refactor ensures that the attestations are not output in the JSON output for "{{ json . }}", and additionally allows future refactors to dynamically load the attestation contents, ensuring faster performance when attestations are not used in the output. Signed-off-by: Justin Chadwell --- util/imagetools/printers.go | 84 ++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/util/imagetools/printers.go b/util/imagetools/printers.go index e1768353..b119a622 100644 --- a/util/imagetools/printers.go +++ b/util/imagetools/printers.go @@ -99,8 +99,6 @@ func (p *Printer) Print(raw bool, out io.Writer) error { } imageconfigs := res.Configs() - provenances := res.Provenance() - sboms := res.SBOM() format := tpl.Root.String() var mfst interface{} @@ -142,44 +140,22 @@ func (p *Printer) Print(raw bool, out io.Writer) error { } default: if len(res.platforms) > 1 { - return tpl.Execute(out, struct { - Name string `json:"name,omitempty"` - Manifest interface{} `json:"manifest,omitempty"` - Image map[string]*ocispecs.Image `json:"image,omitempty"` - Provenance map[string]provenanceStub `json:"Provenance,omitempty"` - SBOM map[string]sbomStub `json:"SBOM,omitempty"` - }{ - Name: p.name, - Manifest: mfst, - Image: imageconfigs, - Provenance: provenances, - SBOM: sboms, + return tpl.Execute(out, tplInputs{ + Name: p.name, + Manifest: mfst, + Image: imageconfigs, + result: res, }) } var ic *ocispecs.Image for _, v := range imageconfigs { ic = v } - var provenance provenanceStub - for _, v := range provenances { - provenance = v - } - var sbom sbomStub - for _, v := range sboms { - sbom = v - } - return tpl.Execute(out, struct { - Name string `json:"name,omitempty"` - Manifest interface{} `json:"manifest,omitempty"` - Image *ocispecs.Image `json:"image,omitempty"` - Provenance provenanceStub `json:"Provenance,omitempty"` - SBOM sbomStub `json:"SBOM,omitempty"` - }{ - Name: p.name, - Manifest: mfst, - Image: ic, - Provenance: provenance, - SBOM: sbom, + return tpl.Execute(out, tplInput{ + Name: p.name, + Manifest: mfst, + Image: ic, + result: res, }) } @@ -227,3 +203,43 @@ func (p *Printer) printManifestList(out io.Writer) error { } return w.Flush() } + +type tplInput struct { + Name string `json:"name,omitempty"` + Manifest interface{} `json:"manifest,omitempty"` + Image *ocispecs.Image `json:"image,omitempty"` + + result *result +} + +func (inp tplInput) SBOM() (sbomStub, error) { + sbom := inp.result.SBOM() + for _, v := range sbom { + return v, nil + } + return sbomStub{}, nil +} + +func (inp tplInput) Provenance() (provenanceStub, error) { + provenance := inp.result.Provenance() + for _, v := range provenance { + return v, nil + } + return provenanceStub{}, nil +} + +type tplInputs struct { + Name string `json:"name,omitempty"` + Manifest interface{} `json:"manifest,omitempty"` + Image map[string]*ocispecs.Image `json:"image,omitempty"` + + result *result +} + +func (inp tplInputs) SBOM() (map[string]sbomStub, error) { + return inp.result.SBOM(), nil +} + +func (inp tplInputs) Provenance() (map[string]provenanceStub, error) { + return inp.result.Provenance(), nil +} From 78d8b926db63c4a4bbd48dc185f96ec6bc0bf677 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Tue, 10 Jan 2023 13:00:39 +0000 Subject: [PATCH 2/2] inspect: lazily load attestation data Delay loading the attestation data immediately, and only compute it upon request. We do this using a deferred function which allows to define the computation in the same place as before, but perform the computation later. With this patch, we ensure that the attestation data is only pulled from the remote if it is actually referenced in the format string - otherwise, we can skip it, for improved performance. Signed-off-by: Justin Chadwell --- util/imagetools/loader.go | 149 ++++++++++++++++++++++-------------- util/imagetools/printers.go | 14 +++- 2 files changed, 100 insertions(+), 63 deletions(-) diff --git a/util/imagetools/loader.go b/util/imagetools/loader.go index 86f5a745..9bba4e21 100644 --- a/util/imagetools/loader.go +++ b/util/imagetools/loader.go @@ -49,6 +49,9 @@ type asset struct { config *ocispec.Image sbom *sbomStub provenance *provenanceStub + + deferredSbom func() (*sbomStub, error) + deferredProvenance func() (*provenanceStub, error) } type result struct { @@ -261,36 +264,40 @@ type sbomStub struct { func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error { ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto") - for _, dgst := range refs { - mfst, ok := r.manifests[dgst] - if !ok { - return errors.Errorf("referenced image %s not found", dgst) - } - for _, layer := range mfst.manifest.Layers { - if layer.MediaType == "application/vnd.in-toto+json" && layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" { - _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) - if err != nil { - return err - } - dt, err := content.ReadBlob(ctx, l.cache, layer) - if err != nil { - return err - } - var spdx struct { - Predicate interface{} `json:"predicate"` - } - if err := json.Unmarshal(dt, &spdx); err != nil { - return err - } - - if as.sbom == nil { - as.sbom = &sbomStub{} - as.sbom.SPDX = spdx.Predicate - } else { - as.sbom.AdditionalSPDXs = append(as.sbom.AdditionalSPDXs, spdx.Predicate) + as.deferredSbom = func() (*sbomStub, error) { + var sbom *sbomStub + for _, dgst := range refs { + mfst, ok := r.manifests[dgst] + if !ok { + return nil, errors.Errorf("referenced image %s not found", dgst) + } + for _, layer := range mfst.manifest.Layers { + if layer.MediaType == "application/vnd.in-toto+json" && layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) + if err != nil { + return nil, err + } + dt, err := content.ReadBlob(ctx, l.cache, layer) + if err != nil { + return nil, err + } + var spdx struct { + Predicate interface{} `json:"predicate"` + } + if err := json.Unmarshal(dt, &spdx); err != nil { + return nil, err + } + + if sbom == nil { + sbom = &sbomStub{} + sbom.SPDX = spdx.Predicate + } else { + sbom.AdditionalSPDXs = append(sbom.AdditionalSPDXs, spdx.Predicate) + } } } } + return sbom, nil } return nil } @@ -301,33 +308,37 @@ type provenanceStub struct { func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error { ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto") - for _, dgst := range refs { - mfst, ok := r.manifests[dgst] - if !ok { - return errors.Errorf("referenced image %s not found", dgst) - } - for _, layer := range mfst.manifest.Layers { - if layer.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") { - _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) - if err != nil { - return err - } - dt, err := content.ReadBlob(ctx, l.cache, layer) - if err != nil { - return err - } - var slsa struct { - Predicate interface{} `json:"predicate"` - } - if err := json.Unmarshal(dt, &slsa); err != nil { - return err - } - as.provenance = &provenanceStub{ - SLSA: slsa.Predicate, + as.deferredProvenance = func() (*provenanceStub, error) { + var provenance *provenanceStub + for _, dgst := range refs { + mfst, ok := r.manifests[dgst] + if !ok { + return nil, errors.Errorf("referenced image %s not found", dgst) + } + for _, layer := range mfst.manifest.Layers { + if layer.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) + if err != nil { + return nil, err + } + dt, err := content.ReadBlob(ctx, l.cache, layer) + if err != nil { + return nil, err + } + var slsa struct { + Predicate interface{} `json:"predicate"` + } + if err := json.Unmarshal(dt, &slsa); err != nil { + return nil, err + } + provenance = &provenanceStub{ + SLSA: slsa.Predicate, + } + break } - break } } + return provenance, nil } return nil } @@ -346,30 +357,50 @@ func (r *result) Configs() map[string]*ocispec.Image { return res } -func (r *result) Provenance() map[string]provenanceStub { +func (r *result) Provenance() (map[string]provenanceStub, error) { if len(r.assets) == 0 { - return nil + return nil, nil } res := make(map[string]provenanceStub) for p, a := range r.assets { - if a.provenance == nil { + if a.deferredProvenance == nil { continue } + if a.provenance == nil { + provenance, err := a.deferredProvenance() + if err != nil { + return nil, err + } + if provenance == nil { + continue + } + a.provenance = provenance + } res[p] = *a.provenance } - return res + return res, nil } -func (r *result) SBOM() map[string]sbomStub { +func (r *result) SBOM() (map[string]sbomStub, error) { if len(r.assets) == 0 { - return nil + return nil, nil } res := make(map[string]sbomStub) for p, a := range r.assets { - if a.sbom == nil { + if a.deferredSbom == nil { continue } + if a.sbom == nil { + sbom, err := a.deferredSbom() + if err != nil { + return nil, err + } + if sbom == nil { + continue + } + a.sbom = sbom + } res[p] = *a.sbom } - return res + return res, nil } diff --git a/util/imagetools/printers.go b/util/imagetools/printers.go index b119a622..78041cc7 100644 --- a/util/imagetools/printers.go +++ b/util/imagetools/printers.go @@ -213,7 +213,10 @@ type tplInput struct { } func (inp tplInput) SBOM() (sbomStub, error) { - sbom := inp.result.SBOM() + sbom, err := inp.result.SBOM() + if err != nil { + return sbomStub{}, nil + } for _, v := range sbom { return v, nil } @@ -221,7 +224,10 @@ func (inp tplInput) SBOM() (sbomStub, error) { } func (inp tplInput) Provenance() (provenanceStub, error) { - provenance := inp.result.Provenance() + provenance, err := inp.result.Provenance() + if err != nil { + return provenanceStub{}, nil + } for _, v := range provenance { return v, nil } @@ -237,9 +243,9 @@ type tplInputs struct { } func (inp tplInputs) SBOM() (map[string]sbomStub, error) { - return inp.result.SBOM(), nil + return inp.result.SBOM() } func (inp tplInputs) Provenance() (map[string]provenanceStub, error) { - return inp.result.Provenance(), nil + return inp.result.Provenance() }