RUBY-316 initial SSL support

This commit is contained in:
Kyle Banker 2011-08-26 17:35:40 -04:00
parent 8db62d2cbf
commit f00c0dfcf0
6 changed files with 58 additions and 8 deletions

View File

@ -59,6 +59,7 @@ require 'mongo/util/core_ext'
require 'mongo/util/pool' require 'mongo/util/pool'
require 'mongo/util/pool_manager' require 'mongo/util/pool_manager'
require 'mongo/util/server_version' require 'mongo/util/server_version'
require 'mongo/util/ssl_socket'
require 'mongo/util/uri_parser' require 'mongo/util/uri_parser'
require 'mongo/collection' require 'mongo/collection'

View File

@ -36,7 +36,7 @@ module Mongo
RESPONSE_HEADER_SIZE = 20 RESPONSE_HEADER_SIZE = 20
attr_reader :logger, :size, :auths, :primary, :safe, :host_to_try, attr_reader :logger, :size, :auths, :primary, :safe, :host_to_try,
:pool_size, :connect_timeout, :primary_pool :pool_size, :connect_timeout, :primary_pool, :socket_class
# Counter for generating unique request ids. # Counter for generating unique request ids.
@@current_request_id = 0 @@current_request_id = 0
@ -73,6 +73,7 @@ module Mongo
# Disabled by default. # Disabled by default.
# @option opts [Float] :connect_timeout (nil) The number of seconds to wait before timing out a # @option opts [Float] :connect_timeout (nil) The number of seconds to wait before timing out a
# connection attempt. # connection attempt.
# @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
# #
# @example localhost, 27017 # @example localhost, 27017
# Connection.new # Connection.new
@ -636,6 +637,14 @@ module Mongo
# Default maximum BSON object size # Default maximum BSON object size
@max_bson_size = Mongo::DEFAULT_MAX_BSON_SIZE @max_bson_size = Mongo::DEFAULT_MAX_BSON_SIZE
# Determine whether to use SSL.
@ssl = opts.fetch(:ssl, false)
if @ssl
@socket_class = Mongo::SSLSocket
else
@socket_class = ::TCPSocket
end
# Authentication objects # Authentication objects
@auths = opts.fetch(:auths, []) @auths = opts.fetch(:auths, [])
@ -729,11 +738,11 @@ module Mongo
if @connect_timeout if @connect_timeout
Mongo::TimeoutHandler.timeout(@connect_timeout, OperationTimeout) do Mongo::TimeoutHandler.timeout(@connect_timeout, OperationTimeout) do
socket = TCPSocket.new(host, port) socket = @socket_class.new(host, port)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
end end
else else
socket = TCPSocket.new(host, port) socket = @socket_class.new(host, port)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
end end

View File

@ -31,10 +31,10 @@ module Mongo
socket = nil socket = nil
if self.connection.connect_timeout if self.connection.connect_timeout
Mongo::TimeoutHandler.timeout(self.connection.connect_timeout, OperationTimeout) do Mongo::TimeoutHandler.timeout(self.connection.connect_timeout, OperationTimeout) do
socket = TCPSocket.new(self.host, self.port) socket = self.connection.socket_class.new(self.host, self.port)
end end
else else
socket = TCPSocket.new(self.host, self.port) socket = self.connection.socket_class.new(self.host, self.port)
end end
if socket.nil? if socket.nil?

View File

@ -110,7 +110,7 @@ module Mongo
# therefore, it runs within a mutex. # therefore, it runs within a mutex.
def checkout_new_socket def checkout_new_socket
begin begin
socket = TCPSocket.new(@host, @port) socket = self.connection.socket_class.new(@host, @port)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
rescue => ex rescue => ex
socket.close if socket socket.close if socket

View File

@ -0,0 +1,38 @@
require 'openssl'
module Mongo
# A basic wrapper over Ruby's SSLSocket that initiates
# a TCP connection over SSL and then provides an basic interface
# mirroring Ruby's TCPSocket, vis., TCPSocket#send and TCPSocket#read.
class SSLSocket
def initialize(host, port)
@socket = ::TCPSocket.new(host, port)
@ssl = OpenSSL::SSL::SSLSocket.new(@socket)
@ssl.sync_close = true
@ssl.connect
end
def setsockopt(key, value, n)
@socket.setsockopt(key, value, n)
end
# Write to the SSL socket.
#
# @param buffer a buffer to send.
# @param flags socket flags. Because Ruby's SSL
def send(buffer, flags=0)
@ssl.syswrite(buffer)
end
def read(length, buffer)
@ssl.sysread(length, buffer)
end
def close
@ssl.close
end
end
end

View File

@ -7,8 +7,10 @@ class NodeTest < Test::Unit::TestCase
end end
should "refuse to connect to node without 'hosts' key" do should "refuse to connect to node without 'hosts' key" do
tcp = mock()
node = Node.new(@connection, ['localhost', 27017]) node = Node.new(@connection, ['localhost', 27017])
TCPSocket.stubs(:new).returns(new_mock_socket) tcp.stubs(:new).returns(new_mock_socket)
@connection.stubs(:socket_class).returns(tcp)
admin_db = new_mock_db admin_db = new_mock_db
admin_db.stubs(:command).returns({'ok' => 1, 'ismaster' => 1}) admin_db.stubs(:command).returns({'ok' => 1, 'ismaster' => 1})
@ -17,7 +19,7 @@ class NodeTest < Test::Unit::TestCase
@connection.expects(:log) @connection.expects(:log)
assert node.connect assert node.connect
assert node.set_config node.set_config
end end
should "load a node from an array" do should "load a node from an array" do