Skip to content

Commit

Permalink
update imagetools to print raw sbom and provenance
Browse files Browse the repository at this point in the history
Signed-off-by: Tonis Tiigi <[email protected]>
  • Loading branch information
tonistiigi authored and crazy-max committed Dec 19, 2022
1 parent b5f51f1 commit 2817533
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 148 deletions.
114 changes: 26 additions & 88 deletions util/imagetools/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package imagetools

import (
"context"
"encoding/base64"
"encoding/json"
"sort"
"strings"
Expand All @@ -15,7 +14,6 @@ import (
"github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/remotes"
"github.com/docker/distribution/reference"
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
"github.com/moby/buildkit/util/contentutil"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -49,8 +47,8 @@ type index struct {

type asset struct {
config *ocispec.Image
sbom map[string]interface{}
provenance *provenance
sbom json.RawMessage
provenance json.RawMessage
}

type result struct {
Expand Down Expand Up @@ -132,7 +130,7 @@ func (l *loader) Load(ctx context.Context, ref string) (*result, error) {
}
}

if err := l.scanProvenance(ctx, fetcher, mfst.manifest.Config, &a); err != nil {
if err := l.scanProvenance(ctx, fetcher, r, refs, &a); err != nil {
return nil, err
}

Expand Down Expand Up @@ -265,7 +263,6 @@ func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *resul
}
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" {
var sbom map[string]interface{}
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer)
if err != nil {
return err
Expand All @@ -274,93 +271,34 @@ func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *resul
if err != nil {
return err
}
if err := json.Unmarshal(dt, &sbom); err != nil {
return err
}
as.sbom = sbom
as.sbom = dt
}
}
}
return nil
}

type provenance struct { // TODO: this is only a stub, to be refactored later
BuildSource string `json:",omitempty"`
BuildDefinition string `json:",omitempty"`
BuildParameters map[string]string `json:",omitempty"`
Materials []material
}

type material struct {
Type string `json:",omitempty"`
Ref string `json:",omitempty"`
Alias string `json:",omitempty"`
Pin string `json:",omitempty"`
}

func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor, as *asset) error {
_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc)
if err != nil {
return err
}
dt, err := content.ReadBlob(ctx, l.cache, desc)
if err != nil {
return err
}

var cfg binfotypes.ImageConfig
if err := json.Unmarshal(dt, &cfg); err != nil {
return err
}

if cfg.BuildInfo == "" {
return nil
}

dt, err = base64.StdEncoding.DecodeString(cfg.BuildInfo)
if err != nil {
return errors.Wrapf(err, "failed to decode buildinfo base64")
}

var bi binfotypes.BuildInfo
if err := json.Unmarshal(dt, &bi); err != nil {
return errors.Wrapf(err, "failed to decode buildinfo")
}

p := &provenance{}
defer func() {
as.provenance = p
}()
if bs := bi.Attrs["context"]; bs != nil {
p.BuildSource = *bs
}

if fn := bi.Attrs["filename"]; fn != nil {
p.BuildDefinition = *fn
}

for key, val := range bi.Attrs {
if val == nil || !strings.HasPrefix(key, "build-arg:") {
continue
}
if p.BuildParameters == nil {
p.BuildParameters = make(map[string]string)
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)
}
p.BuildParameters[strings.TrimPrefix(key, "build-arg:")] = *val
}

p.Materials = make([]material, len(bi.Sources))

for i, src := range bi.Sources {
// TODO: mark base image
p.Materials[i] = material{
Type: string(src.Type),
Ref: src.Ref,
Alias: src.Alias,
Pin: src.Pin,
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
}
as.provenance = dt
}
}
}

return nil
}

Expand All @@ -378,11 +316,11 @@ func (r *result) Configs() map[string]*ocispec.Image {
return res
}

func (r *result) Provenances() map[string]*provenance {
func (r *result) Provenances() map[string]json.RawMessage {
if len(r.assets) == 0 {
return nil
}
res := make(map[string]*provenance)
res := make(map[string]json.RawMessage)
for p, a := range r.assets {
if a.provenance == nil {
continue
Expand All @@ -392,11 +330,11 @@ func (r *result) Provenances() map[string]*provenance {
return res
}

func (r *result) SBOMs() map[string]map[string]interface{} {
func (r *result) SBOMs() map[string]json.RawMessage {
if len(r.assets) == 0 {
return nil
}
res := make(map[string]map[string]interface{})
res := make(map[string]json.RawMessage)
for p, a := range r.assets {
if a.sbom == nil {
continue
Expand Down
78 changes: 18 additions & 60 deletions util/imagetools/printers.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ func (p *Printer) Print(raw bool, out io.Writer) error {
default:
if len(p.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]*provenance `json:"provenance,omitempty"`
SBOM map[string]map[string]interface{} `json:"sbom,omitempty"`
Name string `json:"name,omitempty"`
Manifest interface{} `json:"manifest,omitempty"`
Image map[string]*ocispecs.Image `json:"image,omitempty"`
Provenance map[string]json.RawMessage `json:"SLSA,omitempty"`
SBOM map[string]json.RawMessage `json:"SPDX,omitempty"`
}{
Name: p.name,
Manifest: mfst,
Expand All @@ -166,20 +166,20 @@ func (p *Printer) Print(raw bool, out io.Writer) error {
for _, v := range imageconfigs {
ic = v
}
var prv *provenance
var prv json.RawMessage
for _, v := range provenances {
prv = v
}
var sbom map[string]interface{}
var sbom json.RawMessage
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 *provenance `json:"provenance,omitempty"`
SBOM map[string]interface{} `json:"sbom,omitempty"`
Name string `json:"name,omitempty"`
Manifest interface{} `json:"manifest,omitempty"`
Image *ocispecs.Image `json:"image,omitempty"`
Provenance json.RawMessage `json:"provenance,omitempty"`
SBOM json.RawMessage `json:"sbom,omitempty"`
}{
Name: p.name,
Manifest: mfst,
Expand Down Expand Up @@ -234,18 +234,16 @@ func (p *Printer) printManifestList(out io.Writer) error {
return w.Flush()
}

func (p *Printer) printProvenances(provenances map[string]*provenance, out io.Writer) error {
func (p *Printer) printProvenances(provenances map[string]json.RawMessage, out io.Writer) error {
if len(provenances) == 0 {
return nil
} else if len(provenances) == 1 {
for _, pr := range provenances {
return p.printProvenance(pr, "", out)
}
}
var pkeys []string
for _, pform := range p.res.platforms {
pkeys = append(pkeys, pform)
}
pkeys := append([]string{}, p.res.platforms...)

sort.Strings(pkeys)
for _, platform := range pkeys {
if pr, ok := provenances[platform]; ok {
Expand All @@ -260,47 +258,7 @@ func (p *Printer) printProvenances(provenances map[string]*provenance, out io.Wr
return nil
}

func (p *Printer) printProvenance(pr *provenance, pfx string, out io.Writer) error {
w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
_, _ = fmt.Fprintf(w, "%sBuildSource:\t%s\n", pfx, pr.BuildSource)
_, _ = fmt.Fprintf(w, "%sBuildDefinition:\t%s\n", pfx, pr.BuildDefinition)

if len(pr.BuildParameters) > 0 {
_, _ = fmt.Fprintf(w, "%sBuildParameters:\t\n", pfx)
_ = w.Flush()
for k, v := range pr.BuildParameters {
_, _ = fmt.Fprintf(w, "%s%s:\t%s\n", pfx+defaultPfx, k, v)
}
}

if len(pr.Materials) > 0 {
_, _ = fmt.Fprintf(w, "%sMaterials:\t\n", pfx)
_ = w.Flush()
for i, v := range pr.Materials {
if i != 0 {
_, _ = fmt.Fprintf(w, "\t\n")
}
_, _ = fmt.Fprintf(w, "%sType:\t%s\n", pfx+defaultPfx, v.Type)
_, _ = fmt.Fprintf(w, "%sRef:\t%s\n", pfx+defaultPfx, v.Ref)
_, _ = fmt.Fprintf(w, "%sPin:\t%s\n", pfx+defaultPfx, v.Pin)
}
}

// TODO: deps not yet implemented with provenance
//if len(pr.Deps) > 0 {
// _, _ = fmt.Fprintf(w, "%sDeps:\t\n", pfx)
// _ = w.Flush()
// firstPass := true
// for k, v := range pr.Deps {
// if !firstPass {
// _, _ = fmt.Fprintf(w, "\t\n")
// }
// _, _ = fmt.Fprintf(w, "%sName:\t%s\n", pfx+defaultPfx, k)
// _ = w.Flush()
// _ = p.printProvenance(&v, pfx+defaultPfx, out)
// firstPass = false
// }
//}

return w.Flush()
func (p *Printer) printProvenance(pr json.RawMessage, pfx string, out io.Writer) error {
_, err := out.Write(pr)
return err
}

0 comments on commit 2817533

Please sign in to comment.