From 8ac4731042e7d3d6a2e814f861d47629f8c9cf9b Mon Sep 17 00:00:00 2001 From: Jon Johnson Date: Tue, 19 Dec 2023 11:02:28 -0800 Subject: [PATCH] Use idb to drive sbom file inclusion Note that the golden SBOMs were updated to drop the duplicate etc/os-release entries. Signed-off-by: Jon Johnson --- internal/cli/publish_test.go | 4 +- .../golden/sboms/sbom-aarch64.spdx.json | 19 -------- .../golden/sboms/sbom-x86_64.spdx.json | 19 -------- pkg/build/sbom.go | 3 +- pkg/sbom/generator/spdx/spdx.go | 44 +++++++++++++++---- pkg/sbom/generator/spdx/spdx_test.go | 24 +++++----- pkg/sbom/options/options.go | 2 +- 7 files changed, 53 insertions(+), 62 deletions(-) diff --git a/internal/cli/publish_test.go b/internal/cli/publish_test.go index 33487a6cd..b96466ec4 100644 --- a/internal/cli/publish_test.go +++ b/internal/cli/publish_test.go @@ -113,8 +113,8 @@ func TestPublish(t *testing.T) { // We also want to check the children SBOMs because the index SBOM does not have // references to the children SBOMs, just the children! wantBoms := []string{ - "sha256:bef40bcaa24ff513c1add5e0133a663519081ed5fd770a67609089ebb1b4a5a3", - "sha256:c400175e8fb4d7b3e2eb0442b7991716b3f6a770539f5e0f9d47e52a426e5431", + "sha256:1659428b4c619a57536ad62e473d46adbf9ca51eb8c765a9e835e6d1bddf8bcb", + "sha256:7126743a21e6bc7d81a72bb7570b0b951190eec361c50980592eacd177fd46d5", } for i, m := range im.Manifests { diff --git a/internal/cli/testdata/golden/sboms/sbom-aarch64.spdx.json b/internal/cli/testdata/golden/sboms/sbom-aarch64.spdx.json index 9f720730b..3930210e2 100644 --- a/internal/cli/testdata/golden/sboms/sbom-aarch64.spdx.json +++ b/internal/cli/testdata/golden/sboms/sbom-aarch64.spdx.json @@ -16,25 +16,6 @@ "SPDXRef-Package-sha256-7e3a7384fe0243cc4b498cd54be2455fdcbb7aebd5eb94566926a5088868eef4" ], "files": [ - { - "SPDXID": "SPDXRef-File--etc-os-release", - "fileName": "etc/os-release", - "licenseConcluded": "NOASSERTION", - "checksums": [ - { - "algorithm": "SHA1", - "checksumValue": "2a1171b49b2e3d59abad728c7b0480736ff792eb" - }, - { - "algorithm": "SHA256", - "checksumValue": "1877864cb20a2a48b27fbae1dd33d90918a56c7e24e503976d0ceb3ab5c9eecd" - }, - { - "algorithm": "SHA512", - "checksumValue": "2b2935e9103b1755bf0e4028528accd9f082548180e9e70d9a15383005603ce314ca89c8280a98dc9cc429c6bfe3596c0c6faaa7f6dc9a4d195ce3f560014876" - } - ] - }, { "SPDXID": "SPDXRef-File--etc-os-release", "fileName": "etc/os-release", diff --git a/internal/cli/testdata/golden/sboms/sbom-x86_64.spdx.json b/internal/cli/testdata/golden/sboms/sbom-x86_64.spdx.json index 26073eb36..34aa2c89d 100644 --- a/internal/cli/testdata/golden/sboms/sbom-x86_64.spdx.json +++ b/internal/cli/testdata/golden/sboms/sbom-x86_64.spdx.json @@ -16,25 +16,6 @@ "SPDXRef-Package-sha256-e42af50bd66e8060653eb032153bc77d039807418ee4d80e0fbca5e3e5028926" ], "files": [ - { - "SPDXID": "SPDXRef-File--etc-os-release", - "fileName": "etc/os-release", - "licenseConcluded": "NOASSERTION", - "checksums": [ - { - "algorithm": "SHA1", - "checksumValue": "2a1171b49b2e3d59abad728c7b0480736ff792eb" - }, - { - "algorithm": "SHA256", - "checksumValue": "1877864cb20a2a48b27fbae1dd33d90918a56c7e24e503976d0ceb3ab5c9eecd" - }, - { - "algorithm": "SHA512", - "checksumValue": "2b2935e9103b1755bf0e4028528accd9f082548180e9e70d9a15383005603ce314ca89c8280a98dc9cc429c6bfe3596c0c6faaa7f6dc9a4d195ce3f560014876" - } - ] - }, { "SPDXID": "SPDXRef-File--etc-os-release", "fileName": "etc/os-release", diff --git a/pkg/build/sbom.go b/pkg/build/sbom.go index 74c7e3c29..3800a3396 100644 --- a/pkg/build/sbom.go +++ b/pkg/build/sbom.go @@ -103,10 +103,11 @@ func (bc *Context) GenerateImageSBOM(ctx context.Context, arch types.Architectur s.OS.ID = info.ID s.OS.Version = info.VersionID - pkgs, err := sbom.ReadPackageIndex(bc.fs) + pkgs, err := bc.apk.GetInstalled() if err != nil { return nil, fmt.Errorf("reading apk package index: %w", err) } + s.Packages = pkgs // Get the image digest diff --git a/pkg/sbom/generator/spdx/spdx.go b/pkg/sbom/generator/spdx/spdx.go index 59054b77a..baea159a9 100644 --- a/pkg/sbom/generator/spdx/spdx.go +++ b/pkg/sbom/generator/spdx/spdx.go @@ -139,7 +139,7 @@ func (sx *SPDX) Generate(opts *options.Options, path string) error { }) // Check to see if the apk contains an sbom describing itself - if err := sx.ProcessInternalApkSBOM(opts, doc, &p); err != nil { + if err := sx.ProcessInternalApkSBOM(opts, doc, &p, pkg); err != nil { return fmt.Errorf("parsing internal apk SBOM: %w", err) } } @@ -209,7 +209,7 @@ func locateApkSBOM(fsys apkfs.FullFS, p *Package) (string, error) { return "", nil } -func (sx *SPDX) ProcessInternalApkSBOM(opts *options.Options, doc *Document, p *Package) error { +func (sx *SPDX) ProcessInternalApkSBOM(opts *options.Options, doc *Document, p *Package, ipkg *apk.InstalledPackage) error { // Check if apk installed an SBOM path, err := locateApkSBOM(sx.fs, p) if err != nil { @@ -256,7 +256,7 @@ func (sx *SPDX) ProcessInternalApkSBOM(opts *options.Options, doc *Document, p * todo[id] = struct{}{} } - if err := copySBOMElements(internalDoc, doc, todo); err != nil { + if err := copySBOMElements(internalDoc, doc, todo, ipkg); err != nil { return fmt.Errorf("copying element: %w", err) } @@ -275,7 +275,7 @@ func (sx *SPDX) ProcessInternalApkSBOM(opts *options.Options, doc *Document, p * return nil } -func copySBOMElements(sourceDoc, targetDoc *Document, todo map[string]struct{}) error { +func copySBOMElements(sourceDoc, targetDoc *Document, todo map[string]struct{}, ipkg *apk.InstalledPackage) error { // Walk the graph looking for things to copy. // Loop until we don't find any new todos. for prev, next := 0, len(todo); next != prev; prev, next = next, len(todo) { @@ -286,6 +286,25 @@ func copySBOMElements(sourceDoc, targetDoc *Document, todo map[string]struct{}) } } + // The APK SBOMs we are copying Files from may have duplicate file entries. + // + // A file can be overwritten if: + // 1. one package replaces another package + // 2. the packages are in the same origin + // + // Files with the same checksum are also skipped on install since they don't conflict. + // + // We need to reconcile these files that were overwritten or omitted to avoid having + // conflicting entries (different checksums) for the same file in our image SBOM. + // To do this, we consult /lib/apk/db/installed to know which package's file "won". + // Here, we create a set of the files that are owned by this package and only include + // + // those when copying from sourceDoc.Files. + ownedFiles := map[string]struct{}{} + for _, hdr := range ipkg.Files { + ownedFiles[hdr.Name] = struct{}{} + } + // Now copy everything over. done := make(map[string]struct{}, len(todo)) @@ -297,12 +316,19 @@ func copySBOMElements(sourceDoc, targetDoc *Document, todo map[string]struct{}) } for _, f := range sourceDoc.Files { - if _, ok := todo[f.ID]; ok { - f.Name = strings.TrimPrefix(f.Name, "/") // Strip leading slashes, which SPDX doesn't like. + if _, ok := todo[f.ID]; !ok { + continue + } - targetDoc.Files = append(targetDoc.Files, f) - done[f.ID] = struct{}{} + done[f.ID] = struct{}{} + + if _, ok := ownedFiles[f.Name]; !ok { + continue } + + f.Name = strings.TrimPrefix(f.Name, "/") // Strip leading slashes, which SPDX doesn't like. + + targetDoc.Files = append(targetDoc.Files, f) } for _, r := range sourceDoc.Relationships { @@ -390,7 +416,7 @@ func (sx *SPDX) imagePackage(opts *options.Options) (p *Package) { } // apkPackage returns a SPDX package describing an apk -func (sx *SPDX) apkPackage(opts *options.Options, pkg *apk.Package) Package { +func (sx *SPDX) apkPackage(opts *options.Options, pkg *apk.InstalledPackage) Package { return Package{ ID: stringToIdentifier(fmt.Sprintf( "SPDXRef-Package-%s-%s", pkg.Name, pkg.Version, diff --git a/pkg/sbom/generator/spdx/spdx_test.go b/pkg/sbom/generator/spdx/spdx_test.go index d0f35e3c7..49fa056de 100644 --- a/pkg/sbom/generator/spdx/spdx_test.go +++ b/pkg/sbom/generator/spdx/spdx_test.go @@ -41,18 +41,20 @@ var testOpts = &options.Options{ Version: "3.0", }, FileName: "sbom", - Packages: []*apk.Package{ + Packages: []*apk.InstalledPackage{ { - Name: "musl", - Version: "1.2.2-r7", - Arch: "x86_64", - Description: "the musl c library (libc) implementation", - License: "MIT", - Origin: "musl", - Maintainer: "Pkg Author ", - Checksum: []byte{ - 0xd, 0xe6, 0xf4, 0x8c, 0xdc, 0xad, 0x92, 0xb8, 0xcf, 0x5b, - 0x83, 0x7f, 0x78, 0xa2, 0xd9, 0xe3, 0x70, 0x70, 0x3a, 0x5c, + Package: apk.Package{ + Name: "musl", + Version: "1.2.2-r7", + Arch: "x86_64", + Description: "the musl c library (libc) implementation", + License: "MIT", + Origin: "musl", + Maintainer: "Pkg Author ", + Checksum: []byte{ + 0xd, 0xe6, 0xf4, 0x8c, 0xdc, 0xad, 0x92, 0xb8, 0xcf, 0x5b, + 0x83, 0x7f, 0x78, 0xa2, 0xd9, 0xe3, 0x70, 0x70, 0x3a, 0x5c, + }, }, }, }, diff --git a/pkg/sbom/options/options.go b/pkg/sbom/options/options.go index 3d38325a7..25ac62aa5 100644 --- a/pkg/sbom/options/options.go +++ b/pkg/sbom/options/options.go @@ -52,7 +52,7 @@ type Options struct { Formats []string // Packages is alist of packages which will be listed in the SBOM - Packages []*apk.Package + Packages []*apk.InstalledPackage } type PurlQualifiers map[string]string