# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent module Reporting # Module to house the Resend logic, when there is no response from TS. module Resend # How manny times the client will try to rescue from above error before raising an # error to reflect the events. # RESCUE_ATTEMPTS = 3 TIMEOUT = 5 RETRY_ERRORS = [ Net::OpenTimeout, Net::ReadTimeout, EOFError, OpenSSL::SSL::SSLError, Resolv::ResolvError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ESHUTDOWN, Errno::EHOSTDOWN, NameError, Errno::EHOSTUNREACH, Errno::EISCONN, Errno::ECONNABORTED, Errno::ENETRESET, Errno::ENETUNREACH, SocketError, NameError, NoMethodError, Timeout::Error, RuntimeError ].cs__freeze RESENDING_MESSAGE = '[Reporter] Resending message...' END_RESENDING_MESSAGE = '[Reporter] Reporter tried to resend event and received error:' # This method is mainly used in inside error handling and startup mesasges sending. # # @param event [Contrast::Agent::Reporting::ReportingEvent] event to resend # @param connection [Net::HTTP] open connection # @param error [StandardError] error that was raised # @return [Net::HTTPResponse, nil] response from TS def try_resend event, connection, error # The timeout for agent to sleep is set here, we don't need to raise an error, # but also 15 min is a long time to wait for example if event is startup message. # Override timeout if Contrast::Agent::Reporting::ReporterClientUtils::STARTUP_EVENTS.include?(event.cs__class) response_handler.suspend_reporting(RESENDING_MESSAGE, TIMEOUT, error, event: event, log_error: false, startup: true) end mode.enter_resend_mode handle_resend(event, connection) end # This method will handle the error and will decide if we need to resend the event # # @param event [Contrast::Agent::Reporting::ReportingEvent] # @param connection [Net::HTTP] open connection # @return [Net::HTTPResponse, nil] response from TS def handle_resend event, connection if sleep? logger.debug("[Reporter] sleeping for #{ TIMEOUT } seconds before resending...") Thread.current.send(:sleep, timeout) wake_up end response = mode.status == mode.resending ? send_event(event, connection) : nil response_success! if response response end # Handles errors that occurs before the ResponseHandler do not have a response, or response # code to hande. This Errors requires different handling, because most of errors here # occurs when the response cannot be read, there is open ssl error, or a certain Timeout # is reached. # # @param event [Contrast::Agent::Reporting::ReportingEvent] # One of the DTMs valid for the event field of Contrast::Api::Dtm::Message # @param error [StandardError] # @param connection [Net::HTTP] open connection # @return nil [NilClass] to be passed as response def handle_response_error event, connection, error return end_of_rescue(event, error) if mode.resend.rescue_attempts >= RESCUE_ATTEMPTS if RETRY_ERRORS.include?(error.cs__class) mode.resend.increase_rescue_attempts try_resend(event, connection, error) else end_of_rescue(event, error) end end # End of rescue attempts. # # @param event [Contrast::Agent::Reporting::ReportingEvent] # @param error [StandardError] # @return [NilClass] def end_of_rescue event, error if Contrast::Agent::Reporting::ReporterClientUtils::STARTUP_EVENTS.include?(event.cs__class) # Agent didn't send it's startup events, There will be reporting errors, disable reporting. disable_reporting(event, error) else # There is an error in one of the reported messages, log that error and continue. logger.error(END_RESENDING_MESSAGE, resend_attempts: Contrast::Agent::Reporting::Resend::RESCUE_ATTEMPTS, connection_error: error, client: Contrast::Agent::Reporting::ReporterClient::SERVICE_NAME, event_id: event&.__id__, event_type: event&.cs__class&.cs__name) end end # Disable reporting when there is an error that cannot be handled. # # @param event [Contrast::Agent::Reporting::ReportingEvent] # @param error [StandardError] # @return [NilClass] def disable_reporting event, error logger.error('[Agent] Disabling Reporting...', error: error.message, event_type: event.cs__class&.cs__name) Contrast::AGENT.disable! end # Methods that will initialize the resending state. class Status # RESEND_STARTUP_EVENTS = 4 def initialize @_rescue_attempts = 0 end # return how many times the client has tried to rescue from error. # # @return [Integer] def rescue_attempts @_rescue_attempts ||= 0 end # Reset Error rescues attempts. # # @return [Integer] def reset_rescue_attempts @_rescue_attempts = 0 end # Increase the Error rescues attempts. # # @return [Integer] def increase_rescue_attempts @_rescue_attempts += 1 end end end end end end