Skip to content

Commit

Permalink
Land #254, feature allow certificate validation
Browse files Browse the repository at this point in the history
  • Loading branch information
jmartin-tech committed Feb 14, 2017
2 parents 843318f + fdaa245 commit 959eefa
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 12 deletions.
6 changes: 5 additions & 1 deletion lib/nexpose/ajax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ def https(nsc, timeout = nil)
http = Net::HTTP.new(nsc.host, nsc.port)
http.read_timeout = timeout if timeout
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
if nsc.trust_store.nil?
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
else
http.cert_store = nsc.trust_store
end
http
end

Expand Down
17 changes: 12 additions & 5 deletions lib/nexpose/api_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ class APIRequest
attr_reader :raw_response
attr_reader :raw_response_data

def initialize(req, url, api_version = '1.1')
attr_reader :trust_store

def initialize(req, url, api_version = '1.1', trust_store = nil)
@url = url
@req = req
@api_version = api_version
@url = @url.sub('API_VERSION', @api_version)
@trust_store = trust_store
prepare_http_client
end

Expand All @@ -34,7 +37,11 @@ def prepare_http_client
# a confirmation when the nexpose host is not localhost. In a perfect world, we would present
# the server signature before accepting it, but this requires either a direct callback inside
# of this module back to whatever UI, or opens a race condition between accept and attempt.
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
if @trust_store.nil?
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
else
@http.cert_store = @trust_store
end
@headers = {'Content-Type' => 'text/xml'}
@success = false
end
Expand Down Expand Up @@ -93,7 +100,7 @@ def execute(options = {})
# drops our HTTP connection before processing. We try 5 times to establish a
# connection in these situations. The actual exception occurs in the Ruby
# http library, which is why we use such generic error classes.
rescue OpenSSL::SSL::SSLError
rescue OpenSSL::SSL::SSLError => e
if @conn_tries < 5
@conn_tries += 1
retry
Expand Down Expand Up @@ -133,8 +140,8 @@ def attributes(*args)
@res.root.attributes(*args)
end

def self.execute(url, req, api_version='1.1', options = {})
obj = self.new(req.to_s, url, api_version)
def self.execute(url, req, api_version = '1.1', options = {}, trust_store = nil)
obj = self.new(req.to_s, url, api_version, trust_store)
obj.execute(options)
raise APIError.new(obj, "Action failed: #{obj.error}") unless obj.success
obj
Expand Down
50 changes: 45 additions & 5 deletions lib/nexpose/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ module Nexpose
# # Create a new Nexpose::Connection from a URI or "URI" String
# nsc = Connection.from_uri('https://10.1.40.10:3780', 'nxadmin', 'password')
#
# # Create a new Nexpose::Connection with a specific port
# nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 443)
#
# # Create a new Nexpose::Connection with a silo identifier
# nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 3780, 'default')
#
# # Create a new Nexpose::Connection with a two-factor authentication (2FA) token
# nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 3780, nil, '123456')
#
# # Create a new Nexpose::Connection with an excplicitly trusted web certificate
# trusted_cert = ::File.read('cert.pem')
# nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 3780, nil, nil, trusted_cert)
#
# # Login to NSC and Establish a Session ID
# nsc.login
#
Expand Down Expand Up @@ -44,20 +57,34 @@ class Connection
# The last XML response received by this object, useful for debugging.
attr_reader :response_xml

# The trust store to validate connections against if any
attr_reader :trust_store

# A constructor to load a Connection object from a URI
def self.from_uri(uri, user, pass, silo_id = nil, token = nil)
def self.from_uri(uri, user, pass, silo_id = nil, token = nil, trust_cert = nil)
uri = URI.parse(uri)
new(uri.host, user, pass, uri.port, silo_id, token)
new(uri.host, user, pass, uri.port, silo_id, token, trust_cert)
end

# A constructor for Connection
def initialize(ip, user, pass, port = 3780, silo_id = nil, token = nil)
#
# @param [String] ip The IP address or hostname/FQDN of the Nexpose console.
# @param [String] user The username for Nexpose sessions.
# @param [String] pass The password for Nexpose sessions.
# @param [Fixnum] port The port number of the Nexpose console.
# @param [String] silo_id The silo identifier for Nexpose sessions.
# @param [String] token The two-factor authentication (2FA) token for Nexpose sessions.
# @param [String] trust_cert The PEM-formatted web certificate of the Nexpose console. Used for SSL validation.
def initialize(ip, user, pass, port = 3780, silo_id = nil, token = nil, trust_cert = nil)
@host = ip
@port = port
@username = user
@password = pass
@token = token
@silo_id = silo_id
unless trust_cert.nil?
@trust_store = create_trust_store(trust_cert)
end
@session_id = nil
@url = "https://#{@host}:#{@port}/api/API_VERSION/xml"
end
Expand Down Expand Up @@ -88,7 +115,7 @@ def logout
def execute(xml, version = '1.1', options = {})
@request_xml = xml.to_s
@api_version = version
response = APIRequest.execute(@url, @request_xml, @api_version, options)
response = APIRequest.execute(@url, @request_xml, @api_version, options, @trust_store)
@response_xml = response.raw_response_data
response
end
Expand All @@ -104,7 +131,11 @@ def download(url, file_name = nil)
uri = URI.parse(url)
http = Net::HTTP.new(@host, @port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
if @trust_store.nil?
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
else
http.cert_store = @trust_store
end
headers = {'Cookie' => "nexposeCCSessionID=#{@session_id}"}
resp = http.get(uri.to_s, headers)

Expand All @@ -114,5 +145,14 @@ def download(url, file_name = nil)
resp.body
end
end

def create_trust_store(trust_cert)
store = OpenSSL::X509::Store.new
store.trust
store.add_cert(OpenSSL::X509::Certificate.new(trust_cert))
store
end

private :create_trust_store
end
end
2 changes: 1 addition & 1 deletion test/test_site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def session_id

# Monkey patch API behavior to give static responses.
class Nexpose::APIRequest
def self.execute(url, req, api_version='1.1')
def self.execute(url, _req, _api_version = '1.1', _trust_store)
url
end
end
Expand Down

0 comments on commit 959eefa

Please sign in to comment.