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.
- checksums.yaml +4 -4
- data/bin/nexpose_servicedesk +17 -0
- data/lib/nexpose_ticketing/config/servicedesk.config +19 -0
- data/lib/nexpose_ticketing/config/servicenow.config +1 -1
- data/lib/nexpose_ticketing/config/ticket_service.config +15 -5
- data/lib/nexpose_ticketing/helpers/jira_helper.rb +5 -5
- data/lib/nexpose_ticketing/helpers/remedy_helper.rb +401 -78
- data/lib/nexpose_ticketing/helpers/servicedesk_helper.rb +337 -0
- data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +68 -52
- data/lib/nexpose_ticketing/queries.rb +251 -23
- data/lib/nexpose_ticketing/ticket_repository.rb +151 -8
- data/lib/nexpose_ticketing/ticket_service.rb +80 -37
- metadata +20 -2
@@ -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(
|
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(
|
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(
|
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
|
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
|
-
'
|
148
|
-
'
|
149
|
-
'
|
150
|
-
'
|
151
|
-
'
|
152
|
-
'
|
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(
|
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
|
-
'
|
185
|
-
'
|
186
|
-
'
|
187
|
-
'
|
188
|
-
'
|
189
|
-
'
|
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['
|
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['
|
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
|
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['
|
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
|
217
|
-
@ticket['
|
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(
|
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' => "
|
253
|
-
'
|
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['
|
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['
|
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['
|
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
|
283
|
-
@ticket['
|
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
|
291
|
-
@ticket['
|
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
|
-
|
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
|
-
|
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' => "
|
329
|
+
'sysparm_query' => "u_work_notesCONTAINSNXID: #{@nxid}",
|
314
330
|
'state' => '7'
|
315
331
|
}.to_json
|
316
332
|
tickets.push(ticket)
|