# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/object_share' require 'contrast/agent/reporting/settings/protect_rule' require 'contrast/agent/reporting/settings/virtual_patch' module Contrast module Agent module Reporting # This module will hold all the settings from the TS responce module Settings # Application level settings for the Protect featureset class Protect # modes set by NG endpoints; block at perimeter needs to be check against the blockAtEntry boolean value NG_PROTECT_RULES_MODE = %w[OFF MONITORING BLOCKING].cs__freeze ACTIVE_PROTECT_RULES_LIST = %w[ bot-blocker cmd-injection cmd-injection-command-backdoors cmd-injection-semantic-chained-commands cmd-injection-semantic-dangerous-paths untrusted-deserialization nosql-injection path-traversal path-traversal-semantic-file-security-bypass sql-injection sql-injection-semantic-dangerous-functions unsafe-file-upload reflected-xss xxe ].cs__freeze # The settings for each protect rule for this application # # @return protection_rules [Array<protectRule>] protectRule: { # blockAtEntry [Boolean] If in block mode, to block at perimeter or not. # id [String] The id of a rule in Contrast. # mode [String] The mode that this rule should run in. [OFF, MONITORING, BLOCKING] } def protection_rules @_protection_rules ||= [] end # @return [Hash<String,Contrast::Agent::Reporting::Settings::ProtectRule>] map of rule, by id, to # configuration def rule_settings @_rule_settings ||= {} end # Set the protection_rules array # # @param protection_rules [Array<protectRule>] protectRule: { # blockAtEntry [Boolean] If in block mode, to block at perimeter or not. # id [String] The id of a rule in Contrast. # mode [String] The mode that this rule should run in. [OFF, MONITORING, BLOCKING] } # @return protection_rules [Array<protectRule>] protectRule: { # blockAtEntry [Boolean] If in block mode, to block at perimeter or not. # id [String] The id of a rule in Contrast. # mode [String] The mode that this rule should run in. [OFF, MONITORING, BLOCKING] } def protection_rules= protection_rules @_protection_rules = protection_rules if protection_rules.is_a?(Array) end # The virtual patches to apply for this application # # @return [Array<Contrast::Agent::Reporting::Settings::VirtualPatch>] def virtual_patches @_virtual_patches ||= [] end # Set the virtual patches array # # @param virtual_patches [Array<VirtualPatch>] Array of VirtualPatch: { # name [String] The name of the Virtual Patch # headers [Array] The headers that must be present in the request to result in the request being blocked # parameters [Array] The parameters that must be present in the request # to result in the request being blocked. # urls [Array] The urls that must be present in the request to result in the request being blocked. # uuids [String] The UUID of the Virtual Patch } # @return virtual_patches [Array<VirtualPatch>] Array of VirtualPatch: { # name [String] The name of the Virtual Patch # headers [Array] The headers that must be present in the request to result in the request being blocked # parameters [Array] The parameters that must be present in the request # to result in the request being blocked. # urls [Array] The urls that must be present in the request to result in the request being blocked. # uuids [String] The UUID of the Virtual Patch } def virtual_patches= virtual_patches @_virtual_patches = virtual_patches if virtual_patches.is_a?(Array) end # Converts settings into Agent Settings understandable hash {RULE_ID => MODE} # # @return rules [Hash<RULE_ID => MODE>, nil] Hash with rule_id as key and mode as value def protection_rules_to_settings_hash return {} if protection_rules.empty? modes_by_id = {} protection_rules.each do |rule| setting_mode = rule[:mode] || rule['mode'] # BlockAtEnrtry is only available for the protection_rules Array. # It is used in both ng and non ng payloads. If the array is empty # this method will short circuit at the very first line and return # empty hash. this means that the #rules_settings_to_settings_hash # will be used next to extract the settings. bap = rule[:blockAtEntry] || rule['blockAtEntry'] api_mode = assign_mode(setting_mode, block_at_entry: !!bap == bap) id = rule[:id] || rule['id'] modes_by_id[id] = api_mode end modes_by_id end # Converts settings into Agent Settings understandable hash {RULE_ID => MODE} # Takes Hash<String, Contrast::Agent::Reporting::Settings::ProtectRule> and converts it # to Hash<RULE_ID => MODE> # # @return rules [Hash<RULE_ID => MODE>, nil] Hash with rule_id as key and mode as value def rules_settings_to_settings_hash return {} if rule_settings.empty? modes_by_id = {} rule_settings.each do |rule_id, rule_mode| next unless active_defend_rules.include?(rule_id.to_s) modes_by_id[rule_id.to_s] = assign_mode(rule_mode.mode) end modes_by_id end # Returns list of actively used protection rules to be updated, or default list. # This will be used to query the received settings for the ones used by the Agent. def active_defend_rules return ACTIVE_PROTECT_RULES_LIST unless defined?(Contrast::PROTECT) current_rules = Contrast::PROTECT.defend_rules.keys return current_rules unless current_rules.empty? ACTIVE_PROTECT_RULES_LIST end private # Assigns the received settings mode to be used as actual config. # @param setting_mode [] def assign_mode setting_mode, block_at_entry: false # modes set by newer settings endpoints are of [OFF MONITOR BLOCK BLOCK_AT_PERIMETER] and # can just be cast to symbols return setting_mode.to_sym unless NG_PROTECT_RULES_MODE.include?(setting_mode) case setting_mode when NG_PROTECT_RULES_MODE[1] :MONITOR when NG_PROTECT_RULES_MODE[2] block_at_entry ? :BLOCK_AT_PERIMETER : :BLOCK else :NO_ACTION end end end end end end end