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.
@@ -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