contrast-agent 7.2.0 → 7.3.1
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/assess/policy/policy_node.rb +25 -6
- data/lib/contrast/agent/assess/policy/propagator/response.rb +64 -0
- data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +22 -7
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +4 -1
- 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/exception/obfuscate.rb +4 -3
- data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
- data/lib/contrast/agent/telemetry/identifier.rb +13 -26
- 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/assess.rb +33 -6
- data/lib/contrast/components/base.rb +4 -2
- data/lib/contrast/components/config.rb +6 -6
- data/lib/contrast/components/protect.rb +11 -1
- data/lib/contrast/components/sampling.rb +15 -10
- data/lib/contrast/config/diagnostics/command_line.rb +2 -2
- data/lib/contrast/config/diagnostics/environment_variables.rb +5 -2
- data/lib/contrast/config/diagnostics/tools.rb +15 -5
- data/lib/contrast/config/yaml_file.rb +8 -0
- data/lib/contrast/configuration.rb +61 -29
- data/lib/contrast/framework/rails/support.rb +3 -0
- data/lib/contrast/logger/application.rb +3 -3
- data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
- data/lib/contrast/utils/assess/propagation_method_utils.rb +2 -0
- data/lib/contrast/utils/metrics_hash.rb +1 -1
- data/lib/contrast/utils/object_share.rb +2 -1
- data/lib/contrast/utils/os.rb +1 -9
- data/lib/contrast/utils/response_utils.rb +12 -0
- data/lib/contrast/utils/timer.rb +2 -0
- data/lib/contrast.rb +9 -2
- data/resources/assess/policy.json +80 -3
- data/ruby-agent.gemspec +1 -1
- metadata +22 -6
- data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -1,7 +1,7 @@
|
|
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 'contrast/
|
4
|
+
require 'contrast/agent/protect/rule/input_classification/base'
|
5
5
|
|
6
6
|
module Contrast
|
7
7
|
module Agent
|
@@ -13,28 +13,32 @@ module Contrast
|
|
13
13
|
REFLECTED_XSS_MATCH = 'reflected-xss-input-tracing-v1'.cs__freeze
|
14
14
|
WORTHWATCHING_MATCH = 'xss-worth-watching-v2'.cs__freeze
|
15
15
|
class << self
|
16
|
-
include
|
16
|
+
include Contrast::Agent::Protect::Rule::InputClassification::Base
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
|
-
#
|
21
|
-
# key if needed and Creates new isntance of InputAnalysisResult.
|
20
|
+
# Creates new instance of AgentLib evaluation result with direct call to AgentLib.
|
22
21
|
#
|
23
|
-
# @param request [Contrast::Agent::Request] the current request context.
|
24
22
|
# @param rule_id [String] The name of the Protect Rule.
|
25
23
|
# @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
|
26
24
|
# @param value [String, Array<String>] the value of the input.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
Contrast::AGENT_LIB.
|
36
|
-
eval_option[:PREFER_WORTH_WATCHING])
|
25
|
+
def build_input_eval rule_id, input_type, value
|
26
|
+
Contrast::AGENT_LIB.eval_input(value,
|
27
|
+
Contrast::Agent::Protect::Rule::InputClassification::Base.
|
28
|
+
convert_input_type(input_type),
|
29
|
+
Contrast::AGENT_LIB.rule_set[rule_id],
|
30
|
+
Contrast::AGENT_LIB.
|
31
|
+
eval_option[:PREFER_WORTH_WATCHING])
|
32
|
+
end
|
37
33
|
|
34
|
+
# Creates specific result from the AgentLib evaluation.
|
35
|
+
#
|
36
|
+
# @param rule_id [String] The name of the Protect Rule.
|
37
|
+
# @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
|
38
|
+
# @param value [String, Array<String>] the value of the input.
|
39
|
+
# @param request [Contrast::Agent::Request] the current request context.
|
40
|
+
# @param input_eval [Contrast::AgentLib::EvalResult] the result of the input evaluation.
|
41
|
+
def build_ia_result rule_id, input_type, value, request, input_eval
|
38
42
|
score = input_eval&.score || 0
|
39
43
|
ia_result = new_ia_result(rule_id, input_type, request.path, value)
|
40
44
|
if score >= THRESHOLD
|
@@ -46,8 +50,6 @@ module Contrast
|
|
46
50
|
else
|
47
51
|
ia_result.score_level = IGNORE
|
48
52
|
end
|
49
|
-
|
50
|
-
add_needed_key(request, ia_result, input_type, value)
|
51
53
|
ia_result
|
52
54
|
end
|
53
55
|
end
|
@@ -5,6 +5,7 @@ require 'contrast/utils/object_share'
|
|
5
5
|
require 'contrast/utils/timer'
|
6
6
|
require 'contrast/agent/reporting/attack_result/response_type'
|
7
7
|
require 'contrast/agent/reporting/attack_result/rasp_rule_sample'
|
8
|
+
require 'contrast/utils/duck_utils'
|
8
9
|
|
9
10
|
module Contrast
|
10
11
|
module Agent
|
@@ -65,6 +66,11 @@ module Contrast
|
|
65
66
|
def details= protect_details
|
66
67
|
@_details = protect_details if protect_details.is_a?(Contrast::Agent::Reporting::Details::ProtectRuleDetails)
|
67
68
|
end
|
69
|
+
|
70
|
+
def empty?
|
71
|
+
Contrast::Utils::DuckUtils.empty_duck?(samples) || Contrast::Utils::DuckUtils.empty_duck?(rule_id) ||
|
72
|
+
response == ::Contrast::Agent::Reporting::ResponseType::NO_ACTION
|
73
|
+
end
|
68
74
|
end
|
69
75
|
end
|
70
76
|
end
|
@@ -9,13 +9,8 @@ module Contrast
|
|
9
9
|
module Reporting
|
10
10
|
# This class will do ia analysis for our protect rules
|
11
11
|
class InputAnalysis
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def inputs= extracted_inputs
|
17
|
-
@_inputs = extracted_inputs
|
18
|
-
end
|
12
|
+
# @return [Hash] Stored request inputs for this context.
|
13
|
+
attr_accessor :inputs
|
19
14
|
|
20
15
|
def triggered_rules
|
21
16
|
@_triggered_rules ||= []
|
@@ -61,6 +61,17 @@ module Contrast
|
|
61
61
|
@_key = key if key.is_a?(String)
|
62
62
|
end
|
63
63
|
|
64
|
+
# @param key_type [Symbol]
|
65
|
+
# @return @_key [String]
|
66
|
+
def key_type= key_type
|
67
|
+
@_key_type = key_type if INPUT_TYPE.to_a.include?(key_type)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return @_key [String]
|
71
|
+
def key_type
|
72
|
+
@_key_type ||= INPUT_TYPE::UNDEFINED_TYPE
|
73
|
+
end
|
74
|
+
|
64
75
|
# @return value [String]
|
65
76
|
def value
|
66
77
|
@_value ||= Contrast::Utils::ObjectShare::EMPTY_STRING
|
@@ -30,13 +30,45 @@ module Contrast
|
|
30
30
|
UNKNOWN = :UNKNOWN.cs__freeze
|
31
31
|
|
32
32
|
class << self
|
33
|
+
# @return
|
33
34
|
def to_a
|
34
|
-
[
|
35
|
+
@_to_a ||= [
|
35
36
|
UNDEFINED_TYPE, BODY, COOKIE_NAME, COOKIE_VALUE, HEADER, PARAMETER_NAME, PARAMETER_VALUE,
|
36
37
|
QUERYSTRING, URI, SOCKET, JSON_VALUE, JSON_ARRAYED_VALUE, MULTIPART_CONTENT_TYPE, MULTIPART_VALUE,
|
37
38
|
MULTIPART_FIELD_NAME, MULTIPART_NAME, XML_VALUE, DWR_VALUE, METHOD, REQUEST, URL_PARAMETER, UNKNOWN
|
38
39
|
]
|
39
40
|
end
|
41
|
+
|
42
|
+
# This is a hash of the input types and their corresponding values.
|
43
|
+
#
|
44
|
+
# @return [Hash]
|
45
|
+
|
46
|
+
def to_hash
|
47
|
+
{
|
48
|
+
UNDEFINED_TYPE: '1',
|
49
|
+
BODY: '2',
|
50
|
+
COOKIE_NAME: '3',
|
51
|
+
COOKIE_VALUE: '4',
|
52
|
+
HEADER: '5',
|
53
|
+
PARAMETER_NAME: '6',
|
54
|
+
PARAMETER_VALUE: '7',
|
55
|
+
QUERYSTRING: '8',
|
56
|
+
URI: '9',
|
57
|
+
SOCKET: '10',
|
58
|
+
JSON_VALUE: '11',
|
59
|
+
JSON_ARRAYED_VALUE: '12',
|
60
|
+
MULTIPART_CONTENT_TYPE: '13',
|
61
|
+
MULTIPART_VALUE: '14',
|
62
|
+
MULTIPART_FIELD_NAME: '15',
|
63
|
+
MULTIPART_NAME: '16',
|
64
|
+
XML_VALUE: '17',
|
65
|
+
DWR_VALUE: '18',
|
66
|
+
METHOD: '19',
|
67
|
+
REQUEST: '20',
|
68
|
+
URL_PARAMETER: '21',
|
69
|
+
UNKNOWN: '22'
|
70
|
+
}
|
71
|
+
end
|
40
72
|
end
|
41
73
|
end
|
42
74
|
end
|
@@ -23,7 +23,7 @@ module Contrast
|
|
23
23
|
hash = URI.decode_www_form(query).to_h
|
24
24
|
mask_with_dictionary(results, hash)
|
25
25
|
# Restore to string form.
|
26
|
-
hash.each { |k, v| masked += "#{ k }
|
26
|
+
hash.each { |k, v| masked += "#{ k }#{ EQUALS }#{ v }#{ AMPERSAND }" }
|
27
27
|
query = masked
|
28
28
|
query.chomp!(masked[-1])
|
29
29
|
end
|
@@ -40,6 +40,7 @@ module Contrast
|
|
40
40
|
# @param attack_result [Contrast::Agent::Reporting::AttackResult]
|
41
41
|
def attach_data attack_result
|
42
42
|
return unless attack_result&.cs__is_a?(Contrast::Agent::Reporting::AttackResult)
|
43
|
+
return if attack_result&.empty?
|
43
44
|
|
44
45
|
attacker_activity = Contrast::Agent::Reporting::ApplicationDefendAttackerActivity.new(ia_request: @request)
|
45
46
|
attacker_activity.attach_data(attack_result)
|
@@ -5,6 +5,7 @@ require 'contrast/components/logger'
|
|
5
5
|
require 'contrast/utils/object_share'
|
6
6
|
require 'contrast/utils/duck_utils'
|
7
7
|
require 'contrast/agent/reporting/reporting_events/reportable_hash'
|
8
|
+
require 'contrast/agent/reporting/attack_result/response_type'
|
8
9
|
require 'contrast/agent/reporting/reporting_events/application_defend_attack_activity'
|
9
10
|
|
10
11
|
module Contrast
|
@@ -118,7 +118,7 @@ module Contrast
|
|
118
118
|
mode.resend.reset_rescue_attempts
|
119
119
|
findings_to_return.each do |index|
|
120
120
|
preflight_message = event.messages[index.to_i]
|
121
|
-
corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message
|
121
|
+
corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message&.data)
|
122
122
|
next unless corresponding_finding
|
123
123
|
|
124
124
|
send_event(corresponding_finding, connection)
|
@@ -143,6 +143,8 @@ module Contrast
|
|
143
143
|
super
|
144
144
|
delete_queue!
|
145
145
|
Contrast::TELEMETRY_EXCEPTIONS&.clear
|
146
|
+
Contrast::TELEMETRY_IA_CACHE&.clear
|
147
|
+
Contrast::TELEMETRY_BASE64_HASH&.clear
|
146
148
|
end
|
147
149
|
|
148
150
|
private
|
@@ -174,8 +176,7 @@ module Contrast
|
|
174
176
|
break unless attempt_to_start?
|
175
177
|
|
176
178
|
# Start pushing exceptions to queue for reporting.
|
177
|
-
|
178
|
-
Contrast::TELEMETRY_EXCEPTIONS&.clear
|
179
|
+
gather_telemetry_events
|
179
180
|
until queue.empty?
|
180
181
|
event = queue.pop
|
181
182
|
begin
|
@@ -189,6 +190,31 @@ module Contrast
|
|
189
190
|
end
|
190
191
|
end
|
191
192
|
end
|
193
|
+
|
194
|
+
# Fills the queue with events that were not able to be sent previously.
|
195
|
+
def gather_telemetry_events
|
196
|
+
gather_exceptions
|
197
|
+
gather_encoding_events
|
198
|
+
gather_ia_cache_events
|
199
|
+
end
|
200
|
+
|
201
|
+
# Retrieves the exceptions that were accumulated.
|
202
|
+
def gather_exceptions
|
203
|
+
Contrast::TELEMETRY_EXCEPTIONS&.each_value { |value| queue << value }
|
204
|
+
Contrast::TELEMETRY_EXCEPTIONS&.clear
|
205
|
+
end
|
206
|
+
|
207
|
+
# Retrieves the base64 encoded events that were accumulated.
|
208
|
+
def gather_encoding_events
|
209
|
+
Contrast::TELEMETRY_BASE64_HASH&.each_value { |values| values.each { |event| queue << event } }
|
210
|
+
Contrast::TELEMETRY_BASE64_HASH&.clear
|
211
|
+
end
|
212
|
+
|
213
|
+
# Retrieves the IA cache events that were accumulated.
|
214
|
+
def gather_ia_cache_events
|
215
|
+
Contrast::TELEMETRY_IA_CACHE&.each_value { |values| values.each { |event| queue << event } }
|
216
|
+
Contrast::TELEMETRY_IA_CACHE&.clear
|
217
|
+
end
|
192
218
|
end
|
193
219
|
end
|
194
220
|
end
|
@@ -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_encoding_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 Base64Hash < 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 base64 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 events [Contrast::Agent::Telemetry::InputAnalysisEncodingEvent]
|
49
|
+
def valid_event? events
|
50
|
+
events&.all?(Contrast::Agent::Telemetry::InputAnalysisEncodingEvent)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -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)
|
@@ -16,9 +16,10 @@ module Contrast
|
|
16
16
|
# is the same.
|
17
17
|
CYPHER = CHARS.chars.shuffle.join.cs__freeze
|
18
18
|
VERSION_MATCH = '[^0-9].-'
|
19
|
+
RUBY_EXT = /\.(?:rb|gemspec)$/i
|
19
20
|
|
20
21
|
# List of known places after witch a user name might appear:
|
21
|
-
KNOWN_DIRS = %w[app application project projects git github users home user].cs__freeze
|
22
|
+
KNOWN_DIRS = %w[app application lib project projects git github users home user].cs__freeze
|
22
23
|
|
23
24
|
class << self
|
24
25
|
# Returns paths for known gems.
|
@@ -65,8 +66,8 @@ module Contrast
|
|
65
66
|
name.tr(VERSION_MATCH, Contrast::Utils::ObjectShare::EMPTY_STRING).downcase)
|
66
67
|
|
67
68
|
obscure(name)
|
68
|
-
# obscure username (next dir in line)
|
69
|
-
obscure(dirs[idx + 1]) if dirs[idx + 1]
|
69
|
+
# obscure username (next dir in line), skip if it's a file name.
|
70
|
+
obscure(dirs[idx + 1]) if dirs[idx + 1] && (dirs[idx + 1] !~ RUBY_EXT)
|
70
71
|
end
|
71
72
|
cypher = dirs.join(Contrast::Utils::ObjectShare::SLASH)
|
72
73
|
return cypher if cypher
|
@@ -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
|
|
@@ -12,7 +12,7 @@ module Contrast
|
|
12
12
|
# Gets info about the instrumented application required to build unique identifiers,
|
13
13
|
# used in the agent's Telemetry.
|
14
14
|
module Identifier
|
15
|
-
|
15
|
+
MAC_REGEXP = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.cs__freeze
|
16
16
|
LINUX_OS_REG = /hwaddr=.*?(([A-F0-9]{2}:){5}[A-F0-9]{2})/im.cs__freeze
|
17
17
|
MAC_OS_PRIMARY = 'en0'.cs__freeze
|
18
18
|
LINUX_PRIMARY = 'enp'.cs__freeze
|
@@ -87,7 +87,7 @@ module Contrast
|
|
87
87
|
mac = retrieve_mac(addr)
|
88
88
|
next unless mac
|
89
89
|
|
90
|
-
result = mac if mac
|
90
|
+
result = mac if mac.match?(MAC_REGEXP)
|
91
91
|
break if result
|
92
92
|
end
|
93
93
|
result
|
@@ -118,33 +118,20 @@ module Contrast
|
|
118
118
|
nil
|
119
119
|
end
|
120
120
|
|
121
|
-
# Returns array of network interfaces.
|
122
|
-
# This is OS dependent search.
|
121
|
+
# Returns array of network interfaces belonging to the expected pfamily of this OS.
|
123
122
|
#
|
124
|
-
# @return interfaces [Array]
|
125
|
-
# Socket::Ifaddr - represents a result of getifaddrs().
|
123
|
+
# @return interfaces [Array<Socket::Ifaddr>]
|
126
124
|
def interfaces
|
127
|
-
@_interfaces ||=
|
128
|
-
|
129
|
-
return @_interfaces unless @_interfaces.empty?
|
125
|
+
@_interfaces ||= Socket.getifaddrs.select { |interface| interface.addr&.pfamily == check_family }
|
126
|
+
end
|
130
127
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
check_family = 18 if Contrast::Utils::OS.mac?
|
139
|
-
check_family = 17 if Contrast::Utils::OS.linux?
|
140
|
-
if arr[idx].addr.pfamily != check_family
|
141
|
-
idx += 1
|
142
|
-
next
|
143
|
-
end
|
144
|
-
@_interfaces << arr[idx]
|
145
|
-
idx += 1
|
146
|
-
end
|
147
|
-
@_interfaces
|
128
|
+
# We need only network adapters MACs. Checking for pfamily of every socket address:
|
129
|
+
# 18 for Mac OS and 17 for Linux. Family should be an address family such as: :INET, :INET6, :UNIX, etc.
|
130
|
+
# It corresponds to the Addrinfo.pfamily value.
|
131
|
+
#
|
132
|
+
# @return [Integer]
|
133
|
+
def check_family
|
134
|
+
@_check_family ||= Contrast::Utils::OS.mac? ? 18 : 17
|
148
135
|
end
|
149
136
|
end
|
150
137
|
end
|
@@ -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
|