Skip to content

Commit 152d3bd

Browse files
author
Glenn Snyder
authored
Merge branch 'master' into authentication-overhaul
2 parents 8813b97 + 4debffb commit 152d3bd

23 files changed

+1412
-51
lines changed

blackduck/Authentication.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,4 @@ def authenticate(self):
9292
logger.critical(f"slow or unstable connection, consider increasing timeout (currently set to {self.timeout}s)")
9393
raise read_timeout
9494
else:
95-
logger.info(f"success: auth granted until {self.valid_until} UTC")
95+
logger.info(f"success: auth granted until {self.valid_until} UTC")

blackduck/HubRestApi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Created on Jul 6, 2018
33
44
@author: kumykov
5+
@contributors: gsnyder2007, AR-Calder
56
67
Wrapper for common HUB API queries.
78
Upon initialization Bearer tocken is obtained and used for all subsequent calls
@@ -47,6 +48,7 @@
4748
4849
'''
4950
import logging
51+
import requests
5052

5153
logger = logging.getLogger(__name__)
5254

blackduck/__init__.py

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

22
from .HubRestApi import HubInstance
3-
43
from .Client import Client
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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", encoding="utf8") as output_csv_file:
60+
with open(csvfile, 'r', encoding="utf8") 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('BDSA Id')
67+
row.append('BDSA Url')
68+
row.append('Solution')
69+
row.append('Workaround')
70+
all.append(row)
71+
72+
for row in reader:
73+
vuln_id_cell = row[9] # Currently the column index of 'Vulnerability id' column is 9. Change if required.
74+
#logging.debug(f" CELL [{vuln_id_cell}]")
75+
bdsa_id = parse_bdsa_id(vuln_id_cell)
76+
#logging.debug(f"BDSA ID [{bdsa_id}]")
77+
if bdsa_id != None:
78+
bdsa_data = load_bdsa_data(bdsa_id)
79+
#logging.info(f"{bdsa_data}")
80+
if bdsa_data and "solution" in bdsa_data and "workaround" in bdsa_data and "name" in bdsa_data:
81+
#logging.debug(f"BDSA Data Solution [{bdsa_data['solution']}]")
82+
#logging.debug(f"BDSA Data Workaround [{bdsa_data['workaround']}]")
83+
#row.append(row[0])
84+
row.append(bdsa_data['name'])
85+
row.append(bdsa_data['_meta']['href'])
86+
row.append(bdsa_data['solution'])
87+
row.append(bdsa_data['workaround'])
88+
all.append(row)
89+
else:
90+
logging.debug(f"BDSA Data not found for {bdsa_id}")
91+
row.append(bdsa_id)
92+
row.append('')
93+
row.append('Failed to load BDSA data')
94+
row.append('Failed to load BDSA data')
95+
all.append(row)
96+
else:
97+
# Add the line as is.
98+
logging.debug(f"No BDSA Record")
99+
row.append('N/A')
100+
row.append('N/A')
101+
row.append('')
102+
row.append('')
103+
all.append(row)
104+
105+
logging.info(f"Writing output csv file [{file_to_create}]")
106+
writer.writerows(all)
107+
108+
# Write the report file.
109+
return None
110+
111+
def parse_bdsa_id(record):
112+
# Cell value could be a BDSA id, CVE id or both e.g. BDSA-2020-1128 (CVE-2020-1945)
113+
# This method will return the BDSA id if there is one regardless of the data
114+
if is_bdsa_record(record):
115+
if '(BDSA-' in record:
116+
# CVE-2020-1945 (BDSA-2020-1128)
117+
return record[record.index('(')+len('('):record.index(')')]
118+
elif '(CVE-' in record:
119+
# BDSA-2020-1128 (CVE-2020-1945)
120+
return record[0:record.index('(') - 1]
121+
elif '(' not in record:
122+
# BDSA-2020-1128
123+
return record
124+
125+
else:
126+
return None
127+
128+
129+
def is_bdsa_record(record):
130+
return 'BDSA' in record
131+
132+
def load_bdsa_data(bdsa_id):
133+
logging.debug(f"Retrieving BDSA data for [{bdsa_id}]")
134+
vuln = hub.get_vulnerabilities(bdsa_id)
135+
return vuln
136+
137+
def main():
138+
checkdirs(args.folder)
139+
handle_security_reports(args.folder)
140+
141+
142+
main()
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import argparse
2+
import json
3+
import os
4+
import shutil
5+
import sys
6+
import logging
7+
import zipfile
8+
import uuid
9+
from zipfile import ZIP_DEFLATED
10+
from urllib.parse import quote
11+
12+
parser = argparse.ArgumentParser("Take a directory of bdio files, copy and unzip each file,"
13+
"update the project and project version name to the name specified by the user,"
14+
"zip each jsonld file into a bdio of the same name as the source bdio. ")
15+
parser.add_argument('-d', '--bdio-directory', required=True, help="Directory path containing source bdio files")
16+
parser.add_argument('-n', '--project-name', required=True, default=None, help="Project name")
17+
parser.add_argument('-p', '--project-version', required=True, default=None, help="Target project version name")
18+
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='turn on DEBUG logging')
19+
20+
args = parser.parse_args()
21+
22+
23+
def set_logging_level(log_level):
24+
logging.basicConfig(stream=sys.stderr, level=log_level)
25+
26+
27+
if args.verbose:
28+
set_logging_level(logging.DEBUG)
29+
else:
30+
set_logging_level(logging.INFO)
31+
32+
# examples
33+
working_dir = os.getcwd()
34+
bdio_dir = os.path.join(working_dir, args.bdio_directory)
35+
project_name = args.project_name
36+
target_version = args.project_version
37+
temp_dir = os.path.join(working_dir, 'temp')
38+
renamed_directory = os.path.join(bdio_dir, "renamed_bdio")
39+
40+
41+
def do_refresh(dir_name):
42+
dir = os.path.join(working_dir, dir_name)
43+
for fileName in os.listdir(dir):
44+
print("Removing stale files {}".format(os.path.join(dir, fileName)))
45+
os.remove(os.path.join(dir, fileName))
46+
47+
48+
def check_dirs():
49+
os.chdir(working_dir)
50+
if not os.path.isdir('{}'.format(bdio_dir)) or len(os.listdir('{}'.format(bdio_dir))) <= 0:
51+
parser.print_help(sys.stdout)
52+
sys.exit(1)
53+
54+
if not os.path.isdir(temp_dir):
55+
os.makedirs(temp_dir)
56+
print('Made temp directory')
57+
elif len(os.listdir(temp_dir)) != 0:
58+
do_refresh(temp_dir)
59+
pass
60+
else:
61+
print('Temp directory already exists')
62+
63+
if not os.path.isdir(renamed_directory):
64+
os.makedirs(renamed_directory)
65+
print('Made renamed bdio directory')
66+
elif len(os.listdir(renamed_directory)) != 0:
67+
do_refresh(renamed_directory)
68+
else:
69+
print('Renamed bdio directory already exists')
70+
71+
72+
def zip_extract_files(zip_file, dir_name):
73+
print("Extracting content of {} into {}".format(zip_file, dir_name))
74+
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
75+
zip_ref.extractall(dir_name)
76+
77+
78+
def zip_create_archive(zip_file, dir_name):
79+
print("Writing content of {} into {} file".format(dir_name, zip_file))
80+
with zipfile.ZipFile(zip_file, mode='a', compression=ZIP_DEFLATED) as zipObj:
81+
for folderName, subfolders, filenames in os.walk(dir_name):
82+
jsonld_list = [x for x in filenames if x.endswith('.jsonld')]
83+
for filename in jsonld_list:
84+
filePath = os.path.join(folderName, filename)
85+
zipObj.write(filePath, os.path.basename(filePath))
86+
87+
88+
def jsonld_update_project_name(data, name):
89+
content_array = data['@graph']
90+
if not content_array:
91+
return
92+
for counter, array_entry in enumerate(content_array):
93+
if array_entry['@type'][0] == 'https://blackducksoftware.github.io/bdio#Project':
94+
logging.debug(counter)
95+
logging.debug(content_array[counter].keys())
96+
if "https://blackducksoftware.github.io/bdio#hasName" in content_array[counter]:
97+
content_array[counter]["https://blackducksoftware.github.io/bdio#hasName"][0]['@value'] = name
98+
logging.debug(content_array[counter])
99+
100+
101+
def jsonld_update_uuid(data, new_uuid):
102+
logging.debug("Updating jsonld file to new UUID: {}".format(new_uuid))
103+
data.update({'@id': new_uuid})
104+
105+
106+
def jsonld_update_version_name_in_header(data, version):
107+
scan_name_dict = {k: v[0].get('@value') for (k, v) in data.items() if
108+
k == 'https://blackducksoftware.github.io/bdio#hasName'}
109+
if not scan_name_dict:
110+
return
111+
try:
112+
version_link = scan_name_dict.popitem()[1].split('/')
113+
version_link[1] = version
114+
except IndexError as index_error:
115+
logging.debug("Could not get version link for this header file {} with error {}, header files are not "
116+
"required for upload".format(scan_name_dict, index_error))
117+
return
118+
else:
119+
version_list = [{'@value': '/'.join(version_link)}]
120+
data.update({'https://blackducksoftware.github.io/bdio#hasName': version_list})
121+
122+
123+
def jsonld_update_project_version(data, version):
124+
content_array = data['@graph']
125+
if not content_array:
126+
return
127+
for counter, array_entry in enumerate(content_array):
128+
if array_entry['@type'][0] == 'https://blackducksoftware.github.io/bdio#Project':
129+
logging.debug(counter)
130+
logging.debug(content_array[counter].keys())
131+
if "https://blackducksoftware.github.io/bdio#hasVersion" in content_array[counter]:
132+
content_array[counter]["https://blackducksoftware.github.io/bdio#hasVersion"][0]['@value'] = version
133+
logging.debug(content_array[counter])
134+
135+
136+
def read_json_object(filepath):
137+
with open(os.path.join(temp_dir, filepath)) as jsonfile:
138+
data = json.load(jsonfile)
139+
return data
140+
141+
142+
def write_json_file(filepath, data):
143+
with open(filepath, "w") as outfile:
144+
json.dump(data, outfile)
145+
146+
147+
# copy .bdio file to temp directory
148+
# unzip it
149+
# change the project version in jsonld file
150+
# zip files back in to a bdio file of the same name
151+
# copy from temp in to a directory named "renamed-bdios"
152+
# do the next bdio file
153+
# delete the contents of the temp directory
154+
def bdio_update_project_version():
155+
file_list = [x for x in os.listdir(bdio_dir) if x.endswith('.bdio')]
156+
for file in file_list:
157+
zip_extract_files(os.path.join(bdio_dir, file), temp_dir)
158+
os.chdir(temp_dir)
159+
output_bdio = os.path.join(temp_dir, file)
160+
jsonld_files = [y for y in os.listdir(temp_dir) if y.endswith('.jsonld')]
161+
new_uuid = uuid.uuid1()
162+
for jsonld_file in jsonld_files:
163+
jsonld_path = os.path.join(temp_dir, jsonld_file)
164+
data = read_json_object(jsonld_path)
165+
jsonld_update_project_name(data, project_name)
166+
jsonld_update_uuid(data, "urn:uuid:{}".format(new_uuid))
167+
if jsonld_file.split('-')[1] == 'header.jsonld':
168+
jsonld_update_version_name_in_header(data, target_version)
169+
else:
170+
jsonld_update_project_version(data, target_version)
171+
write_json_file(jsonld_path, data)
172+
zip_create_archive(output_bdio, temp_dir)
173+
shutil.copy(output_bdio, renamed_directory)
174+
print('Cleaning up temp directory ')
175+
do_refresh('temp')
176+
os.chdir(working_dir)
177+
shutil.rmtree('temp')
178+
179+
180+
def main():
181+
check_dirs()
182+
bdio_update_project_version()
183+
184+
185+
main()

0 commit comments

Comments
 (0)