diff --git a/integration/sbom_test.go b/integration/sbom_test.go index e3ee5b89cc3d..e96210c59016 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -20,6 +20,7 @@ func TestSBOM(t *testing.T) { format string artifactType string scanners string + listAllPkgs bool } tests := []struct { name string @@ -101,6 +102,11 @@ func TestSBOM(t *testing.T) { require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)" + + require.Len(t, got.Results[0].Vulnerabilities, 3) + want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "Package-5a18334f22149877" + want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "Package-e16b1cbaa5186199" + want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "Package-e16b1cbaa5186199" }, }, { @@ -117,6 +123,11 @@ func TestSBOM(t *testing.T) { require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)" + + require.Len(t, got.Results[0].Vulnerabilities, 3) + want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "Package-5a18334f22149877" + want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "Package-e16b1cbaa5186199" + want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "Package-e16b1cbaa5186199" }, }, { @@ -129,6 +140,16 @@ func TestSBOM(t *testing.T) { }, golden: "testdata/license-cyclonedx.json.golden", }, + { + name: "multiple OSes", + args: args{ + input: "testdata/fixtures/sbom/multiple-os.sdpx.json", + format: "json", + artifactType: "spdx", + listAllPkgs: true, + }, + golden: "testdata/multiple-os.json.golden", + }, } // Set up testing DB @@ -162,6 +183,10 @@ func TestSBOM(t *testing.T) { osArgs = append(osArgs, "--output", outputFile) osArgs = append(osArgs, tt.args.input) + if tt.args.listAllPkgs { + osArgs = append(osArgs, "--list-all-pkgs") + } + // Run "trivy sbom" runTest(t, osArgs, tt.golden, outputFile, types.Format(tt.args.format), runOptions{ override: overrideFuncs(overrideSBOMReport, overrideUID, tt.override), diff --git a/integration/testdata/fixtures/sbom/multiple-os.sdpx.json b/integration/testdata/fixtures/sbom/multiple-os.sdpx.json new file mode 100644 index 000000000000..8aa7e69f3e17 --- /dev/null +++ b/integration/testdata/fixtures/sbom/multiple-os.sdpx.json @@ -0,0 +1,116 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2024-06-10T14:07:15Z", + "creators": [ + "Organization: foo", + "Tool: bar" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "https://tanzu.vmware.com/application-catalog/spdx/295c513b-b6cc-44c2-b19b-dc7196cdad9d", + "name": "SPDX document for nginx:18.0.3", + "packages": [ + { + "SPDXID": "SPDXRef-OperatingSystem-gnrtd1", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "debian", + "primaryPackagePurpose": "OPERATING_SYSTEM", + "versionInfo": "12.0" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd7", + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libedit2@3.1-20221030-2?arch=amd64\u0026distro=debian-12.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "BSD-3-Clause", + "licenseDeclared": "BSD-3-Clause", + "name": "libedit2", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: libedit 3.1-20221030-2", + "supplier": "Organization: LLVM Packaging Team \u003cpkg-llvm-team@lists.alioth.debian.org\u003e", + "versionInfo": "3.1-20221030-2" + }, + { + "SPDXID": "SPDXRef-OperatingSystem-gnrtd143", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "debian", + "primaryPackagePurpose": "OPERATING_SYSTEM", + "versionInfo": "12.5" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd175", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "99a8c7dd591fae9fb37d8bf8dfdffa850e207fa405b3198c5b24711a5f972381" + } + ], + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libmd0@1.0.4-2?arch=arm64\u0026distro=debian-12.5", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "libmd0", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: libmd 1.0.4-2", + "supplier": "Organization: Guillem Jover \u003cguillem@debian.org\u003e", + "versionInfo": "1.0.4-2" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd259", + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libmount1@2.38.1-5%2Bdeb12u1?arch=arm64\u0026distro=debian-12.5", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "libmount1", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: util-linux 2.38.1-5+deb12u1", + "supplier": "Organization: util-linux packagers \u003cutil-linux@packages.debian.org\u003e", + "versionInfo": "2.38.1-5+deb12u1" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd7", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd1" + }, + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd175", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd143" + }, + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd259", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd143" + } + ], + "spdxVersion": "SPDX-2.3" +} \ No newline at end of file diff --git a/integration/testdata/multiple-os.json.golden b/integration/testdata/multiple-os.json.golden new file mode 100644 index 000000000000..3c29054df91a --- /dev/null +++ b/integration/testdata/multiple-os.json.golden @@ -0,0 +1,67 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/sbom/multiple-os.sdpx.json", + "ArtifactType": "spdx", + "Metadata": { + "OS": { + "Family": "debian", + "Name": "12.5" + }, + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "testdata/fixtures/sbom/multiple-os.sdpx.json (debian 12.5)", + "Class": "os-pkgs", + "Type": "debian", + "Packages": [ + { + "ID": "libmd0@1.0.4-2", + "Name": "libmd0", + "Identifier": { + "PURL": "pkg:deb/debian/libmd0@1.0.4-2?arch=arm64\u0026distro=debian-12.5", + "UID": "bd94e402741d3988", + "BOMRef": "Package-gnrtd175" + }, + "Version": "1.0.4-2", + "Arch": "arm64", + "SrcName": "libmd", + "SrcVersion": "1.0.4", + "SrcRelease": "2", + "Licenses": [ + "MIT" + ], + "Layer": {} + }, + { + "ID": "libmount1@2.38.1-5+deb12u1", + "Name": "libmount1", + "Identifier": { + "PURL": "pkg:deb/debian/libmount1@2.38.1-5%2Bdeb12u1?arch=arm64\u0026distro=debian-12.5", + "UID": "ae2f6c69df04ce11", + "BOMRef": "Package-gnrtd259" + }, + "Version": "2.38.1-5+deb12u1", + "Arch": "arm64", + "SrcName": "util-linux", + "SrcVersion": "2.38.1", + "SrcRelease": "5+deb12u1", + "Licenses": [ + "MIT" + ], + "Layer": {} + } + ] + } + ] +} diff --git a/pkg/fanal/analyzer/sbom/sbom_test.go b/pkg/fanal/analyzer/sbom/sbom_test.go index 542a7f50addd..fba117778166 100644 --- a/pkg/fanal/analyzer/sbom/sbom_test.go +++ b/pkg/fanal/analyzer/sbom/sbom_test.go @@ -42,6 +42,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "apm-agent", Version: "1.36.0", }, + BOMRef: "Package-f0db45781e6813a1", }, }, { @@ -56,6 +57,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "apm-agent-cached-lookup-key", Version: "1.36.0", }, + BOMRef: "Package-efe22bf5916f985f", }, }, { @@ -70,6 +72,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "apm-agent-common", Version: "1.36.0", }, + BOMRef: "Package-33d86d2d11abe114", }, }, { @@ -84,6 +87,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "apm-agent-core", Version: "1.36.0", }, + BOMRef: "Package-b905fcf69ca61281", }, }, }, @@ -110,6 +114,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { }, }, }, + BOMRef: "elasticsearch", }, }, }, @@ -184,6 +189,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "gdal", Version: "3.7.1", }, + BOMRef: "gdal", }, }, { @@ -197,6 +203,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "geos", Version: "3.8.3", }, + BOMRef: "geos", }, }, { @@ -210,6 +217,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "postgresql", Version: "15.3.0", }, + BOMRef: "postgresql", }, DependsOn: []string{ "geos@3.8.3", @@ -228,6 +236,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Name: "proj", Version: "6.3.2", }, + BOMRef: "proj", }, }, }, diff --git a/pkg/fanal/types/package.go b/pkg/fanal/types/package.go index a0734651355d..71e070b03378 100644 --- a/pkg/fanal/types/package.go +++ b/pkg/fanal/types/package.go @@ -56,7 +56,7 @@ func (r *Relationship) UnmarshalJSON(data []byte) error { type PkgIdentifier struct { UID string `json:",omitempty"` // Calculated by the package struct PURL *packageurl.PackageURL `json:"-"` - BOMRef string `json:",omitempty"` // For CycloneDX + BOMRef string `json:",omitempty"` // Taken from `BOMRef` for CycloneDX or from `SPDXID` for SPDX. } // MarshalJSON customizes the JSON encoding of PkgIdentifier. diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index 51875bff8738..7cd4927c5739 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -133,7 +133,7 @@ type Component struct { // SPDX: package.externalRefs.referenceLocator // BOMRef: // CycloneDX: component.bom-ref - // SPDX: N/A + // SPDX: package.SPDXID PkgIdentifier ftypes.PkgIdentifier // Supplier is the name of the supplier of the component diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index 379e1af32d52..5e6aef5574fb 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -6,7 +6,6 @@ import ( "slices" "sort" "strconv" - "sync" debver "github.com/knqyf263/go-deb-version" rpmver "github.com/knqyf263/go-rpm-version" @@ -111,22 +110,12 @@ func (m *Decoder) decodeRoot(s *types.SBOM) error { } func (m *Decoder) decodeComponents(ctx context.Context, sbom *types.SBOM) error { - onceMultiOSWarn := sync.OnceFunc(func() { - m.logger.WarnContext(ctx, "Multiple OS components are not supported, taking the first one and ignoring the rest") - }) + var osComponents []*core.Component for id, c := range m.bom.Components() { switch c.Type { case core.TypeOS: - if m.osID != uuid.Nil { - onceMultiOSWarn() - continue - } - m.osID = id - sbom.Metadata.OS = &ftypes.OS{ - Family: ftypes.OSType(c.Name), - Name: c.Version, - } + osComponents = append(osComponents, c) continue case core.TypeApplication: if app := m.decodeApplication(c); app.Type != "" { @@ -147,9 +136,50 @@ func (m *Decoder) decodeComponents(ctx context.Context, sbom *types.SBOM) error } } + m.selectOS(ctx, osComponents, sbom) return nil } +// selectOS checks all found OSes and selects the OS with the maximum number of packages +// If two or more OS contain the same number of packages - select the first OS alphabetically +func (m *Decoder) selectOS(ctx context.Context, osComponents []*core.Component, sbom *types.SBOM) { + if len(osComponents) == 0 { + return + } + + sort.Slice(osComponents, func(i, j int) bool { + numberOfIPkgs := len(m.bom.Relationships()[osComponents[i].ID()]) + numberOfJPkgs := len(m.bom.Relationships()[osComponents[j].ID()]) + if numberOfIPkgs != numberOfJPkgs { + return numberOfIPkgs > numberOfJPkgs + } + return osComponents[i].PkgIdentifier.BOMRef < osComponents[j].PkgIdentifier.BOMRef + }) + + if len(osComponents) > 1 { + m.logger.WarnContext(ctx, "Multiple OS components are not supported, taking OS with most packages and ignoring the rest", log.String("selected OS", osComponents[0].Name+" "+osComponents[0].Version)) + } + + m.osID = osComponents[0].ID() + sbom.Metadata.OS = &ftypes.OS{ + Family: ftypes.OSType(osComponents[0].Name), + Name: osComponents[0].Version, + } + + // remove pkgs of non-main OS to avoid adding them as orphan packages + m.removeOSesPkgs(osComponents[1:]) +} + +// RemoveOSesPkgs removes associated packages with osComponents from `Decoder.pkgs` +func (m *Decoder) removeOSesPkgs(osComponents []*core.Component) { + for _, osComponent := range osComponents { + for _, pkg := range m.bom.Relationships()[osComponent.ID()] { + delete(m.pkgs, pkg.Dependency) + } + } + return +} + // buildDependencyGraph builds a dependency graph between packages func (m *Decoder) buildDependencyGraph() { for id, rels := range m.bom.Relationships() { diff --git a/pkg/sbom/spdx/testdata/happy/select-os-by-number-of-pkgs.json b/pkg/sbom/spdx/testdata/happy/select-os-by-number-of-pkgs.json new file mode 100644 index 000000000000..8aa7e69f3e17 --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/select-os-by-number-of-pkgs.json @@ -0,0 +1,116 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2024-06-10T14:07:15Z", + "creators": [ + "Organization: foo", + "Tool: bar" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "https://tanzu.vmware.com/application-catalog/spdx/295c513b-b6cc-44c2-b19b-dc7196cdad9d", + "name": "SPDX document for nginx:18.0.3", + "packages": [ + { + "SPDXID": "SPDXRef-OperatingSystem-gnrtd1", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "debian", + "primaryPackagePurpose": "OPERATING_SYSTEM", + "versionInfo": "12.0" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd7", + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libedit2@3.1-20221030-2?arch=amd64\u0026distro=debian-12.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "BSD-3-Clause", + "licenseDeclared": "BSD-3-Clause", + "name": "libedit2", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: libedit 3.1-20221030-2", + "supplier": "Organization: LLVM Packaging Team \u003cpkg-llvm-team@lists.alioth.debian.org\u003e", + "versionInfo": "3.1-20221030-2" + }, + { + "SPDXID": "SPDXRef-OperatingSystem-gnrtd143", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "debian", + "primaryPackagePurpose": "OPERATING_SYSTEM", + "versionInfo": "12.5" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd175", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "99a8c7dd591fae9fb37d8bf8dfdffa850e207fa405b3198c5b24711a5f972381" + } + ], + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libmd0@1.0.4-2?arch=arm64\u0026distro=debian-12.5", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "libmd0", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: libmd 1.0.4-2", + "supplier": "Organization: Guillem Jover \u003cguillem@debian.org\u003e", + "versionInfo": "1.0.4-2" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd259", + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libmount1@2.38.1-5%2Bdeb12u1?arch=arm64\u0026distro=debian-12.5", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "libmount1", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: util-linux 2.38.1-5+deb12u1", + "supplier": "Organization: util-linux packagers \u003cutil-linux@packages.debian.org\u003e", + "versionInfo": "2.38.1-5+deb12u1" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd7", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd1" + }, + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd175", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd143" + }, + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd259", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd143" + } + ], + "spdxVersion": "SPDX-2.3" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/testdata/happy/select-os-by-spdxid.json b/pkg/sbom/spdx/testdata/happy/select-os-by-spdxid.json new file mode 100644 index 000000000000..3b277bde439c --- /dev/null +++ b/pkg/sbom/spdx/testdata/happy/select-os-by-spdxid.json @@ -0,0 +1,86 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2024-06-10T14:07:15Z", + "creators": [ + "Organization: foo", + "Tool: bar" + ] + }, + "dataLicense": "CC0-1.0", + "documentNamespace": "https://tanzu.vmware.com/application-catalog/spdx/295c513b-b6cc-44c2-b19b-dc7196cdad9d", + "name": "SPDX document for nginx:18.0.3", + "packages": [ + { + "SPDXID": "SPDXRef-OperatingSystem-gnrtd1", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "debian", + "primaryPackagePurpose": "OPERATING_SYSTEM", + "versionInfo": "12.0" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd7", + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libedit2@3.1-20221030-2?arch=amd64\u0026distro=debian-12.0", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "BSD-3-Clause", + "licenseDeclared": "BSD-3-Clause", + "name": "libedit2", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: libedit 3.1-20221030-2", + "supplier": "Organization: LLVM Packaging Team \u003cpkg-llvm-team@lists.alioth.debian.org\u003e", + "versionInfo": "3.1-20221030-2" + }, + { + "SPDXID": "SPDXRef-OperatingSystem-gnrtd143", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "name": "debian", + "primaryPackagePurpose": "OPERATING_SYSTEM", + "versionInfo": "12.5" + }, + { + "SPDXID": "SPDXRef-Package-gnrtd259", + "downloadLocation": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:deb/debian/libmount1@2.38.1-5%2Bdeb12u1?arch=arm64\u0026distro=debian-12.5", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "name": "libmount1", + "primaryPackagePurpose": "LIBRARY", + "sourceInfo": "built package from: util-linux 2.38.1-5+deb12u1", + "supplier": "Organization: util-linux packagers \u003cutil-linux@packages.debian.org\u003e", + "versionInfo": "2.38.1-5+deb12u1" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd7", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd1" + }, + { + "relatedSpdxElement": "SPDXRef-Package-gnrtd259", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-OperatingSystem-gnrtd143" + } + ], + "spdxVersion": "SPDX-2.3" +} \ No newline at end of file diff --git a/pkg/sbom/spdx/unmarshal.go b/pkg/sbom/spdx/unmarshal.go index d2ba05b44999..250fa0144b05 100644 --- a/pkg/sbom/spdx/unmarshal.go +++ b/pkg/sbom/spdx/unmarshal.go @@ -14,6 +14,7 @@ import ( "github.com/spdx/tools-golang/tagvalue" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/sbom/core" ) @@ -164,6 +165,9 @@ func (s *SPDX) parsePackage(spdxPkg spdx.Package) (*core.Component, error) { Type: s.parseType(spdxPkg), Name: spdxPkg.PackageName, Version: spdxPkg.PackageVersion, + PkgIdentifier: types.PkgIdentifier{ + BOMRef: string(spdxPkg.PackageSPDXIdentifier), + }, } // PURL diff --git a/pkg/sbom/spdx/unmarshal_test.go b/pkg/sbom/spdx/unmarshal_test.go index 7efcaa87a29e..d755c971bd27 100644 --- a/pkg/sbom/spdx/unmarshal_test.go +++ b/pkg/sbom/spdx/unmarshal_test.go @@ -66,6 +66,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, }, + BOMRef: "Package-b7ebaf0233f1ef7b", }, Layer: ftypes.Layer{ DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", @@ -90,6 +91,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "log", Version: "1.13.1", }, + BOMRef: "Package-2906575950df652b", }, Layer: ftypes.Layer{ DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", @@ -106,6 +108,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "pear_exception", Version: "v1.0.0", }, + BOMRef: "Package-5e2e255ac76747ef", }, Layer: ftypes.Layer{ DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", @@ -128,6 +131,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "packageurl-go", Version: "v0.1.1-0.20220203205134-d70459300c8a", }, + BOMRef: "Package-84ebffe38343d949", }, Layer: ftypes.Layer{ DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", @@ -148,6 +152,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "child-project", Version: "1.0", }, + BOMRef: "Package-2a53baa495b9ddaf", }, Version: "1.0", Layer: ftypes.Layer{ @@ -169,6 +174,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "bootstrap", Version: "5.0.2", }, + BOMRef: "Package-5f1dbaff8de5eb06", }, Licenses: []string{"MIT"}, Layer: ftypes.Layer{ @@ -199,6 +205,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "yargs-parser", Version: "21.1.1", }, + BOMRef: "Package-c3508825bf3861d8", }, FilePath: "node_modules/yargs-parser/package.json", }, @@ -226,6 +233,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "yargs-parser", Version: "21.1.1", }, + BOMRef: "Package-c3508825bf3861d8", }, FilePath: "node_modules/yargs-parser/package.json", }, @@ -254,6 +262,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "log", Version: "1.13.1", }, + BOMRef: "Package-2906575950df652b", }, }, { @@ -267,6 +276,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "pear_exception", Version: "v1.0.0", }, + BOMRef: "Package-5e2e255ac76747ef", }, }, }, @@ -294,6 +304,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "apm-agent", Version: "1.36.0", }, + BOMRef: "Package-d6465ccdd5385c16", }, }, { @@ -308,6 +319,130 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Name: "apm-agent-cached-lookup-key", Version: "1.36.0", }, + BOMRef: "Package-8e3a2cf58d7bd790", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path multiple OS. OS is selected by number of packages", + inputFile: "testdata/happy/select-os-by-number-of-pkgs.json", + want: types.SBOM{ + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "debian", + Name: "12.5", + }, + }, + Packages: []ftypes.PackageInfo{ + { + Packages: ftypes.Packages{ + { + ID: "libmd0@1.0.4-2", + Name: "libmd0", + Version: "1.0.4-2", + Arch: "arm64", + SrcName: "libmd", + SrcVersion: "1.0.4", + SrcRelease: "2", + Licenses: []string{"MIT"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "libmd0", + Version: "1.0.4-2", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "arm64", + }, + { + Key: "distro", + Value: "debian-12.5", + }, + }, + }, + BOMRef: "Package-gnrtd175", + }, + }, + { + ID: "libmount1@2.38.1-5+deb12u1", + Name: "libmount1", + Version: "2.38.1-5+deb12u1", + Arch: "arm64", + SrcName: "util-linux", + SrcVersion: "2.38.1", + SrcRelease: "5+deb12u1", + Licenses: []string{"MIT"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "libmount1", + Version: "2.38.1-5+deb12u1", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "arm64", + }, + { + Key: "distro", + Value: "debian-12.5", + }, + }, + }, + BOMRef: "Package-gnrtd259", + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path multiple OS. OS is selected by SPDXID", + inputFile: "testdata/happy/select-os-by-spdxid.json", + want: types.SBOM{ + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "debian", + Name: "12.0", + }, + }, + Packages: []ftypes.PackageInfo{ + { + Packages: ftypes.Packages{ + { + ID: "libedit2@3.1-20221030-2", + Name: "libedit2", + Version: "3.1-20221030-2", + Arch: "amd64", + SrcName: "libedit", + SrcVersion: "3.1-20221030", + SrcRelease: "2", + Licenses: []string{"BSD-3-Clause"}, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "libedit2", + Version: "3.1-20221030-2", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "amd64", + }, + { + Key: "distro", + Value: "debian-12.0", + }, + }, + }, + BOMRef: "Package-gnrtd7", }, }, },