nexpose_ticketing 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/nexpose_ticketing/config/ticket_service.config +5 -2
- data/lib/nexpose_ticketing/helpers/jira_helper.rb +20 -20
- data/lib/nexpose_ticketing/helpers/remedy_helper.rb +34 -34
- data/lib/nexpose_ticketing/helpers/servicedesk_helper.rb +15 -15
- data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +19 -19
- data/lib/nexpose_ticketing/queries.rb +40 -15
- data/lib/nexpose_ticketing/ticket_repository.rb +208 -141
- data/lib/nexpose_ticketing/ticket_service.rb +123 -56
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6665f45b4fb44b5e107eb13ae98ea53e768e3a3c
|
4
|
+
data.tar.gz: 49e4aefdadb17710a8a927bee0bc858da29a1559
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c36501adcb968939ad7af6cf3e3a599f9d3bb999c3d4300119f4e15cbcd00e9dba144cfc502b62087f66bd33e7dd179e4a48f3a42cdacc07ca06e6b2e7e160b5
|
7
|
+
data.tar.gz: 5ff1131739ab1d3ec3649ded018143fa2688aab89635fda74b6c07d2fdeaf686751cb92a5201b0e886ed1cd3cd497005b57ba5ef66b75ad4c1db52c422fd7ed4
|
data/Gemfile.lock
CHANGED
@@ -11,8 +11,10 @@
|
|
11
11
|
- '1'
|
12
12
|
# Minimum floor severity to report on. Number between 0 and 10.
|
13
13
|
:severity: 8
|
14
|
-
# (M) Name of the report historical file saved in disk.
|
14
|
+
# (M) Name of the report historical file for sites saved in disk.
|
15
15
|
:file_name: last_scan_data.csv
|
16
|
+
# (M) Name of the report historical file for tags saved in disk.
|
17
|
+
:tag_file_name: tag_last_scan_data.csv
|
16
18
|
# (M) Defines the ticket creation mode:
|
17
19
|
# 'D' Default IP *-* Vulnerability
|
18
20
|
# 'I' IP address -* Vulnerability
|
@@ -25,8 +27,9 @@
|
|
25
27
|
# (M) For 'I' & 'V' mode. Set to 'Y' for tickets that have been fixed to be closed after update, set to 'N' for ticket to be left
|
26
28
|
# open for a user to manually close or change the status.
|
27
29
|
:close_old_tickets_on_update: Y
|
28
|
-
#
|
30
|
+
# Filters the reports to specific tags one per line, leave empty for no tags. Note tags cannot be used in conjunction with sites (tags get priority).
|
29
31
|
:tags:
|
32
|
+
#- '1'
|
30
33
|
# A comma separated list of vulnerability categories to include
|
31
34
|
:vulnerabilityCategories:
|
32
35
|
# The asset minimum risk score to open tickets for
|
@@ -22,28 +22,28 @@ class JiraHelper
|
|
22
22
|
# Generates the NXID. The NXID is a unique identifier used to find and update and/or close tickets.
|
23
23
|
#
|
24
24
|
# * *Args* :
|
25
|
-
# - +
|
25
|
+
# - +nexpose_identifier_id+ - Site/TAG ID the tickets are being generated for. Required for all ticketing modes
|
26
26
|
# - +row+ - Row from the generated Nexpose CSV report. Required for default ('D') mode.
|
27
27
|
# - +current_ip+ - The IP address of that this ticket is for. Required for IP mode ('I') mode.
|
28
28
|
#
|
29
29
|
# * *Returns* :
|
30
30
|
# - NXID string.
|
31
31
|
#
|
32
|
-
def generate_nxid(
|
33
|
-
fail '
|
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
34
|
case @options[:ticket_mode]
|
35
35
|
# 'D' Default mode: IP *-* Vulnerability
|
36
36
|
when 'D'
|
37
37
|
fail 'Row is required to generate the NXID in \'D\' mode.' if row.nil? || row.empty?
|
38
|
-
@nxid = "#{
|
38
|
+
@nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
|
39
39
|
# 'I' IP address mode: IP address -* Vulnerability
|
40
40
|
when 'I'
|
41
41
|
fail 'Current IP is required to generate the NXID in \'I\' mode.' if current_ip.nil? || current_ip.empty?
|
42
|
-
@nxid = "#{
|
42
|
+
@nxid = "#{nexpose_identifier_id}#{current_ip.tr('.','')}"
|
43
43
|
# 'V' mode net yet implemented.
|
44
44
|
# 'V' Vulnerability mode: Vulnerability -* IP address
|
45
45
|
# when 'V'
|
46
|
-
# @NXID = "#{
|
46
|
+
# @NXID = "#{nexpose_identifier_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
|
47
47
|
else
|
48
48
|
fail 'Could not close tickets - do not understand the ticketing mode!'
|
49
49
|
end
|
@@ -150,22 +150,22 @@ class JiraHelper
|
|
150
150
|
|
151
151
|
# Prepares tickets from the CSV.
|
152
152
|
# TODO Implement V Version.
|
153
|
-
def prepare_create_tickets(vulnerability_list,
|
153
|
+
def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
|
154
154
|
@ticket = Hash.new(-1)
|
155
155
|
case @options[:ticket_mode]
|
156
156
|
# 'D' Default IP *-* Vulnerability
|
157
157
|
when 'D'
|
158
|
-
prepare_tickets_default(vulnerability_list,
|
158
|
+
prepare_tickets_default(vulnerability_list, nexpose_identifier_id)
|
159
159
|
# 'I' IP address -* Vulnerability
|
160
160
|
when 'I'
|
161
|
-
prepare_tickets_by_ip(vulnerability_list,
|
161
|
+
prepare_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
162
162
|
else
|
163
163
|
fail 'Unsupported ticketing mode selected.'
|
164
164
|
end
|
165
165
|
end
|
166
166
|
|
167
167
|
# Prepares and creates tickets in default mode.
|
168
|
-
def prepare_tickets_default(vulnerability_list,
|
168
|
+
def prepare_tickets_default(vulnerability_list, nexpose_identifier_id)
|
169
169
|
@log.log_message('Preparing tickets for default mode.')
|
170
170
|
tickets = []
|
171
171
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
@@ -176,7 +176,7 @@ class JiraHelper
|
|
176
176
|
'project' => {
|
177
177
|
'key' => "#{@jira_data[:project]}" },
|
178
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(
|
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
180
|
'issuetype' => {
|
181
181
|
'name' => 'Task' }
|
182
182
|
}
|
@@ -190,12 +190,12 @@ class JiraHelper
|
|
190
190
|
# per IP i.e. any vulnerabilities for a single IP in one ticket
|
191
191
|
#
|
192
192
|
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
193
|
-
# - +
|
193
|
+
# - +nexpose_identifier_id+ - Site/TAG ID the vulnerability list was generate from.
|
194
194
|
#
|
195
195
|
# * *Returns* :
|
196
196
|
# - List of JSON-formatted tickets for updating within Jira.
|
197
197
|
#
|
198
|
-
def prepare_tickets_by_ip(vulnerability_list,
|
198
|
+
def prepare_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
199
199
|
@log.log_message('Preparing tickets for IP mode.')
|
200
200
|
tickets = []
|
201
201
|
current_ip = -1
|
@@ -229,13 +229,13 @@ class JiraHelper
|
|
229
229
|
\n\n"
|
230
230
|
end
|
231
231
|
unless current_ip == row['ip_address']
|
232
|
-
@ticket['fields']['description'] += "\n\n\n NXID: #{generate_nxid(
|
232
|
+
@ticket['fields']['description'] += "\n\n\n NXID: #{generate_nxid(nexpose_identifier_id, row, current_ip)}"
|
233
233
|
tickets.push(@ticket.to_json)
|
234
234
|
current_ip = -1
|
235
235
|
redo
|
236
236
|
end
|
237
237
|
end
|
238
|
-
@ticket['fields']['description'] += "\n\n\n NXID: #{generate_nxid(
|
238
|
+
@ticket['fields']['description'] += "\n\n\n NXID: #{generate_nxid(nexpose_identifier_id, nil, current_ip)}"
|
239
239
|
tickets.push(@ticket.to_json) unless @ticket.nil?
|
240
240
|
tickets
|
241
241
|
end
|
@@ -304,12 +304,12 @@ class JiraHelper
|
|
304
304
|
# * *Returns* :
|
305
305
|
# - List of Jira ticket Keys to be closed.
|
306
306
|
#
|
307
|
-
def prepare_close_tickets(vulnerability_list,
|
307
|
+
def prepare_close_tickets(vulnerability_list, nexpose_identifier_id)
|
308
308
|
@log.log_message('Preparing tickets to close.')
|
309
309
|
@nxid = nil
|
310
310
|
tickets = []
|
311
311
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
312
|
-
@nxid = generate_nxid(
|
312
|
+
@nxid = generate_nxid(nexpose_identifier_id, nil, row['ip_address'])
|
313
313
|
# Query Jira for the ticket by unique id (generated NXID)
|
314
314
|
queried_key = get_jira_key("jql=project=#{@jira_data[:project]} AND description ~ \"NXID: #{@nxid}\" AND (status != #{@jira_data[:close_step_name]})&fields=key")
|
315
315
|
if queried_key.nil? || queried_key.empty?
|
@@ -365,16 +365,16 @@ class JiraHelper
|
|
365
365
|
# per IP i.e. any vulnerabilities for a single IP in one ticket
|
366
366
|
#
|
367
367
|
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
368
|
-
# - +
|
368
|
+
# - +nexpose_identifier_id+ - Site/TAG ID the vulnerability list was generate from.
|
369
369
|
#
|
370
370
|
# * *Returns* :
|
371
371
|
# - List of JSON-formated tickets for updating within Jira.
|
372
372
|
#
|
373
|
-
def prepare_update_tickets(vulnerability_list,
|
373
|
+
def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
|
374
374
|
fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'I'
|
375
375
|
@log.log_message('Preparing tickets to update.')
|
376
376
|
#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 = prepare_tickets_by_ip(vulnerability_list,
|
377
|
+
updated_tickets = prepare_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
378
378
|
tickets_to_send = []
|
379
379
|
|
380
380
|
#Find the keys that exist (IPs that have tickets already)
|
@@ -162,18 +162,18 @@ class RemedyHelper
|
|
162
162
|
# * *Returns* :
|
163
163
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
164
164
|
#
|
165
|
-
def prepare_create_tickets(vulnerability_list,
|
165
|
+
def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
|
166
166
|
@ticket = Hash.new(-1)
|
167
167
|
case @options[:ticket_mode]
|
168
168
|
# 'D' Default mode: IP *-* Vulnerability
|
169
169
|
when 'D'
|
170
|
-
prepare_create_tickets_default(vulnerability_list,
|
170
|
+
prepare_create_tickets_default(vulnerability_list, nexpose_identifier_id)
|
171
171
|
# 'I' IP address mode: IP address -* Vulnerability
|
172
172
|
when 'I'
|
173
|
-
prepare_create_tickets_by_ip(vulnerability_list,
|
173
|
+
prepare_create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
174
174
|
# 'V' Vulnerability mode: Vulnerability -* IP address
|
175
175
|
when 'V'
|
176
|
-
prepare_create_tickets_by_vulnerability(vulnerability_list,
|
176
|
+
prepare_create_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
|
177
177
|
else
|
178
178
|
fail 'No ticketing mode selected.'
|
179
179
|
end
|
@@ -188,15 +188,15 @@ class RemedyHelper
|
|
188
188
|
# * *Returns* :
|
189
189
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
190
190
|
#
|
191
|
-
def prepare_update_tickets(vulnerability_list,
|
191
|
+
def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
|
192
192
|
@ticket = Hash.new(-1)
|
193
193
|
case @options[:ticket_mode]
|
194
194
|
# 'I' IP address mode: IP address -* Vulnerability
|
195
195
|
when 'I'
|
196
|
-
prepare_update_tickets_by_ip(vulnerability_list,
|
196
|
+
prepare_update_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
197
197
|
# 'V' Vulnerability mode: Vulnerability -* IP address
|
198
198
|
when 'V'
|
199
|
-
prepare_update_tickets_by_vulnerability(vulnerability_list,
|
199
|
+
prepare_update_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
|
200
200
|
else
|
201
201
|
fail 'No ticketing mode selected.'
|
202
202
|
end
|
@@ -213,7 +213,7 @@ class RemedyHelper
|
|
213
213
|
# * *Returns* :
|
214
214
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
215
215
|
#
|
216
|
-
def prepare_create_tickets_default(vulnerability_list,
|
216
|
+
def prepare_create_tickets_default(vulnerability_list, nexpose_identifier_id)
|
217
217
|
@log.log_message("Preparing tickets by default method.")
|
218
218
|
tickets = []
|
219
219
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
@@ -229,7 +229,7 @@ class RemedyHelper
|
|
229
229
|
'Action' => 'CREATE',
|
230
230
|
'Summary' => "#{row['ip_address']} => #{row['summary']}",
|
231
231
|
'Notes' => "Summary: #{row['summary']} \n\nFix: #{row['fix']} \n\nURL: #{row['url']}
|
232
|
-
\n\nNXID: #{
|
232
|
+
\n\nNXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}",
|
233
233
|
'Urgency' => '1-Critical'
|
234
234
|
}
|
235
235
|
tickets.push(ticket)
|
@@ -248,14 +248,14 @@ class RemedyHelper
|
|
248
248
|
# * *Returns* :
|
249
249
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
250
250
|
#
|
251
|
-
def prepare_create_tickets_by_ip(vulnerability_list,
|
251
|
+
def prepare_create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
252
252
|
@log.log_message('Preparing tickets by IP address.')
|
253
253
|
tickets = []
|
254
254
|
current_ip = -1
|
255
255
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
256
256
|
if current_ip == -1
|
257
257
|
current_ip = row['ip_address']
|
258
|
-
@log.log_message("Creating ticket with IP address: #{row['ip_address']}, Asset ID: #{row['asset_id']} and
|
258
|
+
@log.log_message("Creating ticket with IP address: #{row['ip_address']}, Asset ID: #{row['asset_id']} and Nexpose Identifier ID: #{nexpose_identifier_id}")
|
259
259
|
@ticket = {
|
260
260
|
'First_Name' => "#{@remedy_data[:first_name]}",
|
261
261
|
'Impact' => '1-Extensive/Widespread',
|
@@ -279,14 +279,14 @@ class RemedyHelper
|
|
279
279
|
end
|
280
280
|
unless current_ip == row['ip_address']
|
281
281
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
282
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
282
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
|
283
283
|
tickets.push(@ticket)
|
284
284
|
current_ip = -1
|
285
285
|
redo
|
286
286
|
end
|
287
287
|
end
|
288
288
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
289
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
289
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
|
290
290
|
tickets.push(@ticket) unless @ticket.nil?
|
291
291
|
tickets
|
292
292
|
end
|
@@ -303,7 +303,7 @@ class RemedyHelper
|
|
303
303
|
# * *Returns* :
|
304
304
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
305
305
|
#
|
306
|
-
def prepare_create_tickets_by_vulnerability(vulnerability_list,
|
306
|
+
def prepare_create_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
|
307
307
|
@log.log_message("Preparing tickets by vulnerability.")
|
308
308
|
tickets = []
|
309
309
|
current_vuln_id = -1
|
@@ -317,7 +317,7 @@ class RemedyHelper
|
|
317
317
|
current_vuln_id = row['vulnerability_id']
|
318
318
|
current_solution_id = row['solution_id']
|
319
319
|
current_asset_id = row['asset_id']
|
320
|
-
@log.log_message("Creating ticket with vulnerability id: #{row['vulnerability_id']}, Asset ID: #{row['asset_id']} and
|
320
|
+
@log.log_message("Creating ticket with vulnerability id: #{row['vulnerability_id']}, Asset ID: #{row['asset_id']} and Nexpose Identifier ID: #{nexpose_identifier_id}")
|
321
321
|
summary = "Vulnerability: #{row['title']}"
|
322
322
|
|
323
323
|
#Remedy has a summary field max size of 100 so truncate any summaries that are larger than that and place the full summary in the notes.
|
@@ -373,7 +373,7 @@ class RemedyHelper
|
|
373
373
|
end
|
374
374
|
unless current_vuln_id == row['vulnerability_id']
|
375
375
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
376
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
376
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
|
377
377
|
current_vuln_id = -1
|
378
378
|
current_solution_id = -1
|
379
379
|
current_asset_id = -1
|
@@ -383,7 +383,7 @@ class RemedyHelper
|
|
383
383
|
end
|
384
384
|
end
|
385
385
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
386
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
386
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
|
387
387
|
@ticket = format_notes_by_vulnerability(@ticket, full_summary)
|
388
388
|
tickets.push(@ticket) unless @ticket.nil?
|
389
389
|
tickets
|
@@ -413,7 +413,7 @@ class RemedyHelper
|
|
413
413
|
# * *Returns* :
|
414
414
|
# - List of savon-formated (hash) tickets for updating within Remedy.
|
415
415
|
#
|
416
|
-
def prepare_update_tickets_by_ip(vulnerability_list,
|
416
|
+
def prepare_update_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
417
417
|
fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'I'
|
418
418
|
@ticket = Hash.new(-1)
|
419
419
|
|
@@ -427,11 +427,11 @@ class RemedyHelper
|
|
427
427
|
ticket_status = row['comparison']
|
428
428
|
|
429
429
|
# Query Remedy for the incident by unique id (generated NXID)
|
430
|
-
queried_incident = query_for_ticket("NXID: #{
|
430
|
+
queried_incident = query_for_ticket("NXID: #{nexpose_identifier_id}#{row['ip_address']}")
|
431
431
|
if queried_incident.nil? || queried_incident.empty?
|
432
|
-
@log.log_message("No incident found for NXID: #{
|
432
|
+
@log.log_message("No incident found for NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}")
|
433
433
|
else
|
434
|
-
@log.log_message("Creating ticket update with IP address: #{row['ip_address']} for
|
434
|
+
@log.log_message("Creating ticket update with IP address: #{row['ip_address']} for Nexpose Identifier with ID: #{nexpose_identifier_id}")
|
435
435
|
@log.log_message("Ticket status #{ticket_status}")
|
436
436
|
# Remedy incident updates require populating all fields.
|
437
437
|
@ticket = extract_queried_incident(queried_incident, "++ #{row['comparison']} Vulnerabilities ++++++++++++++++++++++++++\n")
|
@@ -455,14 +455,14 @@ class RemedyHelper
|
|
455
455
|
end
|
456
456
|
unless current_ip == row['ip_address']
|
457
457
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
458
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
458
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
|
459
459
|
tickets.push(@ticket)
|
460
460
|
current_ip = -1
|
461
461
|
redo
|
462
462
|
end
|
463
463
|
end
|
464
464
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
465
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
465
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}" unless @ticket.empty?
|
466
466
|
tickets.push(@ticket) unless @ticket.nil? || @ticket.empty?
|
467
467
|
tickets
|
468
468
|
end
|
@@ -477,7 +477,7 @@ class RemedyHelper
|
|
477
477
|
# * *Returns* :
|
478
478
|
# - List of savon-formated (hash) tickets for updating within Remedy.
|
479
479
|
#
|
480
|
-
def prepare_update_tickets_by_vulnerability(vulnerability_list,
|
480
|
+
def prepare_update_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
|
481
481
|
fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'V'
|
482
482
|
@ticket = Hash.new(-1)
|
483
483
|
|
@@ -497,12 +497,12 @@ class RemedyHelper
|
|
497
497
|
current_solution_id = -1
|
498
498
|
|
499
499
|
# Query Remedy for the incident by unique id (generated NXID)
|
500
|
-
queried_incident = query_for_ticket("NXID: #{
|
500
|
+
queried_incident = query_for_ticket("NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}")
|
501
501
|
if queried_incident.nil? || queried_incident.empty?
|
502
|
-
@log.log_message("No incident found for NXID: #{
|
502
|
+
@log.log_message("No incident found for NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}. Creating...")
|
503
503
|
new_ticket_csv = vulnerability_list.split("\n").first
|
504
504
|
new_ticket_csv += "\n#{row.to_s}"
|
505
|
-
new_ticket = prepare_create_tickets_by_vulnerability(new_ticket_csv,
|
505
|
+
new_ticket = prepare_create_tickets_by_vulnerability(new_ticket_csv, nexpose_identifier_id)
|
506
506
|
@log.log_message('Created ticket. Sending to Remedy...')
|
507
507
|
create_tickets(new_ticket)
|
508
508
|
@log.log_message('Ticket sent. Performing update for ticket...')
|
@@ -510,7 +510,7 @@ class RemedyHelper
|
|
510
510
|
current_vuln_id = -1
|
511
511
|
redo
|
512
512
|
else
|
513
|
-
@log.log_message("Creating ticket update for vulnerability with ID: #{row['vulnerability_id']}, Asset ID: #{row['asset_id']} and
|
513
|
+
@log.log_message("Creating ticket update for vulnerability with ID: #{row['vulnerability_id']}, Asset ID: #{row['asset_id']} and Nexpose Identifier ID: #{nexpose_identifier_id}. Ticket status #{ticket_status}.")
|
514
514
|
# Remedy incident updates require populating all fields.
|
515
515
|
@ticket = extract_queried_incident(queried_incident, "++ #{row['comparison']} Assets ++++++++++++++++++++++\n")
|
516
516
|
end
|
@@ -548,7 +548,7 @@ class RemedyHelper
|
|
548
548
|
unless current_vuln_id == row['vulnerability_id']
|
549
549
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
550
550
|
@ticket['Notes'] += "\n\n" + current_solutions_text
|
551
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
551
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
|
552
552
|
tickets.push(@ticket)
|
553
553
|
current_vuln_id = -1
|
554
554
|
current_solution_id = -1
|
@@ -558,7 +558,7 @@ class RemedyHelper
|
|
558
558
|
end
|
559
559
|
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
560
560
|
@ticket['Notes'] += current_solutions_text
|
561
|
-
@ticket['Notes'] += "\n\nNXID: #{
|
561
|
+
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}" unless @ticket.empty?
|
562
562
|
tickets.push(@ticket) unless @ticket.nil? || @ticket.empty?
|
563
563
|
tickets
|
564
564
|
end
|
@@ -692,7 +692,7 @@ class RemedyHelper
|
|
692
692
|
# * *Returns* :
|
693
693
|
# - List of savon-formated (hash) tickets for closing within Remedy.
|
694
694
|
#
|
695
|
-
def prepare_close_tickets(vulnerability_list,
|
695
|
+
def prepare_close_tickets(vulnerability_list, nexpose_identifier_id)
|
696
696
|
fail 'Ticket closures are only supported in default mode.' if @options[:ticket_mode] == 'I'
|
697
697
|
@log.log_message('Preparing ticket closures by default method.')
|
698
698
|
@nxid = nil
|
@@ -701,13 +701,13 @@ class RemedyHelper
|
|
701
701
|
case @options[:ticket_mode]
|
702
702
|
# 'D' Default mode: IP *-* Vulnerability
|
703
703
|
when 'D'
|
704
|
-
@nxid = "#{
|
704
|
+
@nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
|
705
705
|
# 'I' IP address mode: IP address -* Vulnerability
|
706
706
|
when 'I'
|
707
|
-
@nxid = "#{
|
707
|
+
@nxid = "#{nexpose_identifier_id}#{row['current_ip']}"
|
708
708
|
# 'V' Vulnerability mode: Vulnerability -* IP address
|
709
709
|
when 'V'
|
710
|
-
@nxid = "#{
|
710
|
+
@nxid = "#{nexpose_identifier_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
|
711
711
|
else
|
712
712
|
fail 'Could not close tickets - do not understand the ticketing mode!'
|
713
713
|
end
|
@@ -52,15 +52,15 @@ class ServiceDeskHelper
|
|
52
52
|
db.close()
|
53
53
|
end
|
54
54
|
|
55
|
-
def prepare_create_tickets(vulnerability_list,
|
55
|
+
def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
|
56
56
|
@log.log_message('Preparing ticket requests...')
|
57
57
|
case @options[:ticket_mode]
|
58
58
|
# 'D' Default mode: IP *-* Vulnerability
|
59
59
|
when 'D'
|
60
|
-
tickets = create_tickets_by_default(vulnerability_list,
|
60
|
+
tickets = create_tickets_by_default(vulnerability_list, nexpose_identifier_id)
|
61
61
|
# 'I' IP address mode: IP address -* Vulnerability
|
62
62
|
when 'I'
|
63
|
-
tickets = create_tickets_by_ip(vulnerability_list,
|
63
|
+
tickets = create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
64
64
|
else
|
65
65
|
fail 'No ticketing mode selected.'
|
66
66
|
end
|
@@ -143,27 +143,27 @@ class ServiceDeskHelper
|
|
143
143
|
end
|
144
144
|
|
145
145
|
## Given a bunch of vulnerabilities that passed the filters, make tickets for each one
|
146
|
-
def create_tickets_by_default(vulnerability_list,
|
146
|
+
def create_tickets_by_default(vulnerability_list, nexpose_identifier_id)
|
147
147
|
@log.log_message('Preparing tickets by vulnerability...')
|
148
148
|
tickets = []
|
149
149
|
CSV.parse( vulnerability_list.chomp, headers: :first_row ) do |vuln|
|
150
150
|
subject = "#{vuln['ip_address']}: #{vuln['summary']}"
|
151
151
|
description = "Host: #{ip_address}\nSummary: #{vuln['summary']}\nFix: #{vuln['fix']}\nURL: #{vuln['url']}"
|
152
152
|
|
153
|
-
tickets << { :action => :create, :nxid => "#{
|
153
|
+
tickets << { :action => :create, :nxid => "#{nexpose_identifier_id}#{vuln['asset_id']}#{vuln['vulnerability_id']}#{vuln['solution_id']}",
|
154
154
|
:description => create_ticket_request( subject, description ) }
|
155
155
|
end
|
156
156
|
return tickets
|
157
157
|
end
|
158
158
|
|
159
159
|
|
160
|
-
def create_tickets_by_ip(vulnerability_list,
|
160
|
+
def create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
161
161
|
@log.log_message('Preparing tickets by ip')
|
162
162
|
tickets = []
|
163
163
|
hostVulns = {}
|
164
164
|
CSV.parse( vulnerability_list.chomp, headers: :first_row ) do |vuln|
|
165
|
-
hostVulns["#{
|
166
|
-
hostVulns["#{
|
165
|
+
hostVulns["#{nexpose_identifier_id}#{vuln['ip_address']}"] = { :ip => vuln['ip_address'], :description => "" } if not hostVulns.has_key?("#{nexpose_identifier_id}#{vuln['ip_address']}")
|
166
|
+
hostVulns["#{nexpose_identifier_id}#{vuln['ip_address']}"][:description] += "Summary: #{vuln['summary']}\nFix: #{vuln['fix']}\nURL: #{vuln['url']}\n\n"
|
167
167
|
end
|
168
168
|
|
169
169
|
hostVulns.each do |nxid, vulnInfo|
|
@@ -260,15 +260,15 @@ class ServiceDeskHelper
|
|
260
260
|
end
|
261
261
|
|
262
262
|
|
263
|
-
def prepare_update_tickets(vulnerability_list,
|
263
|
+
def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
|
264
264
|
fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'I'
|
265
265
|
|
266
266
|
@log.log_message('Preparing ticket updates by IP address.')
|
267
267
|
tickets = []
|
268
268
|
hostVulns={}
|
269
269
|
CSV.parse( vulnerability_list.chomp, headers: :first_row ) do |vuln|
|
270
|
-
hostVulns["#{
|
271
|
-
hostVulns["#{
|
270
|
+
hostVulns["#{nexpose_identifier_id}#{vuln['ip_address']}"] = { :ip => vuln['ip_address'], :description => "" } if not hostVulns.has_key?(vuln['asset_id'])
|
271
|
+
hostVulns["#{nexpose_identifier_id}#{vuln['ip_address']}"][:description] += "Summary: #{vuln['summary']}\nFix: #{vuln['fix']}\nURL: #{vuln['url']}\n\n"
|
272
272
|
end
|
273
273
|
|
274
274
|
hostVulns.each do |nxid, vulnInfo|
|
@@ -308,7 +308,7 @@ class ServiceDeskHelper
|
|
308
308
|
# * *Returns* :
|
309
309
|
# - List of savon-formated (hash) tickets for closing within ServiceDesk.
|
310
310
|
#
|
311
|
-
def prepare_close_tickets(vulnerability_list,
|
311
|
+
def prepare_close_tickets(vulnerability_list, nexpose_identifier_id)
|
312
312
|
fail 'Ticket closures are only supported in default mode.' if @options[:ticket_mode] == 'I'
|
313
313
|
@log.log_message('Preparing ticket closures by default method.')
|
314
314
|
@nxid = nil
|
@@ -317,13 +317,13 @@ class ServiceDeskHelper
|
|
317
317
|
case @options[:ticket_mode]
|
318
318
|
# 'D' Default mode: IP *-* Vulnerability
|
319
319
|
when 'D'
|
320
|
-
@nxid = "#{
|
320
|
+
@nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
|
321
321
|
# 'I' IP address mode: IP address -* Vulnerability
|
322
322
|
when 'I'
|
323
|
-
@nxid = "#{
|
323
|
+
@nxid = "#{nexpose_identifier_id}#{row['current_ip']}"
|
324
324
|
# 'V' Vulnerability mode: Vulnerability -* IP address
|
325
325
|
# when 'V'
|
326
|
-
# @NXID = "#{
|
326
|
+
# @NXID = "#{nexpose_identifier_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
|
327
327
|
else
|
328
328
|
fail 'Could not close tickets - do not understand the ticketing mode!'
|
329
329
|
end
|