nexpose_servicenow 0.6.2 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58a608ff53a461dc491abb88f3a42b0043dc38e2
4
- data.tar.gz: fadea12c81f970626257ae7aef396a4a7050c25e
3
+ metadata.gz: ba87a2439d290abbf57e7eb2d6792d691166f312
4
+ data.tar.gz: 0dcb0dc8fc8373431a3ccba7b6879903562a0fee
5
5
  SHA512:
6
- metadata.gz: c326787fa3fded2fc9db9adaa69552a8e4a7c3849865da757ac3735d4540b041d6afe6efa33ca6c708833e61872cd52452569ece6e58ff35dd5d7dd625bd9c49
7
- data.tar.gz: 4651506f36ae3fea4f5842f716b57c270cb44f37a0465820294aee3730a859c0f0731ee57481c9afe5fcbbaf71804e0d845ab016db761be15c9384e169f0d4fe
6
+ metadata.gz: df8bcf8ccf412d0fe87aa41282233fc6e9739ccac94ec7398c42bd73842ec5de92ef7045ae300ad0e47999c808e5c327caf8fe974997066f349dfef54c45de63
7
+ data.tar.gz: 2f8dfbd40ea34d981d75da8b0bad547de128b4e3cd118c9bf3dec4883aeab01a61664f29d37a4b1156d60326864874fd3aa0e6a7d929510e356660cb425a2b76
data/README.md CHANGED
@@ -14,7 +14,7 @@ Alternatively, it is also possible to call the following to see a list of parame
14
14
 
15
15
  ## Support
16
16
  Please contact the following address for support queries:
17
- [support@rapid7.com](support@rapid7.com)
17
+ [Rapid7 Support Portal](https://rapid7support.force.com/customers/login)
18
18
 
19
19
  Please attach both the gem logs and relevant snippets from the agent logs.
20
20
 
@@ -2,8 +2,9 @@ require 'csv'
2
2
  require 'optparse'
3
3
  require 'nexpose'
4
4
  require 'uri'
5
- require 'nexpose_servicenow/queries'
6
- require 'nexpose_servicenow/nexpose_helper'
5
+ require_relative './nexpose_servicenow/helpers/connection_helper'
6
+ require_relative './nexpose_servicenow/helpers/nexpose_console_helper'
7
+ require_relative './nexpose_servicenow/helpers/data_warehouse_helper'
7
8
  require 'nexpose_servicenow/arg_parser'
8
9
  require 'nexpose_servicenow/chunker'
9
10
  require 'nexpose_servicenow/nx_logger'
@@ -18,49 +19,54 @@ module NexposeServiceNow
18
19
  @log = setup_logging(options)
19
20
 
20
21
  censored_options = options.dup
21
- censored_options[:nexpose_username] = '*****'
22
- censored_options[:nexpose_password] = '*****'
22
+ censored_options[:username] = '*****'
23
+ censored_options[:password] = '*****'
23
24
  @log.log_message("Options: #{censored_options}")
24
25
 
25
- options[:nexpose_ids] = get_collection_ids(options)
26
- options[:start_time] = Time.new().strftime('%Y-%m-%m %H:%M:%S')
26
+ query = options[:query]
27
+ site_ids = options[:nexpose_ids]
28
+
29
+ # Filter out irrelevant sites
30
+ if query == :vulnerable_old_items
31
+ site_ids = get_historical_data(options).filter_ids(site_ids)
32
+ if site_ids.count == 0
33
+ puts 'No sites remaining for vulnerable old items query. Exiting.'
34
+ exit 0
35
+ end
36
+ end
37
+
38
+ report_details = ConnectionHelper.get_report_names(query, site_ids)
27
39
 
28
- report_details = NexposeHelper.get_report_names(options[:query],
29
- options[:nexpose_ids])
30
40
  report_details.each do |r|
31
- r[:report_name] = NexposeHelper.get_filepath(r[:report_name],
32
- options[:output_dir])
41
+ r[:report_name] = ConnectionHelper.get_filepath(r[:report_name],
42
+ options[:output_dir])
33
43
  end
34
44
 
35
- update_delta_files(options) if options[:mode] == 'latest_scans'
45
+ report_results = create_report(report_details, options)
36
46
 
37
- create_report(report_details, options)
47
+ # If data was returned, we can short circuit here
48
+ if !report_results.nil? and options[:mode] == 'chunk_info'
49
+ puts report_results
50
+ exit 0
51
+ end
38
52
 
39
53
  @log.log_message("Initialising #{options[:mode]} mode")
40
54
  self.send("#{options[:mode]}_mode", report_details, options)
41
55
  end
42
56
 
43
57
  def self.get_historical_data(options)
44
- HistoricalData.new(options[:output_dir],
45
- options[:nexpose_ids],
46
- options[:id_type],
47
- options[:start_time])
48
- end
49
-
50
- def self.get_nexpose_helper(options)
51
- NexposeHelper.new(options[:nexpose_url],
52
- options[:nexpose_port],
53
- options[:nexpose_username],
54
- options[:nexpose_password])
58
+ HistoricalData.new(options[:output_dir])
55
59
  end
56
60
 
57
- # Retrieves list of all IDs if the user has chosen to import each group
58
- def self.get_collection_ids(options)
59
- return options[:nexpose_ids] if options[:nexpose_ids].first.to_s != '0'
61
+ def self.get_helper(options)
62
+ name = options[:conn_type].to_s.split('_').map(&:capitalize).join('')
60
63
 
61
- @log.log_error_message("Retrieving array of all #{options[:id_type]} IDs")
62
- helper = get_nexpose_helper(options)
63
- helper.collection_ids(options[:id_type]).map(&:to_s)
64
+ helper = eval("#{name}Helper")
65
+ helper.new(options[:url],
66
+ options[:port],
67
+ options[:username],
68
+ options[:password],
69
+ options[:database_name])
64
70
  end
65
71
 
66
72
  def self.setup_logging(options)
@@ -74,13 +80,6 @@ module NexposeServiceNow
74
80
  log
75
81
  end
76
82
 
77
- # Merges in the details from the last time the integration ran reports.
78
- def self.update_delta_files(options)
79
- historical_data = get_historical_data(options)
80
- historical_data.update_delta_file
81
- historical_data.save_vuln_timestamp(filter_sites(options))
82
- end
83
-
84
83
  # Create a report if explicitly required or else an existing
85
84
  # report file isn't found
86
85
  def self.create_report(report_details, options)
@@ -89,76 +88,45 @@ module NexposeServiceNow
89
88
  return
90
89
  end
91
90
 
91
+ # Perform all queries if a file is missing, regardless of other settings
92
92
  unless options[:gen_report]
93
- # If any file is missing, perform all queries
94
93
  return if report_details.all? { |f| File.exists? f[:report_name] }
95
94
  end
96
95
 
97
- credentials = %i{nexpose_username nexpose_password}
96
+ credentials = %i{username password}
98
97
  if credentials.any? { |cred| options[cred].to_s == '' }
99
98
  @log.log_error_message 'Nexpose credentials necessary but not supplied.'
100
99
  exit -1
101
100
  end
102
101
 
103
- #Filter it down to sites which actively need queried
104
- sites_to_scan = filter_sites(options)
105
- nexpose_helper = get_nexpose_helper(options)
106
- hist_data = get_historical_data(options)
107
- vuln_query = options[:query].to_s.start_with? 'vulnerabili'
102
+ # Filter it down to sites which actively need queried
103
+ sites_to_scan = options[:nexpose_ids].keys
108
104
 
109
- delta_values = hist_data.stored_delta_values(sites_to_scan)
110
- query_options = { delta_values: delta_values }
111
- query_options[:vuln_query_date] = hist_data.last_vuln_run if vuln_query
105
+ query_options = { delta_values: options[:nexpose_ids] }
106
+ query_options[:vuln_query_date] = options[:vuln_query_date]
112
107
  query_options[:filters] = options[:filters]
113
-
114
- filename = nexpose_helper.create_report(options[:query],
115
- sites_to_scan,
116
- options[:id_type],
117
- options[:output_dir],
118
- query_options)
119
-
120
- # A single String may be returned or an Array of Strings
121
- if filename.class.to_s == 'Array'
122
- filename.map! { |f| File.expand_path(options[:output_dir], f) }
123
- return filename.join("\n")
124
- end
125
-
126
- File.expand_path(options[:output_dir], filename)
127
- end
128
-
129
- def self.filter_sites(options)
130
- # These queries always run to make sure certain data is up to date
131
- exceptions = %w(vulnerabili asset_groups sites tags)
132
- if exceptions.any? { |e| options[:query].to_s.start_with? e }
133
- return options[:nexpose_ids]
134
- end
135
-
136
- #Always run the query for latest scans or vulnerabilities
137
- if options[:mode] == 'latest_scans' ||
138
- options[:mode] == 'get_chunk'
139
- return options[:nexpose_ids]
140
- end
141
-
142
- historical_data = get_historical_data(options)
143
- imported_sites_only = options[:query].to_s.eql? 'vulnerable_old_items'
144
- sites_to_scan = historical_data.collections_to_import(imported_sites_only)
145
-
146
- return sites_to_scan unless (sites_to_scan.nil? || sites_to_scan.empty?)
147
-
148
- @log.log_message "Sites #{options[:nexpose_ids]} are up to date."
149
- @log.log_message "Query requested was: #{options[:query]}."
150
- exit 0
108
+ query_options[:page_size] = options[:row_limit]
109
+ query_options[:row_limit] = options[:row_limit]
110
+ report_helper = get_helper(options)
111
+ @log.log_message("Querying using the #{report_helper.class}.")
112
+ report_helper.generate_report(options[:query],
113
+ sites_to_scan,
114
+ options[:id_type],
115
+ options[:output_dir],
116
+ query_options)
151
117
  end
152
118
 
153
119
  # Print the chunk info
154
120
  def self.chunk_info_mode(report_details, options)
155
- filtered_sites = filter_sites(options)
121
+ site_ids = options[:nexpose_ids].keys
122
+
123
+ # Assign -1 to reports without site IDs
156
124
  report_details = report_details.select do |d|
157
- d[:id] == -1 or filtered_sites.include? d[:id]
125
+ d[:id] == -1 or site_ids.include? d[:id]
158
126
  end
127
+
159
128
  chunker = Chunker.new(report_details, options[:row_limit])
160
129
 
161
- # TODO: Check why filtered_sites are passed in here
162
130
  puts chunker.preprocess
163
131
  end
164
132
 
@@ -166,16 +134,15 @@ module NexposeServiceNow
166
134
  def self.get_chunk_mode(report_details, options)
167
135
  #Get the byte offset and length
168
136
  chunker = Chunker.new(report_details, options[:row_limit])
169
- filtered_sites = filter_sites(options)
170
137
 
171
138
  puts chunker.read_chunk(options[:chunk_start],
172
139
  options[:chunk_length],
173
- filtered_sites.first)
140
+ options[:nexpose_ids].keys.first)
174
141
  end
175
142
 
176
143
  def self.latest_scans_mode(report_details, options)
177
144
  historical_data = get_historical_data(options)
178
- puts historical_data.filter_report
145
+ puts historical_data.filter_report options[:nexpose_ids].keys
179
146
  end
180
147
 
181
148
  def self.remove_last_scan_mode(report_details, options)
@@ -202,7 +169,7 @@ module NexposeServiceNow
202
169
 
203
170
  def self.remove_diff_comparison_mode(report_details, options)
204
171
  historical_data = get_historical_data(options)
205
- historical_data.remove_last_diff_comparison_data options[:output_dir]
172
+ historical_data.remove_diff_files options[:output_dir]
206
173
  end
207
174
  end
208
175
  end
@@ -1,17 +1,17 @@
1
1
  require 'optparse'
2
2
  require 'json'
3
3
  require 'time'
4
- require_relative './queries'
4
+ require_relative './queries/nexpose_queries'
5
5
  require_relative './nx_logger'
6
6
 
7
7
  module NexposeServiceNow
8
8
  class ArgParser
9
9
  NX_ID_TYPES = %i[site asset_group]
10
+ NX_CONNECTION_TYPES = %i[nexpose_console data_warehouse]
10
11
  MODES = %i[chunk_info get_chunk latest_scans
11
- remove_last_scan update_last_scan
12
- remove_last_vuln update_last_vuln]
13
-
14
- QUERY_NAMES = Queries.methods(false)
12
+ remove_last_scan remove_last_vuln]
13
+ REQUIRED_OPTIONS = %i[url port username password]
14
+ QUERY_NAMES = NexposeQueries.methods(false)
15
15
 
16
16
  def self.parse(args)
17
17
  options = Hash.new
@@ -46,44 +46,71 @@ module NexposeServiceNow
46
46
  options[:query] = query
47
47
  end
48
48
 
49
- opts.on('-t', '--type TYPE', NX_ID_TYPES,
50
- "Select type (#{NX_ID_TYPES.join(', ')})") do |type|
51
- options[:id_type] = type
49
+ opts.on('-t', '--type ID~CONNECTION', 'Select ID type ' \
50
+ "(#{NX_ID_TYPES.join(', ')}) and connection type " \
51
+ "(#{NX_CONNECTION_TYPES.join(', ')})") do |types|
52
+ type = types.split('~')
53
+ options[:id_type] = type[0].intern
54
+ options[:conn_type] = type[1].intern
55
+
56
+ if options[:conn_type].equal? :data_warehouse
57
+ REQUIRED_OPTIONS << :database_name
58
+ end
52
59
  end
53
60
 
54
- opts.on('-i', '--items x,y,z', Array,
55
- 'IDs of the nexpose items to scan') do |items|
56
- options[:nexpose_ids] = items
61
+ opts.on('-i', '--items x~x,y~y,z~z', Array,
62
+ 'IDs of the nexpose items to ' \
63
+ 'scan, provided with their previous scan IDs or timestamp ' \
64
+ 'of last scan') do |items|
65
+ options[:nexpose_ids] = {}
66
+
67
+ # Split the string up into site and scan pairs
68
+ items = items.map { |s| s.split('~') }
69
+
70
+ # Store the information in site:scan_id dict
71
+ items.each { |site, scan| options[:nexpose_ids][site] = scan }
72
+ end
73
+
74
+ opts.on('-a', '--abs-vulntime TIMESTAMP',
75
+ 'Timestamp of last vulnerability definition import') do |vulnt|
76
+ # TODO: Does the date need formatted?
77
+ options[:vuln_query_date] = vulnt
57
78
  end
58
79
 
59
80
  opts.separator ''
60
- opts.separator 'Nexpose options:'
81
+ opts.separator 'Connection options:'
61
82
 
62
- opts.on('-n', '--nexpose-address URL',
63
- 'URL of the Nexpose server') do |url|
83
+ opts.on('-n', '--nexpose-datastore URL',
84
+ 'URL of the Nexpose/Data Warehouse server') do |url|
64
85
  port = url.slice!(/:(\d+)$/)
65
86
  port.slice! ':' unless port.nil?
66
87
 
67
88
  url.slice! 'https://'
68
- options[:nexpose_url] = url
69
- options[:nexpose_port] = port || '3780'
89
+ options[:url] = url
90
+ options[:port] = port
70
91
  end
71
92
 
72
93
  opts.on('-u', '--user USER',
73
- 'Username for Nexpose console') do |username|
74
- options[:nexpose_username] = username
94
+ 'Username for Nexpose/Data Warehouse') do |username|
95
+ options[:username] = username
75
96
  end
76
97
 
77
98
  opts.on('-p', '--password PASSWORD',
78
- 'Password for the Nexpose user') do |password|
79
- options[:nexpose_password] = password
99
+ 'Password for the Nexpose/Data Warehouse user') do |password|
100
+ options[:password] = password
101
+ end
102
+
103
+ opts.on('-b', '--database DATABASE_NAME',
104
+ 'The name of the Postgres Database '\
105
+ '(DataWarehouse Only)') do |database|
106
+ options[:database_name] = database
80
107
  end
81
108
 
82
109
  opts.separator ''
83
110
  opts.separator 'Chunk info mode options:'
84
111
 
85
112
  opts.on('-r', '--row-limit LIMIT',
86
- 'Maximum number of rows per chunk (including header).') do |limit|
113
+ 'Maximum number of rows per chunk (inc. header).') do |limit|
87
114
  options[:row_limit] = limit.to_i
88
115
  options[:row_limit] = 9_999_999 if options[:row_limit] <= 0
89
116
  end
@@ -104,37 +131,7 @@ module NexposeServiceNow
104
131
  opts.separator ''
105
132
  opts.separator 'Filter options:'
106
133
 
107
- =begin
108
- # CVE filter is not currently supported.
109
- opts.on('-v', '--vuln-identifier CVE',
110
- 'The CVE values for which to filter.') do |data|
111
- # A value of 'none' means the user has left field blank
112
- if data.to_s.downcase != 'none'
113
- data = data.to_s.sub(' ', '').split(',')
114
-
115
- invalid_cve = data.select { |f| (f =~ /(CVE-)?\d{4}-\d+/) == nil }
116
- unless invalid_cve.empty?
117
- error = "Invalid CVEs applied: #{invalid_cve}"
118
- puts error
119
- log.log_message error
120
- exit -1
121
- end
122
-
123
- data = data.map do |c|
124
- if c.start_with? 'CVE-'
125
- c
126
- else
127
- "CVE-#{c}"
128
- end
129
- end
130
- else
131
- data = nil
132
- end
133
134
 
134
- options[:filters] ||= {}
135
- options[:filters][:cve] = data
136
- end
137
- =end
138
135
  opts.on('-c', '--cvss-score CVSS',
139
136
  'The minimum CVSS score to import') do |data|
140
137
 
@@ -175,6 +172,10 @@ module NexposeServiceNow
175
172
  dates[0] = dates[0] + ' 00:00:00'
176
173
  dates[1] = dates[1] + ' 23:59:59'
177
174
 
175
+ # Remove rogue '' in datetime format
176
+ dates[0] = dates[0].gsub("'","")
177
+ dates[1] = dates[1].gsub("'","")
178
+
178
179
  # Check for valid dates and placeholders
179
180
  dates.map! do |d|
180
181
  if d =~ /Y{4}-M{1,2}-D{1,2}/i
@@ -193,15 +194,6 @@ module NexposeServiceNow
193
194
  options[:filters][:date] = dates
194
195
  end
195
196
 
196
-
197
- opts.separator ''
198
- opts.separator 'Last scan file modification options:'
199
-
200
- opts.on('-e', '--errata DATA',
201
- 'Date or scan ID to be inserted in last scan file.') do |data|
202
- options[:last_scan_data] = data
203
- end
204
-
205
197
  opts.separator ''
206
198
  opts.separator 'Common options:'
207
199
 
@@ -212,8 +204,8 @@ module NexposeServiceNow
212
204
  end
213
205
 
214
206
  opt_parser.parse!(args)
215
- options = self.validate_input(options)
216
207
  options = self.get_env_settings(options)
208
+ options = self.validate_input(options)
217
209
  options
218
210
  end
219
211
 
@@ -221,8 +213,10 @@ module NexposeServiceNow
221
213
  #Insert defaults. Some are mode-specific.
222
214
  options[:output_dir] ||= '.'
223
215
  options[:row_limit] ||= 9_999_999
216
+ options[:vuln_query_date] ||= '1985-01-01 00:00:00'
224
217
  options[:id_type] ||= 'site'
225
- options[:nexpose_ids] ||= []
218
+ options[:conn_type] ||= :nexpose_console
219
+ options[:nexpose_ids] ||= {}
226
220
  options[:filters] ||= {}
227
221
 
228
222
  options[:query] = 'latest_scans' if options[:mode] == 'latest_scans'
@@ -233,36 +227,47 @@ module NexposeServiceNow
233
227
  options[:mode] == 'latest_scans'
234
228
  end
235
229
 
230
+ options[:port] ||= if options[:conn_type].equal? :nexpose_console
231
+ '3780'
232
+ else
233
+ '5432'
234
+ end
235
+
236
+ log = NexposeServiceNow::NxLogger.instance
237
+
236
238
  if options[:mode].to_s == ''
237
- log = NexposeServiceNow::NxLogger.instance
238
239
  log.log_message('Script was called without mode.')
239
240
  puts 'No mode selected. Use -h to see command line options.'
240
241
  exit -1
241
242
  end
242
243
 
244
+ #Only need to check these if a query is being performed
245
+ return options unless options[:gen_report]
246
+
247
+ REQUIRED_OPTIONS.each do |setting|
248
+ if options[setting].nil?
249
+ error = "Option #{setting} wasn't supplied."
250
+ log.log_error_message error
251
+ $stderr.puts "ERROR: #{error}"
252
+ exit -1
253
+ end
254
+ end
255
+
243
256
  options
244
257
  end
245
258
 
246
259
  def self.get_env_settings(options)
247
260
  #Only need these if a query is being performed
248
- return options if options[:gen_report] == false
261
+ return options unless options[:gen_report]
249
262
 
250
263
  log = NexposeServiceNow::NxLogger.instance
251
264
  log.log_message 'Retrieving environment variables.'
252
265
 
253
266
  # Retrieve environment variable settings
254
- %i[url port username password].each do |setting|
267
+ REQUIRED_OPTIONS.each do |setting|
255
268
  option = "nexpose_#{setting}"
256
- setting = ENV[option.upcase]
257
- sym = option.intern
258
- options[sym] ||= setting
259
-
260
- if options[sym].nil?
261
- error = "Option #{sym} wasn't supplied."
262
- log.log_error_message error
263
- $stderr.puts "ERROR: #{error}"
264
- exit -1
265
- end
269
+ env_setting = ENV[option.upcase]
270
+ options[setting] ||= env_setting
266
271
  end
267
272
 
268
273
  options