Skip to content

Commit 50bbe92

Browse files
author
shanko07
committed
Merge branch 'master' of github.com:blackducksoftware/hub-rest-api-python into shanko07/notices_report_json_to_html
2 parents efc1e26 + 0c4facd commit 50bbe92

File tree

3 files changed

+135
-1
lines changed

3 files changed

+135
-1
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""
2+
append_bdsa_data_to_security_report.py
3+
4+
Created on March 17, 2021
5+
6+
@author: DNicholls
7+
8+
Script that takes a security report CSV file and goes line by line and where it finds a BDSA vulnerability will call
9+
the Black Duck API to get more information from the BDSA record and adds additional columns for eacg row with this data.
10+
11+
Currently retrieves the solution and workaround text and adds these as 2 columns but can be modified to add more if required.
12+
13+
To run this script, you will need to pass the folder containing the security report, it will find any CSV file matching
14+
security*.csv and read the file line by line. Where a BDSA vulnerability is found it will call the Black Duck API
15+
to get more information on the BDSA record and add columns with the BDSA's solution and workarounds if applicable.
16+
17+
For this script to run, the hub-rest-api-python (blackduck) library and csv library will need to be installed.
18+
19+
"""
20+
21+
import argparse
22+
from blackduck.HubRestApi import HubInstance
23+
import os
24+
import glob
25+
import sys
26+
import logging
27+
import csv
28+
29+
parser = argparse.ArgumentParser("A program to add BDSA details to security reports.")
30+
parser.add_argument("folder")
31+
args = parser.parse_args()
32+
hub = HubInstance()
33+
34+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(module)s: %(message)s', stream=sys.stderr, level=logging.DEBUG)
35+
logging.getLogger("requests").setLevel(logging.WARNING)
36+
logging.getLogger("urllib3").setLevel(logging.WARNING)
37+
logging.getLogger("blackduck.HubRestApi").setLevel(logging.WARNING)
38+
39+
def checkdirs(folder):
40+
if os.path.isdir(folder) == False:
41+
raise NotADirectoryError("Folder {} is not a folder".format(folder))
42+
43+
def handle_security_reports(folder):
44+
logging.info(f"Looking for security reports in {folder}/security*.csv")
45+
for csvfile in glob.iglob(f"{folder}/security*.csv"):
46+
if '_with_bdsa' not in os.path.basename(csvfile):
47+
logging.debug(f"Handling security report file {csvfile}")
48+
handle_security_report(csvfile)
49+
50+
def handle_security_report(csvfile):
51+
# Create a new file for this report
52+
file_no_ext = os.path.splitext(csvfile)[0]
53+
file_to_create = file_no_ext + "_with_bdsa.csv"
54+
55+
if os.path.isfile(file_to_create):
56+
raise FileExistsError(f"File {file_to_create} already exists so cannot write output file")
57+
58+
# Read the security report line by line
59+
with open(file_to_create, "w") as output_csv_file:
60+
with open(csvfile, 'r') as read_obj:
61+
writer = csv.writer(output_csv_file, delimiter=',', lineterminator='\n')
62+
reader = csv.reader(read_obj)
63+
64+
all = []
65+
row = next(reader)
66+
row.append('Solution')
67+
row.append('Workaround')
68+
all.append(row)
69+
70+
for row in reader:
71+
vuln_id_cell = row[9] # Currently the column index of 'Vulnerability id' column is 9. Change if required.
72+
#logging.debug(f" CELL [{vuln_id_cell}]")
73+
bdsa_id = parse_bdsa_id(vuln_id_cell)
74+
#logging.debug(f"BDSA ID [{bdsa_id}]")
75+
if bdsa_id != None:
76+
bdsa_data = load_bdsa_data(bdsa_id)
77+
#logging.debug(f"BDSA Data Solution [{bdsa_data['solution']}]")
78+
#logging.debug(f"BDSA Data Workaround [{bdsa_data['workaround']}]")
79+
#row.append(row[0])
80+
row.append(bdsa_data['solution'])
81+
row.append(bdsa_data['workaround'])
82+
all.append(row)
83+
else:
84+
# Add the line as is.
85+
logging.debug(f"No BDSA Record")
86+
row.append(row[0])
87+
all.append(row)
88+
89+
logging.info(f"Writing output csv file [{file_to_create}]")
90+
writer.writerows(all)
91+
92+
# Write the report file.
93+
return None
94+
95+
def parse_bdsa_id(record):
96+
# Cell value could be a BDSA id, CVE id or both e.g. BDSA-2020-1128 (CVE-2020-1945)
97+
# This method will return the BDSA id if there is one regardless of the data
98+
if is_bdsa_record(record):
99+
if '(BDSA-' in record:
100+
# CVE-2020-1945 (BDSA-2020-1128)
101+
return record[record.index('(')+len('('):record.index(')')]
102+
elif '(CVE-' in record:
103+
# BDSA-2020-1128 (CVE-2020-1945)
104+
return record[0:record.index('(') - 1]
105+
elif '(' not in record:
106+
# BDSA-2020-1128
107+
return record
108+
109+
else:
110+
return None
111+
112+
113+
def is_bdsa_record(record):
114+
return 'BDSA' in record
115+
116+
def load_bdsa_data(bdsa_id):
117+
logging.debug(f"Retrieving BDSA data for [{bdsa_id}]")
118+
vuln = hub.get_vulnerabilities(bdsa_id)
119+
return vuln
120+
121+
def main():
122+
checkdirs(args.folder)
123+
handle_security_reports(args.folder)
124+
125+
126+
main()

examples/delete_older_versions_system_wide.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
parser.add_argument("-e", "--excluded_phases", default=excluded_phases_defaults, help=f"Set the phases to exclude from deletion (defaults to {excluded_phases_defaults})")
4242
parser.add_argument("-a", "--age", type=int, help=f"Project-versions older than this age (days) will be deleted unless their phase is in the list of excluded phases ({excluded_phases_defaults})")
4343
parser.add_argument("-d", "--delete", action='store_true', help=f"Because this script can, and will, delete project-versions we require the caller to explicitly ask to delete things. Otherwise, the script runs in a 'test mode' and just says what it would do.")
44+
parser.add_argument("-ncl", "--do_not_delete_code_locations", action='store_true', help=f"By default the script will delete code locations mapped to project versions being deleted. Pass this flag if you do not want to delete code locations.")
4445
args = parser.parse_args()
4546

4647
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(module)s: %(message)s', stream=sys.stderr, level=logging.DEBUG)
@@ -52,6 +53,9 @@
5253

5354
projects = hub.get_projects(limit=9999).get('items', [])
5455

56+
logging.warning(f"The default behaviour of this script has changed. Previously it would not delete mapped code locations while deleting a project version and would rely on these being cleaned up by the system at a later date.")
57+
logging.info(f"If you wish to keep the previous behaviour please pass the -ncl or --do_not_delete_code_locations parameter.")
58+
5559
for project in projects:
5660
versions = hub.get_project_versions(project, limit=9999)
5761
sorted_versions = sorted(versions['items'], key = lambda i: i['createdAt'])
@@ -66,6 +70,10 @@
6670
# TODO: What to do if/when this is the last version in a project to avoid having empty projects being left around
6771
logging.debug(f"Deleting version {version['versionName']} from project {project['name']} cause it is {version_age} days old which is greater than {args.age} days")
6872
url = version['_meta']['href']
73+
if not args.do_not_delete_code_locations:
74+
logging.debug(f"Deleting code locations for version {version['versionName']} from project {project['name']}")
75+
hub.delete_project_version_codelocations(version)
76+
6977
response = hub.execute_delete(url)
7078
if response.status_code == 204:
7179
logging.info(f"Successfully deleted version {version['versionName']} from project {project['name']}")

examples/generate_notices_report_for_project_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
class FailedReportDownload(Exception):
3232
pass
3333

34-
DOWNLOAD_ERROR_CODES = ['{report.main.read.unfinished.report}', '{report.main.download.unfinished.report}']
34+
DOWNLOAD_ERROR_CODES = ['{report.main.read.unfinished.report.contents}', '{report.main.download.unfinished.report}']
3535

3636
def download_report(location, file_name_base, retries=10):
3737
report_id = location.split("/")[-1]

0 commit comments

Comments
 (0)