# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'yaml' require 'contrast/configuration' require 'contrast/agent/reporting/reporter' require 'base64' module Contrast module Config # Helper module with methods to validate the existing yaml file. module Validate # rubocop:disable Rails/Output SKIP_LOG = %w[service_key api_key].cs__freeze REQUIRED = %i[url api_key service_key user_name].cs__freeze PASS = " \u{2705} " FAIL = " \u{274C} " REQUIRED_HEADERS = %i[server_type server_name server_path app_language app_name].cs__freeze def validate_file puts("\u{1F9EA} Validating Agent Configuration...\n") Contrast::Config.validate_config puts('done...') puts("Validating Contrast Reporter Headers...\n") reporter = Contrast::Config.validate_headers puts('done...') puts("Testing Reporter Client Connection...\n") Contrast::Config.test_connection(reporter) if reporter puts('Validation complete') end def validate_config config = Contrast::Configuration.new abort("Unable to Build Config #{ FAIL }") unless config missing = [] api_hash = config.api.to_contrast_hash api_hash.each_key do |key| value = mask_keys(api_hash, key) if value.is_a?(Contrast::Config::ApiProxyConfiguration) Contrast::Config.validate_proxy(value) elsif value.is_a?(Contrast::Config::CertificationConfiguration) Contrast::Config.validate_cert(value) next elsif value.is_a?(Contrast::Config::RequestAuditConfiguration) Contrast::Config.validate_audit(value) next elsif value.nil? && REQUIRED.include?(key.to_sym) missing << key end end return if missing.empty? abort("Validation failed: Missing required API configuration values: #{ missing.join(', ') } #{ FAIL }") end def validate_proxy config puts(" Proxy Enabled: #{ config.enable }") return unless config.enable mark = config.url.nil? ? FAIL : PASS puts(" Proxy URL: #{ config.url } #{ mark }") abort('Proxy Enabled but no Proxy URL given') unless config.url end def validate_cert config puts(" Certification Enabled: #{ config.enable }") return unless config.enable mark = config.ca_file.nil? ? FAIL : PASS puts(" CA File: #{ config.ca_file } #{ mark }") abort('CA file path not provided') unless config.ca_file mark = config.cert_file.nil? ? FAIL : PASS puts(" Cert File: #{ config.cert_file } #{ mark }") abort('Cert file path not provided') unless config.cert_file mark = config.key_file.nil? ? FAIL : PASS puts(" Key File: #{ config.key_file } #{ mark }") abort('Key file path not provided') unless config.key_file end def validate_audit config puts(" Request Audit Enabled: #{ config.enable }") return unless config.enable config.cs__class::CONFIG_VALUES.each do |value_name| puts(" #{ value_name }::#{ config.send(value_name.to_sym) }") end end def init_reporter Contrast::Agent::Reporter.new end def validate_headers missing = [] reporter = init_reporter reporter_headers = reporter.client.headers.to_contrast_hash REQUIRED_HEADERS.each do |key| value = decode_header(reporter_headers, key) missing << key if value.nil? end unless missing.empty? abort("Validation failed: Missing required header values: #{ missing.join(', ') } #{ FAIL }") end reporter end def test_connection reporter puts(" Connection failed #{ FAIL }") unless reporter connection = reporter.client.send(:reporting_connection) abort("Failed to Initialize Connection please check error logs for details #{ FAIL } ") unless connection abort('Failed to Start Client please check error logs for details') unless reporter.client.startup last_response = reporter.client.response_handler.last_response_code if last_response.include?('40') puts(" Last response code: #{ last_response } #{ FAIL }") abort("Failed to Initialize Connection please check error logs for details #{ FAIL } ") elsif connection puts(" Last response code: #{ last_response } #{ PASS }") puts(" Connection successful #{ PASS }") if connection end end def mask_keys hash, key value = hash[key] redacted_value = Contrast::Configuration::REDACTED if SKIP_LOG.include?(key.to_s) puts(" #{ key }::#{ redacted_value || value }") unless value.is_a?(Contrast::Config::BaseConfiguration) value end def decode_header hash, key value = hash[key] decoded_header = Base64.strict_decode64(value) puts(" #{ key }::#{ key == :app_language ? value : decoded_header }") value end # rubocop:enable Rails/Output end end end