contrast-agent 4.13.1 → 4.14.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/.simplecov +1 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
- data/lib/contrast/agent/assess/policy/trigger_method.rb +44 -7
- data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/tagged.rb +51 -57
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
- data/lib/contrast/agent/metric_telemetry_event.rb +2 -2
- data/lib/contrast/agent/middleware.rb +5 -75
- data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
- data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
- data/lib/contrast/agent/patching/policy/patcher.rb +12 -8
- data/lib/contrast/agent/reporting/report.rb +21 -0
- data/lib/contrast/agent/reporting/reporter.rb +142 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
- data/lib/contrast/agent/request.rb +2 -81
- data/lib/contrast/agent/request_context.rb +4 -128
- data/lib/contrast/agent/request_context_extend.rb +138 -0
- data/lib/contrast/agent/response.rb +2 -73
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +39 -16
- data/lib/contrast/agent/static_analysis.rb +1 -1
- data/lib/contrast/agent/telemetry.rb +15 -7
- data/lib/contrast/agent/telemetry_event.rb +8 -9
- data/lib/contrast/agent/thread_watcher.rb +31 -5
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +15 -0
- data/lib/contrast/api/communication/connection_status.rb +10 -7
- data/lib/contrast/api/communication/messaging_queue.rb +37 -3
- data/lib/contrast/api/communication/response_processor.rb +15 -8
- data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
- data/lib/contrast/api/communication/socket.rb +6 -8
- data/lib/contrast/api/communication/socket_client.rb +29 -12
- data/lib/contrast/api/communication/speedracer.rb +37 -1
- data/lib/contrast/api/communication/tcp_socket.rb +4 -3
- data/lib/contrast/api/communication/unix_socket.rb +1 -0
- data/lib/contrast/api/decorators/finding.rb +45 -0
- data/lib/contrast/components/api.rb +56 -0
- data/lib/contrast/components/app_context.rb +10 -65
- data/lib/contrast/components/app_context_extend.rb +78 -0
- data/lib/contrast/components/base.rb +23 -0
- data/lib/contrast/components/config.rb +8 -8
- data/lib/contrast/components/contrast_service.rb +5 -0
- data/lib/contrast/components/sampling.rb +2 -2
- data/lib/contrast/config/agent_configuration.rb +1 -1
- data/lib/contrast/config/api_configuration.rb +9 -4
- data/lib/contrast/config/api_proxy_configuration.rb +14 -0
- data/lib/contrast/config/application_configuration.rb +2 -3
- data/lib/contrast/config/assess_configuration.rb +3 -3
- data/lib/contrast/config/base_configuration.rb +17 -28
- data/lib/contrast/config/certification_configuration.rb +15 -0
- data/lib/contrast/config/env_variables.rb +2 -9
- data/lib/contrast/config/heap_dump_configuration.rb +6 -6
- data/lib/contrast/config/inventory_configuration.rb +1 -5
- data/lib/contrast/config/protect_rule_configuration.rb +1 -1
- data/lib/contrast/config/request_audit_configuration.rb +18 -0
- data/lib/contrast/config/ruby_configuration.rb +6 -6
- data/lib/contrast/config/service_configuration.rb +1 -2
- data/lib/contrast/config.rb +0 -1
- data/lib/contrast/configuration.rb +1 -2
- data/lib/contrast/extension/assess/array.rb +5 -7
- data/lib/contrast/framework/manager.rb +8 -32
- data/lib/contrast/framework/manager_extend.rb +50 -0
- data/lib/contrast/framework/rails/railtie.rb +1 -1
- data/lib/contrast/framework/sinatra/support.rb +2 -1
- data/lib/contrast/logger/log.rb +8 -103
- data/lib/contrast/utils/assess/property/tagged_utils.rb +23 -0
- data/lib/contrast/utils/assess/tracking_util.rb +20 -15
- data/lib/contrast/utils/assess/trigger_method_utils.rb +1 -1
- data/lib/contrast/utils/class_util.rb +18 -14
- data/lib/contrast/utils/findings.rb +62 -0
- data/lib/contrast/utils/hash_digest.rb +10 -73
- data/lib/contrast/utils/hash_digest_extend.rb +86 -0
- data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
- data/lib/contrast/utils/heap_dump_util.rb +2 -65
- data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
- data/lib/contrast/utils/io_util.rb +1 -1
- data/lib/contrast/utils/log_utils.rb +108 -0
- data/lib/contrast/utils/middleware_utils.rb +87 -0
- data/lib/contrast/utils/net_http_base.rb +158 -0
- data/lib/contrast/utils/object_share.rb +1 -0
- data/lib/contrast/utils/request_utils.rb +88 -0
- data/lib/contrast/utils/response_utils.rb +97 -0
- data/lib/contrast/utils/substitution_utils.rb +167 -0
- data/lib/contrast/utils/tag_util.rb +9 -9
- data/lib/contrast/utils/telemetry.rb +4 -2
- data/lib/contrast/utils/telemetry_client.rb +90 -0
- data/lib/contrast/utils/telemetry_identifier.rb +17 -24
- data/ruby-agent.gemspec +5 -5
- metadata +48 -23
- data/lib/contrast/config/default_value.rb +0 -17
- data/lib/contrast/utils/requests_client.rb +0 -150
data/lib/contrast/agent.rb
CHANGED
|
@@ -29,6 +29,9 @@ require 'contrast/utils/os'
|
|
|
29
29
|
require 'contrast/utils/hash_digest'
|
|
30
30
|
require 'contrast/utils/invalid_configuration_util'
|
|
31
31
|
|
|
32
|
+
# Collect findings
|
|
33
|
+
require 'contrast/utils/findings'
|
|
34
|
+
|
|
32
35
|
# scoping
|
|
33
36
|
require 'contrast/agent/scope'
|
|
34
37
|
|
|
@@ -48,25 +51,37 @@ module Contrast
|
|
|
48
51
|
module Agent
|
|
49
52
|
# build a map for tracking the context of the current request
|
|
50
53
|
REQUEST_TRACKER = Contrast::Utils::ThreadTracker.new
|
|
54
|
+
FINDINGS = Contrast::Utils::Findings.new
|
|
51
55
|
|
|
56
|
+
# @return [Contrast::Framework::Manager]
|
|
52
57
|
def self.framework_manager
|
|
53
58
|
@_framework_manager ||= Contrast::Framework::Manager.new
|
|
54
59
|
end
|
|
55
60
|
|
|
61
|
+
# @return [nil, Contrast::Utils::HeapDumpUtil]
|
|
56
62
|
def self.heapdump_util
|
|
57
63
|
thread_watcher.heapdump_util
|
|
58
64
|
end
|
|
59
65
|
|
|
66
|
+
# @return [nil, Contrast::Api::Communication::MessagingQueue]
|
|
60
67
|
def self.messaging_queue
|
|
61
68
|
thread_watcher.messaging_queue
|
|
62
69
|
end
|
|
63
70
|
|
|
71
|
+
# @return [nil, Contrast::Agent::Telemetry]
|
|
64
72
|
def self.telemetry_queue
|
|
65
73
|
return unless thread_watcher.telemetry_queue
|
|
66
74
|
|
|
67
75
|
thread_watcher.telemetry_queue
|
|
68
76
|
end
|
|
69
77
|
|
|
78
|
+
# @return [nil, Contrast::Agent::Reporter]
|
|
79
|
+
def self.reporter_queue
|
|
80
|
+
return unless thread_watcher.reporter_queue
|
|
81
|
+
|
|
82
|
+
thread_watcher.reporter_queue
|
|
83
|
+
end
|
|
84
|
+
|
|
70
85
|
def self.thread_watcher
|
|
71
86
|
@_thread_watcher ||= Contrast::Agent::ThreadWatcher.new
|
|
72
87
|
end
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
module Contrast
|
|
5
5
|
module Api
|
|
6
6
|
module Communication
|
|
7
|
-
# Keeps track of the state of connections to
|
|
7
|
+
# Keeps track of the state of connections to SpeedRacer.
|
|
8
8
|
class ConnectionStatus
|
|
9
9
|
def initialize
|
|
10
10
|
@last_success = nil
|
|
@@ -12,25 +12,28 @@ module Contrast
|
|
|
12
12
|
@startup_messages_sent = false
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# Whether we have sent startup message to
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
# Whether we have sent startup message to SpeedRacer. True after successfully sending startup messages to
|
|
16
|
+
# SpeedRacer and reset to false if we lose connection.
|
|
17
|
+
#
|
|
18
|
+
# @return [Boolean]
|
|
18
19
|
def startup_messages_sent?
|
|
19
20
|
@startup_messages_sent
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
# The last message sent was successful
|
|
23
|
+
# The last message sent was successful or not
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean]
|
|
23
26
|
def connected?
|
|
24
27
|
@last_success && (@last_failure.nil? || @last_success > @last_failure)
|
|
25
28
|
end
|
|
26
29
|
|
|
27
|
-
# The current state of the
|
|
30
|
+
# The current state of the SpeedRacer is active with a successful message sent
|
|
28
31
|
def success!
|
|
29
32
|
@startup_messages_sent = true
|
|
30
33
|
@last_success = Time.now.to_f
|
|
31
34
|
end
|
|
32
35
|
|
|
33
|
-
# The
|
|
36
|
+
# The SpeedRacer may be in some sort of error state
|
|
34
37
|
def failure!
|
|
35
38
|
@startup_messages_sent = false
|
|
36
39
|
@last_failure = Time.now.to_f
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require 'contrast/components/logger'
|
|
5
5
|
require 'contrast/agent/worker_thread'
|
|
6
|
+
require 'contrast/agent/reporting/reporting_utilities/audit'
|
|
6
7
|
|
|
7
8
|
module Contrast
|
|
8
9
|
module Api
|
|
@@ -15,23 +16,46 @@ module Contrast
|
|
|
15
16
|
|
|
16
17
|
def initialize
|
|
17
18
|
@speedracer = Contrast::Api::Communication::Speedracer.new
|
|
19
|
+
@audit = Contrast::Agent::Reporting::Audit.new if ::Contrast::API.request_audit_enable
|
|
18
20
|
super
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
# Use this to bypass the messaging queue and leave response processing to the caller
|
|
23
|
+
# Use this to bypass the messaging queue and leave response processing to the caller. We use this method for
|
|
24
|
+
# those operations which are blocking, meaning they must complete for the Agent to continue operations. These
|
|
25
|
+
# types of events include initial settings/ setup on startup and analysis required to complete before handing
|
|
26
|
+
# execution from our pre-filter operations to the application code (i.e. Protect's input analysis).
|
|
27
|
+
#
|
|
28
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
|
29
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
|
30
|
+
# @return [Contrast::Api::Settings::AgentSettings,nil]
|
|
22
31
|
def send_event_immediately event
|
|
23
32
|
if ::Contrast::AGENT.disabled?
|
|
24
33
|
logger.warn('Attempted to send event immediately with Agent disabled', caller: caller, event: event)
|
|
25
34
|
return
|
|
26
35
|
end
|
|
27
|
-
speedracer.return_response(event)
|
|
36
|
+
response_data = speedracer.return_response(event)
|
|
37
|
+
return response_data unless ::Contrast::API.request_audit_enable
|
|
38
|
+
|
|
39
|
+
@audit&.audit_event(event, response_data)
|
|
40
|
+
response_data
|
|
28
41
|
end
|
|
29
42
|
|
|
43
|
+
# A simple Queue used to hold messages that are ready to be sent to SpeedRacer but for which we do not need an
|
|
44
|
+
# immediate response
|
|
45
|
+
#
|
|
46
|
+
# @return [Queue]
|
|
30
47
|
def queue
|
|
31
48
|
@_queue ||= Queue.new
|
|
32
49
|
end
|
|
33
50
|
|
|
34
|
-
# Use this to add a message to the queue and process the response internally
|
|
51
|
+
# Use this to add a message to the queue and process the response internally. We use this method for those
|
|
52
|
+
# operations which are non-blocking, meaning they don't need to complete for the Agent to continue operations.
|
|
53
|
+
# These types of events include any post-request messaging that occurs after execution is returned from the
|
|
54
|
+
# application to our post-filter operations as well as those that don't alter the application's execution (i.e.
|
|
55
|
+
# Assess' vulnerability reporting).
|
|
56
|
+
#
|
|
57
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
|
58
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
|
35
59
|
def send_event_eventually event
|
|
36
60
|
if ::Contrast::AGENT.disabled?
|
|
37
61
|
logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
|
|
@@ -39,8 +63,14 @@ module Contrast
|
|
|
39
63
|
end
|
|
40
64
|
logger.debug('Enqueued event for sending', event_type: event.cs__class)
|
|
41
65
|
queue << event if event
|
|
66
|
+
return unless ::Contrast::API.request_audit_enable
|
|
67
|
+
|
|
68
|
+
@audit&.audit_event(event)
|
|
42
69
|
end
|
|
43
70
|
|
|
71
|
+
# Create the reporting thread, which will pull from the queue in order to send messages to SpeedRacer. If
|
|
72
|
+
# SpeedRacer is not running and should be, meaning the Agent is configured to control it, then we will also
|
|
73
|
+
# try to start that process.
|
|
44
74
|
def start_thread!
|
|
45
75
|
speedracer.ensure_startup!
|
|
46
76
|
return if running?
|
|
@@ -60,12 +90,16 @@ module Contrast
|
|
|
60
90
|
logger.debug('Started background sending thread.')
|
|
61
91
|
end
|
|
62
92
|
|
|
93
|
+
# When the Agent shuts down, we terminate the message sending operations. This method clears, closes, and
|
|
94
|
+
# destroys the queue.
|
|
63
95
|
def delete_queue!
|
|
64
96
|
@_queue&.clear
|
|
65
97
|
@_queue&.close
|
|
66
98
|
@_queue = nil
|
|
67
99
|
end
|
|
68
100
|
|
|
101
|
+
# When the Agent shuts down, we terminate the thread responsible for reporting. This method destroys that
|
|
102
|
+
# thread and any data we would have sent with it.
|
|
69
103
|
def stop!
|
|
70
104
|
return unless running?
|
|
71
105
|
|
|
@@ -7,10 +7,13 @@ require 'contrast/components/logger'
|
|
|
7
7
|
module Contrast
|
|
8
8
|
module Api
|
|
9
9
|
module Communication
|
|
10
|
-
# Handles processing deferred messages
|
|
10
|
+
# Handles processing deferred messages sent to SpeedRacer.
|
|
11
11
|
class ResponseProcessor
|
|
12
12
|
include Contrast::Components::Logger::InstanceMethods
|
|
13
13
|
|
|
14
|
+
# Use the given response to update the Agent's server features and application settings, allowing it to reflect
|
|
15
|
+
# the latest options configured by the user in TeamServer
|
|
16
|
+
#
|
|
14
17
|
# @param response [Contrast::Api::Settings::AgentSettings]
|
|
15
18
|
def process response
|
|
16
19
|
logger.debug('Received a response', sent_ms: response&.sent_ms)
|
|
@@ -18,8 +21,8 @@ module Contrast
|
|
|
18
21
|
server_features = process_server_response(response)
|
|
19
22
|
app_settings = process_application_response(response)
|
|
20
23
|
|
|
21
|
-
# ReactionProcessor is a design pattern from TeamServer.
|
|
22
|
-
#
|
|
24
|
+
# ReactionProcessor is a design pattern from TeamServer. Right now, there's one potential reaction, which is
|
|
25
|
+
# disabling the agent
|
|
23
26
|
Contrast::Agent::ReactionProcessor.process(response&.application_settings)
|
|
24
27
|
|
|
25
28
|
Contrast::Logger::Log.instance.update(server_features&.log_file, server_features&.log_level)
|
|
@@ -30,10 +33,10 @@ module Contrast
|
|
|
30
33
|
|
|
31
34
|
private
|
|
32
35
|
|
|
33
|
-
# Given some protobuf messages, update server features.
|
|
34
|
-
# This is the bridge between Contrast Service <-> Settings.
|
|
36
|
+
# Given some protobuf messages, update server features. This is the bridge between SpeedRacer <-> Settings.
|
|
35
37
|
#
|
|
36
38
|
# @param response [Contrast::Api::Settings::AgentSettings]
|
|
39
|
+
# @return [Contrast::Api::Settings::ServerFeatures]
|
|
37
40
|
def process_server_response response
|
|
38
41
|
server_features = response&.server_features
|
|
39
42
|
return unless server_features
|
|
@@ -45,10 +48,11 @@ module Contrast
|
|
|
45
48
|
server_features
|
|
46
49
|
end
|
|
47
50
|
|
|
48
|
-
# Given some protobuf messages, update application settings.
|
|
49
|
-
#
|
|
51
|
+
# Given some protobuf messages, update application settings. This is the bridge between SpeedRacer <->
|
|
52
|
+
# Settings.
|
|
50
53
|
#
|
|
51
54
|
# @param response [Contrast::Api::Settings::AgentSettings]
|
|
55
|
+
# @return [Contrast::Api::Settings::ApplicationSettings]
|
|
52
56
|
def process_application_response response
|
|
53
57
|
app_settings = response&.application_settings
|
|
54
58
|
return unless app_settings
|
|
@@ -61,7 +65,10 @@ module Contrast
|
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
# This can't go in the Settings component because protect and assess depend on settings
|
|
64
|
-
# I don't think it should go into contrast_service because that only handles connection specific data
|
|
68
|
+
# I don't think it should go into contrast_service because that only handles connection specific data.
|
|
69
|
+
#
|
|
70
|
+
# @param server_features [Contrast::Api::Settings::AgentSettings]
|
|
71
|
+
# @param app_settings [Contrast::Api::Settings::ApplicationSettings]
|
|
65
72
|
def update_features server_features, app_settings
|
|
66
73
|
return unless !!(server_features || app_settings)
|
|
67
74
|
return unless ::Contrast::AGENT.enabled?
|
|
@@ -13,6 +13,10 @@ module Contrast
|
|
|
13
13
|
|
|
14
14
|
private
|
|
15
15
|
|
|
16
|
+
# Attempt to start up a local process with SpeedRacer. This will ensure the process isn't just a zombie and is
|
|
17
|
+
# a real functioning process.
|
|
18
|
+
#
|
|
19
|
+
# @return [Boolean] Did the SpeedRacer process start?
|
|
16
20
|
def attempt_local_service_startup
|
|
17
21
|
zombie_check
|
|
18
22
|
service_starter_thread.join(5)
|
|
@@ -25,8 +29,8 @@ module Contrast
|
|
|
25
29
|
is_service_started
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
#
|
|
29
|
-
# to initialize
|
|
32
|
+
# Check if there's a zombie service that exists, and wait on it if so. Currently, this only happens when trying
|
|
33
|
+
# to initialize SpeedRacer. If there is a zombie, we'll wait until the zombie completes.
|
|
30
34
|
def zombie_check
|
|
31
35
|
zombie_pid_list = Contrast::Utils::OS.zombie_pids
|
|
32
36
|
zombie_pid_list.each do |pid|
|
|
@@ -36,6 +40,10 @@ module Contrast
|
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
|
|
43
|
+
# Determine the options used to set up the SpeedRacer process. Specifically, this handles if we need to adjust
|
|
44
|
+
# where the process needs to log.
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash] options used to spawn the SpeedRacer process
|
|
39
47
|
def determine_startup_options
|
|
40
48
|
return { out: :out, err: :out } if ::Contrast::CONTRAST_SERVICE.logger_path == 'STDOUT'
|
|
41
49
|
return { out: :err, err: :err } if ::Contrast::CONTRAST_SERVICE.logger_path == 'STDERR'
|
|
@@ -43,13 +51,15 @@ module Contrast
|
|
|
43
51
|
{ out: File::NULL, err: File::NULL }
|
|
44
52
|
end
|
|
45
53
|
|
|
46
|
-
# This is a separate method so we can overwrite it globally in specs
|
|
54
|
+
# Create a new process for the SpeedRacer. This is a separate method so we can overwrite it globally in specs.
|
|
47
55
|
def spawn_service
|
|
48
56
|
options = determine_startup_options
|
|
49
57
|
logger.debug('Spawning service')
|
|
50
58
|
spawn 'contrast_service', options
|
|
51
59
|
end
|
|
52
60
|
|
|
61
|
+
# Create a thread to start the SpeedRacer, making calls to spawn until one starts successfully. As soon as the
|
|
62
|
+
# SpeedRacer process starts, this thread terminates.
|
|
53
63
|
def service_starter_thread
|
|
54
64
|
Contrast::Agent::Thread.new do
|
|
55
65
|
# Always check to see if it already started
|
|
@@ -4,16 +4,15 @@
|
|
|
4
4
|
module Contrast
|
|
5
5
|
module Api
|
|
6
6
|
module Communication
|
|
7
|
-
# Behavior common to all sockets used
|
|
8
|
-
# to communicate with the Contrast Service.
|
|
7
|
+
# Behavior common to all sockets used to communicate with the Contrast Service.
|
|
9
8
|
module Socket
|
|
10
9
|
SOCKET_MONITOR = Monitor.new
|
|
11
10
|
|
|
12
11
|
# Send a message across the socket and read back the response.
|
|
13
|
-
# @param marshaled [String] some marshaled form of data to be sent across
|
|
14
|
-
#
|
|
15
|
-
# @return [String] some marshalled form of data returned by the socket.
|
|
16
|
-
#
|
|
12
|
+
# @param marshaled [String] some marshaled form of data to be sent across the socket. Typically an encoded form
|
|
13
|
+
# of Contrast::Api::Dtm::Message.
|
|
14
|
+
# @return [String] some marshalled form of data returned by the socket. Typically an encoded form of
|
|
15
|
+
# Contrast::Api::Settings::AgentSettings.
|
|
17
16
|
def send_marshaled marshaled
|
|
18
17
|
SOCKET_MONITOR.synchronize do
|
|
19
18
|
socket = new_socket
|
|
@@ -34,8 +33,7 @@ module Contrast
|
|
|
34
33
|
end
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
# Override this method to return a socket.
|
|
38
|
-
# Should be interface compatible with TCPSocket, UNIXSocket, etc.
|
|
36
|
+
# Override this method to return a socket. Should be interface compatible with TCPSocket, UNIXSocket, etc.
|
|
39
37
|
def new_socket
|
|
40
38
|
raise NoMethodError, 'This is abstract, override it.'
|
|
41
39
|
end
|
|
@@ -11,8 +11,8 @@ require 'contrast/components/logger'
|
|
|
11
11
|
module Contrast
|
|
12
12
|
module Api
|
|
13
13
|
module Communication
|
|
14
|
-
# SocketClient acts as a interface between the agent and the service. It instantiates a
|
|
15
|
-
#
|
|
14
|
+
# SocketClient acts as a interface between the agent and the service. It instantiates a service proxy and tracks
|
|
15
|
+
# the state of that proxy.
|
|
16
16
|
class SocketClient
|
|
17
17
|
include Contrast::Components::Logger::InstanceMethods
|
|
18
18
|
|
|
@@ -20,12 +20,13 @@ module Contrast
|
|
|
20
20
|
@socket = init_connection
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# Wrap the given DTM in a Contrast::Api::Dtm::Message and send it to the
|
|
24
|
-
#
|
|
23
|
+
# Wrap the given DTM in a Contrast::Api::Dtm::Message and send it to the SpeedRacer for processing. The
|
|
24
|
+
# Message is the top level object required to communicate to SpeedRacer as it encompasses the information
|
|
25
|
+
# needed to find this process' context.
|
|
25
26
|
#
|
|
26
27
|
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
|
27
28
|
# Contrast::Api::Dtm::Message
|
|
28
|
-
# @return [Contrast::Api::Settings::AgentSettings]
|
|
29
|
+
# @return [Contrast::Api::Settings::AgentSettings, nil]
|
|
29
30
|
def send_one event
|
|
30
31
|
msg = Contrast::Api::Dtm::Message.build(event)
|
|
31
32
|
send_message(msg)
|
|
@@ -33,6 +34,12 @@ module Contrast
|
|
|
33
34
|
|
|
34
35
|
private
|
|
35
36
|
|
|
37
|
+
# Initialize the connection to the SpeedRacer process based on the configuration provided by the user. This can
|
|
38
|
+
# be either TCP or UDP. Note that unlike the Go and the Node Agents, we cannot use the GRPC communication
|
|
39
|
+
# option as we cannot use Google's protobuf gems; they do not compile reliably and result in segmentation
|
|
40
|
+
# faults in customer environments.
|
|
41
|
+
#
|
|
42
|
+
# @return [Contrast::Api::Communication::TcpSocket, Contrast::Api::Communication::UnixSocket]
|
|
36
43
|
def init_connection
|
|
37
44
|
log_connection
|
|
38
45
|
if ::Contrast::CONTRAST_SERVICE.use_tcp?
|
|
@@ -43,6 +50,7 @@ module Contrast
|
|
|
43
50
|
end
|
|
44
51
|
end
|
|
45
52
|
|
|
53
|
+
# Log information about the connection being used to communicate between the Agent and SpeedRacer.
|
|
46
54
|
def log_connection
|
|
47
55
|
# The socket is set,
|
|
48
56
|
if ::Contrast::CONFIG.root.agent.service.socket
|
|
@@ -72,19 +80,24 @@ module Contrast
|
|
|
72
80
|
def log_connection_error_msg
|
|
73
81
|
if ::Contrast::CONFIG.root.agent.service.host
|
|
74
82
|
'Missing a required connection value to the Contrast Service. ' \
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
'`agent.service.port` is not set. ' \
|
|
84
|
+
'Falling back to default TCP socket port.'
|
|
77
85
|
elsif ::Contrast::CONFIG.root.agent.service.port
|
|
78
86
|
'Missing a required connection value to the Contrast Service. ' \
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
'`agent.service.host` is not set. ' \
|
|
88
|
+
'Falling back to default TCP socket host.'
|
|
81
89
|
else
|
|
82
90
|
'Missing a required connection value to the Contrast Service. ' \
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
'Neither `agent.service.socket` nor the pair of `agent.service.host` and `agent.service.port` are set. '\
|
|
92
|
+
'Falling back to default TCP socket.'
|
|
85
93
|
end
|
|
86
94
|
end
|
|
87
95
|
|
|
96
|
+
# Send the given message to SpeedRacer and return the response from it.
|
|
97
|
+
#
|
|
98
|
+
# @param msg [Contrast::Api::Dtm::Message] the packaged message to send to SpeedRacer
|
|
99
|
+
# @return [Contrast::Api::Settings::AgentSettings, nil]
|
|
100
|
+
# @raise [StandardError] if unable to send a message to SpeedRacer
|
|
88
101
|
def send_message msg
|
|
89
102
|
return unless msg
|
|
90
103
|
|
|
@@ -98,9 +111,13 @@ module Contrast
|
|
|
98
111
|
rescue StandardError => e
|
|
99
112
|
logger.error('Sending failed for message.', e, msg_id: msg.__id__, p_id: msg.pid,
|
|
100
113
|
msg_count: msg.message_count, response_id: response&.__id__)
|
|
101
|
-
raise e # reraise to let
|
|
114
|
+
raise e # reraise to let SpeedRacer manage the connection
|
|
102
115
|
end
|
|
103
116
|
|
|
117
|
+
# Send the marshaled Contrast::Api::Dtm::Message across the socket used to talk to SpeedRacer
|
|
118
|
+
#
|
|
119
|
+
# @param marshaled [String]
|
|
120
|
+
# @return [String] the marshaled from of Contrast::Api::Settings::AgentSettings
|
|
104
121
|
def send_marshaled marshaled
|
|
105
122
|
@socket.send_marshaled(marshaled)
|
|
106
123
|
end
|
|
@@ -6,7 +6,10 @@ require 'contrast/components/logger'
|
|
|
6
6
|
module Contrast
|
|
7
7
|
module Api
|
|
8
8
|
module Communication
|
|
9
|
-
# Wraps all connection data to
|
|
9
|
+
# Wraps all connection data to SpeedRacer. SpeedRacer, also known as the Contrast Service, is a standalone
|
|
10
|
+
# executable that sits between the Agent and TeamServer. It handles converting our Protobuf messages into a
|
|
11
|
+
# format consumable by TeamServer. The Agent requires a SpeedRacer process to be running somewhere to which it
|
|
12
|
+
# can connect, as specified by the user configuration, in order to function.
|
|
10
13
|
class Speedracer
|
|
11
14
|
include Contrast::Api::Communication::ServiceLifecycle
|
|
12
15
|
include Contrast::Components::Logger::InstanceMethods
|
|
@@ -20,6 +23,14 @@ module Contrast
|
|
|
20
23
|
@ensure_running = Mutex.new
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
# If there is not a SpeedRacer at the location specified by the configuration of this Agent and the Agent is
|
|
27
|
+
# set such that it should manage one, start up a new child process to run the SpeedRacer executable. If a
|
|
28
|
+
# connection has not already been made to that process, after starting it, this method will also send the
|
|
29
|
+
# messages necessary to create a context for this Agent process in the SpeedRacer as well as trigger
|
|
30
|
+
# SpeedRacer's sending of startup messages which will return features and settings from TeamServer.
|
|
31
|
+
#
|
|
32
|
+
# This operation is synchronous and blocking, so it will only happen one at a time per process and will halt
|
|
33
|
+
# Thread execution here until completion.
|
|
23
34
|
def ensure_startup!
|
|
24
35
|
return if status.connected?
|
|
25
36
|
|
|
@@ -40,12 +51,24 @@ module Contrast
|
|
|
40
51
|
end
|
|
41
52
|
end
|
|
42
53
|
|
|
54
|
+
# Send the given Event to SpeedRacer, returning the response from it. This response will either be new settings
|
|
55
|
+
# if anything's changed in TeamServer meaning the Agent needs to replace its current settings or there is
|
|
56
|
+
# Protect analysis information or nil if the current Agent settings do not need updating.
|
|
57
|
+
#
|
|
58
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
|
59
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
|
60
|
+
# @return [Contrast::Api::Settings::AgentSettings,nil]
|
|
43
61
|
def return_response event
|
|
44
62
|
send_to_speedracer(event) do |response|
|
|
45
63
|
return response
|
|
46
64
|
end
|
|
47
65
|
end
|
|
48
66
|
|
|
67
|
+
# Send the given Event to SpeedRacer and pass the result to our Contrast::Api::Communication::ResponseProcessor
|
|
68
|
+
# as there is no immediate action required from sending this Event.
|
|
69
|
+
#
|
|
70
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
|
71
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
|
49
72
|
def process_internally event
|
|
50
73
|
send_to_speedracer(event) do |response|
|
|
51
74
|
response_processor.process(response)
|
|
@@ -54,6 +77,12 @@ module Contrast
|
|
|
54
77
|
|
|
55
78
|
private
|
|
56
79
|
|
|
80
|
+
# Ensure there is a running SpeedRacer and then send the given Event to it. It is necessary to ensure the
|
|
81
|
+
# SpeedRacer is running as, if the process has crashed or restarted, we must rebuild our context there.
|
|
82
|
+
#
|
|
83
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
|
84
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
|
85
|
+
# @return [Contrast::Api::Settings::AgentSettings, nil]
|
|
57
86
|
def send_to_speedracer event
|
|
58
87
|
ensure_startup!
|
|
59
88
|
|
|
@@ -68,6 +97,9 @@ module Contrast
|
|
|
68
97
|
nil
|
|
69
98
|
end
|
|
70
99
|
|
|
100
|
+
# Send those messages which are required to build a context for this Agent process on SpeedRacer as well as
|
|
101
|
+
# report server and application startup to TeamServer. With these messages, the SpeedRacer will be able to
|
|
102
|
+
# retrieve settings from TeamServer and provide those for the Agent to complete its initialization.
|
|
71
103
|
def send_initialization_messages
|
|
72
104
|
agent_startup_msg = ::Contrast::APP_CONTEXT.build_agent_startup_message
|
|
73
105
|
|
|
@@ -97,6 +129,10 @@ module Contrast
|
|
|
97
129
|
nil
|
|
98
130
|
end
|
|
99
131
|
|
|
132
|
+
# Log the startup message we're sending to SpeedRacer
|
|
133
|
+
#
|
|
134
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
|
135
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
|
100
136
|
def log_send_event event
|
|
101
137
|
logger.debug('Immediately sending event.', event_id: event.__id__, event_type: event.cs__class.cs__name)
|
|
102
138
|
end
|
|
@@ -6,15 +6,15 @@ require 'contrast/api/communication/socket'
|
|
|
6
6
|
module Contrast
|
|
7
7
|
module Api
|
|
8
8
|
module Communication
|
|
9
|
-
# This class allows us to create a TCP Socket to communicate to the Service
|
|
10
|
-
#
|
|
11
|
-
# by the configuration options set for Service communication.
|
|
9
|
+
# This class allows us to create a TCP Socket to communicate to the Service (Speed Racer). Either it or the Unix
|
|
10
|
+
# Socket will be used, as determined by the configuration options set for Service communication.
|
|
12
11
|
class TcpSocket
|
|
13
12
|
include Contrast::Api::Communication::Socket
|
|
14
13
|
|
|
15
14
|
attr_reader :host, :port
|
|
16
15
|
|
|
17
16
|
# Create the socket
|
|
17
|
+
#
|
|
18
18
|
# @param host [String] socket target hostname or IP address
|
|
19
19
|
# @param port [String,Integer] socket target port
|
|
20
20
|
def initialize host, port
|
|
@@ -22,6 +22,7 @@ module Contrast
|
|
|
22
22
|
@port = port.to_i
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# @return [::TCPSocket]
|
|
25
26
|
def new_socket
|
|
26
27
|
::TCPSocket.new(host, port)
|
|
27
28
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'contrast/api/dtm.pb'
|
|
5
|
+
require 'contrast/utils/string_utils'
|
|
6
|
+
require 'contrast/components/base'
|
|
7
|
+
|
|
8
|
+
module Contrast
|
|
9
|
+
module Api
|
|
10
|
+
module Decorators
|
|
11
|
+
# Used to decorate the {Contrast::Api::Dtm::Finding} protobuf
|
|
12
|
+
# model so it can own the request which its data is for.
|
|
13
|
+
module Finding
|
|
14
|
+
def self.included klass
|
|
15
|
+
klass.extend(ClassMethods)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Used to add class methods to the AgentStartup class on inclusion of the decorator
|
|
19
|
+
module ClassMethods
|
|
20
|
+
def build
|
|
21
|
+
new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_controlled_hash finding, *_args
|
|
25
|
+
{
|
|
26
|
+
hash_code: finding.hash_code,
|
|
27
|
+
platform: finding.platform,
|
|
28
|
+
rule_id: finding.rule_id,
|
|
29
|
+
evidence: finding.evidence,
|
|
30
|
+
properties: finding.properties,
|
|
31
|
+
events: finding.events,
|
|
32
|
+
tags: finding.tags,
|
|
33
|
+
version: finding.version,
|
|
34
|
+
routes: finding.routes,
|
|
35
|
+
session_id: finding.session_id,
|
|
36
|
+
teamserver_url: ::Contrast::API.api_url
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Contrast::Api::Dtm::Finding.include(Contrast::Api::Decorators::Finding)
|
|
@@ -28,6 +28,62 @@ module Contrast
|
|
|
28
28
|
def username
|
|
29
29
|
@_username ||= ::Contrast::CONFIG.root.api.user_name
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
def proxy_enabled?
|
|
33
|
+
@_proxy_enabled = true?(::Contrast::CONFIG.root.api.proxy.enable) if @_proxy_enabled.nil?
|
|
34
|
+
@_proxy_enabled
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def proxy_url
|
|
38
|
+
@_proxy_url ||= ::Contrast::CONFIG.root.api.proxy.url
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def request_audit_enable
|
|
42
|
+
@_request_audit_enable ||= true?(::Contrast::CONFIG.root.api.request_audit.enable)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def request_audit_requests
|
|
46
|
+
return @_request_audit_requests unless @_request_audit_requests.nil?
|
|
47
|
+
|
|
48
|
+
@_request_audit_requests = true?(::Contrast::CONFIG.root.api.request_audit.requests)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def request_audit_responses
|
|
52
|
+
return @_request_audit_responses unless @_request_audit_responses.nil?
|
|
53
|
+
|
|
54
|
+
@_request_audit_responses = true?(::Contrast::CONFIG.root.api.request_audit.responses)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def request_audit_path
|
|
58
|
+
@_request_audit_path ||= ::Contrast::CONFIG.root.api.request_audit.path.to_s
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def certification_enabled?
|
|
62
|
+
@_certification_enabled ||= certification_truly_enabled?(::Contrast::CONFIG.root.api.certificate)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def certification_ca_file
|
|
66
|
+
@_certification_ca_file ||= ::Contrast::CONFIG.root.api.certificate.ca_file
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def certification_cert_file
|
|
70
|
+
@_certification_cert_file ||= ::Contrast::CONFIG.root.api.certificate.cert_file
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def certification_key_file
|
|
74
|
+
@_certification_key_file ||= ::Contrast::CONFIG.root.api.certificate.key_file
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def certification_truly_enabled? config_path
|
|
80
|
+
return false unless true?(config_path.enable)
|
|
81
|
+
return true if file_exists?(certification_ca_file) && valid_cert?(certification_ca_file)
|
|
82
|
+
return true if file_exists?(certification_cert_file) && valid_cert?(certification_cert_file)
|
|
83
|
+
return true if file_exists?(certification_key_file) && valid_cert?(certification_key_file)
|
|
84
|
+
|
|
85
|
+
false
|
|
86
|
+
end
|
|
31
87
|
end
|
|
32
88
|
end
|
|
33
89
|
end
|