nexpose_servicenow 0.6.2 → 0.7.1

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