Skip to content

Commit 4c4cbd1

Browse files
feat: Add Red Hat data source (Fixes #2331, #2367) (#2368)
* Fixes #2331 * Fixes #2367
1 parent a33e533 commit 4c4cbd1

File tree

13 files changed

+467
-239
lines changed

13 files changed

+467
-239
lines changed

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ CVE Data Download:
179179
<a href="https://github.com/intel/cve-bin-tool/blob/main/doc/MANUAL.md#--nvd-api-key-nvd_api_key">--nvd-api-key NVD_API_KEY</a>
180180
specify NVD API key (used to improve NVD rate limit)
181181
<a href="https://github.com/intel/cve-bin-tool/blob/main/doc/MANUAL.md#-d-nvdosvgad-nvdosvgad----disable-data-source-nvdosvgad-nvdosvgad-">-d {NVD,OSV} [{NVD,OSV} ...], --disable-data-source {NVD,OSV} [{NVD,OSV} ...]</a>
182-
specify data sources that should be disabled
182+
comma-separated list of data sources (GAD, NVD, OSV, REDHAT) to disable (default: NONE)
183183

184184
Input:
185185
<a href="https://github.com/intel/cve-bin-tool/blob/main/doc/MANUAL.md#directory-positional-argument">directory</a> directory to scan
@@ -289,18 +289,26 @@ This data source consists of majority of the CVE entries and is essential to pro
289289
290290
### [Open Source Vulnerability Database](https://osv.dev/) (OSV)
291291

292-
This data source is based on the OSV schema from google, and consists of CVEs from different ecosystems that might not be covered by NVD.
292+
This data source is based on the OSV schema from Google, and consists of CVEs from different ecosystems that might not be covered by NVD.
293293
NVD is given priority if there are duplicate CVEs as some CVEs from OSV may not contain CVSS scores.
294294
Using OSV will increase number of CVEs and time taken to update the database but searching database for vulnerabilities will have similar performance.
295295

296296
### [Gitlab Advisory Database](https://advisories.gitlab.com/) (GAD)
297297

298298
This data source consists of security advisories used by the GitLab dependency scanner.
299-
Amount of CVEs added from this data source is similar to OSV.
299+
The number of CVEs added from this data source is similar to OSV.
300300

301-
### [RedHat Advisory Database](https://access.redhat.com/security/data) (RSD)
301+
### [RedHat Security Database](https://access.redhat.com/security/data) (REDHAT)
302302

303-
This data source contains CVEs pertaining to RedHat Products. Many are also included in NVD (duplicate CVEs are not reported multiple times).
303+
This data source contains CVEs pertaining to RedHat Products.
304+
305+
Access to the data is subject to [Legal Notice](https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html/red_hat_security_data_api/legal-notice).
306+
307+
### [RedHat Security Database](https://access.redhat.com/security/data) (REDHAT)
308+
309+
This data source contains CVEs pertaining to RedHat Products.
310+
311+
Access to the data is subject to [Legal Notice](https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html/red_hat_security_data_api/legal-notice).
304312

305313
## Binary checker list
306314

cve_bin_tool/cli.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@
4141
from cve_bin_tool.cve_scanner import CVEScanner
4242
from cve_bin_tool.cvedb import CVEDB, OLD_CACHE_DIR
4343
from cve_bin_tool.data_sources import (
44+
DataSourceSupport,
4445
curl_source,
4546
gad_source,
4647
nvd_source,
4748
osv_source,
48-
rsd_source,
49+
redhat_source,
4950
)
5051
from cve_bin_tool.error_handler import (
5152
CVEDataMissing,
@@ -106,7 +107,7 @@ def main(argv=None):
106107
)
107108
+ "\n\n"
108109
+ textwrap.fill(
109-
"Available data-sources: National Vulnerability Database (NVD), Open Source Vulnerability Database (OSV), Gitlab Advisory Database (GAD)"
110+
f'Available data sources: {", ".join(DataSourceSupport.available_data_sources())}'
110111
)
111112
+ "\n\n"
112113
+ textwrap.fill(
@@ -141,13 +142,12 @@ def main(argv=None):
141142
default="",
142143
help="specify NVD API key (used to improve NVD rate limit)",
143144
)
145+
data_source_disable_help = f'comma-separated list of data sources ({", ".join(DataSourceSupport.available_data_sources())}) to disable (default: NONE)'
144146
data_sources_group.add_argument(
145147
"-d",
146148
"--disable-data-source",
147-
nargs="+",
148-
choices=["NVD", "OSV", "GAD", "RSD"],
149-
type=str,
150-
help="specify data sources that should be disabled",
149+
action=StringToListAction,
150+
help=data_source_disable_help,
151151
default=[],
152152
)
153153

@@ -493,8 +493,27 @@ def main(argv=None):
493493
)
494494

495495
# list of sources that can be disabled but are not disabled
496+
# Data Source Processing
497+
# Ensure data source names are all upper case before validating list
498+
disable_data_sources = [d.upper() for d in args["disable_data_source"]]
499+
# Validate data source choices
500+
data_sources = DataSourceSupport()
501+
valid_data_sources = data_sources.get_data_sources()
502+
disabled_sources = []
503+
if len(disable_data_sources) > 0:
504+
LOGGER.debug(f"Processing disabled data sources {disable_data_sources}")
505+
for data_source in disable_data_sources:
506+
if data_source not in valid_data_sources:
507+
LOGGER.warning(
508+
f"Argument --disable-data-source: invalid choice: {data_source} (choose from {valid_data_sources})"
509+
)
510+
else:
511+
LOGGER.info(f"Disabling data source {data_source}")
512+
disabled_sources.append(data_source)
513+
LOGGER.debug(f"Accepted disabled data sources {disabled_sources}")
514+
515+
# Maintain list of sources that are used
496516
enabled_sources = []
497-
disabled_sources = args["disable_data_source"]
498517

499518
if "OSV" not in disabled_sources:
500519
source_osv = osv_source.OSV_Source(incremental_update=incremental_db_update)
@@ -506,12 +525,11 @@ def main(argv=None):
506525
)
507526
enabled_sources.append(source_gad)
508527

509-
if "RSD" not in disabled_sources:
510-
source_rsd = rsd_source.RSD_Source(incremental_update=incremental_db_update)
511-
enabled_sources.append(source_rsd)
512-
513-
for data_source in disabled_sources:
514-
LOGGER.info(f"Disabling data source {data_source}")
528+
if "REDHAT" not in disabled_sources:
529+
source_redhat = redhat_source.REDHAT_Source(
530+
incremental_update=incremental_db_update
531+
)
532+
enabled_sources.append(source_redhat)
515533

516534
default_sources = [source_nvd, curl_source.Curl_Source()]
517535
default_sources.extend(enabled_sources)
@@ -584,7 +602,7 @@ def main(argv=None):
584602
"%d %B %Y at %H:%M:%S", time.localtime(cvedb_orig.get_db_update_date())
585603
)
586604
LOGGER.info(
587-
"CVE database contains CVEs from National Vulnerability Database (NVD), Open Source Vulnerability Database (OSV), Gitlab Advisory Database (GAD)"
605+
"CVE database contains CVEs from National Vulnerability Database (NVD), Open Source Vulnerability Database (OSV), Gitlab Advisory Database (GAD) and RedHat"
588606
)
589607
LOGGER.info(f"CVE database last updated on {db_date}")
590608

cve_bin_tool/cvedb.py

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,7 @@ def populate_db(self) -> None:
283283
for cve_data, source_name in self.data:
284284

285285
if source_name != "NVD" and cve_data[0] is not None:
286-
if source_name != "RSD":
287-
cve_data = self.update_vendors(cve_data)
288-
cve_data = self.filter_duplicate(cve_data, source_name)
286+
cve_data = self.update_vendors(cve_data)
289287

290288
severity_data, affected_data = cve_data
291289

@@ -492,39 +490,6 @@ def update_vendors(self, cve_data):
492490

493491
return updated_severity, updated_affected
494492

495-
def filter_duplicate(self, cve_data, source):
496-
"""Filter out duplicate CVEs in CVE data."""
497-
updated_severity = []
498-
updated_affected = []
499-
500-
severity_data, affected_data = cve_data
501-
502-
cursor = self.db_open_and_get_cursor()
503-
504-
query = """
505-
SELECT cve_number, data_source FROM cve_severity
506-
WHERE cve_number=?
507-
"""
508-
509-
sel_cve = set()
510-
511-
for affected in affected_data:
512-
cursor.execute(query, [affected["cve_id"]])
513-
result = cursor.fetchall()
514-
cve = list(map(lambda x: x[0], result))
515-
516-
if len(cve) == 0 or result[0][1] == source:
517-
updated_affected.append(affected)
518-
sel_cve.add(affected["cve_id"])
519-
520-
for cve in severity_data:
521-
if cve["ID"] in sel_cve:
522-
updated_severity.append(cve)
523-
524-
self.db_close()
525-
526-
return updated_severity, updated_affected
527-
528493
def db_open_and_get_cursor(self) -> sqlite3.Cursor:
529494
"""Opens connection to sqlite database, returns cursor object."""
530495

cve_bin_tool/data_sources/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
# Copyright (C) 2022 Intel Corporation
22
# SPDX-License-Identifier: GPL-3.0-or-later
33

4+
from __future__ import annotations
5+
6+
import sys
47
from abc import ABC, abstractmethod
58
from pathlib import Path
69

10+
if sys.version_info >= (3, 9):
11+
import importlib.resources as resources
12+
else:
13+
import importlib_resources as resources
14+
715
USER_HOME = Path("~")
816

917
# database defaults
@@ -18,3 +26,31 @@ class Data_Source(ABC):
1826
@abstractmethod
1927
async def get_cve_data(self):
2028
pass
29+
30+
31+
class DataSourceSupport:
32+
33+
# Supported Data Sources
34+
DATA_SOURCES_ENTRYPOINT = "cve_bin_tool.data_sources"
35+
36+
def __init__(self):
37+
self.data_sources = self.available_data_sources()
38+
39+
@classmethod
40+
def available_data_sources(cls) -> list[str]:
41+
"""Find Data Sources"""
42+
data_sources_directory = resources.files(cls.DATA_SOURCES_ENTRYPOINT)
43+
sources = data_sources_directory.iterdir()
44+
disable_source = []
45+
disable_source.append("curl_source")
46+
disable_source.append("__init__")
47+
data_sources = []
48+
for data_source in sources:
49+
if str(data_source).endswith(".py"):
50+
source = data_source.name.replace(".py", "")
51+
if source not in disable_source:
52+
data_sources.append(source.replace("_source", "").upper())
53+
return sorted(data_sources)
54+
55+
def get_data_sources(self) -> list[str]:
56+
return self.data_sources

0 commit comments

Comments
 (0)