nexpose_ticketing 0.8.1 → 0.8.2
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.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
|