contrast-agent 7.1.0 → 7.2.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/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 }")
|