nexpose_ticketing 0.3.1 → 0.5.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.
@@ -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)