Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adding locations in CycloneDX reports #3989

Merged
merged 25 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f44970f
feat: Adding locations in CycloneDX reports
Mayankrai449 Mar 29, 2024
bdaf4ac
feat: cleaner organization for code
Mayankrai449 Mar 31, 2024
3abd312
feat: improvement
Mayankrai449 Mar 31, 2024
4dd77ff
feat: improvement
Mayankrai449 Mar 31, 2024
ff4aba0
Merge branch 'cyclonedx_location_feat' of https://github.com/Mayankra…
Mayankrai449 Mar 31, 2024
567573d
test: update
Mayankrai449 Mar 31, 2024
eacbbc6
test: update
Mayankrai449 Apr 1, 2024
743bd1c
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 2, 2024
282c749
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 3, 2024
5bb03d0
test: new test and validation update
Mayankrai449 Apr 3, 2024
a05f2b9
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 3, 2024
a1c8d5d
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 4, 2024
d48dec5
test: test updates for new merges
Mayankrai449 Apr 4, 2024
001ec72
test: update
Mayankrai449 Apr 4, 2024
7154b4e
test: test update for new merges
Mayankrai449 Apr 4, 2024
9c730d5
feat: validation update
Mayankrai449 Apr 4, 2024
81c9dd5
test: update
Mayankrai449 Apr 16, 2024
ad87a7b
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 16, 2024
6e07a84
docs: new func docstring
Mayankrai449 Apr 16, 2024
ed7dd5b
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 16, 2024
148c5ef
test: update
Mayankrai449 Apr 16, 2024
07feefb
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 16, 2024
70db334
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 16, 2024
793340d
test: update
Mayankrai449 Apr 16, 2024
4b26ba4
Merge branch 'main' into cyclonedx_location_feat
Mayankrai449 Apr 17, 2024
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
11 changes: 8 additions & 3 deletions cve_bin_tool/input_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def decode_bom_ref(self, ref) -> ProductInfo:
urn_cdx = re.compile(
r"urn:cdx:(?P<bomSerialNumber>.*?)\/(?P<bom_version>.*?)#(?P<bom_ref>.*)"
)

location = "location/to/product"
if urn_cbt_ext_ref.match(ref):
urn_dict = urn_cbt_ext_ref.match(ref).groupdict()
vendor = urn_dict["vendor"]
Expand Down Expand Up @@ -290,7 +290,9 @@ def decode_bom_ref(self, ref) -> ProductInfo:

product_info = None
if product is not None and self.validate_product(product):
product_info = ProductInfo(vendor.strip(), product.strip(), version.strip())
product_info = ProductInfo(
vendor.strip(), product.strip(), version.strip(), location
)

return product_info

Expand All @@ -314,7 +316,10 @@ def parse_data(self, fields: Set[str], data: Iterable) -> None:

for row in data:
product_info = ProductInfo(
row["vendor"].strip(), row["product"].strip(), row["version"].strip()
row["vendor"].strip(),
row["product"].strip(),
row["version"].strip(),
row.get("location", "location/to/product").strip(),
)
self.parsed_data[product_info][
row.get("cve_number", "").strip() or "default"
Expand Down
5 changes: 4 additions & 1 deletion cve_bin_tool/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,10 @@ def parse_data_from_json(

for row in json_data:
product_info = ProductInfo(
row["vendor"].strip(), row["product"].strip(), row["version"].strip()
row["vendor"].strip(),
row["product"].strip(),
row["version"].strip(),
row.get("location", "location/to/product").strip(),
)
parsed_data[product_info][row.get("cve_number", "").strip() or "default"] = {
"remarks": Remarks(str(row.get("remarks", "")).strip()),
Expand Down
6 changes: 6 additions & 0 deletions cve_bin_tool/output_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def output_csv(
"vendor",
"product",
"version",
"location",
"cve_number",
"severity",
"score",
Expand Down Expand Up @@ -917,6 +918,7 @@ def generate_sbom(
sbom_relationships = []
my_package = SBOMPackage()
sbom_relationship = SBOMRelationship()

# Create root package
my_package.initialise()
root_package = f'CVEBINTOOL-{Path(sbom_root).name.replace(".", "-")}'
Expand All @@ -929,13 +931,15 @@ def generate_sbom(
my_package.set_licensedeclared(license)
my_package.set_licenseconcluded(license)
my_package.set_supplier("UNKNOWN", "NOASSERTION")

# Store package data
sbom_packages[(my_package.get_name(), my_package.get_value("version"))] = (
my_package.get_package()
)
sbom_relationship.initialise()
sbom_relationship.set_relationship(parent, "DESCRIBES", root_package)
sbom_relationships.append(sbom_relationship.get_relationship())

# Add dependent products
for product_data in all_product_data:
my_package.initialise()
Expand All @@ -945,6 +949,8 @@ def generate_sbom(
my_package.set_supplier("Organization", product_data.vendor)
my_package.set_licensedeclared(license)
my_package.set_licenseconcluded(license)
location = product_data.location
my_package.set_evidence(location) # Set location directly
sbom_packages[(my_package.get_name(), my_package.get_value("version"))] = (
my_package.get_package()
)
Expand Down
2 changes: 2 additions & 0 deletions cve_bin_tool/output_engine/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def format_output(
"vendor": "haxx"
"product": "curl",
"version": "1.2.1",
"location": "location/to/product",
"cve_number": "CVE-1234-1234",
"severity": "LOW",
"score": "1.2",
Expand Down Expand Up @@ -191,6 +192,7 @@ def format_output(
"vendor": product_info.vendor,
"product": product_info.product,
"version": product_info.version,
"location": product_info.location,
"cve_number": cve.cve_number,
"severity": cve.severity,
"score": str(cve.score),
Expand Down
6 changes: 4 additions & 2 deletions cve_bin_tool/parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,20 @@ def find_vendor(self, product, version):
vendor_package_pair = self.cve_db.get_vendor_product_pairs(product)
vendorlist: list[ScanInfo] = []
file_path = self.filename
location = file_path
if vendor_package_pair != []:
# To handle multiple vendors, return all combinations of product/vendor mappings
for v in vendor_package_pair:
vendor = v["vendor"]
location = v.get("location", "location/to/product")
self.logger.debug(f"{file_path} {product} {version} by {vendor}")
vendorlist.append(
ScanInfo(ProductInfo(vendor, product, version), file_path)
ScanInfo(ProductInfo(vendor, product, version, location), file_path)
)
else:
# Add entry
vendorlist.append(
ScanInfo(ProductInfo("UNKNOWN", product, version), file_path)
ScanInfo(ProductInfo("UNKNOWN", product, version, location), file_path)
)
return vendorlist

Expand Down
5 changes: 4 additions & 1 deletion cve_bin_tool/parsers/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ def find_vendor(self, product, version):
for pair in vendor_package_pair:
vendor = pair["vendor"]
file_path = self.filename
location = pair.get("location", "location/to/product")
self.logger.debug(f"{file_path} {product} {version} by {vendor}")
info.append(ScanInfo(ProductInfo(vendor, product, version), file_path))
info.append(
ScanInfo(ProductInfo(vendor, product, version, location), file_path)
)
return info
return None

Expand Down
5 changes: 4 additions & 1 deletion cve_bin_tool/parsers/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,12 @@ def run_checker(self, filename):
if vendor_package_pair != []:
for pair in vendor_package_pair:
vendor = pair["vendor"]
location = pair.get("location", "location/to/product")
file_path = self.filename
self.logger.debug(f"{file_path} is {vendor}.{product} {version}")
yield ScanInfo(ProductInfo(vendor, product, version), file_path)
yield ScanInfo(
ProductInfo(vendor, product, version, location), file_path
)

# There are packages with a METADATA file in them containing different data from what the tool expects
except AttributeError:
Expand Down
12 changes: 9 additions & 3 deletions cve_bin_tool/sbom_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import annotations

import re
import sys
from collections import defaultdict
from logging import Logger
from pathlib import Path
Expand All @@ -15,7 +16,7 @@
from cve_bin_tool.cvedb import CVEDB
from cve_bin_tool.input_engine import TriageData
from cve_bin_tool.log import LOGGER
from cve_bin_tool.util import ProductInfo, Remarks
from cve_bin_tool.util import ProductInfo, Remarks, find_product_location
from cve_bin_tool.validator import validate_cyclonedx, validate_spdx

from .swid_parser import SWIDParser
Expand Down Expand Up @@ -73,8 +74,14 @@ def scan_file(self) -> dict[ProductInfo, TriageData]:
# Now add vendor to create product record....
vendor_set = self.get_vendor(product)
for vendor in vendor_set:
location = find_product_location(product)
if location is None:
location = "NotFound"
LOGGER.info(f"version_scan loca = {location}")
Mayankrai449 marked this conversation as resolved.
Show resolved Hide resolved
# if vendor is not None:
parsed_data.append(ProductInfo(vendor, product, version))
parsed_data.append(
ProductInfo(vendor, product, version, location)
)

for row in parsed_data:
self.sbom_data[row]["default"] = {
Expand Down Expand Up @@ -147,7 +154,6 @@ def parse_sbom(self):


if __name__ == "__main__":
import sys

file = sys.argv[1]
sbom = SBOMManager(file)
Expand Down
12 changes: 9 additions & 3 deletions cve_bin_tool/sbom_manager/cyclonedx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import defusedxml.ElementTree as ET

from cve_bin_tool.sbom_manager import SBOMManager
from cve_bin_tool.validator import validate_cyclonedx


Expand Down Expand Up @@ -38,7 +39,10 @@ def parse_cyclonedx_json(self, sbom_file: str) -> list[list[str]]:
if d["type"] in self.components_supported:
package = d["name"]
version = d["version"]
modules.append([package, version])
location = SBOMManager().find_product_location(package)
Mayankrai449 marked this conversation as resolved.
Show resolved Hide resolved
if location is None:
location = "NotFound"
modules.append([package, version, location])

return modules

Expand Down Expand Up @@ -68,8 +72,10 @@ def parse_cyclonedx_xml(self, sbom_file: str) -> list[list[str]]:
if component_version is None:
raise KeyError(f"Could not find version in {component}")
version = component_version.text
if version is not None:
modules.append([package, version])
location = SBOMManager().find_product_location(package)
Mayankrai449 marked this conversation as resolved.
Show resolved Hide resolved
if location is None:
location = "NotFound"
modules.append([package, version, location])
return modules


Expand Down
6 changes: 5 additions & 1 deletion cve_bin_tool/sbom_manager/spdx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import yaml

from cve_bin_tool.log import LOGGER
from cve_bin_tool.sbom_manager import SBOMManager
from cve_bin_tool.validator import validate_spdx


Expand Down Expand Up @@ -61,7 +62,10 @@ def parse_spdx_json(self, sbom_file: str) -> list[list[str]]:
package = d["name"]
try:
version = d["versionInfo"]
modules.append([package, version])
location = SBOMManager().find_product_location(package)
Mayankrai449 marked this conversation as resolved.
Show resolved Hide resolved
if location is None:
location = "NotFound"
modules.append([package, version, location])
except KeyError as e:
LOGGER.debug(e, exc_info=True)

Expand Down
30 changes: 30 additions & 0 deletions cve_bin_tool/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,13 @@ class ProductInfo(NamedTuple):
vendor: str
product: str
version: str
location: str
"""

vendor: str
product: str
version: str
location: str


class ScanInfo(NamedTuple):
Expand Down Expand Up @@ -274,6 +276,34 @@ def make_http_requests(attribute, **kwargs):
LOGGER.error(ve)


def find_product_location(product_name):
for path in sys.path:
product_location = Path(path) / product_name
if product_location.exists():
return str(product_location)

known_installation_directories = [
"/usr/local/bin",
"/usr/local/sbin",
"/usr/bin",
"/opt",
"/usr/sbin",
"/usr/local/lib",
"/usr/lib",
"/usr/local/share",
"/usr/share",
"/usr/local/include",
"/usr/include",
]

for directory in known_installation_directories:
product_location = Path(directory) / product_name
if product_location.exists():
return str(product_location)

return None


class DirWalk:
"""
for filename in DirWalk('*.c').walk(roots):
Expand Down
16 changes: 13 additions & 3 deletions cve_bin_tool/version_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
from cve_bin_tool.log import LOGGER
from cve_bin_tool.parsers.parse import parse, valid_files
from cve_bin_tool.strings import parse_strings
from cve_bin_tool.util import DirWalk, ProductInfo, ScanInfo, inpath
from cve_bin_tool.util import (
DirWalk,
ProductInfo,
ScanInfo,
find_product_location,
inpath,
)

if sys.version_info >= (3, 10):
from importlib import metadata as importlib_metadata
Expand Down Expand Up @@ -246,7 +252,7 @@ def run_checkers(self, filename: str, lines: str) -> Iterator[ScanInfo]:
"""process a Set of checker objects, run them on file lines,
and yield information about detected products and versions.
It uses logging to provide debug and error information along the way."""

LOGGER.info(f"filename = {filename}")
# tko
for dummy_checker_name, checker in self.checkers.items():
checker = checker()
Expand Down Expand Up @@ -277,8 +283,12 @@ def run_checkers(self, filename: str, lines: str) -> Iterator[ScanInfo]:
f'{file_path} {result["is_or_contains"]} {dummy_checker_name} {version}'
)
for vendor, product in checker.VENDOR_PRODUCT:
location = find_product_location(product)
if location is None:
location = file_path
yield ScanInfo(
ProductInfo(vendor, product, version), file_path
ProductInfo(vendor, product, version, location),
file_path,
)

self.logger.debug(f"Done scanning file: {filename}")
Expand Down
21 changes: 18 additions & 3 deletions test/test_available_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,12 @@ def test_redhat_available_fix_output(
assert expected_output == [rec.message for rec in caplog.records]

MOCK_PSPP_CVE_DATA = {
ProductInfo(vendor="gnu", product="pspp", version="1.2.0"): CVEData(
ProductInfo(
vendor="gnu",
product="pspp",
version="1.2.0",
location="location/to/product",
): CVEData(
None,
{
"cves": [
Expand All @@ -176,7 +181,12 @@ def test_redhat_available_fix_output(
}

MOCK_AVAHI_CVE_DATA = {
ProductInfo(vendor="avahi", product="avahi", version="0.6.25"): CVEData(
ProductInfo(
vendor="avahi",
product="avahi",
version="0.6.25",
location="location/to/product",
): CVEData(
None,
{
"cves": [
Expand Down Expand Up @@ -221,7 +231,12 @@ def test_redhat_available_fix_output(
}

MOCK_NODEJS_CVE_DATA = {
ProductInfo(vendor="nodejs", product="node.js", version="14.16.0"): CVEData(
ProductInfo(
vendor="nodejs",
product="node.js",
version="14.16.0",
location="location/to/product",
): CVEData(
None,
{
"cves": [
Expand Down
Loading
Loading