Skip to content

Commit

Permalink
feat: command line arguments for vex (#4226)
Browse files Browse the repository at this point in the history
  • Loading branch information
mastersans authored Jul 16, 2024
1 parent c3dfa4b commit 361f5fd
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 403 deletions.
62 changes: 53 additions & 9 deletions cve_bin_tool/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ def main(argv=None):
help="provide sbom filename",
default="",
)
input_group.add_argument(
"--vex-file",
action="store",
help="provide vulnerability exchange (vex) filename for triage processing",
default="",
)

output_group = parser.add_argument_group("Output")
output_group.add_argument(
Expand Down Expand Up @@ -323,12 +329,6 @@ def main(argv=None):
help="Lists versions of product affected by a given CVE (to facilitate upgrades)",
)

output_group.add_argument(
"--vex",
action="store",
help="Provide vulnerability exchange (vex) filename",
default="",
)
output_group.add_argument(
"--sbom-output",
action="store",
Expand All @@ -349,7 +349,37 @@ def main(argv=None):
choices=["tag", "json", "yaml"],
help="specify format of software bill of materials (sbom) to generate (default: tag)",
)

output_group.add_argument(
"--vex-output",
action="store",
help="Provide vulnerability exchange (vex) filename to generate",
default="",
)
output_group.add_argument(
"--vex-type",
action="store",
default="cyclonedx",
choices=["cyclonedx", "csaf", "openvex"],
help="specify type of vulnerability exchange (vex) to generate (default: cyclonedx)",
)
output_group.add_argument(
"--product",
action="store",
default="",
help="Product Name",
)
output_group.add_argument(
"--release",
action="store",
default="",
help="Release Version",
)
output_group.add_argument(
"--vendor",
action="store",
default="",
help="Vendor/Supplier of Product",
)
parser.add_argument(
"-e",
"--exclude",
Expand Down Expand Up @@ -1075,7 +1105,19 @@ def main(argv=None):
)
)
LOGGER.info(f"Known CVEs in {affected_string}:")

vex_product_info: dict[str, str] = {}
if args["vex_output"]:
if args["product"] and args["release"] and args["vendor"]:
vex_product_info = {
"product": args["product"],
"release": args["release"],
"vendor": args["vendor"],
}
else:
LOGGER.error(
"Please provide --product, --release and --vendor for VEX generation"
)
return ERROR_CODES[InsufficientArgs]
# Creates an Object for OutputEngine
output = OutputEngine(
all_cve_data=cve_scanner.all_cve_data,
Expand All @@ -1097,7 +1139,9 @@ def main(argv=None):
exploits=args["exploits"],
metrics=metrics,
detailed=args["detailed"],
vex_filename=args["vex"],
vex_filename=args["vex_output"],
vex_type=args["vex_type"],
vex_product_info=vex_product_info,
sbom_filename=args["sbom_output"],
sbom_type=args["sbom_type"],
sbom_format=args["sbom_format"],
Expand Down
5 changes: 3 additions & 2 deletions cve_bin_tool/config_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ def config_generator(config_format, organized_arguments):
group_args["sbom-type"]["arg_value"] = None
group_args["sbom-format"]["arg_value"] = None
group_args["sbom-output"]["arg_value"] = None
if group_args["vex"]["arg_value"] == "":
group_args["vex"]["arg_value"] = None
if group_args["vex-output"]["arg_value"] == "":
group_args["vex-type"]["arg_value"] = None
group_args["vex-output"]["arg_value"] = None
f.write(f"{first_char}{group_title}{last_char}\n")
for arg_name, arg_value_help in group_args.items():
arg_value = arg_value_help["arg_value"]
Expand Down
126 changes: 18 additions & 108 deletions cve_bin_tool/output_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from datetime import datetime
from logging import Logger
from pathlib import Path
from typing import IO, Any
from typing import IO

from cve_bin_tool.cve_scanner import CVEData
from cve_bin_tool.cvedb import CVEDB
Expand All @@ -31,6 +31,7 @@
from cve_bin_tool.sbom_manager.generate import SBOMGenerate
from cve_bin_tool.util import ProductInfo, Remarks, VersionInfo
from cve_bin_tool.version import VERSION
from cve_bin_tool.vex_manager.generate import VEXGenerate


def output_json(
Expand Down Expand Up @@ -672,14 +673,16 @@ def __init__(
affected_versions: int = 0,
all_cve_version_info=None,
detailed: bool = False,
vex_filename: str = "",
exploits: bool = False,
metrics: bool = False,
all_product_data=None,
sbom_filename: str = "",
sbom_type: str = "spdx",
sbom_format: str = "tag",
sbom_root: str = "CVE_SBOM",
vex_filename: str = "",
vex_type: str = "cyclonedx",
vex_product_info: dict[str, str] = {},
offline: bool = False,
):
"""Constructor for OutputEngine class."""
Expand All @@ -700,7 +703,6 @@ def __init__(
self.affected_versions = affected_versions
self.all_cve_data = all_cve_data
self.detailed = detailed
self.vex_filename = vex_filename
self.exploits = exploits
self.metrics = metrics
self.all_product_data = all_product_data
Expand All @@ -710,6 +712,9 @@ def __init__(
self.sbom_root = sbom_root
self.offline = offline
self.sbom_packages = {}
self.vex_type = vex_type
self.vex_product_info = vex_product_info
self.vex_filename = vex_filename

def output_cves(self, outfile, output_type="console"):
"""Output a list of CVEs
Expand Down Expand Up @@ -788,7 +793,16 @@ def output_cves(self, outfile, output_type="console"):
self.logger.info(f"Output stored at {self.append}")

if self.vex_filename != "":
self.generate_vex(self.all_cve_data, self.vex_filename)
vexgen = VEXGenerate(
self.vex_product_info["product"],
self.vex_product_info["release"],
self.vex_product_info["vendor"],
self.vex_filename,
self.vex_type,
self.all_cve_data,
logger=self.logger,
)
vexgen.generate_vex()
if self.sbom_filename != "":
sbomgen = SBOMGenerate(
self.all_product_data,
Expand All @@ -800,110 +814,6 @@ def output_cves(self, outfile, output_type="console"):
)
sbomgen.generate_sbom()

def generate_vex(self, all_cve_data: dict[ProductInfo, CVEData], filename: str):
"""Generate a vex file and create vulnerability entry."""
analysis_state = {
Remarks.NewFound: "in_triage",
Remarks.Unexplored: "in_triage",
Remarks.Confirmed: "exploitable",
Remarks.Mitigated: "resolved",
Remarks.FalsePositive: "false_positive",
Remarks.NotAffected: "not_affected",
}
response_state = {
Remarks.NewFound: [],
Remarks.Unexplored: [],
Remarks.Confirmed: ["update"],
Remarks.Mitigated: [],
Remarks.FalsePositive: [],
Remarks.NotAffected: [],
}
# URLs for vulnerability detail
source_url = {
"GAD": "https://nvd.nist.gov/vuln/detail/",
"NVD": "https://nvd.nist.gov/vuln/detail/",
"OSV": "https://osv.dev/list?ecosystem=&q=",
"RSD": "https://nvd.nist.gov/vuln/detail/",
"REDHAT": "https://access.redhat.com/security/cve/",
}
# Generate VEX file
vex_output = {"bomFormat": "CycloneDX", "specVersion": "1.4", "version": 1}
# Extra info considered useful
# "creationInfo": {
# "created": datetime.now().strftime("%Y-%m-%dT%H-%M-%SZ"),
# "creators": ["Tool: cve_bin_tool", "Version:" + VERSION],
# },
# "documentDescribes": ["VEX_File"],
# "externalDocumentRefs": [{
# "sbomDocument": "<FILENAME>"
# }],
# }
vuln_entry = []
for product_info, cve_data in all_cve_data.items():
for cve in cve_data["cves"]:
if isinstance(cve, str):
continue
# Create vulnerability entry. Contains id, scoring, analysis and affected component
vulnerability: dict[str, Any] = dict()
id = cve.cve_number
vulnerability["id"] = id
vulnerability["source"] = {
"name": cve.data_source,
"url": source_url[cve.data_source] + id,
}
# Assume CVSS vulnerability scores are in accordance with NVD guidance
if cve.cvss_version == 3:
url = f"v3-calculator?name={cve.cve_number}&vector={cve.cvss_vector}&version=3.1"
else:
url = f"v2-calculator?name={cve.cve_number}&vector={cve.cvss_vector}&version=2.0"
ratings = [
{
"source": {
"name": "NVD",
"url": "https://nvd.nist.gov/vuln-metrics/cvss/" + url,
},
"score": cve.score,
"severity": cve.severity.lower(),
"method": "CVSSv" + str(cve.cvss_version),
"vector": cve.cvss_vector,
}
]
vulnerability["ratings"] = ratings
vulnerability["description"] = cve.description
vulnerability["recommendation"] = ""
vulnerability["advisories"] = []
vulnerability["created"] = "NOT_KNOWN"
vulnerability["published"] = "NOT_KNOWN"
vulnerability["updated"] = cve.last_modified
detail = (
cve.remarks.name + ": " + cve.comments
if cve.comments
else cve.remarks.name
)

analysis = {
"state": analysis_state[cve.remarks],
"response": cve.response or response_state[cve.remarks],
"detail": detail,
}
if cve.justification:
analysis["justification"] = cve.justification
vulnerability["analysis"] = analysis
bom_version = 1
# cve-bin-tool specific reference string to include vendor information
vulnerability["affects"] = [
{
"ref": f"urn:cbt:{bom_version}/{product_info.vendor}#{product_info.product}:{product_info.version}",
}
]
vuln_entry.append(vulnerability)

vex_output["vulnerabilities"] = vuln_entry

# Generate file
with open(filename, "w") as outfile:
json.dump(vex_output, outfile, indent=" ")

def output_file_wrapper(self, output_types=["console"]):
"""Call output_file method for all output types."""
for output_type in output_types:
Expand Down
3 changes: 2 additions & 1 deletion cve_bin_tool/vex_manager/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ def get_vulnerabilities(self) -> List[Vulnerability]:
vulnerability.set_status(self.analysis_state[self.vextype][cve.remarks])
if cve.justification:
vulnerability.set_justification(cve.justification)
# vulnerability.set_remediation(cve.response)
if cve.response:
vulnerability.set_value("remediation", cve.response[0])
detail = (
f"{cve.remarks.name}: {cve.comments}"
if cve.comments
Expand Down
2 changes: 2 additions & 0 deletions test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ def test_console_output_depending_reportlab_existence(self, caplog):
"log_level : info",
"nvd_api_key : ",
"offline : false",
"vex_file : ",
],
]
tomls = [
Expand Down Expand Up @@ -783,6 +784,7 @@ def test_console_output_depending_reportlab_existence(self, caplog):
"extract = true",
"append = false",
'import = ""',
'vex_file = ""',
],
]

Expand Down
Loading

0 comments on commit 361f5fd

Please sign in to comment.