# Copyright (c) 2022 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' module Contrast # A Rake task to generate a contrast_security.yaml file with some basic settings module Config # rubocop:disable Metrics/ModuleLength extend Rake::DSL DEFAULT_CONFIG = { 'api' => { 'url' => 'Enter your Contrast URL ex: https://app.contrastsecurity.com/Contrast', 'api_key' => 'Enter your Contrast api key', 'service_key' => 'Enter your Contrast service key', 'user_name' => 'Enter your Contrast user name' }, 'agent' => { 'service' => { 'logger' => { 'path' => 'contrast_service.log', 'level' => 'ERROR' # DEBUG | INFO | WARN | ERROR }, 'socket' => '/tmp/contrast_service.sock' }, 'logger' => { 'level' => 'ERROR', 'path' => 'contrast_agent.log' } } }.cs__freeze SKIP_LOG = %w[service_key api_key].cs__freeze REQUIRED = %i[url api_key service_key user_name].cs__freeze namespace :contrast do namespace :config do desc 'Create a contrast_security.yaml in the applications root directory' task :create do execution_directory = Dir.pwd target_path = File.join(execution_directory, 'contrast_security.yaml') if File.exist?(target_path) puts 'WARNING: contrast_security.yaml already exists' else File.write(target_path, YAML.dump(DEFAULT_CONFIG)) puts "Created contrast_security.yaml at #{ target_path }" puts 'Open the file and enter your Contrast Security api keys or set them via environment variables' puts 'Visit our documentation site for more details: https://docs.contrastsecurity.com/installation-rubyconfig.html' end end end end namespace :contrast do namespace :config do desc 'Validate the provided Contrast configuration and confirm connectivity' task validate: :environment do puts 'Validating Agent Configuration...' Contrast::Config.validate_config puts '...done!' puts 'Validating Contrast Reporter Headers...' reporter = Contrast::Config.validate_headers puts '...done!' puts 'Testing Reporter Client Connection...' Contrast::Config.test_connection(reporter) if reporter puts '...done!' end end def self.validate_config config = Contrast::Configuration.new abort('Unable to Build Config') unless config missing = [] api_hash = config.root.api.to_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.includes?(key.to_sym) missing << key end end abort("Missing required API configuration values: #{ missing.join(', ') }") unless missing.empty? end def self.validate_proxy config puts "Proxy Enabled: #{ config.enable }" return unless config.enable puts "Proxy URL: #{ config.url }" abort('Proxy Enabled but no Proxy URL given') unless config.url end def self.validate_cert config puts "Certification Enabled: #{ config.enable }" return unless config.enable puts "CA File: #{ config.ca_file }" abort('CA file path not provided') unless config.ca_file puts "Cert File: #{ config.cert_file }" abort('Cert file path not provided') unless config.cert_file puts "Key File: #{ config.key_file }" abort('Key file path not provided') unless config.key_file end def self.validate_audit config puts "Request Audit Enabled: #{ config.enable }" return unless config.enable config.each do |k, v| puts "#{ k }::#{ v }" end end def self.validate_headers missing = [] reporter = Contrast::Agent::Reporter.new reporter_headers = reporter.client.headers.to_hash reporter_headers.each_key do |key| value = mask_keys reporter_headers, key missing << key if value.nil? end abort("Missing required header values: #{ missing.join(', ') }") unless missing.empty? reporter end def self.test_connection reporter connection = reporter.connection abort('Failed to Initialize Connection please check error logs for details') unless connection abort('Failed to Start Client please check error logs for details') unless reporter.client.startup! connection end def self.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 end end end