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

[TEMP] Fixes to PR#126 #129

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
27 changes: 26 additions & 1 deletion src/sbomnix/cdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

""" CycloneDX utils """


import re

from reuse._licenses import LICENSE_MAP as SPDX_LICENSES
from common.utils import LOG, LOG_SPAM
from vulnxscan.utils import _vuln_source, _vuln_url


def _drv_to_cdx_licenses_entry(drv, column_name, cdx_license_type):
Expand Down Expand Up @@ -128,3 +129,27 @@ def _drv_to_cdx_dependency(drv, deps_list, uid="store_path"):
if deps_list:
dependency["dependsOn"] = deps_list
return dependency


def _vuln_to_cdx_vuln(vuln):
"""Return cdx vulnerability entry from vulnix row"""
vulnerability = {}
vulnerability["bom-ref"] = vuln.store_path
vulnerability["id"] = vuln.vuln_id
source = {}
source["url"] = _vuln_url(vuln)
source["name"] = _vuln_source(vuln)
vulnerability["source"] = source
vulnerability["ratings"] = []
# If the vulnerability is still being assessed, it will be missing a valid number
if vuln.severity != "":
rating = {}
rating["source"] = source
rating["score"] = vuln.severity
vulnerability["ratings"].append(rating)
vulnerability["tools"] = []
for scanner in vuln.scanner:
tool = {}
tool["name"] = scanner
vulnerability["tools"].append(tool)
return vulnerability
1 change: 1 addition & 0 deletions src/sbomnix/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def main():
buildtime=args.buildtime,
depth=args.depth,
flakeref=flakeref,
include_vulns=True,
)
if args.cdx:
sbomdb.to_cdx(args.cdx)
Expand Down
56 changes: 52 additions & 4 deletions src/sbomnix/sbomdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
# SPDX-License-Identifier: Apache-2.0

# pylint: disable=too-many-instance-attributes, too-many-arguments
# pylint: disable=too-many-locals, too-many-statements

""" Module for generating SBOMs in various formats """

from tempfile import NamedTemporaryFile
import uuid
import logging
import json
Expand All @@ -18,10 +20,11 @@
import numpy as np
from reuse._licenses import LICENSE_MAP as SPDX_LICENSES
from nixgraph.graph import NixDependencies
from sbomnix.cdx import _drv_to_cdx_component, _drv_to_cdx_dependency
from sbomnix.cdx import _drv_to_cdx_component, _drv_to_cdx_dependency, _vuln_to_cdx_vuln
from sbomnix.nix import Store, find_deriver
from sbomnix.meta import Meta
from common.utils import LOG, df_to_csv_file, get_py_pkg_version
from vulnxscan.vulnscan import VulnScan

###############################################################################

Expand All @@ -30,7 +33,13 @@ class SbomDb:
"""Generates SBOMs in various formats"""

def __init__(
self, nix_path, buildtime=False, depth=None, flakeref=None, include_meta=True
self,
nix_path,
buildtime=False,
depth=None,
flakeref=None,
include_meta=True,
include_vulns=False,
):
# self.uid specifies the attribute that SbomDb uses as unique
# identifier for the sbom components. See the column names in
Expand All @@ -46,6 +55,7 @@ def __init__(
self.flakeref = flakeref
self.meta = None
self._init_sbomdb(include_meta)
self.include_vulns = include_vulns
self.uuid = uuid.uuid4()
self.sbom_type = "runtime_and_buildtime"
if not self.buildtime:
Expand Down Expand Up @@ -168,7 +178,7 @@ def to_cdx(self, cdx_path, printinfo=True):
"""Export sbomdb to cyclonedx json file"""
cdx = {}
cdx["bomFormat"] = "CycloneDX"
cdx["specVersion"] = "1.3"
cdx["specVersion"] = "1.4"
cdx["version"] = 1
cdx["serialNumber"] = f"urn:uuid:{self.uuid}"
cdx["metadata"] = {}
Expand All @@ -183,7 +193,7 @@ def to_cdx(self, cdx_path, printinfo=True):
if self.depth:
prop = {}
prop["name"] = "sbom_dependencies_depth"
prop["value"] = self.depth
prop["value"] = str(self.depth)
cdx["metadata"]["properties"].append(prop)
tool = {}
tool["vendor"] = "TII"
Expand All @@ -202,6 +212,44 @@ def to_cdx(self, cdx_path, printinfo=True):
deps = self._lookup_dependencies(drv, uid=self.uid)
dependency = _drv_to_cdx_dependency(drv, deps, uid=self.uid)
cdx["dependencies"].append(dependency)
df_vulns = None
if self.include_vulns:
scanner = VulnScan()
scanner.scan_vulnix(self.target_deriver, self.buildtime)
# Write incomplete sbom to a temporary path, then perform a vulnerability scan
with NamedTemporaryFile(
delete=False, prefix="vulnxscan_", suffix=".json"
) as fcdx:
self._write_json(fcdx.name, cdx, printinfo=False)
scanner.scan_grype(fcdx.name)
scanner.scan_osv(fcdx.name)
cdx["vulnerabilities"] = []
# Union all scans into a single dataframe
df_vulns = pd.concat(
[scanner.df_grype, scanner.df_osv, scanner.df_vulnix],
ignore_index=True,
)
if df_vulns is not None and not df_vulns.empty:
# Concat adds a modified column, remove as it will invalidate groupby logic
if "modified" in df_vulns.columns:
df_vulns.drop("modified", axis=1, inplace=True)
# Deduplicate repeated vulnerabilities, making the scanner column into an
# array
vuln_grouped = df_vulns.groupby(
["package", "version", "severity", "vuln_id"],
as_index=False,
).agg({"scanner": pd.Series.unique})
# Do a join so we have access to bom-ref
vulnix_components = pd.merge(
left=vuln_grouped,
right=self.df_sbomdb,
how="left",
left_on=["package", "version"],
right_on=["pname", "version"],
)
for vuln in vulnix_components.itertuples():
vulnix_vuln = _vuln_to_cdx_vuln(vuln)
cdx["vulnerabilities"].append(vulnix_vuln)
self._write_json(cdx_path, cdx, printinfo)

def to_spdx(self, spdx_path, printinfo=True):
Expand Down
30 changes: 9 additions & 21 deletions src/vulnxscan/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@


import json
import logging
import pathlib
import re
import time
import urllib.parse

from tempfile import NamedTemporaryFile
import pandas as pd

from common.utils import (
Expand All @@ -34,7 +31,6 @@
from repology.repology_cli import Repology
from repology.repology_cli import getargs as cli_getargs
from repology.repology_cve import query_cve
from sbomnix.sbomdb import SbomDb


################################################################################
Expand Down Expand Up @@ -67,11 +63,19 @@ def _vuln_url(row):
nvd_url = "https://nvd.nist.gov/vuln/detail/"
if "cve" in row.vuln_id.lower():
return f"{nvd_url}{row.vuln_id}"
if row.osv:
if getattr(row, "osv", False) or ("osv" in getattr(row, "scanner", [])):
return f"{osv_url}{row.vuln_id}"
return ""


def _vuln_source(row):
if "cve" in row.vuln_id.lower():
return "NVD"
if getattr(row, "osv", False) or ("osv" in getattr(row, "scanner", [])):
return "OSV"
return ""


def _is_patched(row):
if row.vuln_id and str(row.vuln_id).lower() in str(row.patches).lower():
patches = row.patches.split()
Expand Down Expand Up @@ -211,22 +215,6 @@ def _github_query(query_str, delay=60):
return resp_json


def _generate_sbom(target_path, buildtime=False):
LOG.info("Generating SBOM for target '%s'", target_path)
sbomdb = SbomDb(target_path, buildtime, include_meta=False)
prefix = "vulnxscan_"
cdx_suffix = ".json"
csv_suffix = ".csv"
with NamedTemporaryFile(
delete=False, prefix=prefix, suffix=cdx_suffix
) as fcdx, NamedTemporaryFile(
delete=False, prefix=prefix, suffix=csv_suffix
) as fcsv:
sbomdb.to_cdx(fcdx.name, printinfo=False)
sbomdb.to_csv(fcsv.name, loglevel=logging.DEBUG)
return pathlib.Path(fcdx.name), pathlib.Path(fcsv.name)


def _is_json(path):
try:
with open(path, encoding="utf-8") as f:
Expand Down
24 changes: 23 additions & 1 deletion src/vulnxscan/vulnxscan_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
import logging
import sys
import pathlib
from tempfile import NamedTemporaryFile

from vulnxscan.utils import _generate_sbom, _is_json
from sbomnix.sbomdb import SbomDb
from vulnxscan.utils import _is_json
from vulnxscan.vulnscan import VulnScan
from common.utils import (
LOG,
Expand Down Expand Up @@ -95,6 +97,26 @@ def getargs():

################################################################################


def _generate_sbom(target_path, buildtime=False):
LOG.info("Generating SBOM for target '%s'", target_path)
sbomdb = SbomDb(target_path, buildtime, include_meta=False)
prefix = "vulnxscan_"
cdx_suffix = ".json"
csv_suffix = ".csv"
with NamedTemporaryFile(
delete=False, prefix=prefix, suffix=cdx_suffix
) as fcdx, NamedTemporaryFile(
delete=False, prefix=prefix, suffix=csv_suffix
) as fcsv:
sbomdb.to_cdx(fcdx.name, printinfo=False)
sbomdb.to_csv(fcsv.name, loglevel=logging.DEBUG)
return pathlib.Path(fcdx.name), pathlib.Path(fcsv.name)


################################################################################


# Main


Expand Down
5 changes: 5 additions & 0 deletions tests/resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ SPDX-License-Identifier: CC-BY-SA-4.0

# Test resources

## CycloneDX 1.4 json schema

- cdx_bom-1.4.schema.json
- <https://github.com/CycloneDX/specification/blob/6638df1da64d4e4d2122c099db44e344cde0055d/schema/bom-1.4.schema.json>

## CycloneDX 1.3 json schema

- cdx_bom-1.3.schema.json
Expand Down
Loading
Loading