Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate and attach multiarch index SBOMs (only SPDX for now) #257

Merged
merged 12 commits into from
Jun 30, 2022
18 changes: 15 additions & 3 deletions internal/cli/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func PublishCmd(ctx context.Context, outputRefs string, archs []types.Architectu
// The build context options is sometimes copied in the next functions. Ensure
// we have the directory defined and created by invoking the function early.
bc.Options.TempDir()
defer os.RemoveAll(bc.Options.TempDir())
// defer os.RemoveAll(bc.Options.TempDir())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have been uncommented? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it's the cleanup and I was testing. I'll fix it.


bc.Logger().Printf("building tags %v", bc.Options.Tags)

Expand All @@ -147,6 +147,8 @@ func PublishCmd(ctx context.Context, outputRefs string, archs []types.Architectu
}

var finalDigest name.Digest
var idx coci.SignedImageIndex

for _, arch := range bc.ImageConfiguration.Archs {
arch := arch
bc := *bc
Expand Down Expand Up @@ -190,12 +192,12 @@ func PublishCmd(ctx context.Context, outputRefs string, archs []types.Architectu

if len(archs) > 1 {
if bc.Options.UseDockerMediaTypes {
finalDigest, err = oci.PublishDockerIndex(imgs, logrus.NewEntry(bc.Options.Log), bc.Options.Tags...)
finalDigest, idx, err = oci.PublishDockerIndex(imgs, logrus.NewEntry(bc.Options.Log), bc.Options.Tags...)
if err != nil {
return fmt.Errorf("failed to build Docker index: %w", err)
}
} else {
finalDigest, err = oci.PublishIndex(imgs, logrus.NewEntry(bc.Options.Log), bc.Options.Tags...)
finalDigest, idx, err = oci.PublishIndex(imgs, logrus.NewEntry(bc.Options.Log), bc.Options.Tags...)
if err != nil {
return fmt.Errorf("failed to build OCI index: %w", err)
}
Expand Down Expand Up @@ -226,6 +228,16 @@ func PublishCmd(ctx context.Context, outputRefs string, archs []types.Architectu
return fmt.Errorf("attaching sboms to %s image: %w", arch, err)
}
}

if err := bc.GenerateIndexSBOM(finalDigest, imgs); err != nil {
return fmt.Errorf("generating index SBOM: %w", err)
}

if _, err := oci.PostAttachSBOM(
idx, sbompath, bc.Options.SBOMFormats, types.Architecture{}, bc.Logger(), bc.Options.Tags...,
); err != nil {
return fmt.Errorf("attaching sboms to index: %w", err)
}
}

// If provided, this is the name of the file to write digest referenced into
Expand Down
5 changes: 5 additions & 0 deletions pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strconv"
"time"

"github.com/google/go-containerregistry/pkg/name"
"github.com/hashicorp/go-multierror"
coci "github.com/sigstore/cosign/pkg/oci"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -66,6 +67,10 @@ func (bc *Context) GenerateImageSBOM(arch types.Architecture, img coci.SignedIma
return bc.impl.GenerateSBOM(&opts)
}

func (bc *Context) GenerateIndexSBOM(indexDigest name.Digest, imgs map[types.Architecture]coci.SignedImage) error {
return bc.impl.GenerateIndexSBOM(&bc.Options, indexDigest, imgs)
}

func (bc *Context) GenerateSBOM() error {
return bc.impl.GenerateSBOM(&bc.Options)
}
Expand Down
82 changes: 82 additions & 0 deletions pkg/build/build_implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
package build

import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
v1tar "github.com/google/go-containerregistry/pkg/v1/tarball"
ggcrtypes "github.com/google/go-containerregistry/pkg/v1/types"
coci "github.com/sigstore/cosign/pkg/oci"
"sigs.k8s.io/release-utils/hash"

chainguardAPK "chainguard.dev/apko/pkg/apk"
"chainguard.dev/apko/pkg/build/types"
Expand All @@ -30,6 +35,7 @@ import (
"chainguard.dev/apko/pkg/options"
"chainguard.dev/apko/pkg/s6"
"chainguard.dev/apko/pkg/sbom"
soptions "chainguard.dev/apko/pkg/sbom/options"
"chainguard.dev/apko/pkg/tarball"
)

Expand All @@ -47,6 +53,7 @@ type buildImplementation interface {
ValidateImageConfiguration(*types.ImageConfiguration) error
BuildImage(*options.Options, *types.ImageConfiguration, *exec.Executor, *s6.Context) error
WriteSupervisionTree(*s6.Context, *types.ImageConfiguration) error
GenerateIndexSBOM(*options.Options, name.Digest, map[types.Architecture]coci.SignedImage) error
}

type defaultBuildImplementation struct{}
Expand Down Expand Up @@ -209,3 +216,78 @@ func buildImage(

return nil
}

func (di *defaultBuildImplementation) GenerateIndexSBOM(
o *options.Options, indexDigest name.Digest, imgs map[types.Architecture]coci.SignedImage,
) error {
if len(o.SBOMFormats) == 0 {
o.Logger().Warnf("skipping index SBOM generation")
return nil
}

s := sbom.NewWithWorkDir(o.WorkDir, o.Arch)
// Parse the image reference
if len(o.Tags) > 0 {
tag, err := name.NewTag(o.Tags[0])
if err != nil {
return fmt.Errorf("parsing tag %s: %w", o.Tags[0], err)
}
s.Options.ImageInfo.Tag = tag.TagStr()
s.Options.ImageInfo.Name = tag.String()
}

o.Logger().Infof("Generating index SBOM")

// Add the image digest
h, err := v1.NewHash(indexDigest.DigestStr())
if err != nil {
return errors.New("getting index hash")
}
s.Options.ImageInfo.IndexDigest = h
s.Options.ImageInfo.SourceDateEpoch = o.SourceDateEpoch
s.Options.Formats = o.SBOMFormats
s.Options.OutputDir = o.TempDir()
if o.SBOMPath != "" {
s.Options.OutputDir = o.SBOMPath
}
s.Options.ImageInfo.IndexMediaType = ggcrtypes.OCIImageIndex
if o.UseDockerMediaTypes {
s.Options.ImageInfo.IndexMediaType = ggcrtypes.DockerManifestList
}
var ext string
switch o.SBOMFormats[0] {
case "spdx":
ext = "spdx.json"
case "cyclonedx":
ext = "cdx"
case "idb":
ext = "idb"
}

// Load the images data into the SBOM generator options
for arch, i := range imgs {
sbomHash, err := hash.SHA256ForFile(filepath.Join(s.Options.OutputDir, fmt.Sprintf("sbom-%s.%s", arch.ToAPK(), ext)))
if err != nil {
return fmt.Errorf("checksumming %s SBOM: %w", arch, err)
}

d, err := i.Digest()
if err != nil {
return fmt.Errorf("getting arch image digest: %w", err)
}

s.Options.ImageInfo.Images = append(
s.Options.ImageInfo.Images,
soptions.ArchImageInfo{
Digest: d,
Arch: arch,
SBOMDigest: sbomHash,
})
}

if _, err := s.GenerateIndex(); err != nil {
return fmt.Errorf("generting index SBOM: %w", err)
}

return nil
}
80 changes: 80 additions & 0 deletions pkg/build/buildfakes/fake_build_implementation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading