nexpose_ticketing 0.8.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|