class R7Insight::Host::CONNECTION

Class for connecting to the Rapid7 Insight Platform and handling the connection

Constants

API_SSL_PORT
CONNECTION_EXCEPTIONS
DATA_ENDPOINT
DATA_PORT_SECURE
DATA_PORT_UNSECURE
SHUTDOWN_COMMAND
SHUTDOWN_MAX_WAIT
SHUTDOWN_WAIT_STEP

Attributes

conn[RW]
custom_host[RW]
datahub_enabled[RW]
datahub_endpoint[RW]
datahub_ip[RW]
datahub_port[RW]
debug[RW]
host_id[RW]
host_name[RW]
host_name_enabled[RW]
local[RW]
queue[RW]
region[RW]
ssl[RW]
started[RW]
thread[RW]
token[RW]
udp_port[RW]
use_data_endpoint[RW]

Public Class Methods

new(token, region, local, debug, ssl, datahub_endpoint, host_id, custom_host, udp_port, use_data_endpoint) click to toggle source
# File lib/r7_insight/host/connection.rb, line 36
     def initialize(token, region, local, debug, ssl, datahub_endpoint,
                    host_id, custom_host, udp_port, use_data_endpoint)
       if local
         device = if local.class <= TrueClass
                    if defined?(Rails)
                      Rails.root.join('log', "#{Rails.env}.log")
                    else
                      STDOUT
                    end
                  else
                    local
                  end
         @logger_console = Logger.new(device)
       end

       @region = region
       @local = local.nil? || local == false ? false : true # Replace "!!"
       @debug = debug
       @ssl = ssl
       @udp_port = udp_port
       @use_data_endpoint = use_data_endpoint

       @datahub_endpoint = datahub_endpoint
       if !@datahub_endpoint[0].empty?
         @datahub_enabled = true
         @datahub_ip = (@datahub_endpoint[0]).to_s
         @datahub_port = @datahub_endpoint[1]
       else
         @datahub_enabled = false
       end

       if @datahub_enabled && @ssl
         puts("\n\nYou Cannot have DataHub and SSL enabled at the same time.
Please set SSL value to false in your environment.rb file or used Token-Based
logging by leaving the Datahub IP address blank. Exiting application. \n\n")
         exit
       end

       # Check if region was specified
       puts("\n\nYou need to specify a region, such as 'eu'") if region.empty?

       # Check if DataHub is enabled
       # If datahub is not enabled, set the token to the token's parameter
       # If DH is enabled, make the token empty.
       if !datahub_enabled
         @token = token
       else
         @token = ''

         # !NOTE THIS @datahub_port conditional MAY NEED TO BE CHANGED IF SSL
         # CAN'T WORK WITH DH
         @datahub_port = @datahub_port.empty? ? API_SSL_PORT : datahub_port
         @datahub_ip = datahub_ip
       end

       @host_name_enabled = custom_host[0]
       @host_name = custom_host[1]

       if !host_id.empty?
         @host_id = host_id
         @host_id = "host_id=#{host_id}"
       else
         @host_id = ''
       end

       #  If no host name is given but required, assign the machine name
       if @host_name_enabled
         @host_name = Socket.gethostname if host_name.empty?

         @host_name = "host_name=#{@host_name}"
       end

       @queue = Queue.new
       @started = false
       @thread = nil

       init_debug if @debug
       at_exit { shutdown! }
     end

Public Instance Methods

check_async_thread() click to toggle source
# File lib/r7_insight/host/connection.rb, line 151
def check_async_thread
  @thread = Thread.new { run } unless @thread&.alive?
end
close() click to toggle source
# File lib/r7_insight/host/connection.rb, line 155
def close
  dbg 'R7Insight: Closing asynchronous socket writer'
  @started = false
end
close_connection() click to toggle source
# File lib/r7_insight/host/connection.rb, line 240
     def close_connection
       begin
         if @conn.respond_to?(:sysclose)
           @conn.sysclose
         elsif @conn.respond_to?(:close)
           @conn.close
         end
       rescue StandardError
         dbg "R7Insight: couldn't close connection, close with exception -
#{$ERROR_INFO}"
       ensure
         @conn = nil
       end
     end
dbg(message) click to toggle source
# File lib/r7_insight/host/connection.rb, line 122
def dbg(message)
  @debug_logger.add(Logger::Severity::DEBUG, message) if @debug
end
init_debug() click to toggle source
# File lib/r7_insight/host/connection.rb, line 116
def init_debug
  file_path = 'r7insightGem.log'
  file_path = 'log/r7insightGem.log' if File.exist?('log/')
  @debug_logger = Logger.new(file_path)
end
open_connection() click to toggle source
# File lib/r7_insight/host/connection.rb, line 160
def open_connection
  dbg 'R7Insight: Reopening connection to R7Insight API server'

  if @use_data_endpoint
    host = @region + DATA_ENDPOINT

    port = @ssl ? DATA_PORT_SECURE : DATA_PORT_UNSECURE
  elsif @udp_port
    host = @region + DATA_ENDPOINT
    port = @udp_port
  elsif @datahub_enabled
    host = @datahub_ip
    port = @datahub_port
  else
    host = @region + DATA_ENDPOINT
    port = @ssl ? DATA_PORT_SECURE : DATA_PORT_UNSECURE
  end

  if @udp_port
    @conn = UDPSocket.new
    @conn.connect(host, port)
  else
    socket = TCPSocket.new(host, port)

    if @ssl
      cert_store = OpenSSL::X509::Store.new
      cert_store.set_default_paths

      ssl_context = OpenSSL::SSL::SSLContext.new
      ssl_context.cert_store = cert_store

      if OpenSSL::SSL::SSLContext.method_defined? :min_version=
        ssl_context.min_version = OpenSSL::SSL::TLS1_1_VERSION
      end
      if OpenSSL::SSL::SSLContext.method_defined? :max_version=
        # For older versions of openssl (prior to 1.1.1) missing support for TLSv1.3
        ssl_context.max_version = if defined?(OpenSSL::SSL::TLS1_3_VERSION)
                                    OpenSSL::SSL::TLS1_3_VERSION
                                  else
                                    OpenSSL::SSL::TLS1_2_VERSION
                                  end
      end
      ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
      ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
      ssl_socket.hostname = host if ssl_socket.respond_to?(:hostname=)
      ssl_socket.sync_close = true
      Timeout.timeout(10) do
        ssl_socket.connect
      end
      @conn = ssl_socket
    else
      @conn = socket
    end
  end

  dbg 'R7Insight: Connection established'
end
reopen_connection() click to toggle source
# File lib/r7_insight/host/connection.rb, line 218
      def reopen_connection
        close_connection
        root_delay = 0.1
        loop do
          begin
            open_connection
            break
          rescue *CONNECTION_EXCEPTIONS
            dbg "R7Insight: Unable to connect to R7Insight due to timeout
(#{$ERROR_INFO})"
          rescue StandardError
            dbg "R7Insight: Got exception in reopenConnection - #{$ERROR_INFO}"
            raise
          end
          root_delay *= 2
          root_delay = 10 if root_delay >= 10
          wait_for = (root_delay + rand(root_delay)).to_i
          dbg "R7Insight: Waiting for #{wait_for}ms"
          sleep(wait_for)
        end
      end
run() click to toggle source
# File lib/r7_insight/host/connection.rb, line 255
      def run
        reopen_connection

        loop do
          data = @queue.pop
          break if data == SHUTDOWN_COMMAND

          loop do
            begin
              @conn.write(data)
            rescue *CONNECTION_EXCEPTIONS
              dbg "R7Insight: Connection timeout(#{$ERROR_INFO}), try to reopen
connection"
              reopen_connection
              next
            rescue StandardError
              dbg "R7Insight: Got exception in run loop - #{$ERROR_INFO}"
              raise
            end
            break
          end
        end

        dbg 'R7Insight: Closing Asynchronous socket writer'

        close_connection
      end
start_async_thread() click to toggle source
# File lib/r7_insight/host/connection.rb, line 145
def start_async_thread
  @thread = Thread.new { run }
  dbg 'R7Insight: Asynchronous socket writer started'
  @started = true
end
write(message) click to toggle source
# File lib/r7_insight/host/connection.rb, line 126
def write(message)
  message = "#{message} #{host_id}" unless host_id.empty?
  message = "#{message} #{host_name}" if host_name_enabled

  @logger_console.add(Logger::Severity::UNKNOWN, message) if @local

  @queue << if message.scan(/\n/).empty?
              "#{@token} #{message} \n"
            else
              "#{message.gsub(/^/, "#{@token} [#{random_message_id}]")}\n"
            end

  if @started
    check_async_thread
  else
    start_async_thread
  end
end

Private Instance Methods

random_message_id() click to toggle source
# File lib/r7_insight/host/connection.rb, line 285
def random_message_id
  @random_message_id_sample_space ||= ('0'..'9').to_a.concat(('A'..'Z')
                                                                 .to_a)
  (0..5).map { @random_message_id_sample_space.sample }.join
end
shutdown!() click to toggle source

at_exit handler. Attempts to clear the queue and terminate the async worker cleanly before process ends.

# File lib/r7_insight/host/connection.rb, line 294
def shutdown!
  return unless @started

  dbg "R7Insight: commencing shutdown, queue has #{queue.size} entries to clear"
  queue << SHUTDOWN_COMMAND
  SHUTDOWN_MAX_WAIT.div(SHUTDOWN_WAIT_STEP).times do
    break if queue.empty?

    sleep SHUTDOWN_WAIT_STEP
  end
  dbg "R7Insight: shutdown complete, queue is #{queue.empty? ? '' : 'not '}
       empty with #{queue.size} entries"
end