contrast-agent 7.1.0 → 7.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/extconf_common.rb +88 -14
- data/lib/contrast/agent/assess/policy/source_method.rb +13 -4
- data/lib/contrast/agent/assess/policy/trigger_method.rb +12 -18
- data/lib/contrast/agent/excluder/excluder.rb +64 -31
- data/lib/contrast/agent/protect/rule/base.rb +4 -6
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +1 -1
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +2 -2
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +1 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +2 -2
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +1 -1
- data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +1 -1
- data/lib/contrast/agent/protect/rule/utils/filters.rb +6 -6
- data/lib/contrast/agent/protect/rule/xxe/xxe.rb +1 -1
- data/lib/contrast/agent/reporting/client/interface.rb +132 -0
- data/lib/contrast/agent/reporting/client/interface_base.rb +27 -0
- data/lib/contrast/agent/reporting/connection_status.rb +0 -1
- data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +6 -4
- data/lib/contrast/agent/reporting/reporter.rb +11 -26
- data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +47 -6
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +40 -31
- data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +144 -0
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +35 -13
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_mode.rb +14 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +11 -11
- data/lib/contrast/agent/request/request.rb +27 -12
- data/lib/contrast/agent/telemetry/base.rb +18 -19
- data/lib/contrast/agent/telemetry/exception/obfuscate.rb +97 -0
- data/lib/contrast/agent/telemetry/exception.rb +1 -0
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/config/sources.rb +6 -5
- data/lib/contrast/components/settings.rb +9 -0
- data/lib/contrast/config/diagnostics/source_config_value.rb +5 -1
- data/lib/contrast/config/diagnostics/tools.rb +4 -4
- data/lib/contrast/config/validate.rb +2 -2
- data/lib/contrast/configuration.rb +11 -19
- data/lib/contrast/framework/grape/support.rb +1 -2
- data/lib/contrast/framework/manager.rb +17 -8
- data/lib/contrast/framework/rack/support.rb +99 -1
- data/lib/contrast/framework/rails/support.rb +1 -2
- data/lib/contrast/framework/sinatra/support.rb +1 -2
- data/lib/contrast/logger/aliased_logging.rb +18 -9
- data/lib/contrast/utils/hash_utils.rb +21 -2
- data/lib/contrast/utils/request_utils.rb +14 -0
- data/resources/assess/policy.json +11 -0
- metadata +6 -3
- data/lib/contrast/agent/reporting/input_analysis/details/bot_blocker_details.rb +0 -27
@@ -0,0 +1,144 @@
|
|
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
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
module Reporting
|
7
|
+
# Module to house the Resend logic, when there is no response from TS.
|
8
|
+
module Resend
|
9
|
+
# How manny times the client will try to rescue from above error before raising an
|
10
|
+
# error to reflect the events.
|
11
|
+
#
|
12
|
+
RESCUE_ATTEMPTS = 3
|
13
|
+
TIMEOUT = 5
|
14
|
+
RETRY_ERRORS = [
|
15
|
+
Net::OpenTimeout, Net::ReadTimeout, ArgumentError, EOFError, OpenSSL::SSL::SSLError, Resolv::ResolvError,
|
16
|
+
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ESHUTDOWN, Errno::EHOSTDOWN, NameError,
|
17
|
+
Errno::EHOSTUNREACH, Errno::EISCONN, Errno::ECONNABORTED, Errno::ENETRESET, Errno::ENETUNREACH, SocketError,
|
18
|
+
NameError, NoMethodError, Timeout::Error, RuntimeError
|
19
|
+
].cs__freeze
|
20
|
+
RESENDING_MESSAGE = '[Reporter] Resending message...'
|
21
|
+
END_RESENDING_MESSAGE = '[Reporter] Reporter tried to resend event and received error:'
|
22
|
+
|
23
|
+
# This method is mainly used in inside error handling and startup mesasges sending.
|
24
|
+
#
|
25
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent] event to resend
|
26
|
+
# @param connection [Net::HTTP] open connection
|
27
|
+
# @param error [StandardError] error that was raised
|
28
|
+
# @return [Net::HTTPResponse, nil] response from TS
|
29
|
+
def try_resend event, connection, error
|
30
|
+
# The timeout for agent to sleep is set here, we don't need to raise an error,
|
31
|
+
# but also 15 min is a long time to wait for example if event is startup message.
|
32
|
+
# Override timeout
|
33
|
+
if Contrast::Agent::Reporting::ReporterClientUtils::STARTUP_EVENTS.include?(event.cs__class)
|
34
|
+
response_handler.suspend_reporting(RESENDING_MESSAGE,
|
35
|
+
TIMEOUT,
|
36
|
+
error,
|
37
|
+
event: event,
|
38
|
+
log_error: false,
|
39
|
+
startup: true)
|
40
|
+
end
|
41
|
+
mode.enter_resend_mode
|
42
|
+
handle_resend(event, connection)
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method will handle the error and will decide if we need to resend the event
|
46
|
+
#
|
47
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
48
|
+
# @param connection [Net::HTTP] open connection
|
49
|
+
# @return [Net::HTTPResponse, nil] response from TS
|
50
|
+
def handle_resend event, connection
|
51
|
+
if sleep?
|
52
|
+
logger.debug("[Reporter] sleeping for #{ TIMEOUT } seconds before resending...")
|
53
|
+
Thread.current.send(:sleep, timeout)
|
54
|
+
wake_up
|
55
|
+
end
|
56
|
+
response = mode.status == mode.resending ? send_event(event, connection) : nil
|
57
|
+
response_success! if response
|
58
|
+
response
|
59
|
+
end
|
60
|
+
|
61
|
+
# Handles errors that occurs before the ResponseHandler do not have a response, or response
|
62
|
+
# code to hande. This Errors requires different handling, because most of errors here
|
63
|
+
# occurs when the response cannot be read, there is open ssl error, or a certain Timeout
|
64
|
+
# is reached.
|
65
|
+
#
|
66
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
67
|
+
# One of the DTMs valid for the event field of Contrast::Api::Dtm::Message
|
68
|
+
# @param error [StandardError]
|
69
|
+
# @param connection [Net::HTTP] open connection
|
70
|
+
# @return nil [NilClass] to be passed as response
|
71
|
+
def handle_response_error event, connection, error
|
72
|
+
return end_of_rescue(event, error) if mode.resend.rescue_attempts >= RESCUE_ATTEMPTS
|
73
|
+
|
74
|
+
if RETRY_ERRORS.include?(error.cs__class)
|
75
|
+
mode.resend.increase_rescue_attempts
|
76
|
+
try_resend(event, connection, error)
|
77
|
+
else
|
78
|
+
end_of_rescue(event, error)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# End of rescue attempts.
|
83
|
+
#
|
84
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
85
|
+
# @param error [StandardError]
|
86
|
+
# @return [NilClass]
|
87
|
+
def end_of_rescue event, error
|
88
|
+
if Contrast::Agent::Reporting::ReporterClientUtils::STARTUP_EVENTS.include?(event.cs__class)
|
89
|
+
# Agent didn't send it's startup events, There will be reporting errors, disable reporting.
|
90
|
+
disable_reporting(event, error)
|
91
|
+
else
|
92
|
+
# There is an error in one of the reported messages, log that error and continue.
|
93
|
+
logger.error(END_RESENDING_MESSAGE,
|
94
|
+
resend_attempts: Contrast::Agent::Reporting::Resend::RESCUE_ATTEMPTS,
|
95
|
+
connection_error: error,
|
96
|
+
client: Contrast::Agent::Reporting::ReporterClient::SERVICE_NAME,
|
97
|
+
event_id: event&.__id__,
|
98
|
+
event_type: event&.cs__class&.cs__name)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Disable reporting when there is an error that cannot be handled.
|
103
|
+
#
|
104
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
105
|
+
# @param error [StandardError]
|
106
|
+
# @return [NilClass]
|
107
|
+
def disable_reporting event, error
|
108
|
+
logger.error('[Agent] Disabling Reporting...', error: error.message, event_type: event.cs__class&.cs__name)
|
109
|
+
Contrast::AGENT.disable!
|
110
|
+
end
|
111
|
+
|
112
|
+
# Methods that will initialize the resending state.
|
113
|
+
class Status
|
114
|
+
# RESEND_STARTUP_EVENTS = 4
|
115
|
+
|
116
|
+
def initialize
|
117
|
+
@_rescue_attempts = 0
|
118
|
+
end
|
119
|
+
|
120
|
+
# return how many times the client has tried to rescue from error.
|
121
|
+
#
|
122
|
+
# @return [Integer]
|
123
|
+
def rescue_attempts
|
124
|
+
@_rescue_attempts ||= 0
|
125
|
+
end
|
126
|
+
|
127
|
+
# Reset Error rescues attempts.
|
128
|
+
#
|
129
|
+
# @return [Integer]
|
130
|
+
def reset_rescue_attempts
|
131
|
+
@_rescue_attempts = 0
|
132
|
+
end
|
133
|
+
|
134
|
+
# Increase the Error rescues attempts.
|
135
|
+
#
|
136
|
+
# @return [Integer]
|
137
|
+
def increase_rescue_attempts
|
138
|
+
@_rescue_attempts += 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -40,7 +40,7 @@ module Contrast
|
|
40
40
|
assess_on: ::Contrast::ASSESS.enabled?)
|
41
41
|
response
|
42
42
|
rescue StandardError => e
|
43
|
-
logger.error('Unable to process response from TeamServer', e)
|
43
|
+
logger.error('[Reporter] Unable to process response from TeamServer', e)
|
44
44
|
nil
|
45
45
|
end
|
46
46
|
|
@@ -63,11 +63,45 @@ module Contrast
|
|
63
63
|
@_mode ||= Contrast::Agent::Reporting::ResponseHandlerMode.new
|
64
64
|
end
|
65
65
|
|
66
|
+
# Puts the reporting service to sleep
|
67
|
+
def put_to_sleep
|
68
|
+
@_sleep = true
|
69
|
+
end
|
70
|
+
|
66
71
|
# Wakes the reporting service
|
67
72
|
def wake_up
|
68
73
|
@_sleep = false
|
69
74
|
end
|
70
75
|
|
76
|
+
# Suspend the reporter and try again in time. If not set
|
77
|
+
# the timeout is set to 15 min As default:
|
78
|
+
#
|
79
|
+
# @param message [String] Message to log.
|
80
|
+
# @param timeout [Integer,nil] The timeout to wait and retry after.
|
81
|
+
# @param error_message [String, nil] Error message if any received.
|
82
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent, nil] The event sent to TeamServer if set
|
83
|
+
# @param log_error [Boolean] Whether to log the error or not.
|
84
|
+
# @param startup [Boolean] Whether this is a startup error or not.
|
85
|
+
def suspend_reporting message, timeout, error_message, event: nil, log_error: true, startup: false
|
86
|
+
@_timeout = timeout || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT
|
87
|
+
if log_error
|
88
|
+
log_error_msg(message,
|
89
|
+
timeout: @_timeout,
|
90
|
+
error_message: error_message || 'none',
|
91
|
+
event_id: event.nil? ? 'none' : event&.__id__,
|
92
|
+
event_type: event.nil? ? 'none' : event&.cs__class&.cs__name)
|
93
|
+
end
|
94
|
+
if startup
|
95
|
+
logger.info(message,
|
96
|
+
connection_error: error_message,
|
97
|
+
client: Contrast::Agent::Reporting::ReporterClient::SERVICE_NAME,
|
98
|
+
event_id: event&.__id__,
|
99
|
+
timeout: timeout,
|
100
|
+
event_type: event&.cs__class&.cs__name)
|
101
|
+
end
|
102
|
+
put_to_sleep
|
103
|
+
end
|
104
|
+
|
71
105
|
private
|
72
106
|
|
73
107
|
# Handles the errors code received from TS and takes appropriate action.
|
@@ -93,18 +127,6 @@ module Contrast
|
|
93
127
|
end
|
94
128
|
end
|
95
129
|
|
96
|
-
# Suspend the reporter and try again in time. If not set
|
97
|
-
# the timeout is set to 15 min As default:
|
98
|
-
#
|
99
|
-
# @param message [String] Message to log.
|
100
|
-
# @param timeout [Integer,nil] The timeout to wait and retry after.
|
101
|
-
# @param error_message [String, nil] Error message if any received.
|
102
|
-
def suspend_reporting message, timeout, error_message
|
103
|
-
@_timeout = timeout || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT
|
104
|
-
log_error_msg(message, timeout: @_timeout, error_message: error_message || 'none')
|
105
|
-
@_sleep = true
|
106
|
-
end
|
107
|
-
|
108
130
|
# Log what we've received.
|
109
131
|
#
|
110
132
|
# @param message [String] Message to log
|
@@ -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 'contrast/agent/reporting/reporting_utilities/resend'
|
5
|
+
|
4
6
|
module Contrast
|
5
7
|
module Agent
|
6
8
|
module Reporting
|
@@ -44,6 +46,17 @@ module Contrast
|
|
44
46
|
@_status ||= running
|
45
47
|
end
|
46
48
|
|
49
|
+
# Resend Status tracker
|
50
|
+
#
|
51
|
+
# @return [Contrast::Agent::Reporting::Resend::Status]
|
52
|
+
def resend
|
53
|
+
@_resend ||= Contrast::Agent::Reporting::Resend::Status.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def enter_resend_mode
|
57
|
+
@_status = resending
|
58
|
+
end
|
59
|
+
|
47
60
|
# Set current mode.
|
48
61
|
#
|
49
62
|
# @return [Symbol] Reporter's client and Response Handler
|
@@ -54,7 +67,7 @@ module Contrast
|
|
54
67
|
|
55
68
|
# Reset mode
|
56
69
|
#
|
57
|
-
def
|
70
|
+
def enter_run_mode
|
58
71
|
@_status = running
|
59
72
|
end
|
60
73
|
end
|
@@ -54,6 +54,16 @@ module Contrast
|
|
54
54
|
@_last_application_modified
|
55
55
|
end
|
56
56
|
|
57
|
+
# Cease reporting about this application
|
58
|
+
#
|
59
|
+
# @param message [String] Message to log
|
60
|
+
# @param info_hash [Hash] information about the context to log.
|
61
|
+
def stop_reporting message, info_hash
|
62
|
+
Contrast::Agent.reporter&.stop!
|
63
|
+
log_error_msg(message, info_hash)
|
64
|
+
::Contrast::AGENT.disable!
|
65
|
+
end
|
66
|
+
|
57
67
|
private
|
58
68
|
|
59
69
|
# check if response code is valid before analyze it
|
@@ -127,7 +137,7 @@ module Contrast
|
|
127
137
|
# log, suspend, disable:
|
128
138
|
if mode == @_mode.running
|
129
139
|
log_error_msg(message,
|
130
|
-
|
140
|
+
response_id: response.__id__,
|
131
141
|
request: Contrast::Agent::REQUEST_TRACKER.current&.request&.type,
|
132
142
|
error_message: error_message || 'none',
|
133
143
|
auth_error: auth_error || 'none')
|
@@ -176,16 +186,6 @@ module Contrast
|
|
176
186
|
end
|
177
187
|
end
|
178
188
|
|
179
|
-
# Cease reporting about this application
|
180
|
-
#
|
181
|
-
# @param message [String] Message to log
|
182
|
-
# @param info_hash [Hash] information about the context to log.
|
183
|
-
def stop_reporting message, info_hash
|
184
|
-
Contrast::Agent.reporter&.stop!
|
185
|
-
log_error_msg(message, info_hash)
|
186
|
-
::Contrast::AGENT.disable!
|
187
|
-
end
|
188
|
-
|
189
189
|
# Applies the settings from the TS response
|
190
190
|
#
|
191
191
|
# @param response [Contrast::Agent::Reporting::Response]
|
@@ -10,6 +10,7 @@ require 'contrast/utils/hash_digest'
|
|
10
10
|
require 'contrast/components/logger'
|
11
11
|
require 'contrast/components/scope'
|
12
12
|
require 'contrast/utils/request_utils'
|
13
|
+
require 'contrast/utils/duck_utils'
|
13
14
|
|
14
15
|
module Contrast
|
15
16
|
module Agent
|
@@ -24,12 +25,7 @@ module Contrast
|
|
24
25
|
|
25
26
|
extend Forwardable
|
26
27
|
|
27
|
-
|
28
|
-
LAST_REST_TOKEN = %r{/\d+$}.cs__freeze
|
29
|
-
INNER_NUMBER_MARKER = '/{n}/'
|
30
|
-
LAST_NUMBER_MARKER = '/{n}'
|
31
|
-
STATIC_SUFFIXES = /\.(?:js|css|jpeg|jpg|gif|png|ico|woff|svg|pdf|eot|ttf|jar)$/i.cs__freeze
|
32
|
-
MEDIA_TYPE_MARKERS = %w[image/ text/css text/javascript].cs__freeze
|
28
|
+
EMPTY_PATH = '/'
|
33
29
|
|
34
30
|
# @return [Rack::Request] The passed to the Agent RackRequest to be wrapped.
|
35
31
|
attr_reader :rack_request
|
@@ -75,12 +71,31 @@ module Contrast
|
|
75
71
|
def normalized_uri
|
76
72
|
@_normalized_uri ||= begin
|
77
73
|
path = rack_request.path_info || rack_request.path.to_s
|
78
|
-
path =
|
79
|
-
|
80
|
-
|
81
|
-
uri =
|
82
|
-
|
83
|
-
uri.
|
74
|
+
path = EMPTY_PATH if Contrast::Utils::DuckUtils.empty_duck?(path)
|
75
|
+
|
76
|
+
# /foo/bar;jsessionid=123 => /foo/bar
|
77
|
+
uri = path.split(Contrast::Utils::ObjectShare::SEMICOLON)[0]
|
78
|
+
# /foo/bar?query_string=123 => /foo/bar
|
79
|
+
uri = uri.split(Contrast::Utils::ObjectShare::QUESTION_MARK)[0]
|
80
|
+
|
81
|
+
# Replace with tokens:
|
82
|
+
# NUM_ => '/{n}/'
|
83
|
+
# ID_ => '{ID}'
|
84
|
+
#
|
85
|
+
# replace UUIDs: /123e4567-e89b-42d3-a456-556642440000/ => /{ID}/
|
86
|
+
uri.gsub!(UUID_PATTERN, ID_)
|
87
|
+
# replace hash patterns: /6f1ed002ab5595859014ebf0951522d9/ => /{ID}/
|
88
|
+
uri.gsub!(HASH_PATTERN, ID_)
|
89
|
+
# replace windows SID: /S-1-5-21-1843332746-572796286-2118856591-1000/ => /{ID}/
|
90
|
+
uri.gsub!(WIN_PATTERN, ID_)
|
91
|
+
# replace interior number tokens: /123/ => /{n}/
|
92
|
+
uri.gsub!(NUM_PATTERN, NUM_)
|
93
|
+
# replace last number tokens: /123 => /{n}
|
94
|
+
uri.gsub!(END_PATTERN, NUM_[0..-2])
|
95
|
+
uri
|
96
|
+
rescue StandardError => e
|
97
|
+
logger.error('error normalizing uri', error: e, backtrace: e.backtrace)
|
98
|
+
EMPTY_PATH
|
84
99
|
end
|
85
100
|
end
|
86
101
|
|
@@ -8,6 +8,7 @@ require 'contrast/agent/thread/worker_thread'
|
|
8
8
|
require 'contrast/agent/telemetry/telemetry'
|
9
9
|
require 'contrast/agent/telemetry/exception'
|
10
10
|
require 'contrast/utils/job_servers_running'
|
11
|
+
require 'contrast/agent/reporting/client/interface'
|
11
12
|
|
12
13
|
module Contrast
|
13
14
|
module Agent
|
@@ -96,11 +97,7 @@ module Contrast
|
|
96
97
|
end
|
97
98
|
|
98
99
|
def client
|
99
|
-
@_client ||= Contrast::Agent::Telemetry::
|
100
|
-
end
|
101
|
-
|
102
|
-
def connection
|
103
|
-
@_connection ||= client.initialize_connection(URL)
|
100
|
+
@_client ||= Contrast::Agent::Reporting::Telemetry::Interface.new
|
104
101
|
end
|
105
102
|
|
106
103
|
def attempt_to_start?
|
@@ -148,16 +145,24 @@ module Contrast
|
|
148
145
|
Contrast::TELEMETRY_EXCEPTIONS&.clear
|
149
146
|
end
|
150
147
|
|
151
|
-
def request_with_response event
|
152
|
-
client.handle_response(client.send_request(event, connection))
|
153
|
-
end
|
154
|
-
|
155
148
|
private
|
156
149
|
|
157
150
|
def queue
|
158
151
|
@_queue ||= Queue.new
|
159
152
|
end
|
160
153
|
|
154
|
+
# Starts sending Telemetry messages.
|
155
|
+
def process_event event
|
156
|
+
logger.debug('[Telemetry] This is the current processed event', event)
|
157
|
+
if (sleep_time = client.request_with_response(event))
|
158
|
+
return sleep(sleep_time)
|
159
|
+
end
|
160
|
+
|
161
|
+
logger.debug('[Telemetry] Retrying to process event', event)
|
162
|
+
retry_sleep_time = client.request_with_response(event)
|
163
|
+
sleep(retry_sleep_time) unless retry_sleep_time.nil?
|
164
|
+
end
|
165
|
+
|
161
166
|
# It is recommended that implementations send a single payload of general metrics every 3 hours, starting from
|
162
167
|
# implementation startup. This returns a thread configured to do so.
|
163
168
|
#
|
@@ -165,22 +170,16 @@ module Contrast
|
|
165
170
|
def create_thread
|
166
171
|
Contrast::Agent::Thread.new do
|
167
172
|
loop do
|
168
|
-
next unless client
|
173
|
+
next unless client.connected?
|
169
174
|
break unless attempt_to_start?
|
170
175
|
|
171
176
|
# Start pushing exceptions to queue for reporting.
|
172
|
-
Contrast::TELEMETRY_EXCEPTIONS
|
173
|
-
Contrast::TELEMETRY_EXCEPTIONS
|
177
|
+
Contrast::TELEMETRY_EXCEPTIONS&.each_value { |value| queue << value }
|
178
|
+
Contrast::TELEMETRY_EXCEPTIONS&.clear
|
174
179
|
until queue.empty?
|
175
180
|
event = queue.pop
|
176
181
|
begin
|
177
|
-
|
178
|
-
if (sleep_time = request_with_response(event))
|
179
|
-
sleep(sleep_time)
|
180
|
-
logger.debug('[Telemetry] Retrying to process event', event)
|
181
|
-
retry_sleep_time = request_with_response(event)
|
182
|
-
sleep(retry_sleep_time) unless retry_sleep_time.nil?
|
183
|
-
end
|
182
|
+
process_event(event)
|
184
183
|
rescue StandardError => e
|
185
184
|
logger.error('[Telemetry] Could not send message to service from telemetry queue.', e)
|
186
185
|
stop!
|
@@ -0,0 +1,97 @@
|
|
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/duck_utils'
|
5
|
+
require 'contrast/utils/object_share'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Telemetry
|
10
|
+
module Exception
|
11
|
+
# Obfuscate sensitive user data before building exception.
|
12
|
+
module Obfuscate
|
13
|
+
CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.cs__freeze
|
14
|
+
# This will generate different chars for each agent startup but will be constant
|
15
|
+
# for current run and will make identical obfuscation to be compared if given type
|
16
|
+
# is the same.
|
17
|
+
CYPHER = CHARS.chars.shuffle.join.cs__freeze
|
18
|
+
VERSION_MATCH = '[^0-9].-'
|
19
|
+
|
20
|
+
# 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
|
+
|
23
|
+
class << self
|
24
|
+
# Returns paths for known gems.
|
25
|
+
#
|
26
|
+
# @return [Array<Regexp>] known paths
|
27
|
+
def known_paths
|
28
|
+
@_known_paths ||= KNOWN_DIRS.map do |name|
|
29
|
+
to_regexp(name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Obfuscate a type and replace it with random characters.
|
34
|
+
#
|
35
|
+
# @param path [String] the StackFrame type to obfuscate
|
36
|
+
# @return [String] obfuscated type
|
37
|
+
def obfuscate_path path
|
38
|
+
return path if Contrast::Utils::DuckUtils.empty_duck?(path)
|
39
|
+
|
40
|
+
cypher(path)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Transforms string to regexp
|
46
|
+
#
|
47
|
+
# @param string [String]
|
48
|
+
def to_regexp string
|
49
|
+
/#{ string }/
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add cypher to path to make it obscure, but unique enough for
|
53
|
+
# comparisons. Mutates original or duplicate if frozen string.
|
54
|
+
#
|
55
|
+
# @param string [String] string to be transformed.
|
56
|
+
# @return [String]
|
57
|
+
def cypher string
|
58
|
+
cypher = string.cs__frozen? ? string.dup : string
|
59
|
+
dirs = cypher.split(Contrast::Utils::ObjectShare::SLASH)
|
60
|
+
return string unless dirs.cs__is_a?(Array)
|
61
|
+
|
62
|
+
dirs.each_with_index do |name, idx|
|
63
|
+
next if Contrast::Utils::DuckUtils.empty_duck?(name)
|
64
|
+
next unless match_known(known_paths,
|
65
|
+
name.tr(VERSION_MATCH, Contrast::Utils::ObjectShare::EMPTY_STRING).downcase)
|
66
|
+
|
67
|
+
obscure(name)
|
68
|
+
# obscure username (next dir in line)
|
69
|
+
obscure(dirs[idx + 1]) if dirs[idx + 1]
|
70
|
+
end
|
71
|
+
cypher = dirs.join(Contrast::Utils::ObjectShare::SLASH)
|
72
|
+
return cypher if cypher
|
73
|
+
|
74
|
+
Contrast::Utils::ObjectShare::EMPTY_STRING
|
75
|
+
rescue StandardError
|
76
|
+
Contrast::Utils::ObjectShare::EMPTY_STRING
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param known [Array<Regexp>] Array of regexp to match against
|
80
|
+
# @param type [String] type to check
|
81
|
+
def match_known known, type
|
82
|
+
known.any? { |regexp| type =~ regexp }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Replaces chars in name.
|
86
|
+
#
|
87
|
+
# @param [string] name
|
88
|
+
# @return [String, nil]
|
89
|
+
def obscure name
|
90
|
+
name.to_s.tr!(CHARS, CYPHER)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -17,3 +17,4 @@ require 'contrast/agent/telemetry/exception/stack_frame'
|
|
17
17
|
require 'contrast/agent/telemetry/exception/message_exception'
|
18
18
|
require 'contrast/agent/telemetry/exception/message'
|
19
19
|
require 'contrast/agent/telemetry/exception/event'
|
20
|
+
require 'contrast/agent/telemetry/exception/obfuscate'
|
@@ -11,14 +11,15 @@ module Contrast
|
|
11
11
|
# This component encapsulates storing the source for each entry in the config,
|
12
12
|
# so that we can report on where the value was set from.
|
13
13
|
class Sources
|
14
|
-
ENVIRONMENT_VARIABLE = '
|
15
|
-
COMMAND_LINE = '
|
16
|
-
CONTRAST_UI = '
|
17
|
-
DEFAULT_VALUE = '
|
14
|
+
ENVIRONMENT_VARIABLE = 'ENVIRONMENT_VARIABLE'
|
15
|
+
COMMAND_LINE = 'COMMAND_LINE'
|
16
|
+
CONTRAST_UI = 'CONTRAST_UI'
|
17
|
+
DEFAULT_VALUE = 'DEFAULT_VALUE'
|
18
|
+
APP_CONFIGURATION_FILE = 'USER_CONFIGURATION_FILE'
|
18
19
|
# Order matters for the Configurations files. This is read when Agent starts up and will always go
|
19
20
|
# through the YAML as priority.
|
20
21
|
# Do not change the order!
|
21
|
-
|
22
|
+
APP_CONFIGURATION_EXTENSIONS = %w[yaml yml].cs__freeze
|
22
23
|
|
23
24
|
# @return [Hash]
|
24
25
|
attr_reader :data
|
@@ -5,6 +5,7 @@ require 'contrast/agent/excluder/excluder'
|
|
5
5
|
require 'contrast/agent/reporting/settings/sensitive_data_masking'
|
6
6
|
require 'contrast/components/config'
|
7
7
|
require 'contrast/components/logger'
|
8
|
+
require 'contrast/utils/duck_utils'
|
8
9
|
|
9
10
|
module Contrast
|
10
11
|
module Components
|
@@ -219,6 +220,14 @@ module Contrast
|
|
219
220
|
exclusions.input_exclusions.each do |exclusion|
|
220
221
|
matchers << Contrast::Agent::ExclusionMatcher.new(exclusion)
|
221
222
|
end
|
223
|
+
# Do not populate the matchers unless we have any. There are certain checks in
|
224
|
+
# SourceMethod that will safe-guard return if there are no exclusions received.
|
225
|
+
# The matching operation is expensive, and the excluder calls are made for each
|
226
|
+
# source, and we do not want to check for exclusions if they are empty. This is
|
227
|
+
# probably redundant as all exclusions default to empty, but will save useless
|
228
|
+
# new object creation at very least.
|
229
|
+
return if Contrast::Utils::DuckUtils.empty_duck?(matchers)
|
230
|
+
|
222
231
|
@excluder = Contrast::Agent::Excluder.new(matchers)
|
223
232
|
end
|
224
233
|
|
@@ -41,7 +41,11 @@ module Contrast
|
|
41
41
|
# @param source [String] name of the source file yaml | yml
|
42
42
|
# @return [Array<String>, nil]
|
43
43
|
def assign_filename source
|
44
|
-
Contrast::Components::Config::Sources::
|
44
|
+
Contrast::Components::Config::Sources::APP_CONFIGURATION_EXTENSIONS.each do |type|
|
45
|
+
# We use the source to transfer the file's name from the mapping of the extensions.
|
46
|
+
# This is done b/c the user_configuration file has a second field to be filled,
|
47
|
+
# and other sources don't. Transfer the source as filename and set the default value
|
48
|
+
# for it later when we find the sources.
|
45
49
|
instance_variable_set(:@filename, source) if source.include?(".#{ type.downcase }")
|
46
50
|
end
|
47
51
|
end
|
@@ -164,10 +164,10 @@ module Contrast
|
|
164
164
|
# For files we keep the whole path as source.
|
165
165
|
source = Contrast::CONFIG.sources.get(new_effective_value.canonical_name)
|
166
166
|
new_effective_value.assign_filename(source)
|
167
|
-
new_source = if source.include?(Contrast::Config::LocalSourceValue::YAML_EXT)
|
168
|
-
|
169
|
-
|
170
|
-
Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE
|
167
|
+
new_source = if source.include?(Contrast::Config::LocalSourceValue::YAML_EXT) ||
|
168
|
+
source.include?(Contrast::Config::LocalSourceValue::YML_EXT)
|
169
|
+
|
170
|
+
Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE
|
171
171
|
else
|
172
172
|
Contrast::Components::Config::Sources::DEFAULT_VALUE
|
173
173
|
end
|
@@ -108,9 +108,9 @@ module Contrast
|
|
108
108
|
|
109
109
|
def test_connection reporter
|
110
110
|
puts(" Connection failed #{ FAIL }") unless reporter
|
111
|
-
connection = reporter.
|
111
|
+
connection = reporter.client.send(:reporting_connection)
|
112
112
|
abort("Failed to Initialize Connection please check error logs for details #{ FAIL } ") unless connection
|
113
|
-
abort('Failed to Start Client please check error logs for details') unless reporter.client.startup
|
113
|
+
abort('Failed to Start Client please check error logs for details') unless reporter.client.startup
|
114
114
|
last_response = reporter.client.response_handler.last_response_code
|
115
115
|
if last_response.include?('40')
|
116
116
|
puts(" Last response code: #{ last_response } #{ FAIL }")
|