1
+ #!/usr/bin/python3
2
+ # encoding: utf-8
3
+ '''
4
+ examples.vulnerability_remediation -- shortdesc
5
+ examples.vulnerability_remediation is a description
6
+ It defines classes_and_methods
7
+ @author: user_name
8
+ @copyright: 2020 organization_name. All rights reserved.
9
+ @license: license
10
+ @contact: user_email
11
+ @deffield updated: Updated
12
+ '''
13
+
14
+ '''
15
+ This script updates vulnerablity remedation status for specific CVEs
16
+ or specific origins in a Black Duck projec/version. The intent is to reduce the number of
17
+ new vulnearblitites that need to be manually reviewed. The script only process
18
+ vulnerablities that are currently have NEW remediation stautus. The script looks for two
19
+ types of matches for vulnerablitites.
20
+ They are:
21
+
22
+ o) Specific CVE - intended to apply remedation status for specific CVE
23
+ o) Origin subtring - intended to apply remediation status for specific origins
24
+ For example, origns for a particular processor architecture (PPC)
25
+
26
+ Each processing step can be turned on or off. At least one step must be run. Default
27
+ is to run both.
28
+
29
+ The script get's it CVE and orign lists from CSV files. The CSV filenames are loaded
30
+ from Custom Fields in the Black Duck project. This allows different groups of projects to
31
+ use different remeidation settings. If a CVE remediation status should apply globally
32
+ to all projects, Black Duck's global remediation feature should be used.
33
+
34
+ Here is an example of the CSV data for the CVE list:
35
+
36
+ "CVE-2016-1840","IGNORED","Applies only to Apple OS"
37
+ "CVE-2019-15847","IGNORED","Applies to Power9 architecture"
38
+ "CVE-2016-4606","IGNORED","Applies only to Apple OS"
39
+
40
+ The 1st column is used for exact matches to CVE ids on vulnerablitites.
41
+ The 2nd column is the new remediation status. Thus must be a valid Black duck status.
42
+ The 3rd column is a comment that will be added to the vulnerablity without change.
43
+
44
+ Here is an example of the CSV data for the origin exclusion list:
45
+
46
+ "ppc","IGNORED","Ignore PPC origins"
47
+ "armv7hl","NEEDS_REVIEW","Review ARMV7HL origins"
48
+
49
+ The 1st column is used for substring match to OriginID
50
+ The 2nd column is the new remediation status. Thus must be a valid Black duck status.
51
+ The 3rd column is a comment that will be added to the vulnerablity without change.
52
+
53
+ If a vulnerablity matches both CVE and origin exclusion, the CVE remeditation is applied.
54
+ The comment will be updated the value from both files.
55
+
56
+ Black Duck custom fields are used to hold the file names. The files are opened
57
+ relative to the directory where the script is run. The default Custom Field labels
58
+ the script looks for are:
59
+ CVE Remediation List
60
+ Origin Exclusion List
61
+ The lables can be changed from the command line, if needed.
62
+
63
+ '''
64
+
65
+ import sys
66
+ import os
67
+ import json
68
+ import csv
69
+ import traceback
70
+
71
+ from argparse import ArgumentParser
72
+ from argparse import RawDescriptionHelpFormatter
73
+
74
+ from blackduck .HubRestApi import HubInstance
75
+
76
+
77
+ __all__ = []
78
+ __version__ = 0.1
79
+ __date__ = '2020-12-21'
80
+ __updated__ = '2021-12-26'
81
+
82
+
83
+ def load_remediation_input (remediation_file ):
84
+ with open (remediation_file , mode = 'r' ) as infile :
85
+ reader = csv .reader (infile )
86
+ return {rows [0 ]:[rows [1 ],rows [2 ]] for rows in reader }
87
+
88
+ def remediation_is_valid (vuln , remediation_data ):
89
+ vulnerability_name = vuln ['vulnerabilityWithRemediation' ]['vulnerabilityName' ]
90
+ # remediation_status = vuln['vulnerabilityWithRemediation']['remediationStatus']
91
+ # remediation_comment = vuln['vulnerabilityWithRemediation'].get('remediationComment','')
92
+ if vulnerability_name in remediation_data .keys ():
93
+ return remediation_data [vulnerability_name ]
94
+ else :
95
+ return None
96
+
97
+ def origin_is_excluded (vuln , exclusion_data ):
98
+ if 'componentVersionOriginId' in vuln .keys ():
99
+ originId = vuln ['componentVersionOriginId' ]
100
+ for excludedOrigin in exclusion_data :
101
+ if excludedOrigin in originId :
102
+ return exclusion_data [excludedOrigin ]
103
+ return None
104
+ else :
105
+ return None
106
+
107
+ def find_custom_field_value (custom_fields , custom_field_label ):
108
+ for field in custom_fields ['items' ]:
109
+ if field ['label' ] == custom_field_label :
110
+ if len (field ['values' ]) > 0 :
111
+ return field ['values' ][0 ]
112
+ else :
113
+ print (f'Error: Custom Field \" { custom_field_label } \" is empty on Black Duck instance.' )
114
+ return None
115
+ return None
116
+
117
+ def process_vulnerabilities (hub , vulnerable_components , remediation_data = None , exclusion_data = None ):
118
+ count = 0
119
+ print ('"Component Name","Component Version","Component OriginID","CVE","Reason","Remeidation Status","HTTP response code"' )
120
+
121
+ for vuln in vulnerable_components ['items' ]:
122
+ if vuln ['vulnerabilityWithRemediation' ]['remediationStatus' ] == "NEW" :
123
+ if (remediation_data ):
124
+ remediation_action = remediation_is_valid (vuln , remediation_data )
125
+
126
+ if (exclusion_data ):
127
+ exclusion_action = origin_is_excluded (vuln , exclusion_data )
128
+
129
+ # If vuln has both a remdiation action and an origin exclusion action, set remdiation status
130
+ # to the remdiation action. Append the exclusion action's comment to the overall comment.
131
+ reason = 'CVE-list'
132
+ if (remediation_action and exclusion_action ):
133
+ remediation_action [1 ] = exclusion_action [1 ] + '\n ' + remediation_action [1 ]
134
+ reason = 'CVE-list and origin-exclusion'
135
+ elif (exclusion_action ): # If only exclusion action found, use it to set remediation status
136
+ remediation_action = exclusion_action
137
+ reason = 'origin-exclusion'
138
+
139
+ if (remediation_action ):
140
+ resp = hub .set_vulnerablity_remediation (vuln , remediation_action [0 ],remediation_action [1 ])
141
+ count += 1
142
+ print ('\" {}\" ,\" {}\" ,\" {}\" ,\" {}\" ,\" {}\" ,\" {}\" ,\" {}\" ' .
143
+ format (vuln ['componentName' ], vuln ['componentVersionName' ],
144
+ vuln ['componentVersionOriginId' ],
145
+ vuln ['vulnerabilityWithRemediation' ]['vulnerabilityName' ],
146
+ reason , remediation_action [0 ], resp .status_code ))
147
+ print (f'Remediated { count } vulnerabilities.' )
148
+
149
+ def main (argv = None ): # IGNORE:C0111
150
+ '''Command line options.'''
151
+
152
+ if argv is None :
153
+ argv = sys .argv
154
+ else :
155
+ sys .argv .extend (argv )
156
+
157
+ program_name = os .path .basename (sys .argv [0 ])
158
+ program_version = "v%s" % __version__
159
+ program_build_date = str (__updated__ )
160
+ program_version_message = '%%(prog)s %s (%s)' % (program_version , program_build_date )
161
+ program_shortdesc = __import__ ('__main__' ).__doc__ .split ("\n " )[1 ]
162
+ program_license = '''%s
163
+
164
+ Created by user_name on %s.
165
+ Copyright 2020 Synopsys. All rights reserved.
166
+
167
+ Licensed under the Apache License 2.0
168
+ http://www.apache.org/licenses/LICENSE-2.0
169
+
170
+ Distributed on an "AS IS" basis without warranties
171
+ or conditions of any kind, either express or implied.
172
+
173
+ USAGE
174
+ ''' % (program_shortdesc , str (__date__ ))
175
+
176
+ try :
177
+ # Setup argument parser
178
+ parser = ArgumentParser (description = program_license , formatter_class = RawDescriptionHelpFormatter )
179
+ parser .add_argument ("projectname" , help = "Project nname" )
180
+ 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" )
182
+ parser .add_argument ("--no-process-origin-exclusion-list" , dest = 'process_origin_exclusion_list' , action = 'store_false' , help = "Disable processing Origin-Exclusion-List" )
183
+ 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' )
184
+ 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' )
185
+ parser .add_argument ('-V' , '--version' , action = 'version' , version = program_version_message )
186
+
187
+ # Process arguments
188
+ args = parser .parse_args ()
189
+
190
+ projectname = args .projectname
191
+ projectversion = args .projectversion
192
+ process_cve_remediation = args .process_cve_remediation_list
193
+ process_origin_exclulsion = args .process_origin_exclusion_list
194
+
195
+ message = f"{ program_version_message } \n \n Project: { projectname } \n Version: { projectversion } \n Process origin exclusion list: { process_origin_exclulsion } \n Process CVE remediation list: { process_cve_remediation } "
196
+ print (message )
197
+
198
+ if (process_cve_remediation == False ) and (process_origin_exclulsion == False ):
199
+ print ('Error: Nothing to do, both --no-process-cve-remediation-list and --no-process-origin-exclusion-list set.' )
200
+ exit (1 )
201
+
202
+ # Connect to Black Duck instance, retrive project, project version, and the project's custom fields.
203
+ hub = HubInstance ()
204
+ project = hub .get_project_by_name (projectname )
205
+ version = hub .get_project_version_by_name (projectname , projectversion )
206
+ custom_fields = hub .get_project_custom_fields (project )
207
+
208
+ 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 } ' )
211
+ remediation_data = load_remediation_input (cve_remediation_file )
212
+ else :
213
+ remediation_data = None
214
+
215
+ 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 } ' )
218
+ exclusion_data = load_remediation_input (exclusion_list_file )
219
+ else :
220
+ exclusion_data = None
221
+
222
+ # Retrieve the vulnerabiltites for the project version
223
+ vulnerable_components = hub .get_vulnerable_bom_components (version )
224
+
225
+ process_vulnerabilities (hub , vulnerable_components , remediation_data , exclusion_data )
226
+
227
+ return 0
228
+ except Exception :
229
+ ### handle keyboard interrupt ###
230
+ traceback .print_exc ()
231
+ return 0
232
+
233
+ if __name__ == "__main__" :
234
+ sys .exit (main ())
0 commit comments