Skip to content

Commit

Permalink
Attach SBOMs to built images.
Browse files Browse the repository at this point in the history
This change attaches SBOMs to the `oci.SignedImage`s we build up, and these are published thanks to the previous commit in this series.

I have added validation that for each of the examples we have that we scan download its SBOM with cosign.

Fixes: #145
  • Loading branch information
mattmoor committed Mar 27, 2022
1 parent b37286c commit 9becafa
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 16 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/mink-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
- uses: imjasonh/setup-ko@2c3450ca27f6e6f2b02e72a40f2163c281a1f675 # v0.4
with:
version: v0.11.1
- uses: imjasonh/[email protected]
- uses: sigstore/cosign-installer@main

- uses: chainguard-dev/actions/setup-kind@main
with:
Expand Down Expand Up @@ -56,9 +58,16 @@ jobs:
- name: Build examples
run: |
for cfg in $(find ./examples/ -name '*.yaml'); do
echo "::group:: $cfg"
DIGEST=$(mink run task apko -- --path=$cfg)
echo $cfg produced: $DIGEST
# Each of the per-architecture images produces should have an SBOM attached to it.
for d in $(crane manifest "${DIGEST}" | jq -r '.manifests[].digest'); do
cosign download sbom ${KO_DOCKER_REPO}/image@${d}
done
echo ::endgroup::
done
- uses: chainguard-dev/actions/kind-diag@main
Expand Down
54 changes: 41 additions & 13 deletions pkg/build/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"sort"
"time"

Expand All @@ -38,7 +40,9 @@ import (
ocimutate "github.com/sigstore/cosign/pkg/oci/mutate"
ociremote "github.com/sigstore/cosign/pkg/oci/remote"
"github.com/sigstore/cosign/pkg/oci/signed"
"github.com/sigstore/cosign/pkg/oci/static"
"github.com/sigstore/cosign/pkg/oci/walk"
ctypes "github.com/sigstore/cosign/pkg/types"

"chainguard.dev/apko/pkg/build/types"
)
Expand All @@ -51,7 +55,7 @@ var keychain = authn.NewMultiKeychain(
github.Keychain,
)

func buildImageFromLayer(layerTarGZ string, ic types.ImageConfiguration, created time.Time, arch types.Architecture, logger *log.Logger) (oci.SignedImage, error) {
func buildImageFromLayer(layerTarGZ string, ic types.ImageConfiguration, created time.Time, arch types.Architecture, logger *log.Logger, sbomPath string, sbomFormats []string) (oci.SignedImage, error) {
logger.Printf("building OCI image from layer '%s'", layerTarGZ)

v1Layer, err := v1tar.LayerFromFile(layerTarGZ)
Expand Down Expand Up @@ -130,21 +134,45 @@ func buildImageFromLayer(layerTarGZ string, ic types.ImageConfiguration, created

si := signed.Image(v1Image)

// TODO(#145): Attach the SBOM, e.g.
// f, err := static.NewFile(sbom, static.WithLayerMediaType(mt))
// if err != nil {
// return nil, err
// }
// si, err = ocimutate.AttachFileToImage(si, "sbom", f)
// if err != nil {
// return nil, err
// }
// Attach the SBOM, e.g.
if len(sbomFormats) > 0 {
var mt ggcrtypes.MediaType
var path string
switch sbomFormats[0] {
case "spdx":
mt = ctypes.SPDXMediaType
path = filepath.Join(sbomPath, fmt.Sprintf("sbom-%s.spdx.json", arch.ToAPK()))
case "cyclonedx":
mt = ctypes.CycloneDXMediaType
path = filepath.Join(sbomPath, fmt.Sprintf("sbom-%s.cdx", arch.ToAPK()))
default:
return nil, fmt.Errorf("unsupported SBOM format: %s", sbomFormats[0])
}
if len(sbomFormats) > 1 {
// When we have multiple formats, warn that we're picking the first.
logger.Printf("uploading SBOM with media type: %s", mt)
}

sbom, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading sbom: %w", err)
}

f, err := static.NewFile(sbom, static.WithLayerMediaType(mt))
if err != nil {
return nil, err
}
si, err = ocimutate.AttachFileToImage(si, "sbom", f)
if err != nil {
return nil, err
}
}

return si, nil
}

func BuildImageTarballFromLayer(imageRef string, layerTarGZ string, outputTarGZ string, ic types.ImageConfiguration, created time.Time, arch types.Architecture, logger *log.Logger) error {
v1Image, err := buildImageFromLayer(layerTarGZ, ic, created, arch, logger)
v1Image, err := buildImageFromLayer(layerTarGZ, ic, created, arch, logger, "", nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -180,8 +208,8 @@ func publishTagFromImage(image oci.SignedImage, imageRef string, hash v1.Hash) (
return imgRef.Context().Digest(hash.String()), nil
}

func PublishImageFromLayer(layerTarGZ string, ic types.ImageConfiguration, created time.Time, arch types.Architecture, logger *log.Logger, tags ...string) (name.Digest, oci.SignedImage, error) {
v1Image, err := buildImageFromLayer(layerTarGZ, ic, created, arch, logger)
func PublishImageFromLayer(layerTarGZ string, ic types.ImageConfiguration, created time.Time, arch types.Architecture, logger *log.Logger, sbomPath string, sbomFormats []string, tags ...string) (name.Digest, oci.SignedImage, error) {
v1Image, err := buildImageFromLayer(layerTarGZ, ic, created, arch, logger, sbomPath, sbomFormats)
if err != nil {
return name.Digest{}, nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/build/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ func (bc *Context) GenerateSBOM() error {
s.Options.Packages = packages
s.Options.Formats = bc.SBOMFormats

if _, err := s.Generate(); err != nil {
files, err := s.Generate()
if err != nil {
return fmt.Errorf("generating SBOMs: %w", err)
}
bc.Log.Printf("wrote sboms: %v", files)

return nil
}
4 changes: 2 additions & 2 deletions pkg/cli/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func PublishCmd(ctx context.Context, outputRefs string, archs []types.Architectu
}
defer os.Remove(layerTarGZ)

digest, _, err = oci.PublishImageFromLayer(layerTarGZ, bc.ImageConfiguration, bc.SourceDateEpoch, bc.Arch, bc.Log, bc.Tags...)
digest, _, err = oci.PublishImageFromLayer(layerTarGZ, bc.ImageConfiguration, bc.SourceDateEpoch, bc.Arch, bc.Log, bc.SBOMPath, bc.SBOMFormats, bc.Tags...)
if err != nil {
return fmt.Errorf("failed to build OCI image: %w", err)
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func PublishCmd(ctx context.Context, outputRefs string, archs []types.Architectu
// TODO(kaniini): clean up everything correctly for multitag scenario
// defer os.Remove(layerTarGZ)

_, img, err := oci.PublishImageFromLayer(layerTarGZ, bc.ImageConfiguration, bc.SourceDateEpoch, arch, bc.Log)
_, img, err := oci.PublishImageFromLayer(layerTarGZ, bc.ImageConfiguration, bc.SourceDateEpoch, arch, bc.Log, bc.SBOMPath, bc.SBOMFormats)
if err != nil {
return fmt.Errorf("failed to build OCI image for %q: %w", arch, err)
}
Expand Down

0 comments on commit 9becafa

Please sign in to comment.