Skip to content

Commit dabd48f

Browse files
committed
vuln status report added
1 parent 2567d47 commit dabd48f

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#!/usr/bin/env python
2+
3+
'''
4+
Copyright (C) 2021 Synopsys, Inc.
5+
http://www.blackducksoftware.com/
6+
7+
Licensed to the Apache Software Foundation (ASF) under one
8+
or more contributor license agreements. See the NOTICE file
9+
distributed with this work for additional information
10+
regarding copyright ownership. The ASF licenses this file
11+
to you under the Apache License, Version 2.0 (the
12+
"License"); you may not use this file except in compliance
13+
with the License. You may obtain a copy of the License at
14+
15+
http://www.apache.org/licenses/LICENSE-2.0
16+
17+
Unless required by applicable law or agreed to in writing,
18+
software distributed under the License is distributed on an
19+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
KIND, either express or implied. See the License for the
21+
specific language governing permissions and limitations
22+
under the License.
23+
24+
'''
25+
import argparse
26+
from datetime import datetime
27+
import json
28+
import logging
29+
import requests
30+
import sys
31+
import time
32+
import csv
33+
34+
from blackduck import Client
35+
36+
DEFAULT_OUTPUT_FILE="vuln_status_report.csv"
37+
38+
parser = argparse.ArgumentParser("Generate a vulnerability status report")
39+
parser.add_argument("--base-url", help="Hub server URL e.g. https://your.blackduck.url")
40+
parser.add_argument("--token-file", help="containing access token")
41+
parser.add_argument("--projects", nargs="+", help="The list of projects to include in the report")
42+
parser.add_argument('-t', '--tries', default=10, type=int, help="How many times to retry downloading the report, i.e. wait for the report to be generated")
43+
parser.add_argument("-o", "--output-file-name",
44+
dest="file_name",
45+
default=DEFAULT_OUTPUT_FILE,
46+
help=f"Name of the output file (default: {DEFAULT_OUTPUT_FILE})")
47+
parser.add_argument("--no-verify",
48+
dest='verify',
49+
action='store_false',
50+
help="disable TLS certificate verification")
51+
args = parser.parse_args()
52+
53+
54+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stdout, level=logging.DEBUG)
55+
logging.getLogger("requests").setLevel(logging.WARNING)
56+
logging.getLogger("urllib3").setLevel(logging.WARNING)
57+
logging.getLogger("blackduck").setLevel(logging.WARNING)
58+
59+
class FailedReportDownload(Exception):
60+
pass
61+
62+
def download_vuln_report(bd_client, location, filename, report_format, retries=args.tries):
63+
if retries:
64+
report_status = bd_client.session.get(location).json()
65+
if report_status['status'] == 'COMPLETED':
66+
download_url = bd_client.list_resources(report_status)['download']
67+
contents_url = download_url + "/contents"
68+
report_contents = bd_client.session.get(contents_url).json()
69+
if report_format == 'JSON':
70+
with open(filename, 'w') as f:
71+
json.dump(report_contents, f, indent=3)
72+
logging.info(f"Wrote vulnerability status report contents to {filename}")
73+
elif report_format == 'CSV':
74+
csv_data = report_contents['reportContent'][0]['fileContent']
75+
with open(filename, 'w') as f:
76+
f.write(csv_data)
77+
logging.info(f"Wrote vulnerability status report contents to {filename}")
78+
else:
79+
logging.error(f"Unrecognized format ({report_format}) given. Exiting")
80+
81+
else:
82+
sleep_time = 25
83+
retries -= 1
84+
logging.debug(f"Report is not ready to download yet, waiting {sleep_time} seconds and then retrying {retries} more times")
85+
time.sleep(sleep_time)
86+
download_vuln_report(bd_client, location, filename, report_format, retries)
87+
else:
88+
raise FailedReportDownload(f"Failed to retrieve report from {location} after {retries} attempts")
89+
90+
91+
def get_projects(client, project_names):
92+
print (project_names)
93+
'''Given a list of project names return a list of the corresponding project URLs'''
94+
project_urls = list()
95+
for project in client.get_items("/api/projects"):
96+
if project['name'] in project_names:
97+
project_urls.append(project['_meta']['href'])
98+
return project_urls
99+
100+
def augment_filename(filename):
101+
if filename.endswith('.csv'):
102+
index = filename.index('.csv')
103+
return filename[:index] + '_augmented' + filename[index:]
104+
else:
105+
return filename + '_augmented.csv'
106+
107+
def correct_vuln_ids(bd, filename, new_filename):
108+
logging.debug(f"Generating file with augmented vuln ids as {new_filename}")
109+
input = open(filename, 'r')
110+
reader = csv.DictReader(input)
111+
fieldnames = reader.fieldnames
112+
rowcount = 0
113+
with open(new_filename, 'w') as output:
114+
writer = csv.DictWriter(output, fieldnames=fieldnames)
115+
writer.writeheader()
116+
for row in reader:
117+
vuln_id = row['Vulnerability id']
118+
related_vuln_id = get_related_vuln_id(bd, vuln_id)
119+
if related_vuln_id:
120+
correct_vuln_id = f"{vuln_id} ({related_vuln_id})"
121+
else:
122+
correct_vuln_id = vuln_id
123+
row['Vulnerability id'] = correct_vuln_id
124+
writer.writerow(row)
125+
rowcount+=1
126+
if rowcount % 100 == 0:
127+
logging.debug(f"{rowcount:15} rows written into {new_filename}")
128+
logging.debug(f"Total of {rowcount} rows written into {new_filename}")
129+
logging.info(f"Wrote vulnerability status report contents to {filename}")
130+
131+
def get_related_vuln_id(bd, vuln_id):
132+
related_id = None
133+
if vuln_id.startswith('BDSA') and 'CVE' not in vuln_id:
134+
vuln_data = bd.get_json(f"/api/vulnerabilities/{vuln_id}")
135+
vuln_resources = bd.list_resources(vuln_data)
136+
related_url = vuln_resources.get('related-vulnerability', None)
137+
if related_url:
138+
related_id = related_url.split('/')[-1:][0]
139+
return related_id
140+
141+
with open(args.token_file, 'r') as tf:
142+
access_token = tf.readline().strip()
143+
144+
bd = Client(
145+
base_url=args.base_url,
146+
token=access_token,
147+
verify=args.verify
148+
)
149+
150+
project_urls = get_projects(bd, args.projects)
151+
logging.debug(f"Generating vulnerability status report for the following projects: {args.projects}")
152+
logging.debug(f"Project list resulted in following project URLs {project_urls}")
153+
post_data = {
154+
'reportFormat': 'CSV',
155+
'projects': project_urls,
156+
'locale': 'en_US'
157+
}
158+
159+
try:
160+
r = bd.session.post("/api/vulnerability-status-reports", json=post_data)
161+
r.raise_for_status()
162+
report_url = r.headers['Location']
163+
logging.debug(f"created vulnerability status report {report_url}")
164+
except requests.HTTPError as err:
165+
# more fine grained error handling here; otherwise:
166+
bd.http_error_handler(err)
167+
logging.error("Failed to generate the report")
168+
sys.exit(1)
169+
170+
download_vuln_report(bd, report_url, args.file_name, 'CSV', retries=args.tries)
171+
172+
correct_vuln_ids(bd, args.file_name, augment_filename(args.file_name))
173+
174+
175+
176+
177+
178+
179+
180+
181+

0 commit comments

Comments
 (0)