nexpose_ticketing 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,337 @@
1
+ require 'net/http'
2
+ require 'nokogiri'
3
+
4
+ class ServiceDeskHelper
5
+ attr_accessor :servicedesk_data, :options, :log
6
+
7
+ def initialize(servicedesk_data, options)
8
+ @servicedesk_data = servicedesk_data
9
+ @options = options
10
+ @log = NexposeTicketing::NXLogger.new
11
+
12
+ @rest_uri = servicedesk_data[:rest_uri]
13
+ @api_key = servicedesk_data[:api_key]
14
+ @ticket_db_path = servicedesk_data[:ticket_db_path]
15
+ end
16
+
17
+
18
+ def open_database()
19
+ DBM.open(@ticket_db_path, 0600, DBM::WRCREAT)
20
+ end
21
+
22
+
23
+ def add_ticket_to_database(workorderid, nxid)
24
+ @log.log_message("Adding ticket <#{workorderid}> for NXID <#{nxid}> to local db.")
25
+ db = open_database()
26
+ db[nxid] = workorderid
27
+ db.close()
28
+ end
29
+
30
+
31
+ def find_ticket_in_database(nxid)
32
+ @log.log_message("Finding workorder id for NXID <#{nxid}> from local db.")
33
+ db = open_database()
34
+ begin
35
+ workorderid = db[nxid]
36
+ @log.log_message("Lookup found incident <#{workorderid}> in the db.")
37
+ rescue Exception => e
38
+ @log.log_message("Threw an exception accessing the dbm <#{e.class} #{e} #{e.message}>.")
39
+ raise e
40
+ end
41
+ db.close()
42
+
43
+ return workorderid
44
+ end
45
+
46
+
47
+ def remove_ticket_from_database(nxid)
48
+ @log.log_message("Removing workorder id from database for NXID <#{nxid}>")
49
+ db = open_database()
50
+ db.delete(nxid)
51
+ db.close()
52
+ end
53
+
54
+ def prepare_create_tickets(vulnerability_list, site_id)
55
+ @log.log_message('Preparing ticket requests...')
56
+ case @options[:ticket_mode]
57
+ # 'D' Default mode: IP *-* Vulnerability
58
+ when 'D'
59
+ tickets = create_tickets_by_default(vulnerability_list, site_id)
60
+ # 'I' IP address mode: IP address -* Vulnerability
61
+ when 'I'
62
+ tickets = create_tickets_by_ip(vulnerability_list, site_id)
63
+ else
64
+ fail 'No ticketing mode selected.'
65
+ end
66
+
67
+ tickets.each { |ticket| @log.log_message("Prepared ticket: #{ticket}")}
68
+ return tickets
69
+ end
70
+
71
+ ## Uses the configured or default options to set up a ticket creation request
72
+ def create_ticket_request(subject, description)
73
+ request = Nokogiri::XML::Builder.new do |xml|
74
+ xml.Operation {
75
+ xml.Details {
76
+ xml.parameter {
77
+ xml.name {
78
+ xml.text 'requester'
79
+ }
80
+ xml.value {
81
+ xml.text @servicedesk_data[:requestor]
82
+ }
83
+ }
84
+ xml.parameter {
85
+ xml.name {
86
+ xml.text 'Group'
87
+ }
88
+ xml.value {
89
+ xml.text @servicedesk_data[:group]
90
+ }
91
+ }
92
+ xml.parameter {
93
+ xml.name {
94
+ xml.text 'subject'
95
+ }
96
+ xml.value {
97
+ xml.text subject
98
+ }
99
+ }
100
+ xml.parameter {
101
+ xml.name {
102
+ xml.text 'description'
103
+ }
104
+ xml.value {
105
+ xml.cdata description
106
+ }
107
+ }
108
+ }
109
+ }
110
+ end
111
+ puts request.to_xml
112
+
113
+ return request.to_xml
114
+ end
115
+
116
+ def modify_ticket_request(description)
117
+ # modifyRequest = """
118
+ # <Operation>
119
+ # <Details>
120
+ # <parameter>
121
+ # <name>description</name>
122
+ # <value>#{description}</value>
123
+ # </parameter>
124
+ # </Details>
125
+ # </Operation>
126
+ # """
127
+ doc = Nokogiri::XML::Builder.new() do |xml|
128
+ xml.Operation {
129
+ xml.Details {
130
+ xml.parameter {
131
+ xml.name {
132
+ xml.text 'Description'
133
+ }
134
+ xml.value {
135
+ xml.cdata description
136
+ }
137
+ }
138
+ }
139
+
140
+ }
141
+ end
142
+
143
+ return doc.to_xml
144
+ end
145
+
146
+ ## Given a bunch of vulnerabilities that passed the filters, make tickets for each one
147
+ def create_tickets_by_default(vulnerability_list, site_id)
148
+ @log.log_message('Preparing tickets by vulnerability...')
149
+ tickets = []
150
+ CSV.parse( vulnerability_list.chomp, headers: :first_row ) do |vuln|
151
+ subject = "#{vuln['ip_address']}: #{vuln['summary']}"
152
+ description = "Host: #{ip_address}\nSummary: #{vuln['summary']}\nFix: #{vuln['fix']}\nURL: #{vuln['url']}"
153
+
154
+ tickets << { :action => :create, :nxid => "#{site_id}#{vuln['asset_id']}#{vuln['vulnerability_id']}#{vuln['solution_id']}",
155
+ :description => create_ticket_request( subject, description ) }
156
+ end
157
+ return tickets
158
+ end
159
+
160
+
161
+ def create_tickets_by_ip(vulnerability_list, site_id)
162
+ @log.log_message('Preparing tickets by ip')
163
+ tickets = []
164
+ hostVulns = {}
165
+ CSV.parse( vulnerability_list.chomp, headers: :first_row ) do |vuln|
166
+ hostVulns["#{site_id}#{vuln['ip_address']}"] = { :ip => vuln['ip_address'], :description => "" } if not hostVulns.has_key?(vuln['asset_id'])
167
+ hostVulns["#{site_id}#{vuln['ip_address']}"][:description] += "Summary: #{vuln['summary']}\nFix: #{vuln['fix']}\nURL: #{vuln['url']}\n\n"
168
+ end
169
+
170
+ hostVulns.each do |nxid, vulnInfo|
171
+ tickets << { :action => :create, :nxid => nxid,
172
+ :description => create_ticket_request( "Vulnerabilities on #{vulnInfo[:ip]}", vulnInfo[:description] ) }
173
+ end
174
+ return tickets
175
+ end
176
+
177
+
178
+ def submit_ticket(ticket)
179
+ @log.log_message("Connecting to #{@rest_uri}.")
180
+ uri = URI( @rest_uri )
181
+ res = Net::HTTP::post_form( uri,
182
+ 'OPERATION_NAME' => 'ADD_REQUEST',
183
+ 'TECHNICIAN_KEY' => @api_key,
184
+ 'INPUT_DATA' => ticket[:description] )
185
+
186
+ response = Nokogiri::XML.parse( res.read_body )
187
+ status = Integer(response.xpath('//statuscode').text)
188
+
189
+ if status != 200
190
+ @log.log_message("Unable to create ticket #{ticket}, got response #{response.to_xml}")
191
+ return
192
+ end
193
+
194
+ workorderid = Integer(response.xpath('//workorderid').text)
195
+
196
+ @log.log_message( "created ticket #{workorderid}")
197
+ add_ticket_to_database( workorderid, ticket[:nxid] )
198
+ end
199
+
200
+
201
+ def modify_ticket(ticket)
202
+ @log.log_message("Connecting to #{@rest_uri}/#{ticket[:workorderid]}")
203
+ uri = URI( "#{@rest_uri}/#{ticket[:workorderid]}" )
204
+ res = Net::HTTP::post_form( uri,
205
+ 'OPERATION_NAME' => 'EDIT_REQUEST',
206
+ 'TECHNICIAN_KEY' => @api_key,
207
+ 'INPUT_DATA' => ticket[:description] )
208
+
209
+ response = Nokogiri::XML.parse( res.read_body )
210
+ begin
211
+ status = Integer(response.xpath('//statuscode').text)
212
+ rescue Exception => e
213
+ @log.log_message("XML request was #{ticket[:description]} response is #{response.to_xml}")
214
+ raise e
215
+ end
216
+
217
+ if status != 200
218
+ @log.log_message("Unable to modify ticket #{ticket}, got response #{response.to_xml}")
219
+ return
220
+ end
221
+ end
222
+
223
+
224
+ def close_ticket(ticket)
225
+ @log.log_message("Connecting to #{@rest_uri}/#{ticket[:workorderid]}")
226
+ uri = URI( "#{@rest_uri}/#{ticket[:workorderid]}" )
227
+ res = Net::HTTP::post_form( uri,
228
+ 'OPERATION_NAME' => 'CLOSE_REQUEST',
229
+ 'TECHNICIAN_KEY' => @api_key )
230
+
231
+ response = Nokogiri::XML.parse( res.read_body )
232
+ begin
233
+ status = Integer(response.xpath('//statuscode').text)
234
+ rescue Exception => e
235
+ @log.log_message("XML request was #{ticket[:description]} response is #{response.to_xml}")
236
+ raise e
237
+ end
238
+
239
+ if status != 200
240
+ @log.log_message("Unable to close ticket #{ticket}, got response #{response.to_xml}")
241
+ return
242
+ end
243
+
244
+ end
245
+
246
+
247
+ def create_tickets(tickets)
248
+ @log.log_message("Creating tickets on server at #{@rest_uri}")
249
+
250
+ tickets.each { |ticket| submit_ticket(ticket) }
251
+ end
252
+
253
+
254
+ def prepare_update_tickets(vulnerability_list, site_id)
255
+ fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'I'
256
+
257
+ @log.log_message('Preparing ticket updates by IP address.')
258
+ tickets = []
259
+ hostVulns={}
260
+ CSV.parse( vulnerability_list.chomp, headers: :first_row ) do |vuln|
261
+ hostVulns["#{site_id}#{vuln['ip_address']}"] = { :ip => vuln['ip_address'], :description => "" } if not hostVulns.has_key?(vuln['asset_id'])
262
+ hostVulns["#{site_id}#{vuln['ip_address']}"][:description] += "Summary: #{vuln['summary']}\nFix: #{vuln['fix']}\nURL: #{vuln['url']}\n\n"
263
+ end
264
+
265
+ hostVulns.each do |nxid, vulnInfo|
266
+ workorderid = find_ticket_in_database(nxid)
267
+ if workorderid.nil? || workorderid.empty?
268
+ @log.log_message("No incident found for assetid #{nxid}, using defaults")
269
+ tickets << { :action => :create, :nxid => nxid,
270
+ :description => create_ticket_request("Vulnerabilities on #{vulnInfo[:ip]}", vulnInfo[:description]) }
271
+ else
272
+ tickets << { :action => :modifty, :nxid => nxid, :workorderid => workorderid,
273
+ :description => modify_ticket_request( vulnInfo[:description] ) }
274
+ end
275
+ end
276
+ return tickets
277
+ end
278
+
279
+
280
+ def update_tickets(tickets)
281
+ @log.log_message('Updating tickets')
282
+ tickets.each do |ticket|
283
+ if ticket[:action] == :create
284
+ @log.log_message('Creating ticket')
285
+ submit_ticket(ticket)
286
+ else
287
+ @log.log_message("Updating ticket #{ticket[:workorderid]}")
288
+ modify_ticket(ticket)
289
+ end
290
+ end
291
+ end
292
+
293
+
294
+ # Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose.
295
+ #
296
+ # * *Args* :
297
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
298
+ #
299
+ # * *Returns* :
300
+ # - List of savon-formated (hash) tickets for closing within ServiceDesk.
301
+ #
302
+ def prepare_close_tickets(vulnerability_list, site_id)
303
+ fail 'Ticket closures are only supported in default mode.' if @options[:ticket_mode] == 'I'
304
+ @log.log_message('Preparing ticket closures by default method.')
305
+ @nxid = nil
306
+ tickets = []
307
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
308
+ case @options[:ticket_mode]
309
+ # 'D' Default mode: IP *-* Vulnerability
310
+ when 'D'
311
+ @nxid = "#{site_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
312
+ # 'I' IP address mode: IP address -* Vulnerability
313
+ when 'I'
314
+ @nxid = "#{site_id}#{row['current_ip']}"
315
+ # 'V' Vulnerability mode: Vulnerability -* IP address
316
+ # when 'V'
317
+ # @NXID = "#{site_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
318
+ else
319
+ fail 'Could not close tickets - do not understand the ticketing mode!'
320
+ end
321
+ workorderid = find_ticket_in_database(@nxid)
322
+ # Query ServiceDesk for the incident by unique id (generated NXID)
323
+ if workorderid.nil? || workorderid.empty?
324
+ @log.log_message("No workorderid found for NXID #{@nxid}")
325
+ else
326
+ tickets << { :action => :close, :nxid => @nxid, :workorderid => workorderid,
327
+ :description => closeTicketRequest() }
328
+ end
329
+ end
330
+ return tickets
331
+ end
332
+
333
+
334
+ def close_tickets( tickets )
335
+ tickets.each { |ticket| close_ticket(ticket) if ticket[:action] == close && !ticket[:workorderid].nil?}
336
+ end
337
+ end
@@ -37,7 +37,7 @@ class ServiceNowHelper
37
37
  #
38
38
  def update_tickets(tickets)
39
39
  if tickets.nil? || tickets.empty?
40
- @log.log_message("No tickets to update.")
40
+ @log.log_message('No tickets to update.')
41
41
  else
42
42
  tickets.each do |ticket|
43
43
  send_ticket(ticket, @servicenow_data[:servicenow_url], @servicenow_data[:redirect_limit])
@@ -53,7 +53,7 @@ class ServiceNowHelper
53
53
  #
54
54
  def close_tickets(tickets)
55
55
  if tickets.nil? || tickets.empty?
56
- @log.log_message("No tickets to close.")
56
+ @log.log_message('No tickets to close.')
57
57
  else
58
58
  tickets.each do |ticket|
59
59
  send_ticket(ticket, @servicenow_data[:servicenow_url], @servicenow_data[:redirect_limit])
@@ -94,7 +94,7 @@ class ServiceNowHelper
94
94
  when Net::HTTPRedirection then send_ticket(ticket, res['location'], limit - 1)
95
95
  else
96
96
  @log.log_message("Error in response: #{res['error']}")
97
- res['error']
97
+ raise ArgumentError, res['error']
98
98
  end
99
99
  end
100
100
 
@@ -107,20 +107,21 @@ class ServiceNowHelper
107
107
  # * *Returns* :
108
108
  # - List of JSON-formated tickets for creating within ServiceNow.
109
109
  #
110
- def prepare_create_tickets(vulnerability_list)
110
+ def prepare_create_tickets(vulnerability_list, site_id)
111
111
  @ticket = Hash.new(-1)
112
112
  case @options[:ticket_mode]
113
113
  # 'D' Default mode: IP *-* Vulnerability
114
114
  when 'D'
115
- prepare_create_tickets_default(vulnerability_list)
115
+ prepare_create_tickets_default(vulnerability_list, site_id)
116
116
  # 'I' IP address mode: IP address -* Vulnerability
117
117
  when 'I'
118
- prepare_create_tickets_by_ip(vulnerability_list)
118
+ prepare_create_tickets_by_ip(vulnerability_list, site_id)
119
119
  else
120
120
  fail 'No ticketing mode selected.'
121
121
  end
122
122
  end
123
-
123
+
124
+
124
125
  # Prepares a list of vulnerabilities into a list of JSON-formatted tickets (incidents) for
125
126
  # ServiceNow. The preparation by default means that each vulnerability within Nexpose is a
126
127
  # separate incident within ServiceNow. This makes for smaller, more actionalble incidents but
@@ -132,28 +133,28 @@ class ServiceNowHelper
132
133
  # * *Returns* :
133
134
  # - List of JSON-formated tickets for creating within ServiceNow.
134
135
  #
135
- def prepare_create_tickets_default(vulnerability_list)
136
- @log.log_message("Preparing tickets by default method.")
136
+ def prepare_create_tickets_default(vulnerability_list, site_id)
137
+ @log.log_message('Preparing tickets by default method.')
137
138
  tickets = []
138
139
  CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
139
140
  # ServiceNow doesn't allow new line characters in the incident short description.
140
141
  summary = row['summary'].gsub(/\n/, ' ')
141
142
 
142
- @log.log_message("Creating ticket with IP address: #{row['ip_address']} and summary: #{summary}")
143
- # NXID in the work_notes is a unique identifier used to query incidents to update/resolve
143
+ @log.log_message("Creating ticket with IP address: #{row['ip_address']}, site id: #{site_id} and summary: #{summary}")
144
+ # NXID in the u_work_notes is a unique identifier used to query incidents to update/resolve
144
145
  # incidents as they are resolved in Nexpose.
145
146
  ticket = {
146
147
  'sysparm_action' => 'insert',
147
- 'caller_id' => "#{@servicenow_data[:username]}",
148
- 'category' => 'Software',
149
- 'impact' => '1',
150
- 'urgency' => '1',
151
- 'short_description' => "#{row['ip_address']} => #{summary}",
152
- 'work_notes' => "Summary: #{summary}
148
+ 'u_caller_id' => "#{@servicenow_data[:username]}",
149
+ 'u_category' => 'Software',
150
+ 'u_impact' => '1',
151
+ 'u_urgency' => '1',
152
+ 'u_short_description' => "#{row['ip_address']} => #{summary}",
153
+ 'u_work_notes' => "Summary: #{summary}
153
154
  Fix: #{row['fix']}
154
155
  ----------------------------------------------------------------------------
155
156
  URL: #{row['url']}
156
- NXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
157
+ NXID: #{site_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
157
158
  }.to_json
158
159
  tickets.push(ticket)
159
160
  end
@@ -171,50 +172,50 @@ class ServiceNowHelper
171
172
  # * *Returns* :
172
173
  # - List of JSON-formated tickets for creating within ServiceNow.
173
174
  #
174
- def prepare_create_tickets_by_ip(vulnerability_list)
175
- @log.log_message("Preparing tickets by IP address.")
175
+ def prepare_create_tickets_by_ip(vulnerability_list, site_id)
176
+ @log.log_message('Preparing tickets by IP address.')
176
177
  tickets = []
177
178
  current_ip = -1
178
179
  CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
179
180
  if current_ip == -1
180
181
  current_ip = row['ip_address']
181
- @log.log_message("Creating ticket with IP address: #{row['ip_address']}")
182
+ @log.log_message("Creating ticket with IP address: #{row['ip_address']} for site with ID: #{site_id}")
182
183
  @ticket = {
183
184
  'sysparm_action' => 'insert',
184
- 'caller_id' => "#{@servicenow_data[:username]}",
185
- 'category' => 'Software',
186
- 'impact' => '1',
187
- 'urgency' => '1',
188
- 'short_description' => "#{row['ip_address']} => Vulnerabilities",
189
- 'work_notes' => "\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
185
+ 'u_caller_id' => "#{@servicenow_data[:username]}",
186
+ 'u_category' => 'Software',
187
+ 'u_impact' => '1',
188
+ 'u_urgency' => '1',
189
+ 'u_short_description' => "#{row['ip_address']} => Vulnerabilities",
190
+ 'u_work_notes' => "\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
190
191
  ++ New Vulnerabilities ++++++++++++++++++++++++++++++++++++
191
192
  +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n"
192
193
  }
193
194
  end
194
195
  if current_ip == row['ip_address']
195
- @ticket['work_notes'] +=
196
+ @ticket['u_work_notes'] +=
196
197
  "\n\n==========================================
197
198
  Summary: #{row['summary']}
198
199
  ----------------------------------------------------------------------------
199
200
  Fix: #{row['fix']}"
200
201
  unless row['url'].nil?
201
- @ticket['work_notes'] +=
202
+ @ticket['u_work_notes'] +=
202
203
  "\n----------------------------------------------------------------------------
203
204
  URL: #{row['url']}"
204
205
  end
205
206
  end
206
207
  unless current_ip == row['ip_address']
207
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
208
+ # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
208
209
  @log.log_message("Found new IP address. Finishing ticket with with IP address: #{current_ip} and moving onto IP #{row['ip_address']}")
209
- @ticket['work_notes'] += "\nNXID: #{current_ip}"
210
+ @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}"
210
211
  @ticket = @ticket.to_json
211
212
  tickets.push(@ticket)
212
213
  current_ip = -1
213
214
  redo
214
215
  end
215
216
  end
216
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
217
- @ticket['work_notes'] += "\nNXID: #{current_ip}" unless (@ticket.size == 0)
217
+ # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
218
+ @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}" unless (@ticket.size == 0)
218
219
  tickets.push(@ticket.to_json) unless @ticket.nil?
219
220
  tickets
220
221
  end
@@ -229,11 +230,11 @@ class ServiceNowHelper
229
230
  # * *Returns* :
230
231
  # - List of JSON-formated tickets for updating within ServiceNow.
231
232
  #
232
- def prepare_update_tickets(vulnerability_list)
233
+ def prepare_update_tickets(vulnerability_list, site_id)
233
234
  fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] == 'D'
234
235
  @ticket = Hash.new(-1)
235
236
 
236
- @log.log_message("Preparing ticket updates by IP address.")
237
+ @log.log_message('Preparing ticket updates by IP address.')
237
238
  tickets = []
238
239
  current_ip = -1
239
240
  ticket_status = 'New'
@@ -241,7 +242,7 @@ class ServiceNowHelper
241
242
  if current_ip == -1
242
243
  current_ip = row['ip_address']
243
244
  ticket_status = row['comparison']
244
- @log.log_message("Creating ticket update with IP address: #{row['ip_address']}")
245
+ @log.log_message("Creating ticket update with IP address: #{row['ip_address']} and site ID: #{site_id}")
245
246
  @log.log_message("Ticket status #{ticket_status}")
246
247
  action = 'update'
247
248
  if ticket_status == 'New'
@@ -249,8 +250,8 @@ class ServiceNowHelper
249
250
  end
250
251
  @ticket = {
251
252
  'sysparm_action' => action,
252
- 'sysparm_query' => "work_notesCONTAINSNXID: #{row['ip_address']}",
253
- 'work_notes' =>
253
+ 'sysparm_query' => "u_work_notesCONTAINSNXID: #{site_id}#{row['ip_address']}",
254
+ 'u_work_notes' =>
254
255
  "\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
255
256
  ++ #{row['comparison']} Vulnerabilities +++++++++++++++++++++++++++++++++++++
256
257
  +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n"
@@ -259,41 +260,42 @@ class ServiceNowHelper
259
260
  if current_ip == row['ip_address']
260
261
  # If the ticket_status is different, add a a new 'header' to signify a new block of tickets.
261
262
  unless ticket_status == row['comparison']
262
- @ticket['work_notes'] +=
263
- "\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
263
+ @ticket['u_work_notes'] +=
264
+ "\n\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++
264
265
  ++ #{row['comparison']} Vulnerabilities +++++++++++++++++++++++++++++++++++++
265
266
  +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n"
266
267
  ticket_status = row['comparison']
267
268
  end
268
269
 
269
- @ticket['work_notes'] +=
270
+ @ticket['u_work_notes'] +=
270
271
  "\n\n==========================================
271
272
  Summary: #{row['summary']}
272
273
  ----------------------------------------------------------------------------
273
274
  Fix: #{row['fix']}"
274
275
  # Only add the URL block if data exists in the row.
275
276
  unless row['url'].nil?
276
- @ticket['work_notes'] +=
277
+ @ticket['u_work_notes'] +=
277
278
  "----------------------------------------------------------------------------
278
279
  URL: #{row['url']}"
279
280
  end
280
281
  end
281
282
  unless current_ip == row['ip_address']
282
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
283
- @ticket['work_notes'] += "\nNXID: #{current_ip}"
283
+ # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
284
+ @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}"
284
285
  @ticket = @ticket.to_json
285
286
  tickets.push(@ticket)
286
287
  current_ip = -1
287
288
  redo
288
289
  end
289
290
  end
290
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
291
- @ticket['work_notes'] += "\nNXID: #{current_ip}" unless (@ticket.size == 0)
291
+ # NXID in the u_work_notes is the unique identifier used to query incidents to update them.
292
+ @ticket['u_work_notes'] += "\nNXID: #{site_id}#{current_ip}" unless (@ticket.size == 0)
292
293
  tickets.push(@ticket.to_json) unless @ticket.nil?
293
294
  tickets
294
295
  end
295
-
296
- # Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose. This method
296
+
297
+
298
+ # Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose. This method
297
299
  # currently only supports updating default mode tickets in ServiceNow.
298
300
  #
299
301
  # * *Args* :
@@ -302,15 +304,29 @@ class ServiceNowHelper
302
304
  # * *Returns* :
303
305
  # - List of JSON-formated tickets for closing within ServiceNow.
304
306
  #
305
- def prepare_close_tickets(vulnerability_list)
306
- fail 'Ticket closures are only supported in default mode.' if @options[:ticket_mode] == 'I'
307
- @log.log_message("Preparing ticket closures by default method.")
307
+ def prepare_close_tickets(vulnerability_list, site_id)
308
+ @log.log_message("Preparing ticket closures for mode #{@options[:ticket_mode]}.")
308
309
  tickets = []
310
+ @nxid = nil
309
311
  CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
312
+ case @options[:ticket_mode]
313
+ # 'D' Default mode: IP *-* Vulnerability
314
+ when 'D'
315
+ @nxid = "#{site_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
316
+ # 'I' IP address mode: IP address -* Vulnerability
317
+ when 'I'
318
+ @nxid = "#{site_id}#{row['ip_address']}"
319
+ # 'V' Vulnerability mode: Vulnerability -* IP address
320
+ ## when 'V'
321
+ ## @nxid = "#{site_id}#{row['asset_id']}#{row['vulnerability_id']}"
322
+ else
323
+ fail 'Could not close tickets - do not understand the ticketing mode!'
324
+ end
310
325
  # 'state' 7 is the "Closed" state within ServiceNow.
326
+ @log.log_message("Closing ticket with NXID: #{@nxid}.")
311
327
  ticket = {
312
328
  'sysparm_action' => 'update',
313
- 'sysparm_query' => "work_notesCONTAINSNXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}",
329
+ 'sysparm_query' => "u_work_notesCONTAINSNXID: #{@nxid}",
314
330
  'state' => '7'
315
331
  }.to_json
316
332
  tickets.push(ticket)