nexpose 0.8.4 → 0.8.5

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