nexpose 0.8.4 → 0.8.5

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: dba4421357b0475f091f72cd2fd61ced926fb6ad
4
- data.tar.gz: e7d37e55dcf159045a3249932c9dfa426e8f0705
3
+ metadata.gz: a14fda65d152d074a2adba56c7180417b04c9569
4
+ data.tar.gz: 6e349f9013e13f8b2972ada209543dee513d612a
5
5
  SHA512:
6
- metadata.gz: abdef9b08034c3fce94402180e33af5dc7c01a05c4a48bb8a8a877e21f8599a19abfc0710fea5b22a5562ae24884198ebaaa4b19849e6b7e3cdf2713bc4adf14
7
- data.tar.gz: 12f470900347b5cac2927cf3138f561f110fdab1246b232dd12dd1167035fd872db9dc9fcab2d87282c9ac8e71345dda73b2f8d76c0ac3e1b618eecadf081257
6
+ metadata.gz: aaf259cadc6172ca32f9a07d6a15240dab9c0baf0f565ffe070c16472c7d3c0768ae86850cc4850eaf9221bf292d00f9c5619566fa62f447e83289e930ef0b04
7
+ data.tar.gz: a7b6825ce9cd0898acdd6784e106c7a3823c88ec1bf227ccd13de18d0355132aea1532d14cf3b458af9f412bb7561a9f900d02715a745edaab7b9f9f6e9724f0
@@ -103,3 +103,11 @@ module Nexpose
103
103
  puts 'response: ' + object.response_xml.to_s
104
104
  end
105
105
  end
106
+
107
+ # Monkey patch from ActiveSupport which rex 2.0.3 incorrectly replies upon.
108
+ # This enables the multipart MIME handling in Connection#import_scan
109
+ class String
110
+ def blank?
111
+ self !~ /\S/
112
+ end
113
+ end
@@ -35,6 +35,11 @@ module Nexpose
35
35
  HTTPS = 'HTTPS'
36
36
  end
37
37
 
38
+ module Type
39
+ V_SPHERE = 'VSPHERE'
40
+ AWS = 'AWS'
41
+ end
42
+
38
43
  # A unique identifier for this connection.
39
44
  attr_accessor :id
40
45
 
@@ -42,6 +47,7 @@ module Nexpose
42
47
  attr_accessor :name
43
48
 
44
49
  # The IP address or fully qualified domain name of the server.
50
+ # For AWS connections, this is the address of the regional server.
45
51
  attr_accessor :address
46
52
 
47
53
  # A user name that can be used to log into the server.
@@ -56,6 +62,12 @@ module Nexpose
56
62
  # The port used for connecting to the server. A valid port from 1 to 65535.
57
63
  attr_accessor :port
58
64
 
65
+ # The type of discovery connection.
66
+ attr_accessor :type
67
+
68
+ # Whether the console and scan engine are inside the AWS network.
69
+ attr_accessor :inside_network
70
+
59
71
  # Whether or not the connection is active.
60
72
  # Discovery is only possible when the connection is active.
61
73
  attr_accessor :status
@@ -73,6 +85,7 @@ module Nexpose
73
85
  @id = -1
74
86
  @port = 443
75
87
  @protocol = Protocol::HTTPS
88
+ @type = Type::V_SPHERE
76
89
  end
77
90
 
78
91
  # Save this discovery connection to a Nexpose console.
@@ -130,13 +143,35 @@ module Nexpose
130
143
  end
131
144
 
132
145
  def as_xml
146
+ # TODO
147
+ # <DiscoveryConfig id="-1" name="aws" port="443" protocol="HTTPS" username="asdf" type="AWS" usePrivateIPAddress="0" engineid="2" region="ec2.us-west-1.amazonaws.com" password="asdf"> </DiscoveryConfig>
148
+ # <DiscoveryConfig id="-1" name="external AWS" port="443" protocol="HTTPS" username="acess-key-id" type="AWS" usePrivateIPAddress="0" engineid="2" region="ec2.sa-east-1.amazonaws.com" password="secret Access Key">
149
+ # <DiscoveryConfig id="-1" name="internal-aws" port="443" protocol="HTTPS" username=" " type="AWS" usePrivateIPAddress="1" engineid="2" region="ec2.ap-northeast-1.amazonaws.com" password="Not required">
150
+ # <DiscoveryConfig id="-1" name="V the Final Battle" fqdn="vcenter001.osdc.lax.rapid7.com" port="443" protocol="HTTPS" username="TOR\lmnexpose" type="VSPHERE" usePrivateIPAddress="0" engineid="2" region="ec2.us-east-1.amazonaws.com" password="22yQCA0P8nSvfrE">
151
+ # <DiscoveryConfig id="-1" name="duplicate vSphere" fqdn="vcenter001.osdc.lax.rapid7.com" port="443" protocol="HTTPS" username="blah" type="VSPHERE" usePrivateIPAddress="0" engineid="2" region="ec2.us-east-1.amazonaws.com" password="blah">
152
+ # <DiscoveryConfig id="10" name ="vCenter" fqdn ="vcenter001.osdc.lax.rapid7.com" port ="443" protocol ="HTTPS" username ="TOR\lmnexpose" type="VSPHERE" usePrivateIPAddress="0"> </DiscoveryConfig>
133
153
  xml = REXML::Element.new('DiscoveryConnection')
134
- xml.add_attributes({ 'name' => @name,
135
- 'address' => @address,
154
+ xml.add_attributes({ 'id' => @id,
155
+ 'name' => @name,
136
156
  'port' => @port,
137
- 'protocol' => @protocol,
138
- 'user-name' => @user,
139
- 'password' => @password })
157
+ 'protocol' => @protocol })
158
+ case @type
159
+ when Type::AWS
160
+ xml.add_attributes({ 'region' => @address })
161
+ if @inside_network
162
+ xml.add_attributes({ 'usePrivateIPAddress' => 1,
163
+ 'username' => ' ',
164
+ 'password' => 'Not required' })
165
+ else
166
+ xml.add_attributes({ 'usePrivateIPAddress' => 0,
167
+ 'username' => @user,
168
+ 'password' => @password })
169
+ end
170
+ when Type::V_SPHERE
171
+ xml.add_attributes({ 'fqdn' => @address,
172
+ 'username' => @user,
173
+ 'password' => @password })
174
+ end
140
175
  xml
141
176
  end
142
177
 
@@ -145,6 +180,9 @@ module Nexpose
145
180
  end
146
181
 
147
182
  def self.parse(xml)
183
+ # <DiscoveryConfig id="10" name ="vCenter" fqdn ="vcenter001.osdc.lax.rapid7.com" port ="443" protocol ="HTTPS" username ="TOR\lmnexpose" type="VSPHERE" usePrivateIPAddress="0"> </DiscoveryConfig>
184
+ # <DiscoveryConfig id="34" name ="external AWS" region ="ec2.sa-east-1.amazonaws.com" port ="443" protocol ="HTTPS" username ="acess-key-id" type="AWS" usePrivateIPAddress="0">
185
+ # <DiscoveryConfig id="32" name ="my-aws" region ="ec2.ap-northeast-1.amazonaws.com" port ="443" protocol ="HTTPS" username =" " type="AWS" usePrivateIPAddress="1">
148
186
  conn = new(xml.attributes['name'],
149
187
  xml.attributes['address'],
150
188
  xml.attributes['user-name'])
@@ -256,51 +256,28 @@ module Nexpose
256
256
  # @return [String] An empty string on success.
257
257
  #
258
258
  def import_scan(site_id, zip_file)
259
+ data = Rex::MIME::Message.new
260
+ data.add_part(site_id.to_s, nil, nil, 'form-data; name="siteid"')
261
+ data.add_part(self.session_id, nil, nil, 'form-data; name="nexposeCCSessionID"')
262
+ scan = File.new(zip_file, 'rb')
263
+ data.add_part(scan.read, 'application/zip', 'binary',
264
+ "form-data; name=\"scan\"; filename=\"#{zip_file}\"")
259
265
 
260
- # ## Ideally, this code should not depend upon rest-client, but should be
261
- # # able to use the Rex library to generate the MIME message. I haven't
262
- # # been able to figure out how, though. Leaving it here, commented out,
263
- # # pending discovery of what to do.
264
-
265
- # data = Rex::MIME::Message.new
266
- # data.add_part(site_id.to_s, nil, nil, 'form-data; name="siteid"')
267
- # data.add_part(self.session_id, nil, nil, 'form-data; name="nexposeCCSessionID"')
268
-
269
- # scan = File.new(zip_file, 'rb')
270
- # data.add_part(scan.read, 'application/zip', nil,
271
- # "form-data; name=\"scan\"; filename=\"#{zip_file}\"")
272
-
273
- # post = Net::HTTP::Post.new('/data/scan/import')
274
- # ## rex 2.0.3 has a bug that requires this monkey-patch for Message#to_s
275
- # # class String
276
- # # def blank?
277
- # # self !~ /\S/
278
- # # end
279
- # # end
280
- # post.body = data.to_s
281
- # post.set_content_type("multipart/form-data; boundary=#{data.bound}")
282
- # AJAX._headers(nsc, post)
283
-
284
- # http = AJAX._https(nsc)
285
- # http.request(post)
266
+ post = Net::HTTP::Post.new('/data/scan/import')
267
+ post.body = data.to_s
268
+ post.set_content_type('multipart/form-data', boundary: data.bound)
286
269
 
287
- scan = File.new(zip_file, 'rb')
288
- url = "https://#{self.host}:#{self.port}/data/scan/import"
289
- payload = { :siteid => site_id,
290
- :scan => scan,
291
- 'nexposeCCSessionID' => self.session_id }
292
- request = RestClient::Request.new(:method => :post,
293
- :url => url,
294
- :verify_ssl => OpenSSL::SSL::VERIFY_NONE,
295
- :payload => payload,
296
- :cookies => { 'nexposeCCSessionID' => self.session_id })
297
-
298
- begin
299
- request.execute
300
- rescue RestClient::Forbidden => fourOhThree
301
- raise Nexpose::PermissionError.new(fourOhThree)
302
- rescue RestClient::InternalServerError => e
303
- raise Nexpose::APIError.new(request, e)
270
+ # Avoiding AJAX#request, because the data can cause binary dump on error.
271
+ http = AJAX._https(self)
272
+ AJAX._headers(self, post)
273
+ response = http.request(post)
274
+ case response
275
+ when Net::HTTPOK
276
+ response.body
277
+ when Net::HTTPUnauthorized
278
+ raise Nexpose::PermissionError.new(response)
279
+ else
280
+ raise Nexpose::APIError.new(post, response.body)
304
281
  end
305
282
  end
306
283
 
@@ -174,6 +174,13 @@ module Nexpose
174
174
  @assets << HostName.new(hostname)
175
175
  end
176
176
 
177
+ # Remove an asset to this site by host name.
178
+ #
179
+ # @param [String] hostname FQDN or DNS-resolvable host name of an asset.
180
+ def remove_host(hostname)
181
+ @assets = assets.reject { |asset| asset == HostName.new(hostname) }
182
+ end
183
+
177
184
  # Adds an asset to this site by IP address.
178
185
  #
179
186
  # @param [String] ip IP address of an asset.
@@ -181,6 +188,13 @@ module Nexpose
181
188
  @assets << IPRange.new(ip)
182
189
  end
183
190
 
191
+ # Remove an asset to this site by IP address.
192
+ #
193
+ # @param [String] ip IP address of an asset.
194
+ def remove_ip(ip)
195
+ @assets = assets.reject { |asset| asset == IPRange.new(ip) }
196
+ end
197
+
184
198
  # Adds assets to this site by IP address range.
185
199
  #
186
200
  # @param [String] from Beginning IP address of a range.
@@ -189,6 +203,14 @@ module Nexpose
189
203
  @assets << IPRange.new(from, to)
190
204
  end
191
205
 
206
+ # Remove assets to this site by IP address range.
207
+ #
208
+ # @param [String] from Beginning IP address of a range.
209
+ # @param [String] to Ending IP address of a range.
210
+ def remove_ip_range(from, to)
211
+ @assets = assets.reject { |asset| asset == IPRange.new(from, to) }
212
+ end
213
+
192
214
  # Adds an asset to this site, resolving whether an IP or hostname is
193
215
  # provided.
194
216
  #
@@ -208,6 +230,25 @@ module Nexpose
208
230
  end
209
231
  end
210
232
 
233
+ # Remove an asset to this site, resolving whether an IP or hostname is
234
+ # provided.
235
+ #
236
+ # @param [String] asset Identifier of an asset, either IP or host name.
237
+ #
238
+ def remove_asset(asset)
239
+ begin
240
+ # If the asset registers as a valid IP, store as IP.
241
+ ip = IPAddr.new(asset)
242
+ remove_ip(asset)
243
+ rescue ArgumentError => e
244
+ if e.message == 'invalid address'
245
+ remove_host(asset)
246
+ else
247
+ raise "Unable to parse asset: '#{asset}'. #{e.message}"
248
+ end
249
+ end
250
+ end
251
+
211
252
  # Load an existing configuration from a Nexpose instance.
212
253
  #
213
254
  # @param [Connection] connection Connection to console where site exists.
@@ -253,8 +294,8 @@ module Nexpose
253
294
 
254
295
  # Have to retrieve and attach shared creds, or saving will fail.
255
296
  xml = _append_shared_creds_to_xml(connection, as_xml)
256
- response = AJAX.post(connection, '/ajax/save_site_config.txml', xml)
257
- saved = REXML::XPath.first(REXML::Document.new(response), 'SaveConfig')
297
+ response = AJAX.post(connection, '/data/site/config', xml)
298
+ saved = REXML::XPath.first(REXML::Document.new(response), 'ajaxResponse')
258
299
  raise APIError.new(response, 'Failed to save dynamic site.') if saved.nil? || saved.attributes['success'].to_i != 1
259
300
 
260
301
  save_dynamic_criteria(connection) unless new_site
@@ -297,8 +338,22 @@ module Nexpose
297
338
  # @return [Fixnum] Site ID.
298
339
  #
299
340
  def save_dynamic_criteria(nsc)
300
- params = to_dynamic_map
301
- response = AJAX.form_post(nsc, '/data/site/saveSite', params)
341
+ # Several parameters are passed through the URI
342
+ params = { 'configID' => @discovery_connection_id,
343
+ 'entityid' => @id > 0 ? @id : false,
344
+ 'mode' => @id > 0 ? 'edit' : false }
345
+ uri = AJAX.parameterize_uri('/data/site/saveSite', params)
346
+
347
+ # JSON body of POST request contains details.
348
+ details = { 'dynamic' => true,
349
+ 'name' => @name,
350
+ 'tag' => @description.nil? ? '' : @description,
351
+ 'riskFactor' => @risk_factor,
352
+ # 'vCenter' => @discovery_connection_id,
353
+ 'searchCriteria' => @criteria.nil? ? { 'operator' => 'AND' } : @criteria.to_map }
354
+ json = JSON.generate(details)
355
+
356
+ response = AJAX.post(nsc, uri, json, AJAX::CONTENT_TYPE::JSON)
302
357
  json = JSON.parse(response)
303
358
  if json['response'] =~ /success/
304
359
  if @id < 1
@@ -335,7 +390,9 @@ module Nexpose
335
390
  xml.attributes['name'] = @name
336
391
  xml.attributes['description'] = @description
337
392
  xml.attributes['riskfactor'] = @risk_factor
338
- xml.attributes['isDynamic'] == '1' if dynamic?
393
+ xml.attributes['isDynamic'] = '1' if dynamic?
394
+ # TODO This should be set to 'Amazon Web Services' for AWS.
395
+ xml.attributes['dynamicConfigType'] = 'vSphere' if dynamic?
339
396
 
340
397
  if @description && !@description.empty?
341
398
  elem = REXML::Element.new('Description')
@@ -394,19 +451,6 @@ module Nexpose
394
451
  as_xml.to_s
395
452
  end
396
453
 
397
- def to_dynamic_map
398
- details = { 'dynamic' => true,
399
- 'name' => @name,
400
- 'tag' => @description.nil? ? '' : @description,
401
- 'riskFactor' => @risk_factor,
402
- # 'vCenter' => @discovery_connection_id,
403
- 'searchCriteria' => @criteria.nil? ? { 'operator' => 'AND' } : @criteria.to_map }
404
- params = { 'configID' => @discovery_connection_id,
405
- 'entityid' => @id > 0 ? @id : false,
406
- 'mode' => @id > 0 ? 'edit' : false,
407
- 'entityDetails' => details }
408
- end
409
-
410
454
  # Parse a response from a Nexpose console into a valid Site object.
411
455
  #
412
456
  # @param [REXML::Document] rexml XML document to parse.
@@ -475,7 +519,7 @@ module Nexpose
475
519
  end
476
520
 
477
521
  def _append_shared_creds_to_xml(connection, xml)
478
- xml_w_creds = AJAX.get(connection, "/ajax/site_config.txml?siteid=#{@id}")
522
+ xml_w_creds = AJAX.get(connection, "/data/site/config?siteid=#{@id}")
479
523
  cred_xml = REXML::XPath.first(REXML::Document.new(xml_w_creds), 'Site/Credentials')
480
524
  unless cred_xml.nil?
481
525
  creds = REXML::XPath.first(xml, 'Credentials')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexpose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4
4
+ version: 0.8.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - HD Moore
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2014-10-01 00:00:00.000000000 Z
14
+ date: 2014-10-09 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rex