Skip to content

Commit 6f6614d

Browse files
author
Glenn Snyder
committed
adding sample to show how to generate and retrieve vulnerability remediation report
1 parent ad230b0 commit 6f6614d

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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+
33+
from dateutil.parser import parse # for parsing of input date/time strings
34+
35+
from blackduck import Client
36+
37+
# TODO: Refactor and put these contants into blackduck/constants in a new version of the Client lib
38+
remediation_types = [
39+
'DUPLICATE',
40+
'IGNORED',
41+
'MITIGATED',
42+
'NEEDS_REVIEW',
43+
'NEW',
44+
'PATCHED',
45+
'REMEDIATION_COMPLETE',
46+
'REMEDIATION_REQUIRED'
47+
]
48+
49+
DEFAULT_OUTPUT_FILE="vuln_remediation_report.json"
50+
51+
parser = argparse.ArgumentParser("Generate a vulnerability remediation report")
52+
parser.add_argument("base_url", help="Hub server URL e.g. https://your.blackduck.url")
53+
parser.add_argument("token_file", help="containing access token")
54+
parser.add_argument("projects", nargs="+", help="The list of projects to include in the report")
55+
parser.add_argument("-s", "--start-date", dest="start_date", required=True, help="The start date for the report (required)")
56+
parser.add_argument("-e", "--end-date",
57+
dest="end_date",
58+
help="The end date for the report. Default is today/now")
59+
parser.add_argument("-f", "--format",
60+
choices=['JSON', 'CSV'],
61+
default='JSON',
62+
help="The report format (either CSV or JSON, default: JSON)")
63+
parser.add_argument("-r", "--remediation-types",
64+
dest="remediation_types",
65+
default = remediation_types,
66+
nargs="+",
67+
help=f"The remediation types which can be one or more of the following: {remediation_types}. The default is all remediation types")
68+
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")
69+
parser.add_argument("-o", "--output-file-name",
70+
dest="file_name",
71+
default=DEFAULT_OUTPUT_FILE,
72+
help=f"Name of the output file (default: {DEFAULT_OUTPUT_FILE})")
73+
parser.add_argument("--no-verify",
74+
dest='verify',
75+
action='store_false',
76+
help="disable TLS certificate verification")
77+
args = parser.parse_args()
78+
79+
80+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stdout, level=logging.DEBUG)
81+
logging.getLogger("requests").setLevel(logging.WARNING)
82+
logging.getLogger("urllib3").setLevel(logging.WARNING)
83+
logging.getLogger("blackduck").setLevel(logging.WARNING)
84+
85+
class FailedReportDownload(Exception):
86+
pass
87+
88+
def download_vuln_report(bd_client, location, filename, report_format, retries=args.tries):
89+
if retries:
90+
report_status = bd_client.session.get(location).json()
91+
if report_status['status'] == 'COMPLETED':
92+
download_url = bd_client.list_resources(report_status)['download']
93+
contents_url = download_url + "/contents"
94+
report_contents = bd_client.session.get(contents_url).json()
95+
if report_format == 'JSON':
96+
with open(filename, 'w') as f:
97+
json.dump(report_contents, f, indent=3)
98+
logging.info(f"Wrote vulnerability remediation report contents to {filename}")
99+
elif report_format == 'CSV':
100+
csv_data = report_contents['reportContent'][0]['fileContent']
101+
with open(filename, 'w') as f:
102+
f.write(csv_data)
103+
logging.info(f"Wrote vulnerability remediation report contents to {filename}")
104+
else:
105+
logging.error(f"Unrecognized format ({report_format}) given. Exiting")
106+
107+
else:
108+
sleep_time = 5
109+
retries -= 1
110+
logging.debug(f"Report is not ready to download yet, waiting {sleep_time} seconds and then retrying {retries} more times")
111+
time.sleep(sleep_time)
112+
download_vuln_report(bd_client, location, filename, report_format, retries)
113+
else:
114+
raise FailedReportDownload(f"Failed to retrieve report from {location} after {retries} attempts")
115+
116+
117+
def get_projects(client, project_names):
118+
'''Given a list of project names return a list of the corresponding project URLs'''
119+
project_urls = list()
120+
for project in client.get_items("/api/projects"):
121+
if project['name'] in project_names:
122+
project_urls.append(project['_meta']['href'])
123+
return project_urls
124+
125+
with open(args.token_file, 'r') as tf:
126+
access_token = tf.readline().strip()
127+
128+
bd = Client(
129+
base_url=args.base_url,
130+
token=access_token,
131+
verify=args.verify
132+
)
133+
134+
start_date = parse(args.start_date)
135+
end_date = parse(args.end_date) if args.end_date else datetime.now()
136+
project_urls = get_projects(bd, args.projects)
137+
138+
logging.debug(f"Generating vulnerability remediation report for the following projects: {args.projects}")
139+
logging.debug(f"start date: {start_date}, end date: {end_date}")
140+
post_data = {
141+
'startDate': start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
142+
'reportFormat': args.format,
143+
'projects': project_urls,
144+
'locale': 'en_US'
145+
}
146+
147+
if end_date:
148+
post_data.update({
149+
'endDate': end_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
150+
})
151+
152+
try:
153+
r = bd.session.post("/api/vulnerability-remediation-reports", json=post_data)
154+
r.raise_for_status()
155+
report_url = r.headers['Location']
156+
logging.debug(f"created vulnerability remediation report {report_url}")
157+
except requests.HTTPError as err:
158+
# more fine grained error handling here; otherwise:
159+
bd.http_error_handler(err)
160+
logging.error("Failed to generate the report")
161+
sys.exit(1)
162+
163+
download_vuln_report(bd, report_url, args.file_name, args.format, retries=args.tries)
164+
165+
166+
167+
168+
169+
170+
171+
172+

0 commit comments

Comments
 (0)