nexpose_ticketing 0.5.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,19 @@
1
- ---
2
- # This configuration file defines all the options necessary to support the helper.
3
- # Fields marked (M) are mandatory.
4
- #
5
-
6
- # (M) Helper class name
7
- :helper_name: ServiceNowHelper
8
-
9
- # (M) ServiceNow url (currently requires using JSON)
10
- :servicenow_url: https://your.service-now.com/u_rpd_vulnerabilities.do?JSON
11
- # (M) Username for ServiceNow
12
- :username: admin
13
- # (M) Password for above username
14
- :password: admin
15
- # (M) If 'Y', SSL connections will output to stderr (default is 'N')
16
- :verbose_mode: N
17
- # (M) Amount of times the helper will follow 301/302 redirections
18
- :redirect_limit: 10
1
+ ---
2
+ # This configuration file defines all the options necessary to support the helper.
3
+ # Fields marked (M) are mandatory.
4
+ #
5
+
6
+ # (M) Helper class name
7
+ :helper_name: ServiceNowHelper
8
+
9
+ # (M) ServiceNow url (currently requires using JSON)
10
+ :servicenow_url: https://Servicenow_url/u_rpd_vulnerabilities.do?JSONv2
11
+ # Use this URL if you have loaded the ticketing UpdateSet for ServiceNow
12
+ # (M) Username for ServiceNow
13
+ :username: admin
14
+ # (M) Password for above username
15
+ :password: nxadmin
16
+ # (M) If 'Y', SSL connections will output to stderr (default is 'N')
17
+ :verbose_mode: N
18
+ # (M) Amount of times the helper will follow 301/302 redirections
19
+ :redirect_limit: 10
@@ -305,7 +305,7 @@ Support Phone: (866) 390-8113</description><device_type>browser</device_type><hi
305
305
  <category>customer</category>
306
306
  <comments/>
307
307
  <name>dictionary_u_rpd_vulnerabilities_55952fb42c46425d84de8f4fda4ce339</name>
308
- <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update><database><element extends="sys_import_set_row" label="Vulnerabilities" name="u_rpd_vulnerabilities" type="collection"><element label="work_notes" max_length="40" name="u_work_notes" type="string"/><element label="urgency" max_length="40" name="u_urgency" type="integer"/><element label="short_description" max_length="40" name="u_short_description" type="string"/><element label="category" max_length="40" name="u_category" type="string"/><element label="impact" max_length="40" name="u_impact" type="integer"/><element label="caller_id" max_length="40" name="u_caller_id" type="string"/></element></database><sys_app_file action="INSERT_OR_UPDATE"><sys_app/><sys_code>!!1#D/</sys_code><sys_created_by>admin</sys_created_by><sys_created_on>2014-04-02 20:14:25</sys_created_on><sys_id>221dc59634735140df776b3ac83c75c2</sys_id><sys_mod_count>0</sys_mod_count><sys_name>u_rpd_vulnerabilities</sys_name><sys_parent display_value="Rpd Vulnerabilities">2a1dc59634735140df776b3ac83c75ae</sys_parent><sys_path>!!1#C/!!1#D/</sys_path><sys_policy/><sys_source_deleted>false</sys_source_deleted><sys_source_id element="NULL" name="u_rpd_vulnerabilities" sys_source_table="sys_dictionary">ee1dc59634735140df776b3ac83c75c0</sys_source_id><sys_source_table>sys_dictionary</sys_source_table><sys_type>code</sys_type><sys_update_name>dictionary_u_rpd_vulnerabilities_55952fb42c46425d84de8f4fda4ce339</sys_update_name><sys_updated_by>admin</sys_updated_by><sys_updated_on>2014-04-08 18:04:59</sys_updated_on></sys_app_file></record_update>]]></payload>
308
+ <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update><database><element extends="sys_import_set_row" label="Vulnerabilities" name="u_rpd_vulnerabilities" type="collection"><element label="work_notes" max_length="50000" name="u_work_notes" type="string"/><element label="urgency" max_length="40" name="u_urgency" type="integer"/><element label="short_description" max_length="40" name="u_short_description" type="string"/><element label="category" max_length="40" name="u_category" type="string"/><element label="impact" max_length="40" name="u_impact" type="integer"/><element label="caller_id" max_length="40" name="u_caller_id" type="string"/></element></database><sys_app_file action="INSERT_OR_UPDATE"><sys_app/><sys_code>!!1#D/</sys_code><sys_created_by>admin</sys_created_by><sys_created_on>2014-04-02 20:14:25</sys_created_on><sys_id>221dc59634735140df776b3ac83c75c2</sys_id><sys_mod_count>0</sys_mod_count><sys_name>u_rpd_vulnerabilities</sys_name><sys_parent display_value="Rpd Vulnerabilities">2a1dc59634735140df776b3ac83c75ae</sys_parent><sys_path>!!1#C/!!1#D/</sys_path><sys_policy/><sys_source_deleted>false</sys_source_deleted><sys_source_id element="NULL" name="u_rpd_vulnerabilities" sys_source_table="sys_dictionary">ee1dc59634735140df776b3ac83c75c0</sys_source_id><sys_source_table>sys_dictionary</sys_source_table><sys_type>code</sys_type><sys_update_name>dictionary_u_rpd_vulnerabilities_55952fb42c46425d84de8f4fda4ce339</sys_update_name><sys_updated_by>admin</sys_updated_by><sys_updated_on>2014-04-08 18:04:59</sys_updated_on></sys_app_file></record_update>]]></payload>
309
309
  <remote_update_set display_value="Rapid7 Nexpose Ticketing">1fe9af88340c6140df776b3ac83c7535</remote_update_set>
310
310
  <replace_on_upgrade>false</replace_on_upgrade>
311
311
  <sys_created_by>admin</sys_created_by>
@@ -21,7 +21,7 @@
21
21
  # Timeout in seconds. The number of seconds the GEM waits for a response from Nexpose before exiting.
22
22
  :timeout: 10800
23
23
  # Ticket batching. Breaks ticket processing into groups of value size controlling resource utilisation of both systems.
24
- :batch_size: 300
24
+ :batch_size: 200
25
25
  # (M) For 'I' & 'V' mode. Set to 'Y' for tickets that have been fixed to be closed after update, set to 'N' for ticket to be left
26
26
  # open for a user to manually close or change the status.
27
27
  :close_old_tickets_on_update: Y
@@ -34,8 +34,8 @@
34
34
  # Nexpose options.
35
35
  :nexpose_data:
36
36
  # (M) Nexpose console hostname.
37
- :nxconsole: 127.0.0.1
37
+ :nxconsole: nexpose-console.host.com
38
38
  # (M) Nexpose username.
39
- :nxuser: nxadmin
39
+ :nxuser: admin
40
40
  # (M) Nexpose password.
41
- :nxpasswd: nxadmin
41
+ :nxpasswd: admin
@@ -3,17 +3,82 @@ require 'net/http'
3
3
  require 'net/https'
4
4
  require 'uri'
5
5
  require 'csv'
6
+
6
7
  # This class serves as the JIRA interface
7
8
  # that creates issues within JIRA from vulnerabilities
8
9
  # found in Nexpose.
9
10
  # Copyright:: Copyright (c) 2014 Rapid7, LLC.
10
11
  class JiraHelper
12
+ # TODO: Add V Mode.
13
+ # TODO: Allow updates/closed loop.
11
14
  attr_accessor :jira_data, :options
12
15
  def initialize(jira_data, options)
13
16
  @jira_data = jira_data
14
17
  @options = options
15
18
  end
16
19
 
20
+ # Generates the NXID. The NXID is a unique indentifier used to find and update and/or close tickets.
21
+ #
22
+ # * *Args* :
23
+ # - +site_id+ - Site ID the tickets are being generated for. Required for all ticketing modes
24
+ # - +row+ - Row from the generated Nexpose CSV report. Required for default ('D') mode.
25
+ # - +current_ip+ - The IP address of that this ticket is for. Required for IP mode ('I') mode.
26
+ #
27
+ # * *Returns* :
28
+ # - NXID string.
29
+ #
30
+ def generate_nxid(site_id, row=nil, current_ip=nil)
31
+ fail 'Site ID is required to generate the NXID.' if site_id.empty?
32
+ case @options[:ticket_mode]
33
+ # 'D' Default mode: IP *-* Vulnerability
34
+ when 'D'
35
+ fail 'Row is required to generate the NXID in \'D\' mode.' if row.nil? || row.empty?
36
+ @nxid = "#{site_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
37
+ # 'I' IP address mode: IP address -* Vulnerability
38
+ when 'I'
39
+ fail 'Current IP is required to generate the NXID in \'I\' mode.' if current_ip.nil? || current_ip.empty?
40
+ @nxid = "#{site_id}#{current_ip.tr('.','')}"
41
+ # 'V' mode net yet implemented.
42
+ # 'V' Vulnerability mode: Vulnerability -* IP address
43
+ # when 'V'
44
+ # @NXID = "#{site_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
45
+ else
46
+ fail 'Could not close tickets - do not understand the ticketing mode!'
47
+ end
48
+ end
49
+
50
+ # Fetches the Jira ticket key e.g INT-1. This is required to post updates to the Jira.
51
+ #
52
+ # * *Args* :
53
+ # - +JQL query string+ - Jira's Query Language string used to search for a ticket key.
54
+ #
55
+ # * *Returns* :
56
+ # - Jira ticket key if found, nil otherwise.
57
+ #
58
+ def get_jira_key(jql_query)
59
+ fail 'JQL query string cannot be empty.' if jql_query.empty?
60
+ headers = { 'Content-Type' => 'application/json',
61
+ 'Accept' => 'application/json' }
62
+
63
+ url = URI.parse(("#{@jira_data[:jira_url]}".split("/")[0..-2].join("/") + '/search'))
64
+ url.query = [url.query, URI.escape(jql_query)].compact.join('&')
65
+ req = Net::HTTP::Get.new(url.to_s, headers)
66
+ req.basic_auth @jira_data[:username], @jira_data[:password]
67
+ resp = Net::HTTP.new(url.host, url.port)
68
+
69
+ # Enable this line for debugging the https call.
70
+ # resp.set_debug_output $stderr
71
+
72
+ resp.use_ssl = true if @jira_data[:jira_url].to_s.start_with?('https')
73
+ resp.verify_mode = OpenSSL::SSL::VERIFY_NONE
74
+ response = resp.request(req)
75
+
76
+ issues = JSON.parse(response.body)['issues']
77
+ #TODO: fail(?) if more than one issue exits with a "unique" NXID
78
+ return nil if issues.nil? || !issues.any? || issues.size > 1
79
+ return issues[0]['key']
80
+ end
81
+
17
82
  def create_tickets(tickets)
18
83
  fail 'Ticket(s) cannot be empty.' if tickets.empty? || tickets.nil?
19
84
  tickets.each do |ticket|
@@ -24,8 +89,10 @@ class JiraHelper
24
89
  req.basic_auth @jira_data[:username], @jira_data[:password]
25
90
  req.body = ticket
26
91
  resp = Net::HTTP.new(url.host, url.port)
92
+
27
93
  # Enable this line for debugging the https call.
28
- # resp.set_debug_output $stderr
94
+ #resp.set_debug_output $stderr
95
+
29
96
  resp.use_ssl = true if @jira_data[:jira_url].to_s.start_with?('https')
30
97
  resp.verify_mode = OpenSSL::SSL::VERIFY_NONE
31
98
  resp.start { |http| http.request(req) }
@@ -33,6 +100,7 @@ class JiraHelper
33
100
  end
34
101
 
35
102
  # Prepares tickets from the CSV.
103
+ # TODO Implement V Version.
36
104
  def prepare_create_tickets(vulnerability_list, site_id)
37
105
  @ticket = Hash.new(-1)
38
106
  case @options[:ticket_mode]
@@ -50,7 +118,7 @@ class JiraHelper
50
118
  # Prepares and creates tickets in default mode.
51
119
  def prepare_tickets_default(vulnerability_list, site_id)
52
120
  tickets = []
53
- CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
121
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
54
122
  # JiraHelper doesn't like new line characters in their summaries.
55
123
  summary = row['summary'].gsub(/\n/, ' ')
56
124
  ticket = {
@@ -58,7 +126,7 @@ class JiraHelper
58
126
  'project' => {
59
127
  'key' => "#{@jira_data[:project]}" },
60
128
  'summary' => "#{row['ip_address']} => #{summary}",
61
- 'description' => "#{row['fix']} \n\n #{row['url']}",
129
+ 'description' => "CVSS Score: #{row['cvss_score']} \n\n #{row['fix']} \n\n #{row['url']} \n\n\n NXID: #{generate_nxid(site_id, row)}",
62
130
  'issuetype' => {
63
131
  'name' => 'Task' }
64
132
  }
@@ -68,11 +136,19 @@ class JiraHelper
68
136
  tickets
69
137
  end
70
138
 
71
- # Prepares and creates tickets in IP mode.
139
+ # Prepare tickets from the CSV of vulnerabilities exported from Nexpose. This method batches tickets
140
+ # per IP i.e. any vulnerabilities for a single IP in one ticket
141
+ #
142
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
143
+ # - +site_id+ - Site ID the vulnerability list was generate from.
144
+ #
145
+ # * *Returns* :
146
+ # - List of JSON-formated tickets for updating within Jira.
147
+ #
72
148
  def prepare_tickets_by_ip(vulnerability_list, site_id)
73
149
  tickets = []
74
150
  current_ip = -1
75
- CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
151
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
76
152
  if current_ip == -1
77
153
  current_ip = row['ip_address']
78
154
  @ticket = {
@@ -86,19 +162,161 @@ class JiraHelper
86
162
  }
87
163
  }
88
164
  end
165
+ # TODO: Better formating this.
89
166
  if current_ip == row['ip_address']
90
- @ticket['fields']['description'] += "\n ==============================\n
91
- #{row['summary']} \n ==============================\n
92
- \n #{row['fix']}\n\n #{row['url']}"
167
+ @ticket['fields']['description'] +=
168
+ "\n ==============================\n\n
169
+ #{row['summary']} \n CVSS Score: #{row['cvss_score']}
170
+ \n\n ==============================\n
171
+ \n Source: #{row['source']}, Reference: #{row['reference']}
172
+ \n
173
+ \n First seen: #{row['first_discovered']}
174
+ \n Last seen: #{row['most_recently_discovered']}
175
+ \n Fix:
176
+ \n #{row['fix']}\n\n #{row['url']}
177
+ \n
178
+ \n\n"
93
179
  end
94
180
  unless current_ip == row['ip_address']
95
- @ticket = @ticket.to_json
96
- tickets.push(@ticket)
181
+ @ticket['fields']['description'] += "\n\n\n NXID: #{generate_nxid(site_id, row, current_ip)}"
182
+ tickets.push(@ticket.to_json)
97
183
  current_ip = -1
98
184
  redo
99
185
  end
100
186
  end
187
+ @ticket['fields']['description'] += "\n\n\n NXID: #{generate_nxid(site_id, nil, current_ip)}"
101
188
  tickets.push(@ticket.to_json) unless @ticket.nil?
102
189
  tickets
103
190
  end
191
+
192
+ # Sends ticket closure (in JSON format) to Jira individually (each ticket in the list
193
+ # as a separate web service call).
194
+ #
195
+ # * *Args* :
196
+ # - +tickets+ - List of Jira ticket Keys to be closed.
197
+ #
198
+ def close_tickets(tickets)
199
+ if tickets.nil? || tickets.empty?
200
+ #@log.log_message("No tickets to close.")
201
+ #TODO: Log error
202
+ else
203
+ headers = { 'Content-Type' => 'application/json',
204
+ 'Accept' => 'application/json' }
205
+
206
+ tickets.each { |ticket|
207
+ url = URI.parse(("#{@jira_data[:jira_url]}#{ticket}/transitions"))
208
+ req = Net::HTTP::Post.new(url.to_s, headers)
209
+ req.basic_auth @jira_data[:username], @jira_data[:password]
210
+
211
+ #TODO: May need to make this customisable as Jira does not allow for some transitions depending on workflow.
212
+ req.body = {"transition" => {"id" => "2"}, "fields" => {"resolution" => {"name" => "Fixed"}}}.to_json
213
+
214
+ resp = Net::HTTP.new(url.host, url.port)
215
+
216
+ # Enable this line for debugging the https call.
217
+ #resp.set_debug_output $stderr
218
+
219
+ resp.use_ssl = true
220
+ resp.verify_mode = OpenSSL::SSL::VERIFY_NONE
221
+ resp.start { |http| http.request(req) }
222
+ }
223
+ end
224
+ end
225
+
226
+ # Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose.
227
+ #
228
+ # * *Args* :
229
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
230
+ #
231
+ # * *Returns* :
232
+ # - List of Jira ticket Keys to be closed.
233
+ #
234
+ def prepare_close_tickets(vulnerability_list, site_id)
235
+ @nxid = nil
236
+ tickets = []
237
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
238
+ @nxid = generate_nxid(site_id, nil, row['ip_address'])
239
+ # Query Jira for the ticket by unique id (generated NXID)
240
+ queried_key = get_jira_key("jql=project=#{@jira_data[:project]} AND description ~ \"NXID: #{@nxid}\" AND (status != Closed AND status != Resolved)&fields=key")
241
+ if queried_key.nil? || queried_key.empty?
242
+ #TODO: Log error
243
+ else
244
+ #Jira uses a post call to the ticket key path to close the ticket. The "prepared batch of tickets" in this case is just a collection Jira ticket key's to close.
245
+ tickets.push(queried_key)
246
+ end
247
+ end
248
+ tickets
249
+ end
250
+
251
+ # Sends ticket updates (in JSON format) to Jira individually (each ticket in the list as a
252
+ # separate HTTP post).
253
+ #
254
+ # * *Args* :
255
+ # - +tickets+ - List of JSON-formatted ticket updates.
256
+ #
257
+ def update_tickets(tickets)
258
+ if (tickets.nil? || tickets.empty?) then
259
+ #@log.log_message('No tickets to update.')
260
+ #TODO: Log error
261
+ else
262
+ tickets.each do |ticket_details|
263
+ headers = {'Content-Type' => 'application/json',
264
+ 'Accept' => 'application/json'}
265
+
266
+ (ticket_details.first.nil?) ? send_whole_ticket = true : send_whole_ticket = false
267
+
268
+ url = "#{jira_data[:jira_url]}"
269
+ url += "#{ticket_details.first}" unless send_whole_ticket
270
+ uri = URI.parse(url)
271
+
272
+ send_whole_ticket ? req = Net::HTTP::Post.new(uri.to_s, headers) : req = Net::HTTP::Put.new(uri.to_s, headers)
273
+
274
+ req.basic_auth @jira_data[:username], @jira_data[:password]
275
+ send_whole_ticket ?
276
+ req.body = ticket_details.last :
277
+ req.body = {'update' => {'description' => [{'set' => "#{JSON.parse(ticket_details[1])['fields']['description']}"}]}}.to_json
278
+
279
+ resp = Net::HTTP.new(uri.host, uri.port)
280
+
281
+ # Enable this line for debugging the https call.
282
+ #resp.set_debug_output $stderr
283
+
284
+ resp.use_ssl = true if uri.to_s.start_with?('https')
285
+ resp.verify_mode = OpenSSL::SSL::VERIFY_NONE
286
+ resp.start { |http| http.request(req) }
287
+ end
288
+ end
289
+ end
290
+
291
+ # Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose. This method batches tickets
292
+ # per IP i.e. any vulnerabilities for a single IP in one ticket
293
+ #
294
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
295
+ # - +site_id+ - Site ID the vulnerability list was generate from.
296
+ #
297
+ # * *Returns* :
298
+ # - List of JSON-formated tickets for updating within Jira.
299
+ #
300
+ def prepare_update_tickets(vulnerability_list, site_id)
301
+ fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'I'
302
+ #Jira uses the ticket key to push updates. Since new IPs won't have a Jira key, generate new tickets for all of the IPs found.
303
+ updated_tickets = prepare_tickets_by_ip(vulnerability_list, site_id)
304
+ tickets_to_send = []
305
+
306
+ #Find the keys that exist (IPs that have tickets already)
307
+ updated_tickets.each do |ticket|
308
+ nxid = JSON.parse(ticket)['fields']['description'].squeeze("\n").lines.to_a.last
309
+ if (nxid.slice! "NXID:").nil?
310
+ #TODO - log error.
311
+ #Could not get NXID from the last line in the description. Do not push the invalid description.
312
+ next
313
+ end
314
+ queried_key = get_jira_key("jql=project=#{@jira_data[:project]} AND description ~ \"NXID: #{nxid.strip}\" AND (status != Closed AND status != Resolved)&fields=key")
315
+ ticket_key_pair = []
316
+ ticket_key_pair << queried_key
317
+ ticket_key_pair << ticket
318
+ tickets_to_send << ticket_key_pair
319
+ end
320
+ tickets_to_send
321
+ end
104
322
  end
@@ -1,5 +1,6 @@
1
1
  require 'net/http'
2
2
  require 'nokogiri'
3
+ require 'dbm'
3
4
 
4
5
  class ServiceDeskHelper
5
6
  attr_accessor :servicedesk_data, :options, :log
@@ -108,8 +109,6 @@ class ServiceDeskHelper
108
109
  }
109
110
  }
110
111
  end
111
- puts request.to_xml
112
-
113
112
  return request.to_xml
114
113
  end
115
114
 
@@ -163,7 +162,7 @@ class ServiceDeskHelper
163
162
  tickets = []
164
163
  hostVulns = {}
165
164
  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'])
165
+ hostVulns["#{site_id}#{vuln['ip_address']}"] = { :ip => vuln['ip_address'], :description => "" } if not hostVulns.has_key?("#{site_id}#{vuln['ip_address']}")
167
166
  hostVulns["#{site_id}#{vuln['ip_address']}"][:description] += "Summary: #{vuln['summary']}\nFix: #{vuln['fix']}\nURL: #{vuln['url']}\n\n"
168
167
  end
169
168
 
@@ -184,14 +183,24 @@ class ServiceDeskHelper
184
183
  'INPUT_DATA' => ticket[:description] )
185
184
 
186
185
  response = Nokogiri::XML.parse( res.read_body )
187
- status = Integer(response.xpath('//statuscode').text)
186
+ begin
187
+ status = response.xpath('//statuscode').text
188
+ if status.empty?
189
+ status_code = -1
190
+ else
191
+ status_code = Integer( status )
192
+ end
188
193
 
189
- if status != 200
190
- @log.log_message("Unable to create ticket #{ticket}, got response #{response.to_xml}")
191
- return
192
- end
194
+ if status_code != 200
195
+ @log.log_message("Unable to create ticket #{ticket}, got response #{response.to_xml}")
196
+ return
197
+ end
193
198
 
194
- workorderid = Integer(response.xpath('//workorderid').text)
199
+ workorderid = Integer(response.xpath('//workorderid').text)
200
+ rescue ArgumentError => ae
201
+ @log.log_message("Failed to parse response from servicedesk #{response}")
202
+ raise ae
203
+ end
195
204
 
196
205
  @log.log_message( "created ticket #{workorderid}")
197
206
  add_ticket_to_database( workorderid, ticket[:nxid] )
@@ -8,7 +8,7 @@ require 'nexpose_ticketing/nx_logger'
8
8
  # Serves as the ServiceNow interface for creating/updating issues from
9
9
  # vulnelrabilities found in Nexpose.
10
10
  class ServiceNowHelper
11
- attr_accessor :servicenow_data, :options, :log
11
+ attr_accessor :servicenow_data, :options, :log, :transform
12
12
  def initialize(servicenow_data, options)
13
13
  @servicenow_data = servicenow_data
14
14
  @options = options
@@ -124,7 +124,7 @@ class ServiceNowHelper
124
124
 
125
125
  # Prepares a list of vulnerabilities into a list of JSON-formatted tickets (incidents) for
126
126
  # ServiceNow. The preparation by default means that each vulnerability within Nexpose is a
127
- # separate incident within ServiceNow. This makes for smaller, more actionalble incidents but
127
+ # separate incident within ServiceNow. This makes for smaller, more actionalble incidents but
128
128
  # could lead to a very large total number of incidents.
129
129
  #
130
130
  # * *Args* :
@@ -143,6 +143,7 @@ class ServiceNowHelper
143
143
  @log.log_message("Creating ticket with IP address: #{row['ip_address']}, site id: #{site_id} and summary: #{summary}")
144
144
  # NXID in the u_work_notes is a unique identifier used to query incidents to update/resolve
145
145
  # incidents as they are resolved in Nexpose.
146
+
146
147
  ticket = {
147
148
  'sysparm_action' => 'insert',
148
149
  'u_caller_id' => "#{@servicenow_data[:username]}",