Skip to content

Commit 697910f

Browse files
kpumukJens-G
authored andcommitted
Fixed en error preventing Thrift::NonblockingServer from working with SSL
1 parent d24b8a0 commit 697910f

File tree

5 files changed

+125
-2
lines changed

5 files changed

+125
-2
lines changed

lib/rb/lib/thrift/transport/server_socket.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def closed?
6060
end
6161

6262
def to_io
63-
@handle || raise(IOError, 'closed stream')
63+
@handle&.to_io || raise(IOError, 'closed stream')
6464
end
6565

6666
def to_s

lib/rb/lib/thrift/transport/socket.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ def close
134134
@handle = nil
135135
end
136136

137-
alias to_io handle
137+
def to_io
138+
@handle&.to_io || raise(IOError, 'closed stream')
139+
end
138140

139141
def to_s
140142
"socket(#{@host}:#{@port})"

lib/rb/spec/nonblocking_server_spec.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#
1919

2020
require 'spec_helper'
21+
require 'timeout'
2122

2223
describe 'NonblockingServer' do
2324

@@ -260,4 +261,102 @@ def setup_client_thread(result)
260261
expect(@server_thread.join(2)).not_to be_nil
261262
end
262263
end
264+
265+
describe "#{Thrift::NonblockingServer} with TLS transport" do
266+
before(:each) do
267+
@port = available_port
268+
handler = Handler.new
269+
processor = SpecNamespace::NonblockingService::Processor.new(handler)
270+
@transport = Thrift::SSLServerSocket.new('localhost', @port, create_server_ssl_context)
271+
transport_factory = Thrift::FramedTransportFactory.new
272+
logger = Logger.new(STDERR)
273+
logger.level = Logger::WARN
274+
@server = Thrift::NonblockingServer.new(processor, @transport, transport_factory, nil, 5, logger)
275+
handler.server = @server
276+
277+
@server_thread = Thread.new(Thread.current) do |master_thread|
278+
begin
279+
@server.serve
280+
rescue => e
281+
master_thread.raise e
282+
end
283+
end
284+
285+
@clients = []
286+
wait_until_listening
287+
end
288+
289+
after(:each) do
290+
@clients.each(&:close)
291+
@server.shutdown if @server
292+
@server_thread.join(2) if @server_thread
293+
@transport.close if @transport
294+
end
295+
296+
it "should handle requests over TLS" do
297+
expect(@server_thread).to be_alive
298+
299+
client = setup_tls_client
300+
expect(client.greeting(true)).to eq(SpecNamespace::Hello.new)
301+
302+
@server.shutdown
303+
expect(@server_thread.join(2)).to be_an_instance_of(Thread)
304+
end
305+
306+
def setup_tls_client
307+
transport = Thrift::FramedTransport.new(
308+
Thrift::SSLSocket.new('localhost', @port, nil, create_client_ssl_context)
309+
)
310+
protocol = Thrift::BinaryProtocol.new(transport)
311+
client = SpecNamespace::NonblockingService::Client.new(protocol)
312+
transport.open
313+
@clients << transport
314+
client
315+
end
316+
317+
def wait_until_listening
318+
Timeout.timeout(2) do
319+
until @transport.handle
320+
raise "Server thread exited unexpectedly" unless @server_thread.alive?
321+
sleep 0.01
322+
end
323+
end
324+
end
325+
326+
def available_port
327+
TCPServer.open('localhost', 0) { |server| server.addr[1] }
328+
end
329+
330+
def ssl_keys_dir
331+
File.expand_path('../../../test/keys', __dir__)
332+
end
333+
334+
def create_server_ssl_context
335+
OpenSSL::SSL::SSLContext.new.tap do |ctx|
336+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
337+
if ctx.respond_to?(:min_version=) && OpenSSL::SSL.const_defined?(:TLS1_2_VERSION)
338+
ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
339+
end
340+
ctx.ca_file = File.join(ssl_keys_dir, 'CA.pem')
341+
ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(ssl_keys_dir, 'server.crt')))
342+
ctx.cert_store = OpenSSL::X509::Store.new
343+
ctx.cert_store.add_file(File.join(ssl_keys_dir, 'client.pem'))
344+
ctx.key = OpenSSL::PKey::RSA.new(File.read(File.join(ssl_keys_dir, 'server.key')))
345+
end
346+
end
347+
348+
def create_client_ssl_context
349+
OpenSSL::SSL::SSLContext.new.tap do |ctx|
350+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
351+
if ctx.respond_to?(:min_version=) && OpenSSL::SSL.const_defined?(:TLS1_2_VERSION)
352+
ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
353+
end
354+
ctx.ca_file = File.join(ssl_keys_dir, 'CA.pem')
355+
ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(ssl_keys_dir, 'client.crt')))
356+
ctx.cert_store = OpenSSL::X509::Store.new
357+
ctx.cert_store.add_file(File.join(ssl_keys_dir, 'server.pem'))
358+
ctx.key = OpenSSL::PKey::RSA.new(File.read(File.join(ssl_keys_dir, 'client.key')))
359+
end
360+
end
361+
end
263362
end

lib/rb/spec/ssl_server_socket_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@
2727
@socket = Thrift::SSLServerSocket.new(1234)
2828
end
2929

30+
it "should delegate to_io to the underlying SSL server handle" do
31+
tcp_server = double("TCPServer")
32+
ssl_server = double("SSLServer")
33+
34+
allow(TCPServer).to receive(:new).with(nil, 1234).and_return(tcp_server)
35+
allow(OpenSSL::SSL::SSLServer).to receive(:new).with(tcp_server, nil).and_return(ssl_server)
36+
allow(ssl_server).to receive(:to_io).and_return(tcp_server)
37+
38+
@socket.listen
39+
expect(@socket.to_io).to eq(tcp_server)
40+
end
41+
3042
it "should provide a reasonable to_s" do
3143
expect(@socket.to_s).to eq("ssl(socket(:1234))")
3244
end

lib/rb/spec/ssl_socket_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
allow(@handle).to receive(:connect_nonblock)
3636
allow(@handle).to receive(:close)
3737
allow(@handle).to receive(:post_connection_check)
38+
allow(@handle).to receive(:to_io).and_return(@simple_socket_handle)
3839

3940
allow(::Socket).to receive(:new).and_return(@simple_socket_handle)
4041
allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(@handle)
@@ -71,6 +72,15 @@
7172
expect(Thrift::SSLSocket.new('localhost', 8080, 5, @context).ssl_context).to eq(@context)
7273
end
7374

75+
it "should delegate to_io to the underlying SSL socket handle" do
76+
@socket.open
77+
expect(@socket.to_io).to eq(@simple_socket_handle)
78+
end
79+
80+
it "should raise IOError when to_io is called on a closed stream" do
81+
expect { @socket.to_io }.to raise_error(IOError, 'closed stream')
82+
end
83+
7484
it "should provide a reasonable to_s" do
7585
expect(Thrift::SSLSocket.new('myhost', 8090).to_s).to eq("ssl(socket(myhost:8090))")
7686
end

0 commit comments

Comments
 (0)