From 5dd94ebc1ffe3f1df511dee6381f92a5daefadf2 Mon Sep 17 00:00:00 2001 From: afdesk Date: Thu, 19 Sep 2024 11:17:42 +0600 Subject: [PATCH] fix(sbom): export bom-ref when converting a package to a component (#7340) Signed-off-by: knqyf263 Co-authored-by: amf Co-authored-by: knqyf263 --- integration/integration_test.go | 1 - integration/sbom_test.go | 12 + ...d-multiple-lockfiles-short.cdx.json.golden | 526 ++++++++++++++++++ pkg/sbom/cyclonedx/marshal_test.go | 4 +- pkg/sbom/cyclonedx/unmarshal.go | 17 +- pkg/sbom/io/encode.go | 26 +- 6 files changed, 571 insertions(+), 15 deletions(-) create mode 100644 integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden diff --git a/integration/integration_test.go b/integration/integration_test.go index c263ec2fc51e..96c8f54a67e9 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -186,7 +186,6 @@ func readCycloneDX(t *testing.T, filePath string) *cdx.BOM { return (*bom.Components)[i].Name < (*bom.Components)[j].Name }) for i := range *bom.Components { - (*bom.Components)[i].BOMRef = "" sort.Slice(*(*bom.Components)[i].Properties, func(ii, jj int) bool { return (*(*bom.Components)[i].Properties)[ii].Name < (*(*bom.Components)[i].Properties)[jj].Name }) diff --git a/integration/sbom_test.go b/integration/sbom_test.go index e3ee5b89cc3d..e887f1520e68 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -25,6 +25,7 @@ func TestSBOM(t *testing.T) { name string args args golden string + fakeUUID string override OverrideFunc }{ { @@ -57,6 +58,16 @@ func TestSBOM(t *testing.T) { }, golden: "testdata/fluentd-multiple-lockfiles.json.golden", }, + { + name: "scan SBOM into SBOM", + args: args{ + input: "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json", + format: "cyclonedx", + artifactType: "cyclonedx", + }, + fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", + golden: "testdata/fluentd-multiple-lockfiles-short.cdx.json.golden", + }, { name: "minikube KBOM", args: args{ @@ -165,6 +176,7 @@ func TestSBOM(t *testing.T) { // Run "trivy sbom" runTest(t, osArgs, tt.golden, outputFile, types.Format(tt.args.format), runOptions{ override: overrideFuncs(overrideSBOMReport, overrideUID, tt.override), + fakeUUID: tt.fakeUUID, }) }) } diff --git a/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden new file mode 100644 index 000000000000..496ca8ae3110 --- /dev/null +++ b/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden @@ -0,0 +1,526 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000010", + "version": 1, + "metadata": { + "timestamp": "2021-08-25T12:20:30+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "95de56ee-980c-413d-8f68-6c674dc3e9d1", + "type": "container", + "name": "integration/testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", + "properties": [ + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:5a992077baba51b97f27591a10d54d2f2723dc9c81a3fe419e261023f2554933" + }, + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "353f2470-9c8b-4647-9d0d-96d893838dc8", + "type": "operating-system", + "name": "debian", + "version": "10.2", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "debian" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "type": "library", + "name": "bash", + "version": "5.0-4", + "purl": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "bash@5.0-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "bash" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.0-4" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "type": "library", + "name": "libidn2-0", + "version": "2.0.5-1", + "purl": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "libidn2-0@2.0.5-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libidn2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.0.5-1" + } + ] + }, + { + "bom-ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "type": "library", + "name": "activesupport", + "version": "6.0.2.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/activesupport@6.0.2.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "activesupport@6.0.2.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + } + ], + "dependencies": [ + { + "ref": "353f2470-9c8b-4647-9d0d-96d893838dc8", + "dependsOn": [ + "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2" + ] + }, + { + "ref": "95de56ee-980c-413d-8f68-6c674dc3e9d1", + "dependsOn": [ + "353f2470-9c8b-4647-9d0d-96d893838dc8", + "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec" + ] + }, + { + "ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "dependsOn": [] + } + ], + "vulnerabilities": [ + { + "id": "CVE-2019-18224", + "source": { + "name": "debian", + "url": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "ratings": [ + { + "source": { + "name": "amazon" + }, + "severity": "medium" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.5, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + { + "source": { + "name": "nvd" + }, + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "redhat" + }, + "score": 5.6, + "severity": "medium", + "method": "CVSSv3", + "vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L" + }, + { + "source": { + "name": "ubuntu" + }, + "severity": "medium" + } + ], + "cwes": [ + 787 + ], + "description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.", + "recommendation": "Upgrade libidn2-0 to version 2.0.5-1+deb10u1", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2019-18224" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2019-18224" + }, + { + "url": "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420" + }, + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224" + }, + { + "url": "https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c" + }, + { + "url": "https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/" + }, + { + "url": "https://seclists.org/bugtraq/2020/Feb/4" + }, + { + "url": "https://security.gentoo.org/glsa/202003-63" + }, + { + "url": "https://ubuntu.com/security/notices/USN-4168-1" + }, + { + "url": "https://usn.ubuntu.com/4168-1/" + }, + { + "url": "https://www.debian.org/security/2020/dsa-4613" + } + ], + "published": "2019-10-21T17:15:00+00:00", + "updated": "2019-10-29T19:15:00+00:00", + "affects": [ + { + "ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "versions": [ + { + "version": "2.0.5-1", + "status": "affected" + } + ] + } + ] + }, + { + "id": "CVE-2019-18276", + "source": { + "name": "debian", + "url": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "ratings": [ + { + "source": { + "name": "cbl-mariner" + }, + "severity": "high" + }, + { + "source": { + "name": "debian" + }, + "severity": "low" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.2, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.8, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "oracle-oval" + }, + "severity": "low" + }, + { + "source": { + "name": "photon" + }, + "severity": "high" + }, + { + "source": { + "name": "redhat" + }, + "score": 7.8, + "severity": "low", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "ubuntu" + }, + "severity": "low" + } + ], + "cwes": [ + 273 + ], + "description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2019-18276" + }, + { + "url": "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2019-18276" + }, + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276" + }, + { + "url": "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff" + }, + { + "url": "https://linux.oracle.com/cve/CVE-2019-18276.html" + }, + { + "url": "https://linux.oracle.com/errata/ELSA-2021-1679.html" + }, + { + "url": "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-18276" + }, + { + "url": "https://security.gentoo.org/glsa/202105-34" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20200430-0003/" + }, + { + "url": "https://www.youtube.com/watch?v=-wGtxJ8opa8" + } + ], + "published": "2019-11-28T01:15:00+00:00", + "updated": "2021-05-26T12:15:00+00:00", + "affects": [ + { + "ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "versions": [ + { + "version": "5.0-4", + "status": "affected" + } + ] + } + ] + }, + { + "id": "CVE-2020-8165", + "source": { + "name": "ghsa", + "url": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Arubygems" + }, + "ratings": [ + { + "source": { + "name": "ghsa" + }, + "severity": "high" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.5, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + { + "source": { + "name": "nvd" + }, + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "redhat" + }, + "score": 9.8, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } + ], + "cwes": [ + 502 + ], + "description": "A deserialization of untrusted data vulnerability exists in rails < 5.2.4.3, rails < 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.", + "recommendation": "Upgrade activesupport to version 6.0.3.1, 5.2.4.3", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2020-8165" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00031.html" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00034.html" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2020-8165" + }, + { + "url": "https://github.com/advisories/GHSA-2p68-f74v-9wc6" + }, + { + "url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2020-8165.yml" + }, + { + "url": "https://groups.google.com/forum/#!msg/rubyonrails-security/bv6fW4S0Y1c/KnkEqM7AAQAJ" + }, + { + "url": "https://groups.google.com/forum/#!topic/rubyonrails-security/bv6fW4S0Y1c" + }, + { + "url": "https://groups.google.com/g/rubyonrails-security/c/bv6fW4S0Y1c" + }, + { + "url": "https://hackerone.com/reports/413388" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2020/06/msg00022.html" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2020/07/msg00013.html" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-8165" + }, + { + "url": "https://weblog.rubyonrails.org/2020/5/18/Rails-5-2-4-3-and-6-0-3-1-have-been-released/" + }, + { + "url": "https://www.debian.org/security/2020/dsa-4766" + } + ], + "published": "2020-06-19T18:15:00+00:00", + "updated": "2020-10-17T12:15:00+00:00", + "affects": [ + { + "ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "versions": [ + { + "version": "6.0.2.1", + "status": "affected" + } + ] + } + ] + } + ] +} diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index e778b803619c..8b88c4ec762a 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -1462,7 +1462,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "com.fasterxml.jackson.core:jackson-databind", Version: "2.13.4.1", Identifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", UID: "9A5066570222D04C", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, @@ -1480,7 +1480,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { PkgName: "com.fasterxml.jackson.core:jackson-databind", PkgPath: "jackson-databind-2.13.4.1.jar", PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", UID: "9A5066570222D04C", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index de410d008794..2a2456063670 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -128,13 +128,17 @@ func (b *BOM) parseComponent(c cdx.Component) (*core.Component, error) { return nil, xerrors.Errorf("failed to unmarshal component type: %w", err) } + identifier := ftypes.PkgIdentifier{ + BOMRef: c.BOMRef, + } + // Parse PURL - var purl packageurl.PackageURL if c.PackageURL != "" { - purl, err = packageurl.FromString(c.PackageURL) + purl, err := packageurl.FromString(c.PackageURL) if err != nil { return nil, xerrors.Errorf("failed to parse PURL: %w", err) } + identifier.PURL = &purl } component := &core.Component{ @@ -148,12 +152,9 @@ func (b *BOM) parseComponent(c cdx.Component) (*core.Component, error) { Digests: b.unmarshalHashes(c.Hashes), }, }, - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &purl, - BOMRef: c.BOMRef, - }, - Supplier: b.unmarshalSupplier(c.Supplier), - Properties: b.unmarshalProperties(c.Properties), + PkgIdentifier: identifier, + Supplier: b.unmarshalSupplier(c.Supplier), + Properties: b.unmarshalProperties(c.Properties), } return component, nil diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 0be0bf361280..975b98d40b10 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -5,6 +5,7 @@ import ( "slices" "strconv" + "github.com/google/uuid" "github.com/package-url/packageurl-go" "github.com/samber/lo" "golang.org/x/xerrors" @@ -19,8 +20,9 @@ import ( ) type Encoder struct { - bom *core.BOM - opts core.Options + bom *core.BOM + opts core.Options + components map[uuid.UUID]*core.Component } func NewEncoder(opts core.Options) *Encoder { @@ -28,6 +30,9 @@ func NewEncoder(opts core.Options) *Encoder { } func (e *Encoder) Encode(report types.Report) (*core.BOM, error) { + if report.BOM != nil { + e.components = report.BOM.Components() + } // Metadata component root, err := e.rootComponent(report) if err != nil { @@ -257,6 +262,16 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { } } +// existedPkgIdentifier tries to look for package identifier (BOM-ref, PURL) by component name and component type +func (e *Encoder) existedPkgIdentifier(name string, componentType core.ComponentType) ftypes.PkgIdentifier { + for _, c := range e.components { + if c.Name == name && c.Type == componentType { + return c.PkgIdentifier + } + } + return ftypes.PkgIdentifier{} +} + func (e *Encoder) resultComponent(root *core.Component, r types.Result, osFound *ftypes.OS) *core.Component { component := &core.Component{ Name: r.Target, @@ -279,8 +294,10 @@ func (e *Encoder) resultComponent(root *core.Component, r types.Result, osFound component.Version = osFound.Name } component.Type = core.TypeOS + component.PkgIdentifier = e.existedPkgIdentifier(component.Name, component.Type) case types.ClassLangPkg: component.Type = core.TypeApplication + component.PkgIdentifier = e.existedPkgIdentifier(component.Name, component.Type) } e.bom.AddRelationship(root, component, core.RelationshipContains) @@ -377,8 +394,9 @@ func (*Encoder) component(result types.Result, pkg ftypes.Package) *core.Compone SrcVersion: utils.FormatSrcVersion(pkg), SrcFile: srcFile, PkgIdentifier: ftypes.PkgIdentifier{ - UID: pkg.Identifier.UID, - PURL: pkg.Identifier.PURL, + UID: pkg.Identifier.UID, + PURL: pkg.Identifier.PURL, + BOMRef: pkg.Identifier.BOMRef, }, Supplier: pkg.Maintainer, Licenses: pkg.Licenses,