nexpose_ticketing 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/nexpose_ticketing/config/ticket_service.config +5 -2
- data/lib/nexpose_ticketing/helpers/jira_helper.rb +20 -20
- data/lib/nexpose_ticketing/helpers/remedy_helper.rb +34 -34
- data/lib/nexpose_ticketing/helpers/servicedesk_helper.rb +15 -15
- data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +19 -19
- data/lib/nexpose_ticketing/queries.rb +40 -15
- data/lib/nexpose_ticketing/ticket_repository.rb +208 -141
- data/lib/nexpose_ticketing/ticket_service.rb +123 -56
- metadata +4 -3
@@ -56,7 +56,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
56
56
|
TICKET_SERVICE_CONFIG_PATH = File.join(File.dirname(__FILE__), '/config/ticket_service.config')
|
57
57
|
LOGGER_FILE = File.join(File.dirname(__FILE__), '/log/ticket_service.log')
|
58
58
|
|
59
|
-
attr_accessor :helper_data, :nexpose_data, :options, :ticket_repository, :first_time, :
|
59
|
+
attr_accessor :helper_data, :nexpose_data, :options, :ticket_repository, :first_time, :nexpose_item_histories
|
60
60
|
|
61
61
|
def setup(helper_data)
|
62
62
|
# Gets the Ticket Service configuration.
|
@@ -106,8 +106,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
106
106
|
end
|
107
107
|
|
108
108
|
# Prepares all the local and nexpose historical data.
|
109
|
-
def prepare_historical_data(ticket_repository, options
|
110
|
-
|
109
|
+
def prepare_historical_data(ticket_repository, options)
|
110
|
+
(options[:tag_run]) ?
|
111
|
+
historical_scan_file = File.join(File.dirname(__FILE__), "#{options[:tag_file_name]}") :
|
112
|
+
historical_scan_file = File.join(File.dirname(__FILE__), "#{options[:file_name]}")
|
113
|
+
|
111
114
|
if File.exists?(historical_scan_file)
|
112
115
|
log_message("Reading historical CSV file: #{historical_scan_file}.")
|
113
116
|
file_site_histories = ticket_repository.read_last_scans(historical_scan_file)
|
@@ -119,32 +122,84 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
119
122
|
|
120
123
|
# Generates a full site(s) report ticket(s).
|
121
124
|
def all_site_report(ticket_repository, options, helper)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
if(options[:tag_run])
|
126
|
+
log_message('Generating full vulnerability report on user entered tags.')
|
127
|
+
items_to_query = Array(options[:tags])
|
128
|
+
log_message("Generating full vulnerability report on the following tags: #{items_to_query}")
|
129
|
+
else
|
130
|
+
log_message('Generating full vulnerability report on user entered sites.')
|
131
|
+
items_to_query = Array(options[:sites])
|
132
|
+
log_message("Generating full vulnerability report on the following sites: #{items_to_query.join(', ')}")
|
133
|
+
end
|
134
|
+
items_to_query.each { |item|
|
135
|
+
log_message("Running full vulnerability report on item #{item}")
|
136
|
+
all_vulns_file = ticket_repository.all_vulns(options, item)
|
130
137
|
log_message('Preparing tickets.')
|
131
|
-
ticket_rate_limiter(options, all_vulns_file, Proc.new { |ticket_batch| helper.prepare_create_tickets(ticket_batch,
|
138
|
+
ticket_rate_limiter(options, all_vulns_file, Proc.new { |ticket_batch| helper.prepare_create_tickets(ticket_batch, options[:tag_run] ? "T#{item}" : item) }, Proc.new { |tickets| helper.create_tickets(tickets) })
|
132
139
|
}
|
140
|
+
|
141
|
+
if(options[:tag_run])
|
142
|
+
items_to_query. each { |item_id|
|
143
|
+
tag_assets_historic_file = File.join(File.dirname(__FILE__), 'tag_assets', "#{options[:tag_file_name]}_#{item_id}.csv")
|
144
|
+
ticket_repository.generate_tag_asset_list(tags: item_id,
|
145
|
+
csv_file: tag_assets_historic_file)
|
146
|
+
}
|
147
|
+
end
|
133
148
|
log_message('Finished process all vulnerabilities.')
|
134
149
|
end
|
135
150
|
|
136
151
|
# There's possibly a new scan with new data.
|
137
152
|
def delta_site_report(ticket_repository, options, helper, file_site_histories)
|
138
|
-
# Compares the
|
153
|
+
# Compares the scan information from file && Nexpose.
|
139
154
|
no_processing = true
|
140
|
-
@
|
141
|
-
# There's no entry in the file, so it's either a new
|
142
|
-
if file_site_histories[
|
143
|
-
full_new_site_report(
|
155
|
+
@nexpose_item_histories.each do |item_id, last_scan_id|
|
156
|
+
# There's no entry in the file, so it's either a new item in Nexpose or a new item we have to monitor.
|
157
|
+
if file_site_histories[item_id].nil? || file_site_histories[item_id] == -1
|
158
|
+
full_new_site_report(item_id, ticket_repository, options, helper)
|
159
|
+
if(options[:tag_run])
|
160
|
+
tag_assets_historic_file = File.join(File.dirname(__FILE__), 'tag_assets', "#{options[:tag_file_name]}_#{item_id}.csv")
|
161
|
+
ticket_repository.generate_tag_asset_list(tags: item_id,
|
162
|
+
csv_file: tag_assets_historic_file)
|
163
|
+
end
|
144
164
|
no_processing = false
|
145
165
|
# Site has been scanned since last seen according to the file.
|
146
|
-
elsif file_site_histories[
|
147
|
-
|
166
|
+
elsif file_site_histories[item_id].to_s != nexpose_item_histories[item_id].to_s
|
167
|
+
if(options[:tag_run])
|
168
|
+
# It's a tag run and something has changed (new/removed asset or new scan ID for an asset). To find out what, we must compare
|
169
|
+
# All tag assets and their scan IDs. Firstly we fetch all the assets in the tags
|
170
|
+
# in the configuration file and store them temporarily
|
171
|
+
tag_assets_tmp_file = File.join(File.dirname(__FILE__), "/tag_assets/#{options[:tag_file_name]}_#{item_id}.tmp")
|
172
|
+
tag_assets_historic_file = File.join(File.dirname(__FILE__), "/tag_assets/#{options[:tag_file_name]}_#{item_id}.csv")
|
173
|
+
ticket_repository.generate_tag_asset_list(tags: item_id,
|
174
|
+
csv_file: tag_assets_tmp_file)
|
175
|
+
new_tag_configuration = ticket_repository.read_tag_asset_list(tag_assets_tmp_file)
|
176
|
+
historic_tag_configuration = ticket_repository.read_tag_asset_list(tag_assets_historic_file)
|
177
|
+
#Compare the assets within the tags and their scan histories to find the ones we need to query
|
178
|
+
changed_assets = Hash[*(historic_tag_configuration.to_a - new_tag_configuration.to_a).flatten]
|
179
|
+
new_assets = Hash[*(new_tag_configuration.to_a - historic_tag_configuration.to_a).flatten]
|
180
|
+
new_assets.delete_if {|asset_id, scan_id| historic_tag_configuration.has_key?(asset_id.to_s)}
|
181
|
+
#all_assets_changed = new_assets.merge(changed_assets)
|
182
|
+
changed_assets.each do |asset_id, scan_id|
|
183
|
+
delta_site_new_scan(ticket_repository, asset_id, options, helper, changed_assets, item_id)
|
184
|
+
end
|
185
|
+
new_assets.each do |asset_id, scan_id|
|
186
|
+
#Since no previous scan IDs - we generate a full report.
|
187
|
+
options[:nexpose_item] = asset_id
|
188
|
+
full_new_site_report(item_id, ticket_repository, options, helper)
|
189
|
+
options.delete(:nexpose_item)
|
190
|
+
end
|
191
|
+
else
|
192
|
+
delta_site_new_scan(ticket_repository, item_id, options, helper, file_site_histories)
|
193
|
+
end
|
194
|
+
#Update the historic file
|
195
|
+
new_tag_asset_list = historic_tag_configuration.merge(new_tag_configuration)
|
196
|
+
trimmed_csv = []
|
197
|
+
trimmed_csv << 'asset_id, last_scan_id'
|
198
|
+
new_tag_asset_list.each do |asset_id, last_scan_id|
|
199
|
+
trimmed_csv << "#{asset_id},#{last_scan_id}"
|
200
|
+
end
|
201
|
+
ticket_repository.save_to_file(tag_assets_historic_file, trimmed_csv)
|
202
|
+
File.delete(tag_assets_tmp_file)
|
148
203
|
no_processing = false
|
149
204
|
end
|
150
205
|
end
|
@@ -155,46 +210,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
155
210
|
end
|
156
211
|
|
157
212
|
# There's a new site we haven't seen before.
|
158
|
-
def full_new_site_report(
|
159
|
-
log_message("New
|
160
|
-
|
213
|
+
def full_new_site_report(nexpose_item, ticket_repository, options, helper)
|
214
|
+
log_message("New nexpose id: #{nexpose_item} detected. Generating report.")
|
215
|
+
new_item_vuln_file = ticket_repository.all_vulns(options, nexpose_item)
|
161
216
|
log_message('Report generated, preparing tickets.')
|
162
|
-
ticket_rate_limiter(options,
|
217
|
+
ticket_rate_limiter(options, new_item_vuln_file, Proc.new {|ticket_batch| helper.prepare_create_tickets(ticket_batch, options[:tag_run] ? "T#{nexpose_item}" : nexpose_item)}, Proc.new {|tickets| helper.create_tickets(tickets)})
|
163
218
|
end
|
164
219
|
|
165
220
|
# There's a new scan with possibly new vulnerabilities.
|
166
|
-
def delta_site_new_scan(ticket_repository,
|
167
|
-
log_message("New scan detected for
|
221
|
+
def delta_site_new_scan(ticket_repository, nexpose_item, options, helper, file_site_histories, tag_id=nil)
|
222
|
+
log_message("New scan detected for nexpose id: #{nexpose_item}. Generating report.")
|
168
223
|
|
169
224
|
if options[:ticket_mode] == 'I' || options[:ticket_mode] == 'V'
|
170
225
|
# I-mode and V-mode tickets require updating the tickets in the target system.
|
171
|
-
log_message("Scan id for new scan: #{file_site_histories[
|
172
|
-
all_scan_vuln_file = ticket_repository.
|
173
|
-
|
226
|
+
log_message("Scan id for new scan: #{file_site_histories[nexpose_item]}.")
|
227
|
+
all_scan_vuln_file = ticket_repository.all_vulns_since(scan_id: file_site_histories[nexpose_item],
|
228
|
+
nexpose_item: nexpose_item,
|
174
229
|
severity: options[:severity],
|
175
230
|
ticket_mode: options[:ticket_mode],
|
176
231
|
riskScore: options[:riskScore],
|
177
232
|
vulnerabilityCategories: options[:vulnerabilityCategories],
|
178
|
-
|
233
|
+
tag_run: options[:tag_run],
|
234
|
+
tag: tag_id)
|
179
235
|
|
180
236
|
if helper.respond_to?('prepare_update_tickets') && helper.respond_to?('update_tickets')
|
181
|
-
ticket_rate_limiter(options, all_scan_vuln_file, Proc.new {|ticket_batch| helper.prepare_update_tickets(ticket_batch,
|
237
|
+
ticket_rate_limiter(options, all_scan_vuln_file, Proc.new {|ticket_batch| helper.prepare_update_tickets(ticket_batch, tag_id.nil? ? nexpose_item : "T#{tag_id}")}, Proc.new {|tickets| helper.update_tickets(tickets)})
|
182
238
|
else
|
183
239
|
log_message('Helper does not implement update methods')
|
184
240
|
fail "Helper using 'I' or 'V' mode must implement prepare_updates and update_tickets"
|
185
241
|
end
|
186
242
|
|
187
243
|
if options[:close_old_tickets_on_update] == 'Y'
|
188
|
-
tickets_to_close_file = ticket_repository.tickets_to_close(scan_id: file_site_histories[
|
189
|
-
|
244
|
+
tickets_to_close_file = ticket_repository.tickets_to_close(scan_id: file_site_histories[nexpose_item],
|
245
|
+
nexpose_item: nexpose_item,
|
190
246
|
severity: options[:severity],
|
191
247
|
ticket_mode: options[:ticket_mode],
|
192
248
|
riskScore: options[:riskScore],
|
193
249
|
vulnerabilityCategories: options[:vulnerabilityCategories],
|
194
|
-
|
250
|
+
tag_run: options[:tag_run],
|
251
|
+
tag: tag_id)
|
195
252
|
|
196
253
|
if helper.respond_to?('prepare_close_tickets') && helper.respond_to?('close_tickets')
|
197
|
-
ticket_rate_limiter(options, tickets_to_close_file, Proc.new {|ticket_batch| helper.prepare_close_tickets(ticket_batch,
|
254
|
+
ticket_rate_limiter(options, tickets_to_close_file, Proc.new {|ticket_batch| helper.prepare_close_tickets(ticket_batch, tag_id.nil? ? nexpose_item : "T#{tag_id}")}, Proc.new {|tickets| helper.close_tickets(tickets)})
|
198
255
|
else
|
199
256
|
log_message('Helper does not implement close methods')
|
200
257
|
fail 'Helper using \'I\' or \'V\' mode must implement prepare_close_tickets and close_tickets'
|
@@ -202,36 +259,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
202
259
|
end
|
203
260
|
else
|
204
261
|
# D-mode tickets require creating new tickets and closing old tickets.
|
205
|
-
new_scan_vuln_file = ticket_repository.
|
206
|
-
|
262
|
+
new_scan_vuln_file = ticket_repository.new_vulns(scan_id: file_site_histories[nexpose_item],
|
263
|
+
nexpose_item: nexpose_item,
|
207
264
|
severity: options[:severity],
|
208
265
|
ticket_mode: options[:ticket_mode],
|
209
266
|
riskScore: options[:riskScore],
|
210
267
|
vulnerabilityCategories: options[:vulnerabilityCategories],
|
211
|
-
|
268
|
+
tag_run: options[:tag_run],
|
269
|
+
tag: tag_id)
|
212
270
|
|
213
271
|
preparse = CSV.new(new_scan_vuln_file.path, headers: :first_row)
|
214
272
|
empty_report = preparse.shift.nil?
|
215
|
-
log_message("No new vulnerabilities found in new scan for site: #{
|
216
|
-
log_message("New vulnerabilities found in new scan for site #{
|
273
|
+
log_message("No new vulnerabilities found in new scan for site: #{nexpose_item}.") if empty_report
|
274
|
+
log_message("New vulnerabilities found in new scan for site #{nexpose_item}, preparing tickets.") unless empty_report
|
217
275
|
unless empty_report
|
218
|
-
ticket_rate_limiter(options, new_scan_vuln_file, Proc.new {|ticket_batch| helper.prepare_create_tickets(ticket_batch,
|
276
|
+
ticket_rate_limiter(options, new_scan_vuln_file, Proc.new {|ticket_batch| helper.prepare_create_tickets(ticket_batch, tag_id.nil? ? nexpose_item : "T#{tag_id}")}, Proc.new {|tickets| helper.create_tickets(tickets)})
|
219
277
|
end
|
220
278
|
|
221
279
|
if helper.respond_to?('prepare_close_tickets') && helper.respond_to?('close_tickets')
|
222
|
-
old_scan_vuln_file = ticket_repository.
|
223
|
-
site_id:
|
280
|
+
old_scan_vuln_file = ticket_repository.old_vulns(scan_id: file_site_histories[nexpose_item],
|
281
|
+
site_id: nexpose_item,
|
224
282
|
severity: options[:severity],
|
225
283
|
riskScore: options[:riskScore],
|
226
284
|
vulnerabilityCategories: options[:vulnerabilityCategories],
|
227
|
-
|
285
|
+
tag_run: options[:tag_run],
|
286
|
+
tag: tag_id)
|
228
287
|
|
229
288
|
preparse = CSV.new(old_scan_vuln_file.path, headers: :first_row, :skip_blanks => true)
|
230
289
|
empty_report = preparse.shift.nil?
|
231
|
-
log_message("No old (closed) vulnerabilities found in new scan for site: #{
|
232
|
-
log_message("Old vulnerabilities found in new scan for site #{
|
290
|
+
log_message("No old (closed) vulnerabilities found in new scan for site: #{nexpose_item}.") if empty_report
|
291
|
+
log_message("Old vulnerabilities found in new scan for site #{nexpose_item}, preparing closures.") unless empty_report
|
233
292
|
unless empty_report
|
234
|
-
ticket_rate_limiter(options, old_scan_vuln_file, Proc.new {|ticket_batch| helper.prepare_close_tickets(ticket_batch,
|
293
|
+
ticket_rate_limiter(options, old_scan_vuln_file, Proc.new {|ticket_batch| helper.prepare_close_tickets(ticket_batch, tag_id.nil? ? nexpose_item : "T#{tag_id}")}, Proc.new {|tickets| helper.close_tickets(tickets)})
|
235
294
|
end
|
236
295
|
else
|
237
296
|
# Create a log message but do not halt execution of the helper if ticket closing is not
|
@@ -315,42 +374,50 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
315
374
|
|
316
375
|
# Starts the Ticketing Service.
|
317
376
|
def start
|
377
|
+
#Decide if this is a tag run (tags always override sites as the API does not allow for the combination of the two)
|
378
|
+
@options[:tag_run] = !@options[:tags].nil? && !@options[:tags].empty?
|
379
|
+
|
318
380
|
# Checks if the csv historical file already exists && reads it, otherwise create it && assume first time run.
|
319
381
|
file_site_histories = prepare_historical_data(@ticket_repository, @options)
|
320
382
|
historical_scan_file = File.join(File.dirname(__FILE__), "#{@options[:file_name]}")
|
383
|
+
historical_tag_file = File.join(File.dirname(__FILE__), "#{@options[:tag_file_name]}")
|
384
|
+
|
321
385
|
# If we didn't specify a site || first time run (no scan history), then it gets all the vulnerabilities.
|
322
|
-
if @options[:sites].nil? || @options[:sites].empty? || file_site_histories.nil?
|
323
|
-
|
386
|
+
if (((@options[:sites].nil? || @options[:sites].empty? || file_site_histories.nil?) && !@options[:tag_run]) || (@options[:tag_run] && file_site_histories.nil?))
|
387
|
+
log_message('Storing current scan state before obtaining all vulnerabilities.')
|
388
|
+
current_scan_state = ticket_repository.load_last_scans(@options)
|
324
389
|
|
325
|
-
if options[:sites].nil? || options[:sites].empty?
|
390
|
+
if (options[:sites].nil? || options[:sites].empty?) && (!@options[:tag_run])
|
326
391
|
log_message('No site(s) specified, generating for all sites.')
|
327
392
|
@ticket_repository.all_site_details.each { |site| (@options[:sites] ||= []) << site.id.to_s }
|
328
393
|
log_message("List of sites is now <#{@options[:sites]}>")
|
329
394
|
end
|
330
395
|
|
331
|
-
current_scan_state = ticket_repository.load_last_scans(@options)
|
332
|
-
|
333
396
|
all_site_report(@ticket_repository, @options, @helper)
|
334
397
|
|
335
398
|
#Generate historical CSV file after completing the fist query.
|
336
399
|
log_message('No historical CSV file found. Generating.')
|
337
|
-
@
|
400
|
+
@options[:tag_run] ?
|
401
|
+
@ticket_repository.save_to_file(historical_tag_file, current_scan_state) :
|
402
|
+
@ticket_repository.save_to_file(historical_scan_file, current_scan_state)
|
338
403
|
log_message('Historical CSV file generated.')
|
339
404
|
else
|
340
405
|
log_message('Obtaining last scan information.')
|
341
|
-
@
|
406
|
+
@nexpose_item_histories = @ticket_repository.last_scans(@options)
|
342
407
|
|
343
408
|
# Scan states can change during our processing. Store the state we are
|
344
|
-
# about to process and move this to the
|
409
|
+
# about to process and move this to the historical file if we
|
345
410
|
# successfully process.
|
346
411
|
log_message('Calculated deltas, storing current scan state.')
|
347
412
|
current_scan_state = ticket_repository.load_last_scans(options)
|
348
413
|
|
349
414
|
# Only run if a scan has been ran ever in Nexpose.
|
350
|
-
unless @
|
415
|
+
unless @nexpose_item_histories.empty?
|
351
416
|
delta_site_report(@ticket_repository, @options, @helper, file_site_histories)
|
352
417
|
# Processing completed successfully. Update historical scan file.
|
353
|
-
@
|
418
|
+
@options[:tag_run] ?
|
419
|
+
@ticket_repository.save_to_file(historical_tag_file, current_scan_state) :
|
420
|
+
@ticket_repository.save_to_file(historical_scan_file, current_scan_state)
|
354
421
|
end
|
355
422
|
end
|
356
423
|
log_message('Exiting ticket service.')
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexpose_ticketing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Damian Finol
|
8
|
+
- JJ Cassidy
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2015-
|
12
|
+
date: 2015-07-06 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: nexpose
|
@@ -95,7 +96,7 @@ dependencies:
|
|
95
96
|
description: This gem provides a Ruby implementation of different integrations with
|
96
97
|
ticketing services for Nexpose.
|
97
98
|
email:
|
98
|
-
-
|
99
|
+
- integrations_support@rapid7.com
|
99
100
|
executables:
|
100
101
|
- nexpose_jira
|
101
102
|
- nexpose_servicenow
|