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
@@ -5,6 +5,8 @@ require 'uri'
|
|
5
5
|
require 'csv'
|
6
6
|
require 'savon'
|
7
7
|
require 'nexpose_ticketing/nx_logger'
|
8
|
+
require 'nexpose_ticketing/version'
|
9
|
+
require 'nexpose_ticketing/common_helper'
|
8
10
|
|
9
11
|
# Serves as the Remedy interface for creating/updating issues from
|
10
12
|
# vulnelrabilities found in Nexpose.
|
@@ -13,42 +15,90 @@ class RemedyHelper
|
|
13
15
|
def initialize(remedy_data, options)
|
14
16
|
@remedy_data = remedy_data
|
15
17
|
@options = options
|
16
|
-
@log = NexposeTicketing::
|
18
|
+
@log = NexposeTicketing::NxLogger.instance
|
19
|
+
@common_helper = NexposeTicketing::CommonHelper.new(@options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generates a savon-based ticket object.
|
23
|
+
#
|
24
|
+
# * *Args* :
|
25
|
+
# - +extra_fields+ - List of mode-specific fields (hash) to be added to the ticket.
|
26
|
+
#
|
27
|
+
def generate_new_ticket(extra_fields=nil)
|
28
|
+
base_ticket = {
|
29
|
+
'First_Name' => "#{@remedy_data[:first_name]}",
|
30
|
+
'Impact' => '1-Extensive/Widespread',
|
31
|
+
'Last_Name' => "#{@remedy_data[:last_name]}",
|
32
|
+
'Reported_Source' => 'Other',
|
33
|
+
'Service_Type' => 'Infrastructure Event',
|
34
|
+
'Status' => 'New',
|
35
|
+
'Action' => 'CREATE',
|
36
|
+
"Summary"=>"",
|
37
|
+
"Notes"=>"",
|
38
|
+
'Urgency' => '1-Critical',
|
39
|
+
}
|
40
|
+
extra_fields.each { |k, v| base_ticket[k.to_s] = v } if extra_fields
|
41
|
+
base_ticket
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list
|
45
|
+
# as a separate web service call).
|
46
|
+
#
|
47
|
+
# * *Args* :
|
48
|
+
# - +wdsl+ - XML file which describes the network service.
|
49
|
+
# - +endpoint+ - Endpoint to which the data will be submitted.
|
50
|
+
#
|
51
|
+
def get_client(wdsl, endpoint)
|
52
|
+
Savon.client(wsdl: File.join(File.dirname(__FILE__), "../config/remedy_wsdl/#{wdsl}"),
|
53
|
+
adapter: :net_http,
|
54
|
+
ssl_verify_mode: :none,
|
55
|
+
open_timeout: @remedy_data[:open_timeout],
|
56
|
+
read_timeout: @remedy_data[:read_timeout],
|
57
|
+
endpoint: @remedy_data[endpoint.intern],
|
58
|
+
soap_header: { 'AuthenticationInfo' =>
|
59
|
+
{ 'userName' => "#{@remedy_data[:username]}",
|
60
|
+
'password' => "#{@remedy_data[:password]}",
|
61
|
+
'authentication' => "#{@remedy_data[:authentication]}"
|
62
|
+
}
|
63
|
+
})
|
17
64
|
end
|
18
65
|
|
19
66
|
# Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list
|
20
67
|
# as a separate web service call).
|
21
68
|
#
|
22
69
|
# * *Args* :
|
70
|
+
# - +service+ - The helpdesk service to which the tickets should be submitted.
|
23
71
|
# - +tickets+ - List of savon-formatted (hash) ticket creates (new tickets).
|
24
72
|
#
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
ssl_verify_mode: :none,
|
29
|
-
open_timeout: @remedy_data[:open_timeout],
|
30
|
-
read_timeout: @remedy_data[:read_timeout],
|
31
|
-
endpoint: @remedy_data[:create_soap_endpoint],
|
32
|
-
soap_header: { 'AuthenticationInfo' =>
|
33
|
-
{ 'userName' => "#{@remedy_data[:username]}",
|
34
|
-
'password' => "#{@remedy_data[:password]}",
|
35
|
-
'authentication' => "#{@remedy_data[:authentication]}"
|
36
|
-
}
|
37
|
-
})
|
73
|
+
def send_tickets(client, service, tickets)
|
74
|
+
service_name = service.to_s.match(/desk_([a-z]*)_/)
|
75
|
+
service_name = service_name.captures.first unless service_name.nil?
|
38
76
|
tickets.each do |ticket|
|
39
77
|
begin
|
40
78
|
@log.log_message(ticket)
|
41
|
-
response = client.call(
|
79
|
+
response = client.call(service, message: ticket)
|
42
80
|
rescue Savon::SOAPFault => e
|
43
|
-
@log.log_message("SOAP exception in
|
81
|
+
@log.log_message("SOAP exception in #{service_name} ticket: #{e.message}")
|
44
82
|
raise
|
45
83
|
rescue Savon::HTTPError => e
|
46
|
-
@log.log_message("HTTP error in
|
84
|
+
@log.log_message("HTTP error in #{service_name} ticket: #{e.message}")
|
47
85
|
raise
|
48
86
|
end
|
49
87
|
end
|
50
88
|
end
|
51
|
-
|
89
|
+
|
90
|
+
# Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list
|
91
|
+
# as a separate web service call).
|
92
|
+
#
|
93
|
+
# * *Args* :
|
94
|
+
# - +tickets+ - List of savon-formatted (hash) ticket creates (new tickets).
|
95
|
+
#
|
96
|
+
def create_tickets(tickets)
|
97
|
+
fail 'Ticket(s) cannot be empty' if tickets.nil? || tickets.empty?
|
98
|
+
client = get_client('HPD_IncidentInterface_Create_WS.xml', :create_soap_endpoint)
|
99
|
+
send_tickets(client, :help_desk_submit_service, tickets)
|
100
|
+
end
|
101
|
+
|
52
102
|
# Sends ticket updates (in SOAP format) to Remedy individually (each ticket in the list
|
53
103
|
# as a separate web service call).
|
54
104
|
#
|
@@ -58,30 +108,10 @@ class RemedyHelper
|
|
58
108
|
def update_tickets(tickets)
|
59
109
|
if tickets.nil? || tickets.empty?
|
60
110
|
@log.log_message("No tickets to update.")
|
61
|
-
|
62
|
-
client = Savon.client(wsdl: File.join(File.dirname(__FILE__), '../config/remedy_wsdl/HPD_IncidentInterface_WS.xml'),
|
63
|
-
ssl_verify_mode: :none,
|
64
|
-
open_timeout: @remedy_data[:open_timeout],
|
65
|
-
read_timeout: @remedy_data[:read_timeout],
|
66
|
-
endpoint: @remedy_data[:query_modify_soap_endpoint],
|
67
|
-
soap_header: { 'AuthenticationInfo' =>
|
68
|
-
{ 'userName' => "#{@remedy_data[:username]}",
|
69
|
-
'password' => "#{@remedy_data[:password]}",
|
70
|
-
'authentication' => "#{@remedy_data[:authentication]}"
|
71
|
-
}
|
72
|
-
})
|
73
|
-
tickets.each do |ticket|
|
74
|
-
begin
|
75
|
-
response = client.call(:help_desk_modify_service, message: ticket)
|
76
|
-
rescue Savon::SOAPFault => e
|
77
|
-
@log.log_message("SOAP exception in create ticket: #{e.message}")
|
78
|
-
raise
|
79
|
-
rescue Savon::HTTPError => e
|
80
|
-
@log.log_message("HTTP error in create ticket: #{e.message}")
|
81
|
-
raise
|
82
|
-
end
|
83
|
-
end
|
111
|
+
return
|
84
112
|
end
|
113
|
+
client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
|
114
|
+
send_tickets(client, :help_desk_modify_service, tickets)
|
85
115
|
end
|
86
116
|
|
87
117
|
# Sends ticket closure (in SOAP format) to Remedy individually (each ticket in the list
|
@@ -93,30 +123,10 @@ class RemedyHelper
|
|
93
123
|
def close_tickets(tickets)
|
94
124
|
if tickets.nil? || tickets.empty?
|
95
125
|
@log.log_message("No tickets to close.")
|
96
|
-
|
97
|
-
client = Savon.client(wsdl: File.join(File.dirname(__FILE__), '../config/remedy_wsdl/HPD_IncidentInterface_WS.xml'),
|
98
|
-
ssl_verify_mode: :none,
|
99
|
-
open_timeout: @remedy_data[:open_timeout],
|
100
|
-
read_timeout: @remedy_data[:read_timeout],
|
101
|
-
endpoint: @remedy_data[:query_modify_soap_endpoint],
|
102
|
-
soap_header: { 'AuthenticationInfo' =>
|
103
|
-
{ 'userName' => "#{@remedy_data[:username]}",
|
104
|
-
'password' => "#{@remedy_data[:password]}",
|
105
|
-
'authentication' => "#{@remedy_data[:authentication]}"
|
106
|
-
}
|
107
|
-
})
|
108
|
-
tickets.each do |ticket|
|
109
|
-
begin
|
110
|
-
response = client.call(:help_desk_modify_service, message: ticket)
|
111
|
-
rescue Savon::SOAPFault => e
|
112
|
-
@log.log_message("SOAP exception in create ticket: #{e.message}")
|
113
|
-
raise
|
114
|
-
rescue Savon::HTTPError => e
|
115
|
-
@log.log_message("HTTP error in create ticket: #{e.message}")
|
116
|
-
raise
|
117
|
-
end
|
118
|
-
end
|
126
|
+
return
|
119
127
|
end
|
128
|
+
client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
|
129
|
+
send_tickets(client, :help_desk_modify_service, tickets)
|
120
130
|
end
|
121
131
|
|
122
132
|
# Sends a query (in SOAP format) to Remedy to return back a single ticket based on the criteria.
|
@@ -128,19 +138,10 @@ class RemedyHelper
|
|
128
138
|
# - Remedy incident information in hash format or nil if no results are found.
|
129
139
|
#
|
130
140
|
def query_for_ticket(unique_id)
|
131
|
-
client =
|
132
|
-
|
133
|
-
open_timeout: @remedy_data[:open_timeout],
|
134
|
-
read_timeout: @remedy_data[:read_timeout],
|
135
|
-
endpoint: @remedy_data[:query_modify_soap_endpoint],
|
136
|
-
soap_header: { 'AuthenticationInfo' =>
|
137
|
-
{ 'userName' => "#{@remedy_data[:username]}",
|
138
|
-
'password' => "#{@remedy_data[:password]}",
|
139
|
-
'authentication' => "#{@remedy_data[:authentication]}"
|
140
|
-
}
|
141
|
-
})
|
141
|
+
client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
|
142
|
+
|
142
143
|
begin
|
143
|
-
response = client.call(:help_desk_query_list_service, message: {'Qualification' => "'Detailed Decription' LIKE \"%#{unique_id}%\""})
|
144
|
+
response = client.call(:help_desk_query_list_service, message: {'Qualification' => "'Status' < \"Closed\" AND 'Detailed Decription' LIKE \"%#{unique_id}%\""})
|
144
145
|
rescue Savon::SOAPFault => e
|
145
146
|
@log.log_message("SOAP exception in query ticket: #{e.message}")
|
146
147
|
return if e.to_hash[:fault][:faultstring].index("ERROR (302)") == 0
|
@@ -163,20 +164,19 @@ class RemedyHelper
|
|
163
164
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
164
165
|
#
|
165
166
|
def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
|
166
|
-
@ticket
|
167
|
+
@log.log_message('Preparing ticket requests...')
|
167
168
|
case @options[:ticket_mode]
|
168
|
-
# 'D' Default
|
169
|
-
when 'D'
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# 'V' Vulnerability mode: Vulnerability -* IP address
|
175
|
-
when 'V'
|
176
|
-
prepare_create_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
|
169
|
+
# 'D' Default IP *-* Vulnerability
|
170
|
+
when 'D' then matching_fields = ['ip_address', 'vulnerability_id']
|
171
|
+
# 'I' IP address -* Vulnerability
|
172
|
+
when 'I' then matching_fields = ['ip_address']
|
173
|
+
# 'V' Vulnerability -* Assets
|
174
|
+
when 'V' then matching_fields = ['vulnerability_id']
|
177
175
|
else
|
178
|
-
|
176
|
+
fail 'Unsupported ticketing mode selected.'
|
179
177
|
end
|
178
|
+
|
179
|
+
prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
|
180
180
|
end
|
181
181
|
|
182
182
|
# Prepare to update tickets from the CSV of vulnerabilities exported from Nexpose. This method determines
|
@@ -189,113 +189,22 @@ class RemedyHelper
|
|
189
189
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
190
190
|
#
|
191
191
|
def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
|
192
|
-
@ticket = Hash.new(-1)
|
193
192
|
case @options[:ticket_mode]
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
fail '
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
|
206
|
-
# Remedy. The preparation by default means that each vulnerability within Nexpose is a
|
207
|
-
# separate incident within Remedy. This makes for smaller, more actionalble incidents but
|
208
|
-
# could lead to a very large total number of incidents.
|
209
|
-
#
|
210
|
-
# * *Args* :
|
211
|
-
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
212
|
-
#
|
213
|
-
# * *Returns* :
|
214
|
-
# - List of savon-formated (hash) tickets for creating within Remedy.
|
215
|
-
#
|
216
|
-
def prepare_create_tickets_default(vulnerability_list, nexpose_identifier_id)
|
217
|
-
@log.log_message("Preparing tickets by default method.")
|
218
|
-
tickets = []
|
219
|
-
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
220
|
-
# NXID in the notes is a unique identifier used to query incidents to update/resolve
|
221
|
-
# incidents as they are resolved in Nexpose.
|
222
|
-
ticket = {
|
223
|
-
'First_Name' => "#{@remedy_data[:first_name]}",
|
224
|
-
'Impact' => '1-Extensive/Widespread',
|
225
|
-
'Last_Name' => "#{@remedy_data[:last_name]}",
|
226
|
-
'Reported_Source' => 'Other',
|
227
|
-
'Service_Type' => 'Infrastructure Event',
|
228
|
-
'Status' => 'New',
|
229
|
-
'Action' => 'CREATE',
|
230
|
-
'Summary' => "#{row['ip_address']} => #{row['summary']}",
|
231
|
-
'Notes' => "Summary: #{row['summary']} \n\nFix: #{row['fix']} \n\nURL: #{row['url']}
|
232
|
-
\n\nNXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}",
|
233
|
-
'Urgency' => '1-Critical'
|
234
|
-
}
|
235
|
-
tickets.push(ticket)
|
193
|
+
# 'D' Default IP *-* Vulnerability
|
194
|
+
when 'D' then fail 'Ticket updates are not supported in Default mode.'
|
195
|
+
# 'I' IP address -* Vulnerability
|
196
|
+
when 'I' then matching_fields = ['ip_address']
|
197
|
+
# 'V' Vulnerability -* Assets
|
198
|
+
when 'V' then matching_fields = ['vulnerability_id']
|
199
|
+
else
|
200
|
+
fail 'Unsupported ticketing mode selected.'
|
236
201
|
end
|
237
|
-
|
202
|
+
|
203
|
+
prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
|
238
204
|
end
|
239
205
|
|
240
206
|
# Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
|
241
|
-
# Remedy.
|
242
|
-
# address are consolidated into a single Remedy incident. This reduces the number of incidents
|
243
|
-
# within ServiceNow but greatly increases the size of the work notes.
|
244
|
-
#
|
245
|
-
# * *Args* :
|
246
|
-
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
247
|
-
#
|
248
|
-
# * *Returns* :
|
249
|
-
# - List of savon-formated (hash) tickets for creating within Remedy.
|
250
|
-
#
|
251
|
-
def prepare_create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
252
|
-
@log.log_message('Preparing tickets by IP address.')
|
253
|
-
tickets = []
|
254
|
-
current_ip = -1
|
255
|
-
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
256
|
-
if current_ip == -1
|
257
|
-
current_ip = row['ip_address']
|
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
|
-
@ticket = {
|
260
|
-
'First_Name' => "#{@remedy_data[:first_name]}",
|
261
|
-
'Impact' => '1-Extensive/Widespread',
|
262
|
-
'Last_Name' => "#{@remedy_data[:last_name]}",
|
263
|
-
'Reported_Source' => 'Other',
|
264
|
-
'Service_Type' => 'Infrastructure Event',
|
265
|
-
'Status' => 'New',
|
266
|
-
'Action' => 'CREATE',
|
267
|
-
'Summary' => "#{row['ip_address']} => Vulnerabilities",
|
268
|
-
'Notes' => "++ New Vulnerabilities +++++++++++++++++++++++++++++++++++++\n",
|
269
|
-
'Urgency' => '1-Critical'
|
270
|
-
}
|
271
|
-
end
|
272
|
-
if current_ip == row['ip_address']
|
273
|
-
@ticket['Notes'] +=
|
274
|
-
"\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
|
275
|
-
unless row['url'].nil?
|
276
|
-
@ticket['Notes'] +=
|
277
|
-
"\nURL: #{row['url']}"
|
278
|
-
end
|
279
|
-
end
|
280
|
-
unless current_ip == row['ip_address']
|
281
|
-
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
282
|
-
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
|
283
|
-
tickets.push(@ticket)
|
284
|
-
current_ip = -1
|
285
|
-
redo
|
286
|
-
end
|
287
|
-
end
|
288
|
-
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
289
|
-
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
|
290
|
-
tickets.push(@ticket) unless @ticket.nil?
|
291
|
-
tickets
|
292
|
-
end
|
293
|
-
|
294
|
-
|
295
|
-
# Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
|
296
|
-
# Remedy. The preparation by vulnerability means that all IP addresses within Nexpose for one vulnerability
|
297
|
-
# are consolidated into a single Remedy incident. This reduces the number of incidents
|
298
|
-
# within ServiceNow but greatly increases the size of the work notes.
|
207
|
+
# Remedy.
|
299
208
|
#
|
300
209
|
# * *Args* :
|
301
210
|
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
@@ -303,266 +212,131 @@ class RemedyHelper
|
|
303
212
|
# * *Returns* :
|
304
213
|
# - List of savon-formated (hash) tickets for creating within Remedy.
|
305
214
|
#
|
306
|
-
def
|
307
|
-
@log.log_message("Preparing tickets by vulnerability.")
|
308
|
-
tickets = []
|
309
|
-
current_vuln_id = -1
|
310
|
-
current_solution_id = -1
|
311
|
-
current_asset_id = -1
|
312
|
-
full_summary = nil
|
313
|
-
vulnerability_list_header = vulnerability_list[0]
|
314
|
-
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
315
|
-
#New vulnerability ID (the query sorting criteria).
|
316
|
-
if current_vuln_id == -1
|
317
|
-
current_vuln_id = row['vulnerability_id']
|
318
|
-
current_solution_id = row['solution_id']
|
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 Nexpose Identifier ID: #{nexpose_identifier_id}")
|
321
|
-
summary = "Vulnerability: #{row['title']}"
|
322
|
-
|
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.
|
324
|
-
if summary.length > 100
|
325
|
-
full_summary = summary += "\n\n\n"
|
326
|
-
summary = summary[0..96]
|
327
|
-
summary += '...'
|
328
|
-
else
|
329
|
-
full_summary = nil
|
330
|
-
end
|
331
|
-
|
332
|
-
@ticket = {
|
333
|
-
'First_Name' => "#{@remedy_data[:first_name]}",
|
334
|
-
'Impact' => '1-Extensive/Widespread',
|
335
|
-
'Last_Name' => "#{@remedy_data[:last_name]}",
|
336
|
-
'Reported_Source' => 'Other',
|
337
|
-
'Service_Type' => 'Infrastructure Event',
|
338
|
-
'Status' => 'New',
|
339
|
-
'Action' => 'CREATE',
|
340
|
-
'Summary' => summary,
|
341
|
-
'Full_Summary' => summary,
|
342
|
-
'Assets' => "++ Assets affected +++++++++++++++++++++++\n",
|
343
|
-
'Solutions' => "++ Details ++++++++++++++++++++++++++\n",
|
344
|
-
'Notes' => "++ Additional information ++++++++++++++++\n",
|
345
|
-
'Urgency' => '1-Critical'
|
346
|
-
}
|
347
|
-
@ticket['Solutions'] +=
|
348
|
-
"\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
|
349
|
-
@ticket['Assets'] +=
|
350
|
-
"#{row['ip_address']}"
|
351
|
-
end
|
352
|
-
if current_vuln_id == row['vulnerability_id']
|
353
|
-
#Add solutions for the now affected assets.
|
354
|
-
if current_solution_id != row['solution_id']
|
355
|
-
new_solution_text = "\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}\n"
|
356
|
-
if @ticket['Solutions'].include? new_solution_text
|
357
|
-
@log.log_message('Ignoring duplicate solution in ticket creation.')
|
358
|
-
else
|
359
|
-
@ticket['Solutions'] += new_solution_text
|
360
|
-
#Add any references.
|
361
|
-
unless row['url'].nil?
|
362
|
-
@ticket['Solutions'] += "\nURL: #{row['url']}"
|
363
|
-
end
|
364
|
-
end
|
365
|
-
current_solution_id = row['solution_id']
|
366
|
-
end
|
367
|
-
|
368
|
-
#Added the new asset to the list of affected systems if it is different (could have been the same asset with a different solution ID).
|
369
|
-
if current_asset_id != row['asset_id']
|
370
|
-
@ticket['Assets'] += ", #{row['ip_address']}"
|
371
|
-
current_asset_id = row['asset_id']
|
372
|
-
end
|
373
|
-
end
|
374
|
-
unless current_vuln_id == row['vulnerability_id']
|
375
|
-
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
376
|
-
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
|
377
|
-
current_vuln_id = -1
|
378
|
-
current_solution_id = -1
|
379
|
-
current_asset_id = -1
|
380
|
-
@ticket = format_notes_by_vulnerability(@ticket, full_summary)
|
381
|
-
tickets.push(@ticket)
|
382
|
-
redo
|
383
|
-
end
|
384
|
-
end
|
385
|
-
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
386
|
-
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
|
387
|
-
@ticket = format_notes_by_vulnerability(@ticket, full_summary)
|
388
|
-
tickets.push(@ticket) unless @ticket.nil?
|
389
|
-
tickets
|
390
|
-
end
|
391
|
-
|
392
|
-
def format_notes_by_vulnerability(ticket, prepend)
|
393
|
-
nxid_holder = ticket['Notes']
|
394
|
-
ticket['Notes'] = ''
|
395
|
-
ticket['Notes'] += prepend unless prepend.nil?
|
396
|
-
ticket['Notes'] += ticket['Assets'] += "\n\n"
|
397
|
-
ticket['Notes'] += ticket['Solutions'] += "\n\n"
|
398
|
-
ticket['Notes'] += nxid_holder
|
399
|
-
ticket.delete("Assets")
|
400
|
-
ticket.delete("Solutions")
|
401
|
-
ticket.delete("Full_Summary")
|
402
|
-
ticket
|
403
|
-
end
|
404
|
-
|
405
|
-
|
406
|
-
# Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose. This method
|
407
|
-
# currently only supports updating IP-address mode tickets in Remedy. The list of vulnerabilities
|
408
|
-
# are ordered by IP address and then by ticket_status, allowing the method to loop through and
|
409
|
-
# display new, old, and same vulnerabilities in that order.
|
410
|
-
#
|
411
|
-
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
412
|
-
#
|
413
|
-
# * *Returns* :
|
414
|
-
# - List of savon-formated (hash) tickets for updating within Remedy.
|
415
|
-
#
|
416
|
-
def prepare_update_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
|
417
|
-
fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'I'
|
215
|
+
def prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
|
418
216
|
@ticket = Hash.new(-1)
|
419
217
|
|
420
|
-
@log.log_message(
|
218
|
+
@log.log_message("Preparing tickets for #{@options[:ticket_mode]} mode.")
|
421
219
|
tickets = []
|
422
|
-
|
423
|
-
|
220
|
+
previous_row = nil
|
221
|
+
description = nil
|
424
222
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
425
|
-
if
|
426
|
-
|
427
|
-
|
223
|
+
if previous_row.nil?
|
224
|
+
previous_row = row.dup
|
225
|
+
description = @common_helper.get_description(nexpose_identifier_id, row)
|
226
|
+
@ticket = generate_new_ticket({'Summary' => "#{@common_helper.get_title(row, 100)}",
|
227
|
+
'Notes' => ""})
|
228
|
+
#Skip querying for ticket if it's the initial scan
|
229
|
+
next if row['comparison'].nil?
|
428
230
|
|
429
231
|
# Query Remedy for the incident by unique id (generated NXID)
|
430
|
-
queried_incident = query_for_ticket("NXID: #{nexpose_identifier_id
|
431
|
-
if queried_incident.nil?
|
432
|
-
|
433
|
-
else
|
434
|
-
@log.log_message("Creating ticket update with IP address: #{row['ip_address']} for Nexpose Identifier with ID: #{nexpose_identifier_id}")
|
435
|
-
@log.log_message("Ticket status #{ticket_status}")
|
436
|
-
# Remedy incident updates require populating all fields.
|
437
|
-
@ticket = extract_queried_incident(queried_incident, "++ #{row['comparison']} Vulnerabilities ++++++++++++++++++++++++++\n")
|
438
|
-
end
|
439
|
-
end
|
440
|
-
if current_ip == row['ip_address']
|
441
|
-
# If the ticket_status is different, add a a new 'header' to signify a new block of tickets.
|
442
|
-
unless ticket_status == row['comparison']
|
443
|
-
@ticket['Notes'] +=
|
444
|
-
"\n\n\n++ #{row['comparison']} Vulnerabilities ++++++++++++++++++++++++++\n"
|
445
|
-
ticket_status = row['comparison']
|
446
|
-
end
|
447
|
-
|
448
|
-
@ticket['Notes'] +=
|
449
|
-
"\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
|
450
|
-
# Only add the URL block if data exists in the row.
|
451
|
-
unless row['url'].nil?
|
452
|
-
@ticket['Notes'] +=
|
453
|
-
"\nURL: #{row['url']}"
|
232
|
+
queried_incident = query_for_ticket("NXID: #{@common_helper.generate_nxid(nexpose_identifier_id, row)}")
|
233
|
+
if !queried_incident.nil? && queried_incident.first.is_a?(Hash)
|
234
|
+
queried_incident.select! { |t| !['Closed', 'Resolved', 'Cancelled'].include?(t[:status]) }
|
454
235
|
end
|
455
|
-
end
|
456
|
-
unless current_ip == row['ip_address']
|
457
|
-
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
458
|
-
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
|
459
|
-
tickets.push(@ticket)
|
460
|
-
current_ip = -1
|
461
|
-
redo
|
462
|
-
end
|
463
|
-
end
|
464
|
-
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
465
|
-
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}" unless @ticket.empty?
|
466
|
-
tickets.push(@ticket) unless @ticket.nil? || @ticket.empty?
|
467
|
-
tickets
|
468
|
-
end
|
469
|
-
|
470
|
-
# Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose. This method
|
471
|
-
# currently only supports updating vulnerability mode tickets in Remedy. The list of vulnerabilities
|
472
|
-
# are ordered by vulnerability ID and then by ticket_status, allowing the method to loop through and
|
473
|
-
# display new, old, and same vulnerabilities in that order.
|
474
|
-
#
|
475
|
-
# - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
|
476
|
-
#
|
477
|
-
# * *Returns* :
|
478
|
-
# - List of savon-formated (hash) tickets for updating within Remedy.
|
479
|
-
#
|
480
|
-
def prepare_update_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
|
481
|
-
fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'V'
|
482
|
-
@ticket = Hash.new(-1)
|
483
|
-
|
484
|
-
@log.log_message('Preparing ticket updates by IP address.')
|
485
|
-
tickets = []
|
486
|
-
current_vuln_id = -1
|
487
|
-
current_solution_id = -1
|
488
|
-
current_asset_id = -1
|
489
|
-
current_solutions_text = "\n++ Details ++++++++++++++\n"
|
490
|
-
ticket_status = 'New'
|
491
|
-
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
492
|
-
if current_vuln_id == -1
|
493
|
-
current_solutions_text = "\n++ Details +++++++++++++++\n"
|
494
|
-
current_vuln_id = row['vulnerability_id']
|
495
|
-
ticket_status = row['comparison']
|
496
|
-
current_asset_id = -1
|
497
|
-
current_solution_id = -1
|
498
|
-
|
499
|
-
# Query Remedy for the incident by unique id (generated NXID)
|
500
|
-
queried_incident = query_for_ticket("NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}")
|
501
236
|
if queried_incident.nil? || queried_incident.empty?
|
502
|
-
@log.log_message("No incident found for NXID: #{nexpose_identifier_id
|
237
|
+
@log.log_message("No incident found for NXID: #{@common_helper.generate_nxid(nexpose_identifier_id, row)}. Creating...")
|
503
238
|
new_ticket_csv = vulnerability_list.split("\n").first
|
504
239
|
new_ticket_csv += "\n#{row.to_s}"
|
505
|
-
|
240
|
+
|
241
|
+
#delete the comparison row
|
242
|
+
data = CSV::Table.new(CSV.parse(new_ticket_csv, headers: true))
|
243
|
+
data.delete("comparison")
|
244
|
+
|
245
|
+
new_ticket = prepare_create_tickets(data.to_s, nexpose_identifier_id)
|
506
246
|
@log.log_message('Created ticket. Sending to Remedy...')
|
507
247
|
create_tickets(new_ticket)
|
508
248
|
@log.log_message('Ticket sent. Performing update for ticket...')
|
509
249
|
#Now that there is a ticket for this NXID update it as if it existed this whole time...
|
510
|
-
|
250
|
+
previous_row = nil
|
511
251
|
redo
|
512
252
|
else
|
513
|
-
@
|
253
|
+
info = @common_helper.get_field_info(matching_fields, previous_row)
|
254
|
+
@log.log_message("Creating ticket update with #{info} for Nexpose Identifier with ID: #{nexpose_identifier_id}")
|
255
|
+
@log.log_message("Ticket status #{row['comparison']}")
|
514
256
|
# Remedy incident updates require populating all fields.
|
515
|
-
@ticket = extract_queried_incident(queried_incident, "
|
516
|
-
end
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
unless ticket_status == row['comparison']
|
521
|
-
@ticket['Notes'] +=
|
522
|
-
"\n\n\n++ #{row['comparison']} Assets +++++++++++++++++++++++\n"
|
523
|
-
ticket_status = row['comparison']
|
524
|
-
end
|
257
|
+
@ticket = extract_queried_incident(queried_incident, "")
|
258
|
+
end
|
259
|
+
elsif matching_fields.any? { |x| previous_row[x].nil? || previous_row[x] != row[x] }
|
260
|
+
info = @common_helper.get_field_info(matching_fields, previous_row)
|
261
|
+
@log.log_message("Generated ticket with #{info}")
|
525
262
|
|
526
|
-
|
527
|
-
if current_asset_id != row['asset_id']
|
528
|
-
@ticket['Notes'] += "#{row['ip_address']}, "
|
529
|
-
current_asset_id = row['asset_id']
|
530
|
-
end
|
531
|
-
|
532
|
-
#Add solutions for the now affected assets.
|
533
|
-
if current_solution_id != row['solution_id']
|
534
|
-
new_solution_text = "\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}\n"
|
535
|
-
if current_solutions_text.include? new_solution_text
|
536
|
-
@log.log_message('Ignoring duplicate solution for ticket update.')
|
537
|
-
else
|
538
|
-
current_solutions_text += new_solution_text
|
539
|
-
#Add any references.
|
540
|
-
unless row['url'].nil?
|
541
|
-
current_solutions_text +=
|
542
|
-
"\nURL: #{row['url']}"
|
543
|
-
end
|
544
|
-
end
|
545
|
-
current_solution_id = row['solution_id']
|
546
|
-
end
|
547
|
-
end
|
548
|
-
unless current_vuln_id == row['vulnerability_id']
|
549
|
-
# NXID in the work_notes is the unique identifier used to query incidents to update them.
|
550
|
-
@ticket['Notes'] += "\n\n" + current_solutions_text
|
551
|
-
@ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
|
263
|
+
@ticket['Notes'] = @common_helper.print_description(description)
|
552
264
|
tickets.push(@ticket)
|
553
|
-
|
554
|
-
|
555
|
-
current_asset_id = -1
|
265
|
+
previous_row = nil
|
266
|
+
description = nil
|
556
267
|
redo
|
268
|
+
else
|
269
|
+
description = @common_helper.update_description(description, row)
|
557
270
|
end
|
558
271
|
end
|
559
|
-
|
560
|
-
@ticket
|
561
|
-
|
562
|
-
|
272
|
+
|
273
|
+
unless @ticket.nil? || @ticket.empty?
|
274
|
+
@ticket['Notes'] = @common_helper.print_description(description)
|
275
|
+
tickets.push(@ticket)
|
276
|
+
end
|
277
|
+
|
278
|
+
@log.log_message("Generated <#{tickets.count.to_s}> tickets.")
|
563
279
|
tickets
|
564
280
|
end
|
565
281
|
|
282
|
+
# Creates a ticket with the extracted data from a queried Remedy incident.
|
283
|
+
#
|
284
|
+
# - +queried_incident+ - The queried incident from Remedy
|
285
|
+
# - +notes_header+ - The texted to be placed at the top of the Remedy 'Notes' field.
|
286
|
+
# - +status+ - The status to which the ticket will be set.
|
287
|
+
#
|
288
|
+
# * *Returns* :
|
289
|
+
# - A single savon-formated (hash) ticket for updating within Remedy.
|
290
|
+
#
|
291
|
+
def ticket_from_queried_incident(queried_incident, notes_header, status)
|
292
|
+
{
|
293
|
+
'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
|
294
|
+
'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
|
295
|
+
'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
|
296
|
+
'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
|
297
|
+
'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
|
298
|
+
'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
|
299
|
+
'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
|
300
|
+
'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
|
301
|
+
'Closure_Product_Name' => queried_incident[:closure_product_name],
|
302
|
+
'Company' => queried_incident[:company],
|
303
|
+
'Summary' => queried_incident[:summary],
|
304
|
+
'Notes' => notes_header || queried_incident[:notes],
|
305
|
+
'Impact' => queried_incident[:impact],
|
306
|
+
'Manufacturer' => queried_incident[:manufacturer],
|
307
|
+
'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
|
308
|
+
'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
|
309
|
+
'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
|
310
|
+
'Product_Model_Version' => queried_incident[:product_model_version],
|
311
|
+
'Product_Name' => queried_incident[:product_name],
|
312
|
+
'Reported_Source' => queried_incident[:reported_source],
|
313
|
+
'Resolution' => queried_incident[:resolution],
|
314
|
+
'Resolution_Category' => queried_incident[:resolution_category],
|
315
|
+
'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
|
316
|
+
'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
|
317
|
+
'Resolution_Method' => queried_incident[:resolution_method],
|
318
|
+
'Service_Type' => queried_incident[:service_type],
|
319
|
+
'Status' => status || queried_incident[:status],
|
320
|
+
'Urgency' => queried_incident[:urgency],
|
321
|
+
'Action' => 'MODIFY',
|
322
|
+
'Work_Info_Summary' => queried_incident[:work_info_summary],
|
323
|
+
'Work_Info_Notes' => queried_incident[:work_info_notes],
|
324
|
+
'Work_Info_Type' => queried_incident[:work_info_type],
|
325
|
+
'Work_Info_Date' => queried_incident[:work_info_date],
|
326
|
+
'Work_Info_Source' => queried_incident[:work_info_source],
|
327
|
+
'Work_Info_Locked' => queried_incident[:work_info_locked],
|
328
|
+
'Work_Info_View_Access' => queried_incident[:work_info_view_access],
|
329
|
+
'Incident_Number' => queried_incident[:incident_number],
|
330
|
+
'Status_Reason' => queried_incident[:status_reason],
|
331
|
+
'ServiceCI' => queried_incident[:service_ci],
|
332
|
+
'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
|
333
|
+
'HPD_CI' => queried_incident[:hpd_ci],
|
334
|
+
'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
|
335
|
+
'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
|
336
|
+
'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
566
340
|
# Extracts from a queried Remedy incident the relevant data required for an update to be made to said incident.
|
567
341
|
# Creates a ticket with the extracted data.
|
568
342
|
#
|
@@ -573,116 +347,13 @@ class RemedyHelper
|
|
573
347
|
# - A single savon-formated (hash) ticket for updating within Remedy.
|
574
348
|
#
|
575
349
|
def extract_queried_incident(queried_incident, notes_header)
|
576
|
-
|
577
|
-
|
578
|
-
#Hash of hashes
|
579
|
-
@log.log_message("More than one ticket returned from Remedy. Number of tickets returned: #{queried_incident.count}. Parsing return to check returned ticket status...")
|
580
|
-
@ticket = nil
|
581
|
-
queried_incident.count.times do |x|
|
582
|
-
#TODO: This should be moved to the config for the Remedy helper.
|
583
|
-
if ['Closed', 'Resolved', 'Cancelled'].include? queried_incident[x][:status]
|
584
|
-
@log.log_message("Returned ticket number <#{x}> of <#{(queried_incident.count - 1)}> is of status <#{queried_incident[x][:status]}>. Ignoring.")
|
585
|
-
else
|
586
|
-
fail 'When trying to update tickets Remedy returned multiple tickets with the same NXID and in progress status!' unless @ticket.nil?
|
587
|
-
@ticket = {
|
588
|
-
'Categorization_Tier_1' => queried_incident[x][:categorization_tier_1],
|
589
|
-
'Categorization_Tier_2' => queried_incident[x][:categorization_tier_2],
|
590
|
-
'Categorization_Tier_3' => queried_incident[x][:categorization_tier_3],
|
591
|
-
'Closure_Manufacturer' => queried_incident[x][:closure_manufacturer],
|
592
|
-
'Closure_Product_Category_Tier1' => queried_incident[x][:closure_product_category_tier1],
|
593
|
-
'Closure_Product_Category_Tier2' => queried_incident[x][:closure_product_category_tier2],
|
594
|
-
'Closure_Product_Category_Tier3' => queried_incident[x][:closure_product_category_tier3],
|
595
|
-
'Closure_Product_Model_Version' => queried_incident[x][:closure_product_model_version],
|
596
|
-
'Closure_Product_Name' => queried_incident[x][:closure_product_name],
|
597
|
-
'Company' => queried_incident[x][:company],
|
598
|
-
'Summary' => queried_incident[x][:summary],
|
599
|
-
'Notes' => notes_header,
|
600
|
-
'Impact' => queried_incident[x][:impact],
|
601
|
-
'Manufacturer' => queried_incident[x][:manufacturer],
|
602
|
-
'Product_Categorization_Tier_1' => queried_incident[x][:product_categorization_tier_1],
|
603
|
-
'Product_Categorization_Tier_2' => queried_incident[x][:product_categorization_tier_2],
|
604
|
-
'Product_Categorization_Tier_3' => queried_incident[x][:product_categorization_tier_3],
|
605
|
-
'Product_Model_Version' => queried_incident[x][:product_model_version],
|
606
|
-
'Product_Name' => queried_incident[x][:product_name],
|
607
|
-
'Reported_Source' => queried_incident[x][:reported_source],
|
608
|
-
'Resolution' => queried_incident[x][:resolution],
|
609
|
-
'Resolution_Category' => queried_incident[x][:resolution_category],
|
610
|
-
'Resolution_Category_Tier_2' => queried_incident[x][:resolution_category_tier_2],
|
611
|
-
'Resolution_Category_Tier_3' => queried_incident[x][:resolution_category_tier_3],
|
612
|
-
'Resolution_Method' => queried_incident[x][:resolution_method],
|
613
|
-
'Service_Type' => queried_incident[x][:service_type],
|
614
|
-
'Status' => queried_incident[x][:status],
|
615
|
-
'Urgency' => queried_incident[x][:urgency],
|
616
|
-
'Action' => 'MODIFY',
|
617
|
-
'Work_Info_Summary' => queried_incident[x][:work_info_summary],
|
618
|
-
'Work_Info_Notes' => queried_incident[x][:work_info_notes],
|
619
|
-
'Work_Info_Type' => queried_incident[x][:work_info_type],
|
620
|
-
'Work_Info_Date' => queried_incident[x][:work_info_date],
|
621
|
-
'Work_Info_Source' => queried_incident[x][:work_info_source],
|
622
|
-
'Work_Info_Locked' => queried_incident[x][:work_info_locked],
|
623
|
-
'Work_Info_View_Access' => queried_incident[x][:work_info_view_access],
|
624
|
-
'Incident_Number' => queried_incident[x][:incident_number],
|
625
|
-
'Status_Reason' => queried_incident[x][:status_reason],
|
626
|
-
'ServiceCI' => queried_incident[x][:service_ci],
|
627
|
-
'ServiceCI_ReconID' => queried_incident[x][:service_ci_recon_id],
|
628
|
-
'HPD_CI' => queried_incident[x][:hpd_ci],
|
629
|
-
'HPD_CI_ReconID' => queried_incident[x][:hpd_ci_recon_id],
|
630
|
-
'HPD_CI_FormName' => queried_incident[x][:hpd_ci_form_name],
|
631
|
-
'z1D_CI_FormName' => queried_incident[x][:z1d_ci_form_name]
|
632
|
-
}
|
633
|
-
end
|
634
|
-
end
|
635
|
-
else
|
636
|
-
@ticket = {
|
637
|
-
'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
|
638
|
-
'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
|
639
|
-
'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
|
640
|
-
'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
|
641
|
-
'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
|
642
|
-
'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
|
643
|
-
'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
|
644
|
-
'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
|
645
|
-
'Closure_Product_Name' => queried_incident[:closure_product_name],
|
646
|
-
'Company' => queried_incident[:company],
|
647
|
-
'Summary' => queried_incident[:summary],
|
648
|
-
'Notes' => notes_header,
|
649
|
-
'Impact' => queried_incident[:impact],
|
650
|
-
'Manufacturer' => queried_incident[:manufacturer],
|
651
|
-
'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
|
652
|
-
'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
|
653
|
-
'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
|
654
|
-
'Product_Model_Version' => queried_incident[:product_model_version],
|
655
|
-
'Product_Name' => queried_incident[:product_name],
|
656
|
-
'Reported_Source' => queried_incident[:reported_source],
|
657
|
-
'Resolution' => queried_incident[:resolution],
|
658
|
-
'Resolution_Category' => queried_incident[:resolution_category],
|
659
|
-
'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
|
660
|
-
'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
|
661
|
-
'Resolution_Method' => queried_incident[:resolution_method],
|
662
|
-
'Service_Type' => queried_incident[:service_type],
|
663
|
-
'Status' => queried_incident[:status],
|
664
|
-
'Urgency' => queried_incident[:urgency],
|
665
|
-
'Action' => 'MODIFY',
|
666
|
-
'Work_Info_Summary' => queried_incident[:work_info_summary],
|
667
|
-
'Work_Info_Notes' => queried_incident[:work_info_notes],
|
668
|
-
'Work_Info_Type' => queried_incident[:work_info_type],
|
669
|
-
'Work_Info_Date' => queried_incident[:work_info_date],
|
670
|
-
'Work_Info_Source' => queried_incident[:work_info_source],
|
671
|
-
'Work_Info_Locked' => queried_incident[:work_info_locked],
|
672
|
-
'Work_Info_View_Access' => queried_incident[:work_info_view_access],
|
673
|
-
'Incident_Number' => queried_incident[:incident_number],
|
674
|
-
'Status_Reason' => queried_incident[:status_reason],
|
675
|
-
'ServiceCI' => queried_incident[:service_ci],
|
676
|
-
'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
|
677
|
-
'HPD_CI' => queried_incident[:hpd_ci],
|
678
|
-
'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
|
679
|
-
'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
|
680
|
-
'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
|
681
|
-
}
|
350
|
+
unless queried_incident.first.is_a?(Hash)
|
351
|
+
return ticket_from_queried_incident(queried_incident, notes_header, nil)
|
682
352
|
end
|
683
|
-
@ticket
|
684
|
-
end
|
685
353
|
|
354
|
+
fail "Multiple tickets returned for same NXID" if queried_incidents.count > 1
|
355
|
+
ticket_from_queried_incident(queried_incident.first, notes_header, nil)
|
356
|
+
end
|
686
357
|
|
687
358
|
# Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose.
|
688
359
|
#
|
@@ -693,79 +364,22 @@ class RemedyHelper
|
|
693
364
|
# - List of savon-formated (hash) tickets for closing within Remedy.
|
694
365
|
#
|
695
366
|
def prepare_close_tickets(vulnerability_list, nexpose_identifier_id)
|
696
|
-
|
697
|
-
@log.log_message('Preparing ticket closures by default method.')
|
367
|
+
@log.log_message("Preparing ticket closures for mode #{@options[:ticket_mode]}.")
|
698
368
|
@nxid = nil
|
699
369
|
tickets = []
|
700
370
|
CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
|
701
|
-
|
702
|
-
|
703
|
-
when 'D'
|
704
|
-
@nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
|
705
|
-
# 'I' IP address mode: IP address -* Vulnerability
|
706
|
-
when 'I'
|
707
|
-
@nxid = "#{nexpose_identifier_id}#{row['current_ip']}"
|
708
|
-
# 'V' Vulnerability mode: Vulnerability -* IP address
|
709
|
-
when 'V'
|
710
|
-
@nxid = "#{nexpose_identifier_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
|
711
|
-
else
|
712
|
-
fail 'Could not close tickets - do not understand the ticketing mode!'
|
713
|
-
end
|
371
|
+
@nxid = @common_helper.generate_nxid(nexpose_identifier_id, row)
|
372
|
+
|
714
373
|
# Query Remedy for the incident by unique id (generated NXID)
|
715
374
|
queried_incident = query_for_ticket("NXID: #{@nxid}")
|
716
375
|
if queried_incident.nil? || queried_incident.empty?
|
717
376
|
@log.log_message("No incident found for NXID: #{@nxid}")
|
718
377
|
else
|
719
378
|
# Remedy incident updates require populating all fields.
|
720
|
-
ticket =
|
721
|
-
'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
|
722
|
-
'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
|
723
|
-
'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
|
724
|
-
'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
|
725
|
-
'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
|
726
|
-
'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
|
727
|
-
'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
|
728
|
-
'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
|
729
|
-
'Closure_Product_Name' => queried_incident[:closure_product_name],
|
730
|
-
'Company' => queried_incident[:company],
|
731
|
-
'Summary' => queried_incident[:summary],
|
732
|
-
'Notes' => queried_incident[:notes],
|
733
|
-
'Impact' => queried_incident[:impact],
|
734
|
-
'Manufacturer' => queried_incident[:manufacturer],
|
735
|
-
'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
|
736
|
-
'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
|
737
|
-
'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
|
738
|
-
'Product_Model_Version' => queried_incident[:product_model_version],
|
739
|
-
'Product_Name' => queried_incident[:product_name],
|
740
|
-
'Reported_Source' => queried_incident[:reported_source],
|
741
|
-
'Resolution' => queried_incident[:resolution],
|
742
|
-
'Resolution_Category' => queried_incident[:resolution_category],
|
743
|
-
'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
|
744
|
-
'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
|
745
|
-
'Resolution_Method' => queried_incident[:resolution_method],
|
746
|
-
'Service_Type' => queried_incident[:service_type],
|
747
|
-
'Status' => 'Closed',
|
748
|
-
'Urgency' => queried_incident[:urgency],
|
749
|
-
'Action' => 'MODIFY',
|
750
|
-
'Work_Info_Summary' => queried_incident[:work_info_summary],
|
751
|
-
'Work_Info_Notes' => queried_incident[:work_info_notes],
|
752
|
-
'Work_Info_Type' => queried_incident[:work_info_type],
|
753
|
-
'Work_Info_Date' => queried_incident[:work_info_date],
|
754
|
-
'Work_Info_Source' => queried_incident[:work_info_source],
|
755
|
-
'Work_Info_Locked' => queried_incident[:work_info_locked],
|
756
|
-
'Work_Info_View_Access' => queried_incident[:work_info_view_access],
|
757
|
-
'Incident_Number' => queried_incident[:incident_number],
|
758
|
-
'Status_Reason' => queried_incident[:status_reason],
|
759
|
-
'ServiceCI' => queried_incident[:service_ci],
|
760
|
-
'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
|
761
|
-
'HPD_CI' => queried_incident[:hpd_ci],
|
762
|
-
'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
|
763
|
-
'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
|
764
|
-
'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
|
765
|
-
}
|
379
|
+
ticket = ticket_from_queried_incident(queried_incident, nil, 'Closed')
|
766
380
|
tickets.push(ticket)
|
767
381
|
end
|
768
382
|
end
|
769
383
|
tickets
|
770
384
|
end
|
771
|
-
end
|
385
|
+
end
|