# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/utils/object_share' cs__scoped_require 'contrast/utils/os' cs__scoped_require 'contrast/components/interface' module Contrast module Api # This class acts as the interface for the communication between the Agent # and the Service (SpeedRacer) as well as functions as a way for the Agent # to manage the bundled Service. class Speedracer include Contrast::Components::Interface access_component :logging, :contrast_service, :app_context attr_reader :client_number, :host, :port, :socket, :connection, :messages_total @instance_count = 0 @instance_mutex = Mutex.new def self.next_client_number @instance_mutex.synchronize do begin @instance_count += 1 # Rollover things rescue StandardError @instance_count = 1 end end end def self.read_only_instance_count @instance_count.dup end def initialize connection @client_number = Contrast::Api::Speedracer.next_client_number @messages_total = 0 @pid = Process.pid.to_i @ppid = Process.ppid.to_i @connection = connection end # Allow for the start of a Service, but block so that we only attempt to # start the service one try at a time in the case multiple threads are # trying to send messages. def start_service zombie_check service_starter_thread.join(5) is_service_started = Contrast::Utils::OS.running? logger.error(nil, 'The bundled service could not be started. The agent will not function properly.') unless is_service_started is_service_started end def send_one msg case msg when Contrast::Api::Dtm::ServerActivity send_server_activity(msg) when Contrast::Api::Dtm::AgentStartup send_agent_startup(msg) when Contrast::Api::Dtm::ApplicationCreate send_application_create(msg) when Contrast::Api::Dtm::ApplicationUpdate send_application_update(msg) when Contrast::Api::Dtm::Activity send_application_activity(msg) when Contrast::Api::Dtm::HttpRequest send_prefilter(msg) when Contrast::Api::Dtm::HttpResponse send_postfilter(msg) when Contrast::Api::Dtm::Noop send_noop(msg) when Contrast::Api::Dtm::ObservedRoute send_observed_route(msg) end end private # check if there's a zombie service that exists, and wait on it if so. # currently, this only happens when trying to initialize speedracer def zombie_check return if windows? zombie_pid_list = Contrast::Utils::OS.zombie_pids zombie_pid_list.each do |pid| Process.wait(pid.to_i) end end def windows? @_windows = Contrast::Utils::OS.windows? if @_windows.nil? @_windows end def determine_startup_options return { out: :out, err: :err } if Contrast::Agent::FeatureState.instance.service_control[:logger_path] == 'STDOUT' { out: File::NULL, err: File::NULL } end # This is a separate method so we can overwrite it globally in specs def spawn_service options = determine_startup_options spawn 'contrast_service', options end def service_starter_thread Contrast::Agent::Thread.new do # Always check to see if it already started unless Contrast::Utils::OS.running? # Spawn the service process spawn_service # Block until service is running sleep(0.1) until Contrast::Utils::OS.running? end end end def send_server_activity server_activity msg = base_message msg.server_activity = server_activity send_message(msg) end def send_agent_startup agent_startup msg = base_message msg.agent_startup = agent_startup send_message(msg) end def send_application_create app_create msg = base_message msg.application_create = app_create send_message(msg) end def send_application_update app_update msg = base_message msg.application_update = app_update send_message(msg) end def send_application_activity activity msg = base_message msg.activity = activity send_message(msg) end def send_prefilter http_request msg = base_message msg.prefilter = http_request send_message(msg) end def send_postfilter http_response msg = base_message msg.postfilter = http_response send_message(msg) end def send_noop noop = nil msg = base_message msg.noop = noop || Contrast::Api::Dtm::Noop.new send_message(msg) end def send_observed_route observed_route msg = base_message msg.observed_route = observed_route send_message(msg) end def base_message msg = Contrast::Api::Dtm::Message.new msg.app_name = APP_CONTEXT.name msg.app_path = APP_CONTEXT.path msg.app_language = Contrast::Utils::ObjectShare::RUBY msg.client_id = APP_CONTEXT.client_id msg.client_number = client_number msg.client_total = Contrast::Api::Speedracer.read_only_instance_count msg.message_count = (@messages_total += 1) msg.pid = APP_CONTEXT.pid msg.ppid = APP_CONTEXT.ppid msg end def send_message message to_service = Contrast::Api::Dtm::Message.encode(message) from_service = send_marshaled(to_service) Contrast::Api::Settings::AgentSettings.decode(from_service) end def send_marshaled marshaled connection.send_marshaled(marshaled) end end end end