lib/contrast/components/settings.rb in contrast-agent-5.3.0 vs lib/contrast/components/settings.rb in contrast-agent-6.0.0

- old
+ new

@@ -1,9 +1,10 @@ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/api/settings.pb' +require 'contrast/agent/reporting/settings/sensitive_data_masking' module Contrast module Components # This component encapsulates the statefulness of settings. # When we say 'settings', we're referring specifically to external @@ -11,24 +12,82 @@ # 'Settings' is not a generic term for 'configurable stuff'. module Settings APPLICATION_STATE_BASE = Struct.new(:modes_by_id, :exclusion_matchers). new(Hash.new(Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION), []) PROTECT_STATE_BASE = Struct.new(:enabled, :rules).new(false, {}) - ASSESS_STATE_BASE = Struct.new(:enabled, :sampling_settings, :disabled_assess_rules).new(false, nil, []) do + ASSESS_STATE_BASE = Struct.new(:enabled, :sampling_settings, :disabled_assess_rules, :session_id).new(false, nil, + [], nil) do def sampling_settings= new_val @sampling_settings = new_val Contrast::Utils::Assess::SamplingUtil.instance.update end end + SENSITIVE_DATA_MASKING_BASE = Contrast::Agent::Reporting::Settings::SensitiveDataMasking.new # This is a class. class Interface extend Contrast::Components::Config # tainted_columns are database columns that receive unsanitized input. attr_reader :tainted_columns # This can probably go into assess_state? - attr_reader :assess_state, :protect_state, :application_state + # Current state for Assess. + # enabled [Boolean] Indicate if the assess feature set is enabled for this server or not. + # + # sampling [Hash<AssessSampling>] Hash of AssessSampling Used to control the sampling feature in the agent: { + # baseline [Integer] The number of baseline requests to take before switching to sampling + # for the window. + # enabled [Boolean] If the sampling feature should be used or not. + # frequency [Integer] The number of requests to skip before observing during the sampling + # window after the baseline. + # responseFrequency [Integer] + # window [Integer] + # } + # + # disabled_assess_rules [array<AssessRuleID(String)>] Assess rules to disable for this application. + attr_reader :assess_state + # Current State for Protect. + # enabled [Boolean] Indicate if the protect feature set is enabled for this server or not. + # + # Protection rules are returned as: + # rules [Hash<RULE_ID => MODE>, nil] Hash with rule_id as key and mode as value + attr_reader :protect_state + # Current Application State. + # + # modes_by_id [Hash<Rule_id => Mode] Returns Hash with rules and their current mode. + # exclusion_matchers [Array] Array of all the exclusions. + # code_exclusions [Array<CodeExclusion>] Array of CodeExclusion: { + # name [String] The name of the exclusion as defined by the user in TS. + # modes [String] If this exclusion applies to assess or protect. [assess, defend] + # assess_rules [Array] Array of assess rules to which this exclusion applies. AssessRuleID [String] + # denylist [String] The call, if in the stack, should result in the agent not taking action. + # input_exclusions [Array<InputExclusions>] Array of InputExclusions: { + # name [String] The name of the input. + # modes [String] If this exclusion applies to assess or protect. [assess, defend] + # assess_rules [Array] Array of assess rules to which this exclusion applies. AssessRuleID [String] + # protect_rules [Array] Array of ProtectRuleID [String] The protect rules to which this exclusion applies. + # urls [Array] Array of URLs to which the exclusions apply. URL [String] + # match_strategy [String] If this exclusion applies to all URLs or only those specified. [ALL, ONLY] + # type [String] The type of the input [COOKIE, PARAMETER, HEADER, BODY, QUERYSTRING] + # } + # url_exclusions [Array<UrlExclusions>] Array of UrlExclusions: { + # name [String] The name of the input. + # modes [String] If this exclusion applies to assess or protect. [assess, defend] + # assess_rules [Array] Array of assess rules to which this exclusion applies. AssessRuleID [String] + # protect_rules [Array] Array of ProtectRuleID [String] The protect rules to which this exclusion applies. + # urls [Array] Array of URLs to which the exclusions apply. URL [String] + # match_strategy [String] If this exclusion applies to all URLs or only those specified. [ALL, ONLY] + # type [String] The type of the input [COOKIE, PARAMETER, HEADER, BODY, QUERYSTRING] + # } + attr_reader :application_state + # This the structure that will hold the masking rules send from TS. + # + # @return [Contrast::Agent::Reporting::Settings::SensitiveDataMasking]: + # mask_http_body [Boolean] Policy flag to enable the use of masking on request body. + # rules [Array<Contrast::Agent::Reporting::Settings::SensitiveDataMaskingRule>] + # Rules to follow when using the masking. Each rules contains Id [String] + # and Keywords [Array<String>]. + attr_reader :sensitive_data_masking def initialize reset_state end @@ -36,28 +95,35 @@ @application_state.exclusion_matchers.select(&:code?) end # @param features [Contrast::Api::Settings::ServerFeatures, Contrast::Agent::Reporting::Response] def update_from_server_features features - if features&.class == Contrast::Agent::Reporting::Response - @protect_state.enabled = features.server_features.protect.enabled? - @assess_state.enabled = features.server_features.assess.enabled? - @assess_state.sampling_settings = features.server_features.assess.sampling + if features.cs__is_a?(Contrast::Agent::Reporting::Response) + server_features = features.server_features + log_file = server_features.log_file + log_level = server_features.log_level + Contrast::Logger::Log.instance.update(log_file, log_level) if log_file || log_level + @protect_state.enabled = server_features.protect.enabled? + @assess_state.enabled = server_features.assess.enabled? + @assess_state.sampling_settings = server_features.assess.sampling else @protect_state.enabled = features.protect_enabled? @assess_state.enabled = features.assess_enabled? @assess_state.sampling_settings = features.assess.sampling end end # @param features [Contrast::Api::Settings::ApplicationSettings, Contrast::Agent::Reporting::Response] def update_from_application_settings features if features&.class == Contrast::Agent::Reporting::Response - @application_state.modes_by_id = features.application_settings.protect.protection_rules_to_settings_hash + settings = features.application_settings + @application_state.modes_by_id = settings.protect.protection_rules_to_settings_hash # TODO: RUBY-1438 this needs to be translated # @application_state.exclusion_matchers = new_vals[:exclusion_matchers] - @assess_state.disabled_assess_rules = features.application_settings.assess.disabled_rules + update_sensitive_data_policy(settings.sensitive_data_masking) + @assess_state.disabled_assess_rules = settings.assess.disabled_rules + @assess_state.session_id = settings.assess.session_id if settings.assess.session_id else new_vals = features.application_state_translation @application_state.modes_by_id = new_vals[:modes_by_id] @application_state.exclusion_matchers = new_vals[:exclusion_matchers] @assess_state.disabled_assess_rules = new_vals[:disabled_assess_rules] @@ -68,10 +134,11 @@ def reset_state @protect_state = PROTECT_STATE_BASE.dup @assess_state = ASSESS_STATE_BASE.dup @application_state = APPLICATION_STATE_BASE.dup @tainted_columns = {} + @sensitive_data_masking = SENSITIVE_DATA_MASKING_BASE.dup end def build_protect_rules @protect_state.rules = {} @@ -83,9 +150,40 @@ Contrast::Agent::Protect::Rule::PathTraversal.new Contrast::Agent::Protect::Rule::Sqli.new Contrast::Agent::Protect::Rule::UnsafeFileUpload.new Contrast::Agent::Protect::Rule::Xss.new Contrast::Agent::Protect::Rule::Xxe.new + end + + # Update the sensitive data masking policy from settings, + # received from TS. In case the settings are empty, + # keep current ones. + # + # @param sensitive_data_masking [Contrast::Agent::Reporting::Settings::SensitiveDataMasking] + # Ts Response settings for sensitive data masking policy + def update_sensitive_data_policy sensitive_data_masking + @sensitive_data_masking.mask_http_body = sensitive_data_masking.mask_http_body? unless + settings_empty?(sensitive_data_masking.mask_http_body?) + @sensitive_data_masking.mask_attack_vector = sensitive_data_masking.mask_attack_vector? unless + settings_empty?(sensitive_data_masking.mask_attack_vector?) + return if settings_empty?(sensitive_data_masking.rules) + + @sensitive_data_masking.rules = sensitive_data_masking.rules + # update with the newly received rules. + Contrast::Agent::Reporting::Masker.send(:update_dictionary) + end + + private + + # check if settings are empty and return true if so. + # + # @param settings [String, Boolean, Array, Hash] + # @return true | false + def settings_empty? settings + return false if !!settings == settings + return true if settings.nil? || settings.empty? + + false end end end end end