Skip to content

Commit c0d43ab

Browse files
author
Jakob Maier
committed
added functionality, added new script
1 parent 5bc9937 commit c0d43ab

File tree

2 files changed

+129
-14
lines changed

2 files changed

+129
-14
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'''
2+
Export the vulnerabilites from a project as CSV. Can be used to apply batch vulnerability
3+
remediation with vuln_batch_remediation.py
4+
5+
Output is in format:
6+
identifier, status, comment, componentName, componentVersion, description
7+
8+
The API token should be specified in a .env file.
9+
'''
10+
import re
11+
import os
12+
import sys
13+
import csv
14+
import logging
15+
import argparse
16+
from pprint import pprint
17+
from blackduck import Client
18+
from dotenv import load_dotenv
19+
20+
load_dotenv()
21+
22+
API_TOKEN = os.getenv('API_TOKEN')
23+
24+
logging.basicConfig(
25+
level=logging.DEBUG,
26+
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
27+
)
28+
29+
def main():
30+
program_name = os.path.basename(sys.argv[0])
31+
parser = argparse.ArgumentParser(prog=program_name, usage="%(prog)s [options]", description="Automated Assessment")
32+
parser.add_argument("--output", required=False,help="csv output path" )
33+
parser.add_argument("--project", required=True, help="project name")
34+
parser.add_argument("--base-url", required=False, help="base url", default="https://blackduck.omicron.at")
35+
parser.add_argument("--version", required=False, help="project version, e.g. latest")
36+
parser.add_argument("--component", required=False, help="component name")
37+
args = parser.parse_args()
38+
39+
component = args.component
40+
projectname = args.project
41+
projectversion = args.version
42+
output = args.output if args.output != None else "output.csv"
43+
44+
csv_file = open(output, mode='w', newline='', encoding='utf-8')
45+
csv_writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
46+
47+
bd = Client(
48+
token=API_TOKEN,
49+
base_url=args.base_url,
50+
verify=False # TLS certificate verification
51+
)
52+
53+
for project in bd.get_resource('projects'):
54+
if (project['name'] == projectname):
55+
for version in bd.get_resource('versions', project):
56+
if (projectversion == None):
57+
pprint(version['versionName'])
58+
59+
else:
60+
if (version['versionName'] == projectversion):
61+
for vulnverable_component in bd.get_resource('vulnerable-components', version):
62+
componentName = vulnverable_component["componentName"]
63+
64+
if (component == None or re.search(component, componentName, re.IGNORECASE)):
65+
componentVersion = vulnverable_component["componentVersionName"]
66+
remediation = vulnverable_component['vulnerabilityWithRemediation']
67+
68+
status = remediation['remediationStatus']
69+
identifier = remediation['vulnerabilityName']
70+
description = remediation['description'].replace('\r', '').replace('\n', '')
71+
comment = remediation.get('remediationComment', "").replace('\r', '').replace('\n', '')
72+
73+
row = [identifier, status, comment, componentName, componentVersion, description]
74+
csv_writer.writerow(row)
75+
break
76+
break
77+
78+
79+
80+
if __name__ == "__main__":
81+
main()

examples/vuln_batch_remediation.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
Each processing step can be turned on or off. At least one step must be run. Default
2727
is to run both.
2828
29-
The script get's it CVE and orign lists from CSV files. The CSV filenames are loaded
29+
The script can get's its CVE and orign lists from CSV files. The CSV filenames are loaded
3030
from Custom Fields in the Black Duck project. This allows different groups of projects to
3131
use different remeidation settings. If a CVE remediation status should apply globally
3232
to all projects, Black Duck's global remediation feature should be used.
3333
34+
The script can also get the CSV filenames from the command line arguments.
35+
3436
Here is an example of the CSV data for the CVE list:
3537
3638
"CVE-2016-1840","IGNORED","Applies only to Apple OS"
@@ -81,15 +83,19 @@
8183

8284

8385
def load_remediation_input(remediation_file):
84-
with open(remediation_file, mode='r') as infile:
86+
with open(remediation_file, mode='r', encoding="utf-8") as infile:
8587
reader = csv.reader(infile)
8688
return {rows[0]:[rows[1],rows[2]] for rows in reader}
8789

8890
def remediation_is_valid(vuln, remediation_data):
8991
vulnerability_name = vuln['vulnerabilityWithRemediation']['vulnerabilityName']
90-
# remediation_status = vuln['vulnerabilityWithRemediation']['remediationStatus']
91-
# remediation_comment = vuln['vulnerabilityWithRemediation'].get('remediationComment','')
92+
remediation_status = vuln['vulnerabilityWithRemediation']['remediationStatus']
93+
remediation_comment = vuln['vulnerabilityWithRemediation'].get('remediationComment','')
94+
9295
if vulnerability_name in remediation_data.keys():
96+
remediation = remediation_data[vulnerability_name]
97+
if (remediation_status == remediation[0] and remediation_comment == remediation[1]):
98+
return None
9399
return remediation_data[vulnerability_name]
94100
else:
95101
return None
@@ -114,9 +120,19 @@ def find_custom_field_value (custom_fields, custom_field_label):
114120
return None
115121
return None
116122

123+
124+
125+
def set_vulnerablity_remediation(hub, vuln, remediation_status, remediation_comment):
126+
url = vuln['_meta']['href']
127+
update={}
128+
update['remediationStatus'] = remediation_status
129+
update['comment'] = remediation_comment
130+
response = hub.execute_put(url, data=update)
131+
return response
132+
117133
def process_vulnerabilities(hub, vulnerable_components, remediation_data=None, exclusion_data=None):
118134
count = 0
119-
print('"Component Name","Component Version","Component OriginID","CVE","Reason","Remeidation Status","HTTP response code"')
135+
print('"Component Name","Component Version","CVE","Reason","Remeidation Status","HTTP response code"')
120136

121137
for vuln in vulnerable_components['items']:
122138
if vuln['vulnerabilityWithRemediation']['remediationStatus'] == "NEW":
@@ -125,6 +141,8 @@ def process_vulnerabilities(hub, vulnerable_components, remediation_data=None, e
125141

126142
if (exclusion_data):
127143
exclusion_action = origin_is_excluded(vuln, exclusion_data)
144+
else:
145+
exclusion_action = None
128146

129147
# If vuln has both a remdiation action and an origin exclusion action, set remdiation status
130148
# to the remdiation action. Append the exclusion action's comment to the overall comment.
@@ -137,13 +155,14 @@ def process_vulnerabilities(hub, vulnerable_components, remediation_data=None, e
137155
reason = 'origin-exclusion'
138156

139157
if (remediation_action):
140-
resp = hub.set_vulnerablity_remediation(vuln, remediation_action[0],remediation_action[1])
158+
resp = set_vulnerablity_remediation(hub, vuln, remediation_action[0],remediation_action[1])
141159
count += 1
142-
print ('\"{}\",\"{}\",\"{}\",\"{}\",\"{}\",\"{}\",\"{}\"'.
160+
print ('\"{}\",\"{}\",\"{}\",\"{}\",\"{}\",\"{}\"'.
143161
format(vuln['componentName'], vuln['componentVersionName'],
144-
vuln['componentVersionOriginId'],
145162
vuln['vulnerabilityWithRemediation']['vulnerabilityName'],
146163
reason, remediation_action[0], resp.status_code))
164+
165+
147166
print (f'Remediated {count} vulnerabilities.')
148167

149168
def main(argv=None): # IGNORE:C0111
@@ -178,7 +197,9 @@ def main(argv=None): # IGNORE:C0111
178197
parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
179198
parser.add_argument("projectname", help="Project nname")
180199
parser.add_argument("projectversion", help="Project vesrsion")
181-
parser.add_argument("--no-process-cve-remediation-list", dest='process_cve_remediation_list', action='store_false', help="Disbable processing CVE-Remediation-list")
200+
parser.add_argument("--remediation-list", dest="local_remediation_list", default=None, help="Filename of cve remediation list csv file")
201+
parser.add_argument("--origin-exclusion-list", dest="local_origin_exclusion_list", default=None, help="Filename of origin exclusion list csv file")
202+
parser.add_argument("--no-process-cve-remediation-list", dest='process_cve_remediation_list', action='store_false', help="Disable processing CVE-Remediation-list")
182203
parser.add_argument("--no-process-origin-exclusion-list", dest='process_origin_exclusion_list', action='store_false', help="Disable processing Origin-Exclusion-List")
183204
parser.add_argument("--cve-remediation-list-custom-field-label", default='CVE Remediation List', help='Label of Custom Field on Black Duck that contains remeidation list file name')
184205
parser.add_argument("--origin-exclusion-list-custom-field-label", default='Origin Exclusion List', help='Label of Custom Field on Black Duck that containts origin exclusion list file name')
@@ -189,6 +210,8 @@ def main(argv=None): # IGNORE:C0111
189210

190211
projectname = args.projectname
191212
projectversion = args.projectversion
213+
local_cve_remediation_file = args.local_remediation_list
214+
local_origin_exclusion_file = args.local_origin_exclusion_list
192215
process_cve_remediation = args.process_cve_remediation_list
193216
process_origin_exclulsion = args.process_origin_exclusion_list
194217

@@ -203,21 +226,32 @@ def main(argv=None): # IGNORE:C0111
203226
hub = HubInstance()
204227
project = hub.get_project_by_name(projectname)
205228
version = hub.get_project_version_by_name(projectname, projectversion)
206-
custom_fields = hub.get_project_custom_fields (project)
229+
230+
custom_fields = hub.get_cf_values(project)
207231

208232
if (process_cve_remediation):
209-
cve_remediation_file = find_custom_field_value (custom_fields, args.cve_remediation_list_custom_field_label)
210-
print (f' Opening: {args.cve_remediation_list_custom_field_label}:{cve_remediation_file}')
233+
if (local_cve_remediation_file):
234+
cve_remediation_file = local_cve_remediation_file
235+
print (f' Opening CVE remediation file: {cve_remediation_file}')
236+
else:
237+
cve_remediation_file = find_custom_field_value (custom_fields, args.cve_remediation_list_custom_field_label)
238+
print (f' Opening: {args.cve_remediation_list_custom_field_label}:{cve_remediation_file}')
239+
211240
remediation_data = load_remediation_input(cve_remediation_file)
212241
else:
213242
remediation_data = None
214243

215244
if (process_origin_exclulsion):
216-
exclusion_list_file = find_custom_field_value (custom_fields, args.origin_exclusion_list_custom_field_label)
217-
print (f' Opening: {args.origin_exclusion_list_custom_field_label}:{exclusion_list_file}')
245+
if local_origin_exclusion_file:
246+
exclusion_list_file = local_origin_exclusion_file
247+
print (f' Opening origin exclusion list: {exclusion_list_file}')
248+
else:
249+
exclusion_list_file = find_custom_field_value (custom_fields, args.origin_exclusion_list_custom_field_label)
250+
print (f' Opening: {args.origin_exclusion_list_custom_field_label}:{exclusion_list_file}')
218251
exclusion_data = load_remediation_input(exclusion_list_file)
219252
else:
220253
exclusion_data = None
254+
221255

222256
# Retrieve the vulnerabiltites for the project version
223257
vulnerable_components = hub.get_vulnerable_bom_components(version)

0 commit comments

Comments
 (0)