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.
- 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]}",
|