contrast-agent 7.2.0 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +62 -23
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +37 -4
- data/lib/contrast/agent/protect/rule/base.rb +5 -1
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +27 -11
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +0 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -2
- data/lib/contrast/agent/protect/rule/input_classification/base.rb +191 -0
- data/lib/contrast/agent/protect/rule/input_classification/base64_statistic.rb +71 -0
- data/lib/contrast/agent/protect/rule/input_classification/cached_result.rb +37 -0
- data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +109 -0
- data/lib/contrast/agent/protect/rule/input_classification/encoding_rates.rb +47 -0
- data/lib/contrast/agent/protect/rule/input_classification/extendable.rb +80 -0
- data/lib/contrast/agent/protect/rule/input_classification/lru_cache.rb +198 -0
- data/lib/contrast/agent/protect/rule/input_classification/match_rates.rb +66 -0
- data/lib/contrast/agent/protect/rule/input_classification/rates.rb +53 -0
- data/lib/contrast/agent/protect/rule/input_classification/statistics.rb +115 -0
- data/lib/contrast/agent/protect/rule/input_classification/utils.rb +23 -0
- data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +17 -7
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +18 -15
- data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +18 -15
- data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +19 -17
- data/lib/contrast/agent/reporting/attack_result/attack_result.rb +6 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +2 -7
- data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +11 -0
- data/lib/contrast/agent/reporting/input_analysis/input_type.rb +33 -1
- data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -1
- data/lib/contrast/agent/telemetry/base.rb +28 -2
- data/lib/contrast/agent/telemetry/base64_hash.rb +55 -0
- data/lib/contrast/agent/telemetry/cache_hash.rb +55 -0
- data/lib/contrast/agent/telemetry/client.rb +10 -2
- data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
- data/lib/contrast/agent/telemetry/input_analysis_cache_event.rb +27 -0
- data/lib/contrast/agent/telemetry/input_analysis_encoding_event.rb +26 -0
- data/lib/contrast/agent/telemetry/input_analysis_event.rb +91 -0
- data/lib/contrast/agent/telemetry/metric_event.rb +12 -0
- data/lib/contrast/agent/telemetry/startup_metrics_event.rb +0 -8
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/config.rb +4 -4
- data/lib/contrast/components/protect.rb +11 -1
- data/lib/contrast/components/sampling.rb +15 -10
- data/lib/contrast/config/diagnostics/environment_variables.rb +3 -1
- data/lib/contrast/config/yaml_file.rb +8 -0
- data/lib/contrast/framework/rails/support.rb +3 -0
- data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
- data/lib/contrast/utils/metrics_hash.rb +1 -1
- data/lib/contrast/utils/object_share.rb +2 -1
- data/lib/contrast/utils/response_utils.rb +12 -0
- data/lib/contrast/utils/timer.rb +2 -0
- data/lib/contrast.rb +9 -2
- data/ruby-agent.gemspec +1 -1
- metadata +21 -6
- data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -0,0 +1,55 @@
|
|
1
|
+
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/telemetry/input_analysis_cache_event'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Agent
|
8
|
+
module Telemetry
|
9
|
+
# This hash will store the telemetry data for the Protect InputAnalysis cache.
|
10
|
+
class CacheHash < Hash
|
11
|
+
include Contrast::Components::Logger::InstanceMethods
|
12
|
+
# Set per request:
|
13
|
+
HASH_SIZE_LIMIT = 100
|
14
|
+
|
15
|
+
# Wrapper to set a value in this Telemetry::Hash only if the provided value is of the data_type for this
|
16
|
+
# Telemetry::CacheHash or the hash has not reached its limit for unique keys.
|
17
|
+
# Saves Array of reportable events.
|
18
|
+
#
|
19
|
+
# @param key [Object] the key to which to associate the value
|
20
|
+
# @param events [array<Object>]
|
21
|
+
# @return [Object, nil] echo back out the value as the Hash#[]= method does, or nil if not of the expected
|
22
|
+
# data_type
|
23
|
+
def []= key, events
|
24
|
+
# If telemetry is not running, do not add more as we want to avoid a memory leak.
|
25
|
+
return unless Contrast::Agent.telemetry_queue&.running?
|
26
|
+
# If the Hash is full, do not add more as we want to avoid consuming all application resources.
|
27
|
+
return if at_limit?
|
28
|
+
# If the given value is of unexpected type, do not add it to avoid issues later where type is assumed.
|
29
|
+
return unless valid_event?(events)
|
30
|
+
|
31
|
+
super(key, events)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Determine if hash has reached exception event limit.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
def at_limit?
|
38
|
+
unless length < HASH_SIZE_LIMIT
|
39
|
+
logger.debug("[Telemetry] Number of IA cache events exceeds limit of #{ HASH_SIZE_LIMIT }")
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Checks to see if the given object is a valid event.
|
48
|
+
# @param event [Contrast::Agent::Telemetry::InputAnalysisCacheEvent]
|
49
|
+
def valid_event? events
|
50
|
+
events&.all?(Contrast::Agent::Telemetry::InputAnalysisCacheEvent)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -65,6 +65,7 @@ module Contrast
|
|
65
65
|
else
|
66
66
|
60
|
67
67
|
end
|
68
|
+
logger.debug('[Telemetry] received response.', response_code: status_code)
|
68
69
|
ready_after if status_code == 429
|
69
70
|
end
|
70
71
|
|
@@ -76,6 +77,8 @@ module Contrast
|
|
76
77
|
return true if event.cs__is_a?(Contrast::Agent::Telemetry::Event)
|
77
78
|
return true if event.cs__is_a?(Contrast::Agent::Telemetry::StartupMetricsEvent)
|
78
79
|
return true if event.cs__is_a?(Contrast::Agent::Telemetry::Exception::Event)
|
80
|
+
return true if event.cs__is_a?(Contrast::Agent::Telemetry::InputAnalysisCacheEvent)
|
81
|
+
return true if event.cs__is_a?(Contrast::Agent::Telemetry::InputAnalysisEncodingEvent)
|
79
82
|
|
80
83
|
false
|
81
84
|
end
|
@@ -93,12 +96,17 @@ module Contrast
|
|
93
96
|
"#{ Contrast::Agent::Telemetry::Base::URL }#{ endpoint }#{ path }"
|
94
97
|
end
|
95
98
|
|
96
|
-
# Helper Method to get json representation of Telemetry Event data, handles error on to_json
|
99
|
+
# Helper Method to get json representation of Telemetry Event data, handles error on to_json.
|
100
|
+
# Generating bodies for exceptions and startup metrics is different.
|
97
101
|
#
|
98
102
|
# @param event [Contrast::Agent::Telemetry::Event, Array<Contrast::Agent::Telemetry::Exception::Event>]
|
99
103
|
# @return [String] - JSON
|
100
104
|
def get_event_json event
|
101
|
-
|
105
|
+
if event.cs__is_a?(Contrast::Agent::Telemetry::Exception::Event)
|
106
|
+
return Array(event.to_controlled_hash).to_json
|
107
|
+
end
|
108
|
+
|
109
|
+
[event.to_controlled_hash].to_json
|
102
110
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
103
111
|
logger.error('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
|
104
112
|
raise(e)
|
@@ -10,7 +10,7 @@ module Contrast
|
|
10
10
|
# This is the Telemetry::Hash, which will store Contrast::Agent::Telemetry::Exception::Event, so we can push
|
11
11
|
# freely, without worrying about validating the event before that. Telemetry::Hash has a max size of events,
|
12
12
|
# default is 10 events
|
13
|
-
class
|
13
|
+
class ExceptionHash < Hash
|
14
14
|
include Contrast::Components::Logger::InstanceMethods
|
15
15
|
HASH_SIZE_LIMIT = 10
|
16
16
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/telemetry/input_analysis_event'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Agent
|
8
|
+
module Telemetry
|
9
|
+
# Event to report all gather information from the Input Analysis Cache statistics, hits and misses.
|
10
|
+
class InputAnalysisCacheEvent < Contrast::Agent::Telemetry::InputAnalysisEvent
|
11
|
+
NAME = 'InputAnalysis cache event'
|
12
|
+
PATH = '/protect_input_analysis_cache'
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Creates the tags for the event
|
17
|
+
#
|
18
|
+
# @param rule_id [String]
|
19
|
+
# @param match_rates [Contrast::Agent::Protect::Rule::InputClassification::MatchRates]
|
20
|
+
def add_tags rule_id, match_rates
|
21
|
+
super(rule_id, match_rates)
|
22
|
+
@tags['score_level'] = match_rates&.score_level.dup&.to_s || NOT_APPLICABLE
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/telemetry/input_analysis_event'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Agent
|
8
|
+
module Telemetry
|
9
|
+
# Event to report all gather information from the Input Analysis Cache statistics, hits and misses.
|
10
|
+
class InputAnalysisEncodingEvent < Contrast::Agent::Telemetry::InputAnalysisEvent
|
11
|
+
NAME = 'InputAnalysis encoding event'
|
12
|
+
PATH = '/protect_input_analysis_encoding'
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Creates the tags for the event
|
17
|
+
#
|
18
|
+
# @param _rule_id [String]
|
19
|
+
# @param encode_rates [Contrast::Agent::Protect::Rule::InputClassification::EncodingRates]
|
20
|
+
def add_tags _rule_id, encode_rates
|
21
|
+
super(nil, encode_rates)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/metrics_hash'
|
5
|
+
require 'contrast/agent/telemetry/metric_event'
|
6
|
+
require 'contrast/agent/version'
|
7
|
+
require 'contrast/components/logger'
|
8
|
+
|
9
|
+
module Contrast
|
10
|
+
module Agent
|
11
|
+
module Telemetry
|
12
|
+
# Event to report all gather information from the Input Analysis metrics.
|
13
|
+
class InputAnalysisEvent < Contrast::Agent::Telemetry::MetricEvent
|
14
|
+
include Contrast::Components::Logger::InstanceMethods
|
15
|
+
NOT_APPLICABLE = 'n/a'
|
16
|
+
|
17
|
+
attr_reader :fields
|
18
|
+
|
19
|
+
# Override the name for any derived classes
|
20
|
+
NAME = 'InputAnalysis event'
|
21
|
+
# Override the path for any derived classes
|
22
|
+
PATH = '/protect_input_analysis'
|
23
|
+
|
24
|
+
# @param rule_id [String] the rule name.
|
25
|
+
# @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates] base class for all rates.
|
26
|
+
def initialize rule_id, rates
|
27
|
+
super()
|
28
|
+
@fields = MetricsHash.new(Integer)
|
29
|
+
add_tags(rule_id, rates)
|
30
|
+
generate_fields(rates)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the name of the event.
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
def name
|
37
|
+
cs__class::NAME
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the path to report the event.
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def path
|
44
|
+
cs__class::PATH
|
45
|
+
end
|
46
|
+
|
47
|
+
# Override the empty check for any derived classes if needed.
|
48
|
+
def empty?
|
49
|
+
super && Contrast::Utils::DuckUtils.empty_duck?(@tags)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Creates the tags for the event. Overrides the base class to add the rule_id and match_rate.
|
55
|
+
#
|
56
|
+
# @param rule_id [String, nil]
|
57
|
+
# @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates]
|
58
|
+
def add_tags rule_id, rates
|
59
|
+
return if Contrast::Utils::DuckUtils.empty_duck?(rates)
|
60
|
+
return unless rates.cs__is_a?(Contrast::Agent::Protect::Rule::InputClassification::Rates)
|
61
|
+
|
62
|
+
@tags['event_type'] = name # rubocop:disable Security/Module/Name
|
63
|
+
@tags['test_environment'] = ENV['CONTRAST_AGENT_TELEMETRY_TEST'] == '1' ? 'true' : 'false'
|
64
|
+
@tags['rule_id'] = rule_id if rule_id
|
65
|
+
@tags['input_type'] = rates&.input_type.dup&.to_s || NOT_APPLICABLE
|
66
|
+
add_system_tags
|
67
|
+
end
|
68
|
+
|
69
|
+
# Creates the fields for the event.
|
70
|
+
# Override if needed.
|
71
|
+
#
|
72
|
+
# @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates] base class for all rates.
|
73
|
+
def generate_fields rates
|
74
|
+
return if Contrast::Utils::DuckUtils.empty_duck?(rates)
|
75
|
+
return unless rates.cs__is_a?(Contrast::Agent::Protect::Rule::InputClassification::Rates)
|
76
|
+
|
77
|
+
rates.to_fields.each { |field, value| @fields[field] = value.dup }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds the system tags to the event.
|
81
|
+
def add_system_tags
|
82
|
+
@tags['agent_version'] = VERSION
|
83
|
+
@tags['ruby_version'] = RUBY_VERSION
|
84
|
+
@tags['os_type'] = sys_info['os_type'] == 'Darwin' ? 'MacOS' : 'Linux'
|
85
|
+
@tags['os_arch'] = sys_info['os_arch']
|
86
|
+
@tags['os_version'] = sys_info['os_version']
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
require 'contrast/utils/metrics_hash'
|
5
5
|
require 'contrast/agent/telemetry/event'
|
6
|
+
require 'contrast/utils/duck_utils'
|
7
|
+
require 'contrast/utils/os'
|
6
8
|
|
7
9
|
module Contrast
|
8
10
|
module Agent
|
@@ -10,6 +12,7 @@ module Contrast
|
|
10
12
|
# This class will hold the basic information for a Telemetry Event
|
11
13
|
class MetricEvent < Contrast::Agent::Telemetry::Event
|
12
14
|
include Contrast::Utils
|
15
|
+
include Contrast::Utils::OS
|
13
16
|
|
14
17
|
attr_reader :fields
|
15
18
|
|
@@ -19,6 +22,15 @@ module Contrast
|
|
19
22
|
@fields['_filler'] = 0
|
20
23
|
end
|
21
24
|
|
25
|
+
def sys_info
|
26
|
+
@sys_info ||= get_system_information if @sys_info.nil?
|
27
|
+
@sys_info
|
28
|
+
end
|
29
|
+
|
30
|
+
def empty?
|
31
|
+
Contrast::Utils::DuckUtils.empty_duck?(@fields)
|
32
|
+
end
|
33
|
+
|
22
34
|
def to_controlled_hash **_args
|
23
35
|
super.merge!({ fields: @fields })
|
24
36
|
end
|
@@ -4,7 +4,6 @@
|
|
4
4
|
require 'contrast/utils/metrics_hash'
|
5
5
|
require 'contrast/agent/telemetry/metric_event'
|
6
6
|
require 'contrast/agent/version'
|
7
|
-
require 'contrast/utils/os'
|
8
7
|
|
9
8
|
module Contrast
|
10
9
|
module Agent
|
@@ -15,8 +14,6 @@ module Contrast
|
|
15
14
|
# application framework and version and server framework
|
16
15
|
# It will be initialized and send in Middleware#agent_startup_routine
|
17
16
|
class StartupMetricsEvent < Contrast::Agent::Telemetry::MetricEvent
|
18
|
-
include Contrast::Utils::OS
|
19
|
-
|
20
17
|
APP_AND_SERVER_DATA = ::Contrast::APP_CONTEXT.app_and_server_information.cs__freeze
|
21
18
|
# Multi-tenant Production Environments
|
22
19
|
SAAS_DEFAULT = { addr: 'app.contrastsecurity.com', type: 'SAAS_DEFAULT' }.cs__freeze
|
@@ -82,11 +79,6 @@ module Contrast
|
|
82
79
|
end
|
83
80
|
end
|
84
81
|
|
85
|
-
def sys_info
|
86
|
-
@sys_info ||= get_system_information if @sys_info.nil?
|
87
|
-
@sys_info
|
88
|
-
end
|
89
|
-
|
90
82
|
private
|
91
83
|
|
92
84
|
# Here we extract the TeamServer url type
|
@@ -25,7 +25,7 @@ module Contrast
|
|
25
25
|
# time than to silently fail to deliver functionality.
|
26
26
|
module Config
|
27
27
|
CONTRAST_ENV_MARKER = 'CONTRAST__'
|
28
|
-
CONTRAST_LOG = '
|
28
|
+
CONTRAST_LOG = 'contrast.log'
|
29
29
|
CONTRAST_NAME = 'Contrast Agent'
|
30
30
|
DATE_TIME = '%Y-%m-%dT%H:%M:%S.%L%z'
|
31
31
|
|
@@ -63,7 +63,7 @@ module Contrast
|
|
63
63
|
env_overrides
|
64
64
|
validate
|
65
65
|
rescue ArgumentError => e
|
66
|
-
proto_logger.error('Configuration failed with error: ', e)
|
66
|
+
proto_logger.error('[PROTO_LOGGER] Configuration failed with error: ', e)
|
67
67
|
end
|
68
68
|
alias_method :rebuild, :build
|
69
69
|
|
@@ -157,7 +157,7 @@ module Contrast
|
|
157
157
|
# @return [boolean]
|
158
158
|
def valid_session_metadata?
|
159
159
|
if !session_id&.empty? && !session_metadata&.empty?
|
160
|
-
proto_logger.error(SESSION_VARIABLES)
|
160
|
+
proto_logger.error("[PROTO_LOGGER] #{ SESSION_VARIABLES }")
|
161
161
|
return false
|
162
162
|
end
|
163
163
|
true
|
@@ -172,7 +172,7 @@ module Contrast
|
|
172
172
|
msg << API_KEY unless api_key
|
173
173
|
msg << API_SERVICE_KEY unless api_service_key
|
174
174
|
msg << API_USERNAME unless api_username
|
175
|
-
msg.any? { |m| proto_logger.error(m) }
|
175
|
+
msg.any? { |m| proto_logger.error("[PROTO_LOGGER] #{ m }") }
|
176
176
|
msg.empty?
|
177
177
|
end
|
178
178
|
|
@@ -15,12 +15,14 @@ module Contrast
|
|
15
15
|
include Contrast::Config::BaseConfiguration
|
16
16
|
|
17
17
|
CANON_NAME = 'protect'
|
18
|
-
CONFIG_VALUES = %w[enabled?].cs__freeze
|
18
|
+
CONFIG_VALUES = %w[enabled? normalize_base64?].cs__freeze
|
19
19
|
RULES = 'rules'
|
20
20
|
MODE = 'mode'
|
21
21
|
|
22
22
|
# @return [Boolean, nil]
|
23
23
|
attr_accessor :enable
|
24
|
+
# @return [Boolean, nil]
|
25
|
+
attr_accessor :normalize_base64
|
24
26
|
# @return [String]
|
25
27
|
attr_reader :canon_name
|
26
28
|
# @return [Array]
|
@@ -36,6 +38,7 @@ module Contrast
|
|
36
38
|
@_exceptions = Contrast::Config::ExceptionConfiguration.new(hsh[:exceptions])
|
37
39
|
@_rules = Contrast::Config::ProtectRulesConfiguration.new(hsh[:rules])
|
38
40
|
@enable = hsh[:enable]
|
41
|
+
@normalize_base64 = hsh[:normalize_base64]
|
39
42
|
@agent_lib = hsh[:agent_lib]
|
40
43
|
end
|
41
44
|
|
@@ -68,6 +71,13 @@ module Contrast
|
|
68
71
|
::Contrast::SETTINGS.protect_state.enabled == true
|
69
72
|
end
|
70
73
|
|
74
|
+
# Check to determine if the base64 decoding is required for user inputs.
|
75
|
+
def normalize_base64?
|
76
|
+
@normalize_base64 = Contrast::CONFIG.protect.normalize_base64 if @normalize_base64.nil?
|
77
|
+
|
78
|
+
true?(@normalize_base64)
|
79
|
+
end
|
80
|
+
|
71
81
|
# Current Configuration for the protect rules
|
72
82
|
#
|
73
83
|
# @return [Contrast::Config::ProtectRulesConfiguration]
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/components/base'
|
5
|
+
require 'contrast/utils/duck_utils'
|
5
6
|
|
6
7
|
module Contrast
|
7
8
|
module Components
|
@@ -27,7 +28,7 @@ module Contrast
|
|
27
28
|
config_settings = ::Contrast::CONFIG.assess&.sampling
|
28
29
|
settings = ::Contrast::SETTINGS&.assess_state&.sampling_settings
|
29
30
|
{
|
30
|
-
enabled: enabled?(config_settings, settings),
|
31
|
+
enabled: enabled?(config_settings&.enable, settings&.enabled),
|
31
32
|
baseline: check_baseline(config_settings, settings),
|
32
33
|
request_frequency: check_request_frequency(config_settings, settings),
|
33
34
|
response_frequency: check_response_frequency(config_settings, settings),
|
@@ -43,13 +44,20 @@ module Contrast
|
|
43
44
|
|
44
45
|
private
|
45
46
|
|
46
|
-
# @param
|
47
|
+
# @param config_value [Boolean, nil] the Sampling configuration as provided by
|
47
48
|
# local user input
|
48
|
-
# @param
|
49
|
+
# @param settings_value [Boolean, nil] the Sampling settings as provided by
|
49
50
|
# TeamServer
|
50
51
|
# @return [Boolean] the resolution of the config_settings, settings, and default value
|
51
|
-
def enabled?
|
52
|
-
|
52
|
+
def enabled? config_value, settings_value
|
53
|
+
# Check the Assess State received from TS:
|
54
|
+
sampling_enable = settings_value unless Contrast::Utils::DuckUtils.empty_duck?(settings_value)
|
55
|
+
# Check for local settings, YAML, ENV, CLI (it's with higher priority than Web Interface)
|
56
|
+
# see: https://docs.contrastsecurity.com/en/order-of-precedence.html
|
57
|
+
sampling_enable = config_value unless Contrast::Utils::DuckUtils.empty_duck?(config_value)
|
58
|
+
# Use default value, unless a false or true is set from local or TS settings.
|
59
|
+
sampling_enable = DEFAULT_SAMPLING_ENABLED if Contrast::Utils::DuckUtils.empty_duck?(sampling_enable)
|
60
|
+
true?(sampling_enable)
|
53
61
|
end
|
54
62
|
|
55
63
|
# @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
|
@@ -106,6 +114,8 @@ module Contrast
|
|
106
114
|
NAME_PREFIX = "#{ CONTRAST }.#{ CANON_NAME }".cs__freeze
|
107
115
|
CONFIG_VALUES = %w[enable baseline request_frequency response_frequency window_ms].cs__freeze
|
108
116
|
|
117
|
+
# @return [Boolean, nil]
|
118
|
+
attr_accessor :enable
|
109
119
|
# @return [Integer, nil]
|
110
120
|
attr_accessor :baseline
|
111
121
|
# @return [Integer, nil]
|
@@ -128,11 +138,6 @@ module Contrast
|
|
128
138
|
@window_ms = hsh[:window_ms]
|
129
139
|
end
|
130
140
|
|
131
|
-
# @return [Boolean, false]
|
132
|
-
def enable
|
133
|
-
!!@enable
|
134
|
-
end
|
135
|
-
|
136
141
|
# Converts current configuration to effective config values class and appends them to
|
137
142
|
# EffectiveConfig class.
|
138
143
|
#
|
@@ -11,7 +11,9 @@ module Contrast
|
|
11
11
|
# Reads All ENV variables.
|
12
12
|
module EnvironmentVariables
|
13
13
|
class << self
|
14
|
-
NON_COMMON_ENV = %w[
|
14
|
+
NON_COMMON_ENV = %w[
|
15
|
+
CONTRAST_CONFIG_PATH CONTRAST_AGENT_TELEMETRY_OPTOUT CONTRAST_AGENT_TELEMETRY_TEST
|
16
|
+
].cs__freeze
|
15
17
|
|
16
18
|
# This method will fill the canonical name for each env var and will check for any uncommon ones.
|
17
19
|
#
|
@@ -11,6 +11,8 @@ module Contrast
|
|
11
11
|
# instrumentation.
|
12
12
|
module YamlFile
|
13
13
|
CONFIG_FILE_NAME = 'contrast_security'
|
14
|
+
CONTRAST_ENV_MARKER = 'CONTRAST__'
|
15
|
+
|
14
16
|
EXT = { yml: 'yml', yaml: 'yaml' }.freeze # rubocop:disable Security/Object/Freeze
|
15
17
|
|
16
18
|
POSSIBLE_TARGET_PATHS = %w[
|
@@ -89,6 +91,8 @@ module Contrast
|
|
89
91
|
#
|
90
92
|
def create
|
91
93
|
# rubocop:disable Rails/Output
|
94
|
+
return puts("\u{02C3} Contrast configuration set by ENV variables.") if env_config_set?
|
95
|
+
|
92
96
|
puts("\u{1F48E} Generating: Contrast Configuration file.")
|
93
97
|
if Contrast::Config::YamlFile.created?
|
94
98
|
puts("\u{2705} Configuration file already exists: #{ Contrast::Config::YamlFile.find! }")
|
@@ -123,6 +127,10 @@ module Contrast
|
|
123
127
|
def file_name
|
124
128
|
ENV['CONTRAST_YAML_FILE_TEST_CREATE_CONFIG_FILE_NAME_VALUE'] || CONFIG_FILE_NAME
|
125
129
|
end
|
130
|
+
|
131
|
+
def env_config_set?
|
132
|
+
ENV.keys&.select { |config| config.include?(CONTRAST_ENV_MARKER) }&.any?
|
133
|
+
end
|
126
134
|
end
|
127
135
|
end
|
128
136
|
end
|
@@ -4,6 +4,7 @@
|
|
4
4
|
require 'contrast/framework/base_support'
|
5
5
|
require 'contrast/framework/rails/patch/support'
|
6
6
|
require 'contrast/utils/string_utils'
|
7
|
+
require 'contrast/utils/duck_utils'
|
7
8
|
|
8
9
|
module Contrast
|
9
10
|
module Framework
|
@@ -129,6 +130,8 @@ module Contrast
|
|
129
130
|
if engine_route?(route)
|
130
131
|
new_req = retrieve_request(request.env)
|
131
132
|
new_req.path_info = new_req.path_info.gsub(match.to_s, '')
|
133
|
+
# solves the issue when requiring base path '/' without the slash
|
134
|
+
new_req.path_info = '/' if Contrast::Utils::DuckUtils.empty_duck?(new_req.path_info)
|
132
135
|
get_full_route(new_req, route.app.app.routes.router, path << match.to_s)
|
133
136
|
else
|
134
137
|
[match, params, route, path]
|
@@ -70,13 +70,13 @@ module Contrast
|
|
70
70
|
if current_count == event_max
|
71
71
|
return if event_limit_counts.key?(get_event_limit_key(method_policy, context))
|
72
72
|
|
73
|
-
logger.
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
logger.debug('Event Limit Reached:',
|
74
|
+
{
|
75
|
+
count: current_count,
|
76
|
+
max: event_max,
|
77
|
+
policy: method_policy.method_name,
|
78
|
+
node: method_policy
|
79
|
+
})
|
80
80
|
# increment to be over count for logging purposes
|
81
81
|
increment_event_count(method_policy)
|
82
82
|
increment_event_limit_logs(method_policy, context)
|
@@ -87,12 +87,12 @@ module Contrast
|
|
87
87
|
return if event_limit_counts.key?(get_event_limit_key(method_policy, context))
|
88
88
|
|
89
89
|
# increment to be over count for logging purposes
|
90
|
-
logger.
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
logger.debug('Event Limit Exceeded:',
|
91
|
+
{
|
92
|
+
count: current_count,
|
93
|
+
policy: method_policy.method_name,
|
94
|
+
node: method_policy
|
95
|
+
})
|
96
96
|
increment_event_limit_logs(method_policy, context)
|
97
97
|
return true
|
98
98
|
end
|
@@ -17,7 +17,7 @@ module Contrast
|
|
17
17
|
'The key extends the allowed length.',
|
18
18
|
'The provided value is not the right data type'
|
19
19
|
].cs__freeze
|
20
|
-
KEY_REGEXP = /[a-zA-Z0-
|
20
|
+
KEY_REGEXP = /[a-zA-Z0-9._-]{1,63}/.cs__freeze
|
21
21
|
|
22
22
|
def initialize data_type, *several_variants
|
23
23
|
super
|
@@ -19,6 +19,7 @@ module Contrast
|
|
19
19
|
def extract_body body
|
20
20
|
return unless body
|
21
21
|
return if body_is_file?(body)
|
22
|
+
return if body_is_sprockets?(body)
|
22
23
|
|
23
24
|
return handle_rack_body_proxy(body) if body.is_a?(Rack::BodyProxy)
|
24
25
|
return extract_body(body.body) if sub_extractable?(body)
|
@@ -74,6 +75,17 @@ module Contrast
|
|
74
75
|
|
75
76
|
false
|
76
77
|
end
|
78
|
+
|
79
|
+
# Detects Rails' Sprockets asset pipeline objects passed as body.
|
80
|
+
# Returns false if Sprockets is passed as body, the Agent does not
|
81
|
+
# support Sprockets::Asset for body extraction.
|
82
|
+
#
|
83
|
+
# @param body [String, Rack::File, Rack::BodyProxy]
|
84
|
+
def body_is_sprockets? body
|
85
|
+
return body.cs__is_a?(Sprockets::Asset) if defined?(Sprockets) && defined?(Sprockets::Asset)
|
86
|
+
|
87
|
+
false
|
88
|
+
end
|
77
89
|
end
|
78
90
|
end
|
79
91
|
end
|
data/lib/contrast/utils/timer.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'time'
|
5
|
+
|
4
6
|
module Contrast
|
5
7
|
module Utils
|
6
8
|
# Timer is class that can track state about when an event starts and how long it takes
|
data/lib/contrast.rb
CHANGED
@@ -61,10 +61,12 @@ require 'contrast/components/sampling'
|
|
61
61
|
require 'contrast/components/scope'
|
62
62
|
require 'contrast/components/settings'
|
63
63
|
require 'contrast/utils/routes_sent'
|
64
|
-
require 'contrast/agent/telemetry/
|
64
|
+
require 'contrast/agent/telemetry/exception_hash'
|
65
65
|
require 'contrast/agent/telemetry/telemetry'
|
66
66
|
require 'contrast/agent/telemetry/exception/event'
|
67
67
|
require 'contrast/agent_lib/interface'
|
68
|
+
require 'contrast/agent/telemetry/cache_hash'
|
69
|
+
require 'contrast/agent/telemetry/base64_hash'
|
68
70
|
|
69
71
|
module Contrast # :nodoc:
|
70
72
|
CONFIG = Contrast::Components::Config::Interface.new
|
@@ -82,7 +84,12 @@ module Contrast # :nodoc:
|
|
82
84
|
end
|
83
85
|
|
84
86
|
module Contrast
|
85
|
-
TELEMETRY_EXCEPTIONS = (
|
87
|
+
TELEMETRY_EXCEPTIONS = (if Contrast::Agent::Telemetry.exceptions_enabled?
|
88
|
+
Contrast::Agent::Telemetry::ExceptionHash.new
|
89
|
+
end)
|
90
|
+
TELEMETRY_IA_CACHE = (Contrast::Agent::Telemetry::CacheHash.new if Contrast::Agent::Telemetry::Base.enabled?)
|
91
|
+
TELEMETRY_BASE64_HASH = (Contrast::Agent::Telemetry::Base64Hash.new if Contrast::Agent::Telemetry::Base.enabled? &&
|
92
|
+
Contrast::PROTECT.normalize_base64?)
|
86
93
|
ROUTES_SENT = Contrast::Utils::RoutesSent.new
|
87
94
|
end
|
88
95
|
|
data/ruby-agent.gemspec
CHANGED
@@ -121,7 +121,7 @@ def self.add_dependencies spec
|
|
121
121
|
spec.add_dependency 'rack', '~> 2.0'
|
122
122
|
|
123
123
|
# bind this directly as we've had issues w/ build changes on bug release
|
124
|
-
spec.add_dependency 'contrast-agent-lib', '1.1.
|
124
|
+
spec.add_dependency 'contrast-agent-lib', '1.1.1'
|
125
125
|
end
|
126
126
|
|
127
127
|
# Enumerate the files required to build the Agent.
|