# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'json' require 'fileutils' require 'contrast/utils/timer' require 'contrast/utils/log_utils' require 'contrast/components/logger' require 'contrast/utils/object_share' require 'contrast/config/diagnostics/config' require 'contrast/config/diagnostics/effective_config' require 'contrast/config/diagnostics/effective_config_value' require 'contrast/utils/duck_utils' module Contrast module Config module Diagnostics # This class is responsible for logging to file the effective Agent configurations after startup. class Monitor include Contrast::Components::Logger::InstanceMethods include Contrast::Utils::LogUtils # @return [String] path to write the file. attr_reader :path DEFAULT_PATH = File.join(Dir.pwd).cs__freeze ERROR_MESSAGE = '[Configuration Diagnostics] Could not write effective Agent configuration to file' FILE_NAME = 'contrast_connection.json' WRITE = 'w+' # @param path [String] path to write to file. def initialize path @path = path unless Contrast::Utils::DuckUtils.empty_duck?(path) end # Write current settings to file. # # @param reset [Boolean] should we reset the config we have # @return [Boolean] def write_to_file reset: true logger&.info('[Configuration Diagnostics] Writing Effective Configurations to file', path: dir_name) status = false write_to_file_logic(status, reset: reset) rescue IOError => e logger&.warn(ERROR_MESSAGE, e) false end def extract_settings Contrast::AGENT.to_effective_config(config.effective_config) Contrast::API.to_effective_config(config.effective_config) Contrast::APP_CONTEXT.to_effective_config(config.effective_config) Contrast::ASSESS.to_effective_config(config.effective_config) Contrast::INVENTORY.to_effective_config(config.effective_config) Contrast::PROTECT.to_effective_config(config.effective_config) end # Determine the path of the current logger and permissions required for # writing to path. # # @return [String] Path def dir_name @_dir_name ||= if write_permission?(@path) File.dirname(File.absolute_path(@path)) else DEFAULT_PATH end rescue Errno::EROFS => e logger.warn(ERROR_MESSAGE, e) end # Returns effective configurations of the agent. # # @return [Contrast::Config::Diagnostics::Config] def config @_config ||= Contrast::Config::Diagnostics::Config.new end # Reset the state of the current effective config. def reset_config # salvage status if status: status = if Contrast::Utils::DuckUtils.empty_duck?(@_config.config_status) nil else @_config.config_status end @_config = Contrast::Config::Diagnostics::Config.new @_config.config_status = status if status @_config end def to_controlled_hash { report_create: report_create_time, config: config.to_controlled_hash } end def write_to_file_logic status, reset: true # We need to reset the config before updating it's values reset_config if reset extract_settings File.open(File.join(dir_name, FILE_NAME), WRITE) do |file| file.truncate(0) file.write(JSON.pretty_generate(to_controlled_hash, { space: Contrast::Utils::ObjectShare::EMPTY_STRING })) status = true if file file.close end status end private # Return current time in iso8601 format. # # @return [String] Time of creation def report_create_time Contrast::Utils::Timer.time_now end end end end end