nexpose_ticketing 0.5.0 → 0.8.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.
@@ -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]}",