# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent module Reporting # This module holds utilities required by the reporting service response handler module ResponseHandlerUtils ANALYZE_WHEN = '200' SLEEP_WHEN = %w[401 408 500].cs__freeze private # check if response code is valid before analyze it # # @param response_code [Integer] # @return [Boolean] def analyze_response_code? response_code # 200 Successful recording of the server record on the database. FeatureSet successfully returned # 401: # If any authentication errors occur. Agents should enter a suspended mode # (no reporting) and attempt to reconnect in 15 minutes. # 408: # If the FeatureSet from TeamServer cannot be constructed in time. # Agents should enter a suspended mode (no reporting) and attempt to reconnect in 15 minutes. # 500: # If any server error occurs. Agents should enter a suspended mode # (no reporting) and attempt to reconnect in 15 minutes. # 204, 304 # Successful update of the server record on the database. # No FeatureSet update since the last time an activity was sent. @_sleep = true if SLEEP_WHEN.include? response_code return true if ANALYZE_WHEN.include? response_code false end # Applies the settings from the TS response # # @param response [Contrast::Agent::Reporting::Response] def update_agent_settings response if response.server_features log_file = response.server_features.log_file log_level = response.server_features.log_level Contrast::Logger::Log.instance.update(log_file, log_level) if log_file && log_level Contrast::SETTINGS.update_from_server_features(response) end Contrast::SETTINGS.update_from_application_settings(response) if response.application_settings end # Process the given Reactions from the application settings based on what # TeamServer has indicated. Each Reaction will result in a log message # and, optionally, an action. # # @param response [Contrast::Agent::Reporting::Response] def update_reaction response return unless response.application_settings.reactions.any? response.application_settings.reactions.each do |reaction| # The enums are all uppercase, we need to downcase them before attempting to log. level = if reaction.level.nil? :error else reaction.level.downcase end logger.with_level(level, reaction.message) if reaction.message case reaction.operation when Contrast::Agent::Reporting::Settings::Reaction::OPERATIONS.second # DISABLED Contrast::Agent::DisableReaction.run reaction, level when Contrast::Agent::Reporting::Settings::Reaction::OPERATIONS.first # NOOP else logger.warn('ReactionProcessor received a reaction with an unknown operation', operation: reaction.operation) end end end # Converts response from Net to Reporting Response object # # @param response [Net::HTTP::Response, nil] # @return response [Contrast::Agent::Reporting::Response] def convert_response response response_body = response&.body return unless response_body response_data = JSON.parse(response_body) return unless response_data.cs__is_a?(Hash) response_data = response_data.deep_symbolize_keys # check if response contains application settings or Feature settings if response_data[:settings] # the response contains ApplicationSettings logger.trace('Agent: Received updated application settings') build_application_settings response_data else # the response contains FeatureSettings logger.trace('Agent: Received updated server features') build_feature_settings response_data end rescue StandardError => e logger.error('Unable to convert response', e) nil end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def build_application_settings response_data res = Contrast::Agent::Reporting::Response.new extract_assess response_data, res extract_protect response_data, res extract_exclusions response_data, res extract_reactions response_data, res res end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def build_feature_settings response_data res = Contrast::Agent::Reporting::Response.new extract_assess_server_features response_data, res extract_protect_server_features response_data, res extract_protect_lists response_data, res res.server_features.log_level = response_data[:logLevel] res.server_features.log_file = response_data[:logFile] res.server_features.telemetry = response_data[:telemetry] res end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def extract_assess response_data, res assessments = response_data[:settings][:assessment] res.application_settings.assess.disabled_rules = assessments[:disabledRules] res.application_settings.assess.session_id = assessments[:session_id] end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def extract_protect response_data, res protect = response_data[:settings][:defend] res.application_settings.protect.protection_rules = protect[:protectionRules] res.application_settings.protect.virtual_patches = protect[:virtualPatches] end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def extract_exclusions response_data, res exclusions = response_data[:settings][:exceptions] res.application_settings.exclusions.code_exclusions = exclusions[:codeExceptions] res.application_settings.exclusions.input_exclusions = exclusions[:inputExceptions] res.application_settings.exclusions.url_exclusions = exclusions[:urlExceptions] end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def extract_reactions response_data, res res.application_settings.reactions = response_data[:settings][:reactions] end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def extract_assess_server_features response_data, res assess = response_data[:assessment] res.server_features.assess.enabled = assess[:enabled] res.server_features.assess.disabled_rules = assess[:disabledRules] res.server_features.assess.sampling = assess[:sampling] res.server_features.assess.sanitizers = assess[:sanitizers] res.server_features.assess.validators = assess[:validators] end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def extract_protect_server_features response_data, res protect = response_data[:defend] res.server_features.protect.enabled = protect[:enabled] res.server_features.protect.bot_blocker = protect[:bot_blocker] res.server_features.protect.syslog = protect[:syslog] end # @param response_data [Hash] # @return res [Contrast::Agent::Reporting::Response] def extract_protect_lists response_data, res protect = response_data[:defend] res.server_features.protect.ip_allowlist = protect[:ipAllowlist] res.server_features.protect.ip_denylist = protect[:ipDenyList] res.server_features.protect.log_enchancers = protect[:logEnhancers] res.server_features.protect.rule_definition_list = protect[:ruleDefinitionList] end end end end end