contrast-agent 7.2.0 → 7.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|