# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/env_configuration_item' require 'ougai' require 'contrast/configuration' module Contrast module Components # This component encapsulates reference to the configuration file. # At the time of writing, the configuration file is a yaml file reflecting # the 'common agent configuration' specification. # 'Config' and 'configuration' are to be interpreted as referring # specifically to these files and specifications, they are not generic # terms for other avenues of user configuration. # # This component is responsible for... # - encapsulating file access & concomitant error conditions # - implementing validity checks with respect to the specification # - memoizing/streamline field accesses # # Config fails fast. if it's not valid, the agent should break, and # it should break LOUDLY. Better to waste half an hour of the sysadmin's # time than to silently fail to deliver functionality. module Config CONTRAST_ENV_MARKER = 'CONTRAST__' CONTRAST_LOG = 'contrast_agent.log' CONTRAST_NAME = 'Contrast Agent' class Interface # :nodoc: def initialize build end # Basic logger for handling configuration validation logging # the file to log is determined by the default one or set # by the config file, if that configuration is found def proto_logger @_proto_logger ||= begin @_proto_logger = ::Ougai::Logger.new(logger_path || CONTRAST_LOG) @_proto_logger.progname = CONTRAST_NAME @_proto_logger.level = ::Ougai::Logging::Severity::WARN @_proto_logger.formatter = Contrast::Logger::Format.new @_proto_logger.formatter.datetime_format = '%Y-%m-%dT%H:%M:%S.%L%z' @_proto_logger end end def build @_valid = nil @config = Contrast::Configuration.new env_overrides validate end alias_method :rebuild, :build # @return [Contrast::Config::RootConfiguration] def root @config.root end def valid? @_valid = validate if @_valid.nil? @_valid end def invalid? !valid? end def loggable @config.loggable end private SESSION_VARIABLES = 'Invalid configuration. '\ "Setting both application.session_id and application.session_metadata is not allowed.\n" API_URL = "Invalid configuration. Missing a required connection value 'url' is not set." API_KEY = "Invalid configuration. Missing a required connection value 'api_key' is not set." API_SERVICE_KEY = "Invalid configuration. Missing a required connection value 'service_tag' is not set." API_USERNAME = "Invalid configuration. Missing a required connection value 'user_name' is not set." def validate # The config has information about how to construct the logger. # If the config is invalid, and you want to know about it, then # you have a circular dependency if you try to log it, # so we use basic proto_logger to do this job. if !session_id.empty? && !session_metadata.empty? proto_logger.error(SESSION_VARIABLES) return false end if bypass msg = [] msg << API_URL unless api_url msg << API_KEY unless api_key msg << API_SERVICE_KEY unless api_service_key msg << API_USERNAME unless api_username msg.any? { |m| proto_logger.error(m) } return false unless msg.empty? end true end def env_overrides # For env variables resembling CONTRAST__WHATEVER__NESTED_VALUE # override raw.whatever.nested_value ENV.each do |env_key, env_value| next unless env_key.to_s.start_with?(CONTRAST_ENV_MARKER) config_item = Contrast::Utils::EnvConfigurationItem.new(env_key, env_value) @config.assign_value_to_path_array(config_item.dot_path_array, config_item.value) end end # Typically, this would be accessed through # Contrast::Components::AppContext, but we're too early in the # initialization of the Agent to use that mechanism, so we look it up # directly for ourselves # # @return [String,nil] the value of the session id set in the # configuration, or nil if unset def session_id @config.application.session_id end # Typically, this would be accessed through # Contrast::Components::AppContext, but we're too early in the # initialization of the Agent to use that mechanism, so we look it up # directly for ourselves # # @return [String,nil] the value of the session metadata set in the # configuration, or nil if unset def session_metadata @config.application.session_metadata end # Typically, the following values would be accessed through Contrast::Components::AppContext # and Contrast::Components::API, but we're too early in the initialization of the Agent to use # that mechanism, so we look it up directly for ourselves. # # @return [String, nil] def api_url @config.api.url end # Typically, the following values would be accessed through Contrast::Components::AppContext # and Contrast::Components::API, but we're too early in the initialization of the Agent to use # that mechanism, so we look it up directly for ourselves. # # @return [String, nil] def api_key @config.api.api_key end # Typically, the following values would be accessed through Contrast::Components::AppContext # and Contrast::Components::API, but we're too early in the initialization of the Agent to use # that mechanism, so we look it up directly for ourselves. # # @return [String, nil] def api_service_key @config.api.service_key end # Typically, the following values would be accessed through Contrast::Components::AppContext # and Contrast::Components::API, but we're too early in the initialization of the Agent to use # that mechanism, so we look it up directly for ourselves. # # @return [String, nil] def api_username @config.api.user_name end # Typically, the following values would be accessed through Contrast::Components::AppContext # and Contrast::Components::API, but we're too early in the initialization of the Agent to use # that mechanism, so we look it up directly for ourselves. # # @return [String, nil] def bypass @config.root.agent.service.bypass end # Typically, the following values would be accessed through Contrast::Components::AppContext # and Contrast::Components::Logger, but we're too early in the initialization of the Agent to use # that mechanism, so we look it up directly for ourselves. # # @return [String, nil] def logger_path @config.root.agent.logger.path end end end end end