class Lumberjack::Beats::Server

Constants

SOCKET_TIMEOUT

Attributes

port[R]

Public Class Methods

new(options={}) click to toggle source

Create a new Lumberjack server.

  • options is a hash. Valid options are:

  • :port - the port to listen on

  • :address - the host/address to bind to

  • :ssl_certificate - the path to the ssl cert to use

  • :ssl_key - the path to the ssl key to use

  • :ssl_key_passphrase - the key passphrase (optional)

# File lib/lumberjack/beats/server.rb, line 25
def initialize(options={})
  @options = {
    :port => 0,
    :address => "0.0.0.0",
    :ssl => true,
    :ssl_certificate => nil,
    :ssl_key => nil,
    :ssl_key_passphrase => nil,
    :ssl_certificate_authorities => nil,
    :ssl_verify_mode => :none # By default we dont verify client
  }.merge(options)

  if @options[:ssl]
    if verify_client?(@options[:ssl_verify_mode]) && certificate_authorities.empty?
      raise "When `ssl_verify_mode` is set to `peer` OR `force_peer` you need to specify the `ssl_certificate_authorities`"
    end

    if !verify_client?(@options[:ssl_verify_mode]) && certificate_authorities.size > 0 
      raise "When `ssl_certificate_authorities` is configured you need to set `ssl_verify_mode` to either `peer` or `force_peer`"
    end

    if @options[:ssl_certificate].nil? || @options[:ssl_key].nil?
      raise "You must specify `ssl_certificate` AND `ssl_key`"
    end
  end

  @server = TCPServer.new(@options[:address], @options[:port])
  @close = Concurrent::AtomicBoolean.new
  @port = retrieve_current_port

  setup_ssl if ssl?
end

Public Instance Methods

accept(&block) click to toggle source
# File lib/lumberjack/beats/server.rb, line 91
def accept(&block)
  begin
    socket = @server.accept_nonblock
    # update the socket with a SSL layer
    socket = accept_ssl(socket) if ssl?

    if block_given?
      block.call(socket, self)
    else
      return Connection.new(socket, self)
    end
  rescue OpenSSL::SSL::SSLError, IOError, EOFError, Errno::EBADF
    socket.close rescue nil
    retry unless closed?
  rescue IO::WaitReadable, Errno::EAGAIN # Resource not ready yet, so lets try again
    begin
      IO.select([@server], nil, nil, SOCKET_TIMEOUT)
      retry unless closed?
    rescue IOError, Errno::EBADF => e # we currently closing
      raise e unless closed?
    end
  end
end
accept_ssl(tcp_socket) click to toggle source
# File lib/lumberjack/beats/server.rb, line 115
def accept_ssl(tcp_socket)
  ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl)
  ssl_socket.sync_close

  begin
    ssl_socket.accept_nonblock

    return ssl_socket
  rescue IO::WaitReadable # handshake
    IO.select([ssl_socket], nil, nil, SOCKET_TIMEOUT)
    retry unless closed?
  rescue IO::WaitWritable # handshake
    IO.select(nil, [ssl_socket], nil, SOCKET_TIMEOUT)
    retry unless closed?
  end
end
close() click to toggle source
# File lib/lumberjack/beats/server.rb, line 136
def close
  @close.make_true
  @server.close unless @server.closed?
end
closed?() click to toggle source
# File lib/lumberjack/beats/server.rb, line 132
def closed?
  @close.value
end
run(&block) click to toggle source

Server#run method, allow the library to manage all the connection threads, this handing is quite minimal and don't handler all the possible cases deconnection/connection.

To have a more granular control over the connection you should manage them yourself, see Server#accept method which return a Connection instance.

# File lib/lumberjack/beats/server.rb, line 65
def run(&block)
  while !closed?
    connection = accept

    # Some exception may occur in the accept loop
    # we will try again in the next iteration
    # unless the server is closing
    next unless connection

    Thread.new(connection) do |connection|
      begin
        connection.run(&block)
      rescue Lumberjack::Beats::Connection::ConnectionClosed
        # Connection will raise a wrapped exception upstream,
        # but if the threads are managed by the library we can simply ignore it.
        #
        # Note: This follow the previous behavior of the perfect silence.
      end
    end
  end
end
ssl?() click to toggle source
# File lib/lumberjack/beats/server.rb, line 87
def ssl?
  @options[:ssl]
end

Private Instance Methods

certificate_authorities() click to toggle source
# File lib/lumberjack/beats/server.rb, line 153
def certificate_authorities
  Array(@options[:ssl_certificate_authorities])
end
jruby?() click to toggle source
# File lib/lumberjack/beats/server.rb, line 176
def jruby?
  RUBY_PLATFORM == "java"
end
retrieve_current_port() click to toggle source
# File lib/lumberjack/beats/server.rb, line 147
def retrieve_current_port
  # Query the port in case the port number is '0'
  # TCPServer#addr == [ address_family, port, address, address ]
  @server.addr[1]
end
server_certificate() click to toggle source
# File lib/lumberjack/beats/server.rb, line 161
def server_certificate
  OpenSSL::X509::Certificate.new(File.read(@options[:ssl_certificate]))
end
server_private_key() click to toggle source
# File lib/lumberjack/beats/server.rb, line 157
def server_private_key
  OpenSSL::PKey::RSA.new(File.read(@options[:ssl_key]), @options[:ssl_key_passphrase])
end
setup_ssl() click to toggle source
# File lib/lumberjack/beats/server.rb, line 203
def setup_ssl
  @ssl = OpenSSL::SSL::SSLContext.new

  # @ssl.verify_callback = lambda do |preverify_ok, context|
  #   require "pry"
  #   binding.pry
  # end
  @ssl.cert_store = trust_store
  @ssl.verify_mode = verify_mode
  # @ssl.ca_file = certificate_authorities.first
  @ssl.cert = server_certificate
  @ssl.key = server_private_key
end
trust_store() click to toggle source
# File lib/lumberjack/beats/server.rb, line 180
def trust_store
  store = OpenSSL::X509::Store.new

  if certificate_authorities.size > 0
    certificate_authorities.each do |certificate_authority|
      if File.file?(certificate_authority)
        store.add_file(certificate_authority)
      else
        # `#add_path` is not implemented under jruby
        # so recursively try to load all the certificate from this directory
        # https://github.com/jruby/jruby-openssl/blob/master/src/main/java/org/jruby/ext/openssl/X509Store.java#L159
        if jruby?
          Dir.glob(File.join(certificate_authority, "**", "*")).each { |f| store.add_file(f) }
        else
          store.add_path(certificate_authority)
        end
      end
    end
  end

  store
end
verify_client?(mode) click to toggle source
# File lib/lumberjack/beats/server.rb, line 142
def verify_client?(mode)
  mode = mode.to_sym
  mode == :peer || mode == :force_peer
end
verify_mode() click to toggle source
# File lib/lumberjack/beats/server.rb, line 165
def verify_mode
  case @options[:ssl_verify_mode].to_sym
  when :none
    OpenSSL::SSL::VERIFY_NONE
  when :peer
    OpenSSL::SSL::VERIFY_PEER
  when :force_peer
    OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
  end
end