Testing TLS Socket Helper Implementation in Fluentd
This test suite validates the socket helper functionality in Fluentd, focusing on TLS/SSL socket creation and certificate handling. It ensures secure communication capabilities with various certificate configurations and validation scenarios.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
fluent/fluentd
test/plugin_helper/test_socket.rb
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