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.
@@ -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, :nexpose_site_histories
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
- historical_scan_file = File.join(File.dirname(__FILE__), "#{options[:file_name]}"))
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
- log_message('Generating full vulnerability report on user entered sites.')
123
- sites_to_query = Array(options[:sites])
124
-
125
- log_message("Generating full vulnerability report on the following sites: #{sites_to_query.join(', ')}")
126
-
127
- sites_to_query.each { |site|
128
- log_message("Running full vulnerability report on site #{site}")
129
- all_vulns_file = ticket_repository.all_vulns(options, site)
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, site) }, Proc.new { |tickets| helper.create_tickets(tickets) })
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 Scan information from the File && Nexpose.
153
+ # Compares the scan information from file && Nexpose.
139
154
  no_processing = true
140
- @nexpose_site_histories.each do |site_id, scan_id|
141
- # There's no entry in the file, so it's either a new site in Nexpose or a new site we have to monitor.
142
- if file_site_histories[site_id].nil? || file_site_histories[site_id] == -1
143
- full_new_site_report(site_id, ticket_repository, options, helper)
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[site_id].to_i < nexpose_site_histories[site_id]
147
- delta_site_new_scan(ticket_repository, site_id, options, helper, file_site_histories)
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(site_id, ticket_repository, options, helper)
159
- log_message("New site id: #{site_id} detected. Generating report.")
160
- new_site_vuln_file = ticket_repository.all_vulns(options, site_id)
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, new_site_vuln_file, Proc.new {|ticket_batch| helper.prepare_create_tickets(ticket_batch, site_id)}, Proc.new {|tickets| helper.create_tickets(tickets)})
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, site_id, options, helper, file_site_histories)
167
- log_message("New scan detected for site: #{site_id}. Generating report.")
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[site_id]}.")
172
- all_scan_vuln_file = ticket_repository.all_vulns_sites(scan_id: file_site_histories[site_id],
173
- site_id: site_id,
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
- tags: options[:tags])
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, site_id)}, Proc.new {|tickets| helper.update_tickets(tickets)})
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[site_id],
189
- site_id: site_id,
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
- tags: options[:tags])
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, site_id)}, Proc.new {|tickets| helper.close_tickets(tickets)})
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.new_vulns_sites(scan_id: file_site_histories[site_id],
206
- site_id: site_id,
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
- tags: options[:tags])
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: #{site_id}.") if empty_report
216
- log_message("New vulnerabilities found in new scan for site #{site_id}, preparing tickets.") unless empty_report
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, site_id)}, Proc.new {|tickets| helper.create_tickets(tickets)})
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.old_vulns_sites(scan_id: file_site_histories[site_id],
223
- site_id: 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
- tags: options[:tags])
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: #{site_id}.") if empty_report
232
- log_message("Old vulnerabilities found in new scan for site #{site_id}, preparing closures.") unless empty_report
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, site_id)}, Proc.new {|tickets| helper.close_tickets(tickets)})
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
- log_message('Storing current scan state before obtaining all vulnerabilities.')
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
- @ticket_repository.save_scans_to_file(historical_scan_file, current_scan_state)
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
- @nexpose_site_histories = @ticket_repository.last_scans(@options)
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 historical_scan_file if we
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 @nexpose_site_histories.empty?
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
- @ticket_repository.save_scans_to_file(historical_scan_file, current_scan_state)
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.1
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-05-22 00:00:00.000000000 Z
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
- - damian_finol@rapid7.com
99
+ - integrations_support@rapid7.com
99
100
  executables:
100
101
  - nexpose_jira
101
102
  - nexpose_servicenow