require_relative '../helper' require 'fluent/plugin_helper/socket' require 'fluent/plugin/base' require 'socket' require 'openssl' class SocketHelperTest < Test::Unit::TestCase CERT_DIR = File.expand_path(File.dirname(__FILE__) + '/data/cert/without_ca') CA_CERT_DIR = File.expand_path(File.dirname(__FILE__) + '/data/cert/with_ca') CERT_CHAINS_DIR = File.expand_path(File.dirname(__FILE__) + '/data/cert/cert_chains') def setup @port = unused_port(protocol: :tcp) end def teardown @port = nil end class SocketHelperTestPlugin < Fluent::Plugin::TestBase helpers :socket end class EchoTLSServer def initialize(port, host: '127.0.0.1', cert_path: nil, private_key_path: nil, ca_path: nil) server = TCPServer.open(host, port) ctx = OpenSSL::SSL::SSLContext.new ctx.cert = OpenSSL::X509::Certificate.new(File.open(cert_path)) if cert_path cert_store = OpenSSL::X509::Store.new cert_store.set_default_paths cert_store.add_file(ca_path) if ca_path ctx.cert_store = cert_store ctx.key = OpenSSL::PKey::RSA.new(File.open(private_key_path)) if private_key_path ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER ctx.verify_hostname = false @server = OpenSSL::SSL::SSLServer.new(server, ctx) @thread = nil @r, @w = IO.pipe end def start do_start if block_given? begin yield @thread.join(5) ensure stop end end end def stop unless @w.closed? @w.write('stop') end [@server, @w, @r].each do |s| next if s.closed? s.close end @thread.join(5) end private def do_start @thread = Thread.new(@server) do |s| socks, _, _ = IO.select([s.accept, @r], nil, nil) if socks.include?(@r) break end sock = socks.first buf = +'' loop do b = sock.read_nonblock(1024, nil, exception: false) if b == :wait_readable || b.nil? break end buf << b end sock.write(buf) sock.close end end end test 'with self-signed cert/key pair' do cert_path = File.join(CERT_DIR, 'cert.pem') private_key_path = File.join(CERT_DIR, 'cert-key.pem') EchoTLSServer.new(@port, cert_path: cert_path, private_key_path: private_key_path).start do client = SocketHelperTestPlugin.new.socket_create_tls('127.0.0.1', @port, verify_fqdn: false, cert_paths: [cert_path]) client.write('hello') assert_equal 'hello', client.readpartial(100) client.close end end test 'with cert/key signed by self-signed CA' do cert_path = File.join(CA_CERT_DIR, 'cert.pem') private_key_path = File.join(CA_CERT_DIR, 'cert-key.pem') ca_cert_path = File.join(CA_CERT_DIR, 'ca-cert.pem') EchoTLSServer.new(@port, cert_path: cert_path, private_key_path: private_key_path).start do client = SocketHelperTestPlugin.new.socket_create_tls('127.0.0.1', @port, verify_fqdn: false, cert_paths: [ca_cert_path]) client.write('hello') assert_equal 'hello', client.readpartial(100) client.close end end test 'with cert/key signed by self-signed CA in server and client cert chain' do cert_path = File.join(CERT_DIR, 'cert.pem') private_key_path = File.join(CERT_DIR, 'cert-key.pem') client_ca_cert_path = File.join(CERT_CHAINS_DIR, 'ca-cert.pem') client_cert_path = File.join(CERT_CHAINS_DIR, 'cert.pem') client_private_key_path = File.join(CERT_CHAINS_DIR, 'cert-key.pem') EchoTLSServer.new(@port, cert_path: cert_path, private_key_path: private_key_path, ca_path: client_ca_cert_path).start do client = SocketHelperTestPlugin.new.socket_create_tls('127.0.0.1', @port, verify_fqdn: false, cert_path: client_cert_path, private_key_path: client_private_key_path, cert_paths: [cert_path]) client.write('hello') assert_equal 'hello', client.readpartial(100) client.close end end test 'with empty cert file' do cert_path = File.expand_path(File.dirname(__FILE__) + '/data/cert/empty.pem') assert_raise Fluent::ConfigError do SocketHelperTestPlugin.new.socket_create_tls('127.0.0.1', @port, cert_path: cert_path) end end end