nexpose_ticketing 0.8.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/nexpose_jira +7 -0
- data/bin/nexpose_remedy +6 -0
- data/bin/nexpose_servicedesk +6 -0
- data/bin/nexpose_servicenow +6 -0
- data/lib/nexpose_ticketing/common_helper.rb +344 -0
- data/lib/nexpose_ticketing/config/servicedesk.config +3 -3
- data/lib/nexpose_ticketing/config/ticket_service.config +2 -0
- data/lib/nexpose_ticketing/helpers/jira_helper.rb +53 -107
- data/lib/nexpose_ticketing/helpers/remedy_helper.rb +208 -594
- data/lib/nexpose_ticketing/helpers/servicedesk_helper.rb +302 -289
- data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +74 -165
- data/lib/nexpose_ticketing/nx_logger.rb +139 -30
- data/lib/nexpose_ticketing/queries.rb +148 -59
- data/lib/nexpose_ticketing/ticket_repository.rb +6 -0
- data/lib/nexpose_ticketing/ticket_service.rb +28 -16
- data/lib/nexpose_ticketing/version.rb +3 -0
- metadata +14 -6
- data/Gemfile.lock +0 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16cf7f70522d51ed7a38070041ad4396e4e56ce7
|
4
|
+
data.tar.gz: 946842a9a482cbf608fbdf1bf9853919db1cdbe5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa83dfe6f2d916f880fc95c99cbebb01ae1998f066affc7585ed52de884201c0b85d2e3980bad6ac2f6d8d918975a0c208f68a28b6cc5664b9ae2562709b6aaf
|
7
|
+
data.tar.gz: 9002b4380aa921cdad95f5188011a47887865ef88d75b929817ef137bd83ec3f31094acd3cf679a9eca6d74170fb493c31d5fcc4763d2926982805246d106134
|
data/bin/nexpose_jira
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'yaml'
|
3
3
|
require 'nexpose_ticketing'
|
4
|
+
require 'nexpose_ticketing/nx_logger'
|
5
|
+
require 'nexpose_ticketing/version'
|
6
|
+
|
7
|
+
log = NexposeTicketing::NxLogger.instance
|
8
|
+
log.setup_statistics_collection('Atlassian', 'JIRA', NexposeTicketing::VERSION)
|
9
|
+
log.setup_logging(true, 'info')
|
10
|
+
|
4
11
|
# Path to the JIRA Configuration file.
|
5
12
|
JIRA_CONFIG_PATH = File.join(File.dirname(__FILE__),
|
6
13
|
'../lib/nexpose_ticketing/config/jira.config')
|
data/bin/nexpose_remedy
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'yaml'
|
3
3
|
require 'nexpose_ticketing'
|
4
|
+
require 'nexpose_ticketing/nx_logger'
|
5
|
+
require 'nexpose_ticketing/version'
|
6
|
+
|
7
|
+
log = NexposeTicketing::NxLogger.instance
|
8
|
+
log.setup_statistics_collection('bmc', 'Remedy', NexposeTicketing::VERSION)
|
9
|
+
log.setup_logging(true, 'info')
|
4
10
|
|
5
11
|
# Path to ServiceNow configuration file
|
6
12
|
REMEDY_CONFIG_PATH = File.join(File.dirname(__FILE__),
|
data/bin/nexpose_servicedesk
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'yaml'
|
3
3
|
require 'nexpose_ticketing'
|
4
|
+
require 'nexpose_ticketing/nx_logger'
|
5
|
+
require 'nexpose_ticketing/version'
|
6
|
+
|
7
|
+
log = NexposeTicketing::NxLogger.instance
|
8
|
+
log.setup_statistics_collection('ManageEngine', 'ServiceDesk', NexposeTicketing::VERSION)
|
9
|
+
log.setup_logging(true, 'info')
|
4
10
|
|
5
11
|
# Path to ServiceNow configuration file
|
6
12
|
SERVICEDESK_CONFIG_PATH = File.join(File.dirname(__FILE__),
|
data/bin/nexpose_servicenow
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'yaml'
|
3
3
|
require 'nexpose_ticketing'
|
4
|
+
require 'nexpose_ticketing/nx_logger'
|
5
|
+
require 'nexpose_ticketing/version'
|
6
|
+
|
7
|
+
log = NexposeTicketing::NxLogger.instance
|
8
|
+
log.setup_statistics_collection('ServiceNow', 'ServiceNow', NexposeTicketing::VERSION)
|
9
|
+
log.setup_logging(true, 'info')
|
4
10
|
|
5
11
|
# Path to ServiceNow configuration file
|
6
12
|
SERVICENOW_CONFIG_PATH = File.join(File.dirname(__FILE__),
|
@@ -0,0 +1,344 @@
|
|
1
|
+
module NexposeTicketing
|
2
|
+
class CommonHelper
|
3
|
+
MAX_NUM_REFS = 3
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@ticketing_mode = options[:ticket_mode]
|
7
|
+
end
|
8
|
+
|
9
|
+
# Gets the base description hash from the relevant mode-specific method
|
10
|
+
# which can converted into a finished description.
|
11
|
+
#
|
12
|
+
# - +nexpose_id+ - The site or tag indentifier.
|
13
|
+
# - +options+ - The options read from the ticket config file.
|
14
|
+
#
|
15
|
+
# * *Returns* :
|
16
|
+
# - Hash containing ticket description information.
|
17
|
+
#
|
18
|
+
def get_description(nexpose_id, row)
|
19
|
+
description = { nxid: "NXID: #{generate_nxid(nexpose_id, row)}" }
|
20
|
+
case @ticketing_mode
|
21
|
+
when 'D' then get_default_ticket_description(description, row)
|
22
|
+
when 'I' then get_ip_ticket_description(description, row)
|
23
|
+
when 'V' then get_vuln_ticket_description(description, row)
|
24
|
+
else fail "Ticketing mode #{@ticketing_mode} not recognised."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Updates an existing description hash containing information
|
29
|
+
# necessary to generate a ticket description.
|
30
|
+
# Note that Default mode tickets may not be updated.
|
31
|
+
#
|
32
|
+
# - +description+ - The existing ticket hash to be updated.
|
33
|
+
# - +row+ - CSV row containing vulnerability data.
|
34
|
+
#
|
35
|
+
# * *Returns* :
|
36
|
+
# - Hash containing ticket description information.
|
37
|
+
#
|
38
|
+
def update_description(description, row)
|
39
|
+
case @ticketing_mode
|
40
|
+
when 'I' then return update_ip_ticket_description(description, row)
|
41
|
+
when 'V' then return update_vuln_ticket_description(description, row)
|
42
|
+
else description
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generates a final description string based on a description hash.
|
47
|
+
#
|
48
|
+
# - +description+ - The finished ticket hash to be converted.
|
49
|
+
#
|
50
|
+
# * *Returns* :
|
51
|
+
# - String containing ticket description text.
|
52
|
+
#
|
53
|
+
def print_description(description)
|
54
|
+
ticket = case @ticketing_mode
|
55
|
+
when 'D' then print_default_ticket_description(description)
|
56
|
+
when 'I' then print_ip_ticket_description(description)
|
57
|
+
when 'V' then print_vuln_ticket_description(description)
|
58
|
+
else fail "Ticketing mode #{@ticketing_mode} not recognised."
|
59
|
+
end
|
60
|
+
ticket << "\n\n\n#{description[:nxid]}"
|
61
|
+
ticket
|
62
|
+
end
|
63
|
+
|
64
|
+
# Generates a hash containing the information necessary
|
65
|
+
# to generate a Default-mode ticket description.
|
66
|
+
#
|
67
|
+
# - +description+ - Base ticket hash with NXID.
|
68
|
+
# - +row+ - CSV row containing vulnerability data.
|
69
|
+
#
|
70
|
+
# * *Returns* :
|
71
|
+
# - Hash containing ticket description information.
|
72
|
+
#
|
73
|
+
def get_default_ticket_description(description, row)
|
74
|
+
description[:header] = get_vuln_header(row)
|
75
|
+
description[:header] << get_discovery_info(row)
|
76
|
+
description[:references] = get_references(row)
|
77
|
+
description[:solutions] = get_solutions(row)
|
78
|
+
description
|
79
|
+
end
|
80
|
+
|
81
|
+
# Generates a hash containing the information necessary
|
82
|
+
# to generate an IP-mode ticket description.
|
83
|
+
#
|
84
|
+
# - +description+ - Base ticket hash with NXID.
|
85
|
+
# - +row+ - CSV row containing vulnerability data.
|
86
|
+
#
|
87
|
+
# * *Returns* :
|
88
|
+
# - Hash containing ticket description information.
|
89
|
+
#
|
90
|
+
def get_ip_ticket_description(description, row)
|
91
|
+
description[:vulnerabilities] = []
|
92
|
+
|
93
|
+
status = row['comparison']
|
94
|
+
vuln_info = "++ #{status} Vulnerabilities ++\n" if !status.nil?
|
95
|
+
description[:ticket_status] = status
|
96
|
+
|
97
|
+
vuln_info = vuln_info.to_s + get_vuln_info(row)
|
98
|
+
description[:vulnerabilities] << vuln_info
|
99
|
+
description
|
100
|
+
end
|
101
|
+
|
102
|
+
# Generates a hash containing the information necessary
|
103
|
+
# to generate a Vulnerability-mode ticket description.
|
104
|
+
#
|
105
|
+
# - +description+ - Base ticket hash with NXID.
|
106
|
+
# - +row+ - CSV row containing vulnerability data.
|
107
|
+
#
|
108
|
+
# * *Returns* :
|
109
|
+
# - Hash containing ticket description information.
|
110
|
+
#
|
111
|
+
def get_vuln_ticket_description(description, row)
|
112
|
+
description[:header] = get_vuln_header(row)
|
113
|
+
description[:references] = get_references(row)
|
114
|
+
description[:solutions] = get_solutions(row)
|
115
|
+
description[:assets] = get_assets(row)
|
116
|
+
description
|
117
|
+
end
|
118
|
+
|
119
|
+
# Updates an existing IP-mode description hash containing information
|
120
|
+
# necessary to generate a ticket description.
|
121
|
+
#
|
122
|
+
# - +description+ - The existing ticket hash to be updated.
|
123
|
+
# - +row+ - CSV row containing vulnerability data.
|
124
|
+
#
|
125
|
+
# * *Returns* :
|
126
|
+
# - Hash containing updated ticket description information.
|
127
|
+
#
|
128
|
+
def update_ip_ticket_description(description, row)
|
129
|
+
status = row['comparison']
|
130
|
+
header = "++ #{status} Vulnerabilities ++\n"
|
131
|
+
header = "" unless description[:ticket_status] != status
|
132
|
+
|
133
|
+
description[:vulnerabilities] << "#{header}#{get_vuln_info(row)}"
|
134
|
+
description
|
135
|
+
end
|
136
|
+
|
137
|
+
# Updates an existing Vulnerability-mode description hash containing
|
138
|
+
# information necessary to generate a ticket description.
|
139
|
+
#
|
140
|
+
# - +description+ - The existing ticket hash to be updated.
|
141
|
+
# - +row+ - CSV row containing vulnerability data.
|
142
|
+
#
|
143
|
+
# * *Returns* :
|
144
|
+
# - Hash containing updated ticket description information.
|
145
|
+
#
|
146
|
+
def update_vuln_ticket_description(description, row)
|
147
|
+
description[:assets] += "\n#{get_assets(row)}"
|
148
|
+
description
|
149
|
+
end
|
150
|
+
|
151
|
+
# Generates a final description string based on a Default-mode
|
152
|
+
# description hash.
|
153
|
+
#
|
154
|
+
# - +description+ - The finished ticket hash to be converted.
|
155
|
+
#
|
156
|
+
# * *Returns* :
|
157
|
+
# - String containing ticket description text.
|
158
|
+
#
|
159
|
+
def print_default_ticket_description(description)
|
160
|
+
ticket = "#{description[:header]}\n#{description[:references]}"
|
161
|
+
ticket << "#{description[:solutions]}"
|
162
|
+
ticket
|
163
|
+
end
|
164
|
+
|
165
|
+
# Generates a final description string based on an IP-mode
|
166
|
+
# description hash.
|
167
|
+
#
|
168
|
+
# - +description+ - The finished ticket hash to be converted.
|
169
|
+
#
|
170
|
+
# * *Returns* :
|
171
|
+
# - String containing ticket description text.
|
172
|
+
#
|
173
|
+
def print_ip_ticket_description(description)
|
174
|
+
ticket = ''
|
175
|
+
description[:vulnerabilities].each { |v| ticket << "#{v}\n" }
|
176
|
+
ticket
|
177
|
+
end
|
178
|
+
|
179
|
+
# Generates a final description string based on a Vulnerability-mode
|
180
|
+
# description hash.
|
181
|
+
#
|
182
|
+
# - +description+ - The finished ticket hash to be converted.
|
183
|
+
#
|
184
|
+
# * *Returns* :
|
185
|
+
# - String containing ticket description text.
|
186
|
+
#
|
187
|
+
def print_vuln_ticket_description(description)
|
188
|
+
ticket = "#{description[:header]}\n#{description[:assets]}"
|
189
|
+
ticket << "\n#{description[:references]}\n#{description[:solutions]}"
|
190
|
+
ticket
|
191
|
+
end
|
192
|
+
|
193
|
+
# Generates the NXID. The NXID is a unique identifier used to find and update and/or close tickets.
|
194
|
+
#
|
195
|
+
# * *Args* :
|
196
|
+
# - +nexpose_identifier_id+ - Site/TAG ID the tickets are being generated for. Required for all? { |e| } ticketing modes
|
197
|
+
# - +row+ - Row from the generated Nexpose CSV report. Required for default ('D') mode.
|
198
|
+
# - +current_ip+ - The IP address of that this ticket is for. Required for IP mode ('I') mode.
|
199
|
+
#
|
200
|
+
# * *Returns* :
|
201
|
+
# - NXID string.
|
202
|
+
#
|
203
|
+
def generate_nxid(nexpose_id, row)
|
204
|
+
fail 'Row data is nil' if row.nil?
|
205
|
+
|
206
|
+
case @ticketing_mode
|
207
|
+
when 'D' then "#{nexpose_id}d#{row['asset_id']}d#{row['vulnerability_id']}"
|
208
|
+
when 'I' then "#{nexpose_id}i#{row['ip_address']}"
|
209
|
+
when 'V' then "#{nexpose_id}v#{row['vulnerability_id']}"
|
210
|
+
else fail 'Ticketing mode not recognised.'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Formats the row data to be inserted into a 'D' or 'I' mode ticket description.
|
215
|
+
#
|
216
|
+
# - +row+ - CSV row containing vulnerability data.
|
217
|
+
#
|
218
|
+
# * *Returns* :
|
219
|
+
# - String formatted with vulnerability data.
|
220
|
+
#
|
221
|
+
def get_vuln_info(row)
|
222
|
+
ticket = get_vuln_header(row)
|
223
|
+
ticket << get_discovery_info(row)
|
224
|
+
ticket << get_references(row)
|
225
|
+
ticket << "\n#{get_solutions(row)}"
|
226
|
+
ticket.gsub("\n", "\n ")
|
227
|
+
end
|
228
|
+
|
229
|
+
# Generates the vulnerability header from the row data.
|
230
|
+
#
|
231
|
+
# - +row+ - CSV row containing vulnerability data.
|
232
|
+
#
|
233
|
+
# * *Returns* :
|
234
|
+
# - String formatted with vulnerability data.
|
235
|
+
#
|
236
|
+
def get_vuln_header(row)
|
237
|
+
ticket = "\n=============================="
|
238
|
+
ticket << "\nVulnerability ID: #{row['vulnerability_id']}"
|
239
|
+
ticket << "\nCVSS Score: #{row['cvss_score']}"
|
240
|
+
ticket << "\n=============================="
|
241
|
+
ticket
|
242
|
+
end
|
243
|
+
|
244
|
+
# Generates the ticket's title depending on the ticketing mode.
|
245
|
+
#
|
246
|
+
# - +row+ - CSV row containing vulnerability data.
|
247
|
+
#
|
248
|
+
# * *Returns* :
|
249
|
+
# - String containing the ticket title.
|
250
|
+
#
|
251
|
+
def get_title(row, maximum=nil)
|
252
|
+
title = case @ticketing_mode
|
253
|
+
when 'D' then "#{row['ip_address']} => #{get_short_summary(row)}"
|
254
|
+
when 'I' then "#{row['ip_address']} => Vulnerabilities"
|
255
|
+
when 'V' then "Vulnerability: #{row['title']}"
|
256
|
+
else fail 'Ticketing mode not recognised.'
|
257
|
+
end
|
258
|
+
return title if maximum == nil || title.length < maximum
|
259
|
+
|
260
|
+
title = "#{title[0, 97]}..."
|
261
|
+
end
|
262
|
+
|
263
|
+
# Generates a short summary for a vulnerability.
|
264
|
+
#
|
265
|
+
# - +row+ - CSV row containing vulnerability data.
|
266
|
+
#
|
267
|
+
# * *Returns* :
|
268
|
+
# - String containing a short summary of the vulnerability.
|
269
|
+
#
|
270
|
+
def get_short_summary(row)
|
271
|
+
summary = row['solutions']
|
272
|
+
delimiter = summary.to_s.index('|')
|
273
|
+
return summary[summary.index(':')+1...delimiter].strip if delimiter
|
274
|
+
summary.length <= 100 ? summary : summary[0...100]
|
275
|
+
end
|
276
|
+
|
277
|
+
# Formats the solutions for a vulnerability in a format suitable to be inserted into a ticket.
|
278
|
+
#
|
279
|
+
# - +row+ - CSV row containing vulnerability data.
|
280
|
+
#
|
281
|
+
# * *Returns* :
|
282
|
+
# - String formatted with solution information.
|
283
|
+
#
|
284
|
+
def get_solutions(row)
|
285
|
+
row['solutions'].to_s.gsub('|', "\n").gsub('~', "\n--\n")
|
286
|
+
end
|
287
|
+
|
288
|
+
def get_discovery_info(row)
|
289
|
+
return '' if row['first_discovered'].to_s == ""
|
290
|
+
info = "\nFirst Seen: #{row['first_discovered']}\n"
|
291
|
+
info << "Last Seen: #{row['most_recently_discovered']}\n"
|
292
|
+
info
|
293
|
+
end
|
294
|
+
|
295
|
+
# Formats the references for a vulnerability in a format suitable to be inserted into a ticket.
|
296
|
+
#
|
297
|
+
# - +row+ - CSV row containing vulnerability data.
|
298
|
+
#
|
299
|
+
# * *Returns* :
|
300
|
+
# - String formatted with source and reference.
|
301
|
+
#
|
302
|
+
def get_references(row)
|
303
|
+
return '' if row['references'].nil?
|
304
|
+
references = "\nSources:\n"
|
305
|
+
refs = row['references'].split(', ')
|
306
|
+
refs[MAX_NUM_REFS] = '...' if refs.count > MAX_NUM_REFS
|
307
|
+
refs[0..MAX_NUM_REFS].each { |r| references << " - #{r}\n" }
|
308
|
+
references
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
# Returns the assets for a vulnerability in a format suitable to be inserted into a ticket.
|
313
|
+
#
|
314
|
+
# - +row+ - CSV row containing vulnerability data.
|
315
|
+
#
|
316
|
+
# * *Returns* :
|
317
|
+
# - String formatted with affected assets.
|
318
|
+
#
|
319
|
+
def get_assets(row)
|
320
|
+
status = row['comparison']
|
321
|
+
header = "\n#{status || 'Affected' } Assets\n"
|
322
|
+
|
323
|
+
assets = []
|
324
|
+
row['assets'].to_s.split('~').each do |a|
|
325
|
+
details = a.split('|')
|
326
|
+
assets << " - #{details[1]} #{"\t(#{details[2]})" if !details[2].empty?}"
|
327
|
+
end
|
328
|
+
asset_list = assets.join("\n")
|
329
|
+
"#{header}#{asset_list}"
|
330
|
+
end
|
331
|
+
|
332
|
+
# Returns the relevant row values for printing.
|
333
|
+
#
|
334
|
+
# - +fields+ - The fields which are relevant to the ticket.
|
335
|
+
# - +row+ - CSV row containing vulnerability data.
|
336
|
+
#
|
337
|
+
# * *Returns* :
|
338
|
+
# - String formatted with relevant fields.
|
339
|
+
#
|
340
|
+
def get_field_info(fields, row)
|
341
|
+
fields.map { |x| "#{x.sub("_", " ")}: #{row[x]}" }.join(", ")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
@@ -6,14 +6,14 @@
|
|
6
6
|
# (M) Helper class name
|
7
7
|
:helper_name: ServiceDeskHelper
|
8
8
|
|
9
|
-
# (M) REST
|
9
|
+
# (M) REST Endpoint on the ServiceDesk instance
|
10
10
|
:rest_uri: https://uritoservicedesk:8080/sdpapi/request
|
11
11
|
# (M) API Key to access the ServiceDesk instance
|
12
12
|
:api_key: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
13
13
|
# (M) local ticket database path
|
14
14
|
:ticket_db_path: /var/lib/gems/1.9.1/gems/nexpose_ticketing-0.2.3.jh/lib/nexpose_ticketing/log/servicedesk_ticketdb
|
15
15
|
|
16
|
-
# (M) ServiceDesk
|
16
|
+
# (M) ServiceDesk requester name
|
17
17
|
:requester: nexpose
|
18
|
-
# (M)
|
18
|
+
# (M) ServiceDesk incident group
|
19
19
|
:group: Network
|
@@ -4,49 +4,21 @@ require 'net/https'
|
|
4
4
|
require 'uri'
|
5
5
|
require 'csv'
|
6
6
|
require 'nexpose_ticketing/nx_logger'
|
7
|
+
require 'nexpose_ticketing/version'
|
8
|
+
require 'nexpose_ticketing/common_helper'
|
7
9
|
|
8
10
|
# This class serves as the JIRA interface
|
9
11
|
# that creates issues within JIRA from vulnerabilities
|
10
12
|
# found in Nexpose.
|
11
13
|
# Copyright:: Copyright (c) 2014 Rapid7, LLC.
|
12
14
|
class JiraHelper
|
13
|
-
# TODO: Add V Mode.
|
14
|
-
# TODO: Allow updates/closed loop.
|
15
15
|
attr_accessor :jira_data, :options
|
16
16
|
def initialize(jira_data, options)
|
17
17
|
@jira_data = jira_data
|
18
18
|
@options = options
|
19
|
-
@log = NexposeTicketing::
|
20
|
-
end
|
19
|
+
@log = NexposeTicketing::NxLogger.instance
|
21
20
|
|
22
|
-
|
23
|
-
#
|
24
|
-
# * *Args* :
|
25
|
-
# - +nexpose_identifier_id+ - Site/TAG ID the tickets are being generated for. Required for all ticketing modes
|
26
|
-
# - +row+ - Row from the generated Nexpose CSV report. Required for default ('D') mode.
|
27
|
-
# - +current_ip+ - The IP address of that this ticket is for. Required for IP mode ('I') mode.
|
28
|
-
#
|
29
|
-
# * *Returns* :
|
30
|
-
# - NXID string.
|
31
|
-
#
|
32
|
-
def generate_nxid(nexpose_identifier_id, row=nil, current_ip=nil)
|
33
|
-
fail 'Nexpose Identifier ID is required to generate the NXID.' if nexpose_identifier_id.empty?
|
34
|
-
case @options[:ticket_mode]
|
35
|
-
# 'D' Default mode: IP *-* Vulnerability
|
36
|
-
when 'D'
|
37
|
-
fail 'Row is required to generate the NXID in \'D\' mode.' if row.nil? || row.empty?
|
38
|
-
@nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
|
39
|
-
# 'I' IP address mode: IP address -* Vulnerability
|
40
|
-
when 'I'
|
41
|
-
fail 'Current IP is required to generate the NXID in \'I\' mode.' if current_ip.nil? || current_ip.empty?
|
42
|
-
@nxid = "#{nexpose_identifier_id}#{current_ip.tr('.','')}"
|
43
|
-
# 'V' mode net yet implemented.
|
44
|
-
# 'V' Vulnerability mode: Vulnerability -* IP address
|
45
|
-
# when 'V'
|
46
|
-
# @NXID = "#{nexpose_identifier_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
|
47
|
-
else
|
48
|
-
fail 'Could not close tickets - do not understand the ticketing mode!'
|
49
|
-
end
|
21
|
+
@common_helper = NexposeTicketing::CommonHelper.new(@options)
|
50
22
|
end
|
51
23
|
|
52
24
|
# Fetches the Jira ticket key e.g INT-1. This is required to post updates to the Jira.
|
@@ -123,12 +95,13 @@ class JiraHelper
|
|
123
95
|
end
|
124
96
|
end
|
125
97
|
end
|
126
|
-
|
98
|
+
error = "Response was <#{transitions}> and desired close Step ID was <#{@jira_data[:close_step_id]}>. Jira returned no valid transition to close the ticket!"
|
99
|
+
@log.log_message(error)
|
127
100
|
return nil
|
128
101
|
end
|
129
102
|
|
130
103
|
def create_tickets(tickets)
|
131
|
-
fail 'Ticket(s) cannot be empty.' if tickets.
|
104
|
+
fail 'Ticket(s) cannot be empty.' if tickets.nil? || tickets.empty?
|
132
105
|
tickets.each do |ticket|
|
133
106
|
headers = { 'Content-Type' => 'application/json',
|
134
107
|
'Accept' => 'application/json' }
|
@@ -149,94 +122,64 @@ class JiraHelper
|
|
149
122
|
end
|
150
123
|
|
151
124
|
# Prepares tickets from the CSV.
|
152
|
-
# TODO Implement V Version.
|
153
125
|
def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
|
154
|
-
@ticket
|
126
|
+
@log.log_message('Preparing ticket requests...')
|
155
127
|
case @options[:ticket_mode]
|
156
128
|
# 'D' Default IP *-* Vulnerability
|
157
|
-
when 'D'
|
158
|
-
prepare_tickets_default(vulnerability_list, nexpose_identifier_id)
|
129
|
+
when 'D' then matching_fields = ['ip_address', 'vulnerability_id']
|
159
130
|
# 'I' IP address -* Vulnerability
|
160
|
-
when 'I'
|
161
|
-
|
131
|
+
when 'I' then matching_fields = ['ip_address']
|
132
|
+
# 'V' Vulnerability -* Assets
|
133
|
+
when 'V' then matching_fields = ['vulnerability_id']
|
162
134
|
else
|
163
135
|
fail 'Unsupported ticketing mode selected.'
|
164
136
|
end
|
165
|
-
end
|
166
137
|
|
167
|
-
|
168
|
-
def prepare_tickets_default(vulnerability_list, nexpose_identifier_id)
|
169
|
-
@log.log_message('Preparing tickets for default mode.')
|
170
|
-
tickets = []
|
171
|
-
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
172
|
-
# JiraHelper doesn't like new line characters in their summaries.
|
173
|
-
summary = row['summary'].gsub(/\n/, ' ')
|
174
|
-
ticket = {
|
175
|
-
'fields' => {
|
176
|
-
'project' => {
|
177
|
-
'key' => "#{@jira_data[:project]}" },
|
178
|
-
'summary' => "#{row['ip_address']} => #{summary}",
|
179
|
-
'description' => "CVSS Score: #{row['cvss_score']} \n\n #{row['fix']} \n\n #{row['url']} \n\n\n NXID: #{generate_nxid(nexpose_identifier_id, row)}",
|
180
|
-
'issuetype' => {
|
181
|
-
'name' => 'Task' }
|
182
|
-
}
|
183
|
-
}.to_json
|
184
|
-
tickets.push(ticket)
|
185
|
-
end
|
186
|
-
tickets
|
138
|
+
prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
|
187
139
|
end
|
188
140
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
# - +nexpose_identifier_id+ - Site/TAG ID the vulnerability list was generate from.
|
194
|
-
#
|
195
|
-
# * *Returns* :
|
196
|
-
# - List of JSON-formatted tickets for updating within Jira.
|
197
|
-
#
|
198
|
-
def prepare_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
199
|
-
@log.log_message('Preparing tickets for IP mode.')
|
141
|
+
def prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
|
142
|
+
@ticket = Hash.new(-1)
|
143
|
+
|
144
|
+
@log.log_message("Preparing tickets for #{@options[:ticket_mode]} mode.")
|
200
145
|
tickets = []
|
201
|
-
|
146
|
+
previous_row = nil
|
147
|
+
description = nil
|
202
148
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
203
|
-
if
|
204
|
-
|
149
|
+
if previous_row.nil?
|
150
|
+
previous_row = row
|
151
|
+
|
205
152
|
@ticket = {
|
206
153
|
'fields' => {
|
207
154
|
'project' => {
|
208
155
|
'key' => "#{@jira_data[:project]}" },
|
209
|
-
'summary' =>
|
156
|
+
'summary' => @common_helper.get_title(row),
|
210
157
|
'description' => '',
|
211
158
|
'issuetype' => {
|
212
159
|
'name' => 'Task' }
|
213
160
|
}
|
214
161
|
}
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
@ticket
|
219
|
-
|
220
|
-
|
221
|
-
\n\n ==============================\n
|
222
|
-
\n Source: #{row['source']}, Reference: #{row['reference']}
|
223
|
-
\n
|
224
|
-
\n First seen: #{row['first_discovered']}
|
225
|
-
\n Last seen: #{row['most_recently_discovered']}
|
226
|
-
\n Fix:
|
227
|
-
\n #{row['fix']}\n\n #{row['url']}
|
228
|
-
\n
|
229
|
-
\n\n"
|
230
|
-
end
|
231
|
-
unless current_ip == row['ip_address']
|
232
|
-
@ticket['fields']['description'] += "\n\n\n NXID: #{generate_nxid(nexpose_identifier_id, row, current_ip)}"
|
162
|
+
description = @common_helper.get_description(nexpose_identifier_id, row)
|
163
|
+
elsif matching_fields.any? { |x| previous_row[x].nil? || previous_row[x] != row[x] }
|
164
|
+
info = @common_helper.get_field_info(matching_fields, previous_row)
|
165
|
+
@log.log_message("Generated ticket with #{info}")
|
166
|
+
|
167
|
+
@ticket['fields']['description'] = @common_helper.print_description(description)
|
233
168
|
tickets.push(@ticket.to_json)
|
234
|
-
|
169
|
+
previous_row = nil
|
170
|
+
description = nil
|
235
171
|
redo
|
172
|
+
else
|
173
|
+
description = @common_helper.update_description(description, row)
|
236
174
|
end
|
237
175
|
end
|
238
|
-
|
239
|
-
|
176
|
+
|
177
|
+
unless @ticket.nil? || @ticket.empty?
|
178
|
+
@ticket['fields']['description'] = @common_helper.print_description(description)
|
179
|
+
tickets.push(@ticket.to_json)
|
180
|
+
end
|
181
|
+
|
182
|
+
@log.log_message("Generated <#{tickets.count.to_s}> tickets.")
|
240
183
|
tickets
|
241
184
|
end
|
242
185
|
|
@@ -309,13 +252,13 @@ class JiraHelper
|
|
309
252
|
@nxid = nil
|
310
253
|
tickets = []
|
311
254
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
312
|
-
@nxid = generate_nxid(nexpose_identifier_id,
|
255
|
+
@nxid = @common_helper.generate_nxid(nexpose_identifier_id, row)
|
313
256
|
# Query Jira for the ticket by unique id (generated NXID)
|
314
257
|
queried_key = get_jira_key("jql=project=#{@jira_data[:project]} AND description ~ \"NXID: #{@nxid}\" AND (status != #{@jira_data[:close_step_name]})&fields=key")
|
315
258
|
if queried_key.nil? || queried_key.empty?
|
316
259
|
@log.log_message("Error when closing tickets - query for NXID <#{@nxid}> should have returned a Jira key!!")
|
317
260
|
else
|
318
|
-
#Jira uses a post call to the ticket key path to close the ticket. The "prepared batch of tickets" in this case is just a collection Jira ticket
|
261
|
+
#Jira uses a post call to the ticket key path to close the ticket. The "prepared batch of tickets" in this case is just a collection Jira ticket keys to close.
|
319
262
|
tickets.push(queried_key)
|
320
263
|
end
|
321
264
|
end
|
@@ -361,8 +304,7 @@ class JiraHelper
|
|
361
304
|
end
|
362
305
|
end
|
363
306
|
|
364
|
-
# Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose.
|
365
|
-
# per IP i.e. any vulnerabilities for a single IP in one ticket
|
307
|
+
# Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose.
|
366
308
|
#
|
367
309
|
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
368
310
|
# - +nexpose_identifier_id+ - Site/TAG ID the vulnerability list was generate from.
|
@@ -371,21 +313,25 @@ class JiraHelper
|
|
371
313
|
# - List of JSON-formated tickets for updating within Jira.
|
372
314
|
#
|
373
315
|
def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
|
374
|
-
fail 'Ticket updates are
|
316
|
+
fail 'Ticket updates are not supported in Default mode.' if @options[:ticket_mode] == 'D'
|
375
317
|
@log.log_message('Preparing tickets to update.')
|
376
318
|
#Jira uses the ticket key to push updates. Since new IPs won't have a Jira key, generate new tickets for all of the IPs found.
|
377
|
-
updated_tickets =
|
319
|
+
updated_tickets = prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
|
320
|
+
|
378
321
|
tickets_to_send = []
|
379
322
|
|
380
323
|
#Find the keys that exist (IPs that have tickets already)
|
381
324
|
updated_tickets.each do |ticket|
|
382
|
-
|
383
|
-
|
325
|
+
description = JSON.parse(ticket)['fields']['description']
|
326
|
+
nxid_index = description.rindex("NXID")
|
327
|
+
nxid = nxid_index.nil? ? nil : description[nxid_index..-1]
|
328
|
+
|
329
|
+
if (nxid).nil?
|
384
330
|
#Could not get NXID from the last line in the description. Do not push the invalid description.
|
385
331
|
@log.log_message("Failed to parse the NXID from a generated ticket update! Ignoring ticket <#{nxid}>")
|
386
332
|
next
|
387
333
|
end
|
388
|
-
queried_key = get_jira_key("jql=project=#{@jira_data[:project]} AND description ~ \"
|
334
|
+
queried_key = get_jira_key("jql=project=#{@jira_data[:project]} AND description ~ \"#{nxid.strip}\" AND (status != #{@jira_data[:close_step_name]})&fields=key")
|
389
335
|
ticket_key_pair = []
|
390
336
|
ticket_key_pair << queried_key
|
391
337
|
ticket_key_pair << ticket
|
@@ -393,4 +339,4 @@ class JiraHelper
|
|
393
339
|
end
|
394
340
|
tickets_to_send
|
395
341
|
end
|
396
|
-
end
|
342
|
+
end
|