Skip to content

Commit 5ea2f5c

Browse files
author
Glenn Snyder
authored
Merge pull request #29 from blackducksoftware/gsnyder/cve-info
Adding support for retrieving vulnerability info for BOM components
2 parents 1127988 + be2e87c commit 5ea2f5c

File tree

5 files changed

+110
-50
lines changed

5 files changed

+110
-50
lines changed

blackduck/HubRestApi.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ def get_link(self, bd_rest_obj, link_name):
213213
else:
214214
logging.debug("This does not appear to be a BD REST object. It should have ['_meta']['links']")
215215

216+
def get_limit_paramstring(self, limit):
217+
return "?limit={}".format(limit)
218+
219+
def get_apibase(self):
220+
return self.config['baseurl'] + "/api"
221+
216222
###
217223
#
218224
# Role stuff
@@ -484,26 +490,18 @@ def find_component_info_for_protex_component(self, protex_component_id, protex_c
484490
else:
485491
return component_list_d['items']
486492

487-
def get_limit_paramstring(self, limit):
488-
return "?limit={}".format(limit)
489-
490-
def get_apibase(self):
491-
return self.config['baseurl'] + "/api"
492-
493-
def get_version_by_name(self, project, version_name):
494-
version_list = self.get_project_versions(project)
495-
for version in version_list['items']:
496-
if version['versionName'] == version_name:
497-
return version
498-
499-
def _get_version_link(self, version, link_type):
500-
# if link_type == 'licenseReports':
501-
# version_id = version['_meta']['href'].split("/")[-1]
502-
# return self.get_urlbase() + "/api/v1/versions/{}/reports".format(version_id)
503-
# else:
504-
for link in version['_meta']['links']:
505-
if link['rel'] == link_type:
506-
return link['href']
493+
def get_vulnerable_bom_components(self, version_obj, limit=9999):
494+
url = "{}/vulnerable-bom-components".format(version_obj['_meta']['href'])
495+
custom_headers = {'Content-Type': 'application/vnd.blackducksoftware.bill-of-materials-4+json'}
496+
param_string = self._get_parameter_string({'limit': limit})
497+
url = "{}{}".format(url, param_string)
498+
response = self.execute_get(url, custom_headers=custom_headers)
499+
if response.status_code == 200:
500+
vulnerable_bom_components = response.json()
501+
return vulnerable_bom_components
502+
else:
503+
logging.warning("Failed to retrieve vulnerable bom components for project {}, status code {}".format(
504+
version_obj, response.status_code))
507505

508506
##
509507
#
@@ -524,7 +522,7 @@ def create_version_reports(self, version, report_list, format="CSV"):
524522
'reportType': 'VERSION',
525523
'reportFormat': format
526524
}
527-
version_reports_url = self._get_version_link(version, 'versionReport')
525+
version_reports_url = self.get_link(version, 'versionReport')
528526
return self.execute_post(version_reports_url, post_data)
529527

530528
valid_notices_formats = ["TEXT", "HTML"]
@@ -537,7 +535,7 @@ def create_version_notices_report(self, version, format="TEXT"):
537535
'reportType': 'VERSION_LICENSE',
538536
'reportFormat': format
539537
}
540-
notices_report_url = self._get_version_link(version, 'licenseReports')
538+
notices_report_url = self.get_link(version, 'licenseReports')
541539
return self.execute_post(notices_report_url, post_data)
542540

543541
def download_report(self, report_id):
@@ -788,20 +786,22 @@ def get_project_by_name(self, project_name):
788786
if project['name'] == project_name:
789787
return project
790788

789+
def get_version_by_name(self, project, version_name):
790+
version_list = self.get_project_versions(project, parameters={'q':"versionName:{}".format(version_name)})
791+
# A query by name can return more than one version if other versions
792+
# have names that include the search term as part of their name
793+
for version in version_list['items']:
794+
if version['versionName'] == version_name:
795+
return version
796+
791797
def get_project_version_by_name(self, project_name, version_name):
792798
project = self.get_project_by_name(project_name)
793799
if project:
794-
project_versions = self.get_project_versions(
795-
project,
796-
parameters={'q':"versionName:{}".format(version_name)}
797-
)
798-
# A query by name can return more than one version if other versions
799-
# have names that include the search term as part of their name
800-
for project_version in project_versions['items']:
801-
if project_version['versionName'] == version_name:
802-
logging.debug("Found matching version: {}".format(project_version))
803-
return project_version
804-
logging.debug("Did not find any project version matching {}".format(version_name))
800+
version = self.get_version_by_name(project, version_name)
801+
if version == None:
802+
logging.debug("Did not find any project version matching {}".format(version_name))
803+
else:
804+
return version
805805
else:
806806
logging.debug("Did not find a project with name {}".format(project_name))
807807

blackduck/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11

2-
VERSION = (0, 0, 14)
2+
VERSION = (0, 0, 15)
33

44
__version__ = '.'.join(map(str, VERSION))
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
from datetime import datetime
5+
import json
6+
import logging
7+
import sys
8+
import timestring
9+
10+
from blackduck.HubRestApi import HubInstance
11+
12+
#
13+
# Example usage:
14+
# To get all the vulnerabilities (first time) and save the last run date/time,
15+
# python examples/get_bom_component_vulnerability_info.py struts2-showcase 2.6-SNAPSHOT -s > vulnerabilities.json
16+
#
17+
# Having saved the last run date/time, use it to view any newly published vulnerabilities that have come out since the last run,
18+
# python examples/get_bom_component_vulnerability_info.py struts2-showcase 2.6-SNAPSHOT -n `cat .last_run` > vulnerabilities_since.json
19+
#
20+
# Having saved the last run date/time, use it to view any newly published vulnerabilities that have come out since the last run
21+
# and update the last run date/time,
22+
# python examples/get_bom_component_vulnerability_info.py struts2-showcase 2.6-SNAPSHOT -s -n `cat .last_run` > vulnerabilities_since.json
23+
#
24+
# Use --newer_than (aka -n) to specify your own date (or date/time),
25+
# python examples/get_bom_component_vulnerability_info.py struts2-showcase 2.6-SNAPSHOT -n "2017" > vulnerabilities_since_2017.json
26+
# python examples/get_bom_component_vulnerability_info.py struts2-showcase 2.6-SNAPSHOT -n "July 1 2018" > vulnerabilities_since_July_1_2018.json
27+
# python examples/get_bom_component_vulnerability_info.py struts2-showcase 2.6-SNAPSHOT -n "July 1 2018 5:30 pm" > vulnerabilities_since_July_1_2018_1730.json
28+
29+
parser = argparse.ArgumentParser("Retreive BOM component vulnerability information for the given project and version")
30+
parser.add_argument("project_name")
31+
parser.add_argument("version")
32+
parser.add_argument("-n", "--newer_than",
33+
default=None,
34+
type=str,
35+
help="Set this option to see all vulnerabilities published since the given date/time.")
36+
parser.add_argument("-s", "--save_dt",
37+
action='store_true',
38+
help="If set, the date/time will be saved to a file named '.last_run' in the current directory which can be used later with the -n option to see vulnerabilities published since the last run.")
39+
args = parser.parse_args()
40+
41+
if args.newer_than:
42+
newer_than = timestring.Date(args.newer_than).date
43+
else:
44+
newer_than = None
45+
46+
if args.save_dt:
47+
with open(".last_run", "w") as f:
48+
f.write(datetime.now().isoformat())
49+
50+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stderr, level=logging.DEBUG)
51+
logging.getLogger("requests").setLevel(logging.WARNING)
52+
logging.getLogger("urllib3").setLevel(logging.WARNING)
53+
54+
hub = HubInstance()
55+
56+
project = hub.get_project_by_name(args.project_name)
57+
58+
version = hub.get_version_by_name(project, args.version)
59+
60+
vulnerable_bom_components_info = hub.get_vulnerable_bom_components(version)
61+
62+
vulnerable_bom_components = vulnerable_bom_components_info.get('items', [])
63+
64+
if vulnerable_bom_components:
65+
vulnerable_bom_components = sorted(
66+
vulnerable_bom_components,
67+
key = lambda k: k['vulnerabilityWithRemediation']['vulnerabilityPublishedDate'])
68+
if newer_than:
69+
vulnerable_bom_components = [v for v in vulnerable_bom_components
70+
if timestring.Date(v['vulnerabilityWithRemediation']['vulnerabilityPublishedDate']) > newer_than ]
71+
else:
72+
logging.debug("Did not find any vulnerable BOM components in project {}, version {}".format(args.project_name, args.version))
73+
74+
print(json.dumps(vulnerable_bom_components))
75+

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ python-dateutil>=2.8.0
44

55
# for examples printing tables to the terminal
66
terminaltables
7+
timestring
78

89
# for unit testing
910
pytest

test/test_hub_rest_api_python.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -402,22 +402,6 @@ def test_create_version_reports(requests_mock, mock_hub_instance):
402402
def test_create_version_notices_report(requests_mock, mock_hub_instance):
403403
pass
404404

405-
def test_get_version_link(mock_hub_instance):
406-
version_json = json.load(open("version.json"))
407-
408-
# replace the base URL with the mocked base url in all the links
409-
baseurl = mock_hub_instance.get_urlbase()
410-
for link_d in version_json['_meta']['links']:
411-
link_d['href'] = re.sub("https://.*/api", "{}/api".format(baseurl), link_d['href'])
412-
413-
for link_name in ['versionReport', 'licenseReports', 'riskProfile', 'components', 'vulnerable-components', 'comparison', 'project', 'policy-status', 'codelocations']:
414-
url = mock_hub_instance._get_version_link(version_json, link_name)
415-
assert url # we got something
416-
417-
parsed_url = urlparse(url)
418-
assert parsed_url.scheme == urlparse(fake_hub_host).scheme
419-
assert parsed_url.netloc == urlparse(fake_hub_host).netloc
420-
421405
@pytest.fixture()
422406
def unreviewed_snippet_json():
423407
with open("unreviewed_snippet.json") as f:

0 commit comments

Comments
 (0)