Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions internal/cli/build-minirootfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"chainguard.dev/apko/pkg/build"
"chainguard.dev/apko/pkg/build/types"
"chainguard.dev/apko/pkg/options"
"chainguard.dev/apko/pkg/sbom/generator"
"chainguard.dev/apko/pkg/tarfs"
)

Expand All @@ -48,7 +49,7 @@ func buildMinirootFS() *cobra.Command {
Example: ` apko build-minirootfs <config.yaml> <output.tar.gz>`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return BuildMinirootFSCmd(cmd.Context(),
opts := []build.Option{
build.WithConfig(args[0], []string{}),
build.WithExtraKeys(extraKeys),
build.WithExtraBuildRepos(extraBuildRepos),
Expand All @@ -60,7 +61,11 @@ func buildMinirootFS() *cobra.Command {
build.WithArch(types.ParseArchitecture(buildArch)),
build.WithIgnoreSignatures(ignoreSignatures),
build.WithSizeLimits(sizeLimits),
)
}
if sbomPath != "" {
opts = append(opts, build.WithSBOMGenerators(generator.Generators("spdx")...))
}
return BuildMinirootFSCmd(cmd.Context(), opts...)
},
}

Expand Down Expand Up @@ -97,11 +102,17 @@ func BuildMinirootFSCmd(ctx context.Context, opts ...build.Option) error {
}

log.Debugf("building minirootfs %s", bc.TarballPath())
layerTarGZ, _, err := bc.BuildLayer(ctx)
layerTarGZ, layer, err := bc.BuildLayer(ctx)
if err != nil {
return fmt.Errorf("failed to build layer image: %w", err)
}
log.Debugf("wrote minirootfs to %s", layerTarGZ)

if bc.WantSBOM() {
if _, err := bc.GenerateLayerSBOM(ctx, bc.Arch(), layer); err != nil {
return fmt.Errorf("generating sbom: %w", err)
}
}

return nil
}
31 changes: 31 additions & 0 deletions internal/cli/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,34 @@ func TestBuildWithBase(t *testing.T) {

require.Equal(t, want, got)
}

func TestBuildMinirootFSWritesSBOM(t *testing.T) {
ctx := context.Background()
tmp := t.TempDir()

sbomPath := filepath.Join(tmp, "sboms")
require.NoError(t, os.MkdirAll(sbomPath, 0o750))

output := filepath.Join(tmp, "minirootfs.tar.gz")
err := cli.BuildMinirootFSCmd(ctx,
build.WithConfig(filepath.Join("testdata", "apko.yaml"), []string{}),
build.WithArch(types.ParseArchitecture("amd64")),
build.WithTarball(output),
build.WithSBOM(sbomPath),
build.WithSBOMGenerators(spdx.New()),
)
require.NoError(t, err)

require.FileExists(t, output)

sbom := filepath.Join(sbomPath, "sbom-x86_64.spdx.json")
require.FileExists(t, sbom)

data, err := os.ReadFile(sbom)
require.NoError(t, err)

var doc map[string]any
require.NoError(t, json.Unmarshal(data, &doc))
require.Equal(t, "SPDX-2.3", doc["spdxVersion"])
require.NotEmpty(t, doc["packages"])
}
75 changes: 75 additions & 0 deletions pkg/build/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,81 @@ func (bc *Context) GenerateImageSBOM(ctx context.Context, arch types.Architectur
return sboms, nil
}

func (bc *Context) GenerateLayerSBOM(ctx context.Context, arch types.Architecture, layer v1.Layer) ([]types.SBOM, error) {
log := clog.FromContext(ctx).With("arch", arch.ToAPK())
ctx = clog.WithLogger(ctx, log)

_, span := otel.Tracer("apko").Start(ctx, "GenerateLayerSBOM")
defer span.End()

if !bc.WantSBOM() {
log.Warnf("skipping SBOM generation")
return nil, nil
}

bde, err := bc.GetBuildDateEpoch()
if err != nil {
return nil, fmt.Errorf("computing build date epoch: %w", err)
}

layerDigest, err := layer.Digest()
if err != nil {
return nil, fmt.Errorf("getting %s layer digest: %w", arch, err)
}

layerSize, err := layer.Size()
if err != nil {
return nil, fmt.Errorf("getting %s layer size: %w", arch, err)
}

layerMediaType, err := layer.MediaType()
if err != nil {
return nil, fmt.Errorf("getting %s layer media type: %w", arch, err)
}

s := newSBOM(ctx, bc.fs, bc.o, bc.ic, bde)
log.Debug("Generating layer SBOM")

s.ImageInfo.Layers = []v1.Descriptor{{
MediaType: layerMediaType,
Size: layerSize,
Digest: layerDigest,
}}

info, err := fetchFSReleaseData(bc.fs)
if err != nil {
return nil, fmt.Errorf("reading release data: %w", err)
}

s.OS.Name = info.Name
s.OS.ID = info.ID
s.OS.Version = info.VersionID
s.ImageInfo.Arch = arch

pkgs, err := bc.apk.GetInstalled()
if err != nil {
return nil, fmt.Errorf("reading apk package index: %w", err)
}

s.Packages = pkgs

sboms := make([]types.SBOM, 0)
for _, gen := range bc.o.SBOMGenerators {
filename := filepath.Join(s.OutputDir, s.FileName+"."+gen.Ext())
if err := gen.Generate(ctx, &s, filename); err != nil {
return nil, fmt.Errorf("generating %s sbom: %w", gen.Key(), err)
}
sboms = append(sboms, types.SBOM{
Path: filename,
Format: gen.Key(),
PredicateType: gen.PredicateType(),
Arch: arch.String(),
Digest: layerDigest,
})
}
return sboms, nil
}

type ReleaseData struct {
ID string
Name string
Expand Down