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.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/Gemfile.lock +67 -0
- data/README.md +43 -43
- data/bin/nexpose_jira +16 -16
- data/bin/nexpose_remedy +17 -17
- data/bin/nexpose_servicenow +17 -17
- data/lib/nexpose_ticketing.rb +10 -10
- data/lib/nexpose_ticketing/config/jira.config +11 -11
- data/lib/nexpose_ticketing/config/remedy.config +26 -26
- data/lib/nexpose_ticketing/config/remedy_wsdl/HPD_IncidentInterface_Create_WS.xml +296 -296
- data/lib/nexpose_ticketing/config/remedy_wsdl/HPD_IncidentInterface_WS.xml +675 -675
- data/lib/nexpose_ticketing/config/servicenow.config +19 -18
- data/lib/nexpose_ticketing/config/servicenow_updateset/Rapid7 Nexpose Ticketing.glide-calgary-02-15-2013__patch2-hotfix5.xml +1 -1
- data/lib/nexpose_ticketing/config/ticket_service.config +4 -4
- data/lib/nexpose_ticketing/helpers/jira_helper.rb +228 -10
- data/lib/nexpose_ticketing/helpers/servicedesk_helper.rb +18 -9
- data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +3 -2
- data/lib/nexpose_ticketing/nx_logger.rb +35 -35
- data/lib/nexpose_ticketing/queries.rb +5 -1
- data/lib/nexpose_ticketing/ticket_repository.rb +5 -3
- metadata +48 -6
@@ -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://
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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="
|
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:
|
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:
|
37
|
+
:nxconsole: nexpose-console.host.com
|
38
38
|
# (M) Nexpose username.
|
39
|
-
:nxuser:
|
39
|
+
:nxuser: admin
|
40
40
|
# (M) Nexpose password.
|
41
|
-
:nxpasswd:
|
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
|
-
#
|
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)
|
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
|
-
#
|
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)
|
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'] +=
|
91
|
-
|
92
|
-
|
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
|
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['
|
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
|
-
|
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
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
-
|
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]}",
|