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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  4. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  5. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  6. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  7. data/lib/contrast/agent/assess/policy/trigger_method.rb +44 -7
  8. data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
  9. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  10. data/lib/contrast/agent/assess/property/tagged.rb +51 -57
  11. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  12. data/lib/contrast/agent/metric_telemetry_event.rb +2 -2
  13. data/lib/contrast/agent/middleware.rb +5 -75
  14. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
  15. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  16. data/lib/contrast/agent/patching/policy/patcher.rb +12 -8
  17. data/lib/contrast/agent/reporting/report.rb +21 -0
  18. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  19. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  20. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  21. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  22. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  23. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  24. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  25. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  26. data/lib/contrast/agent/request.rb +2 -81
  27. data/lib/contrast/agent/request_context.rb +4 -128
  28. data/lib/contrast/agent/request_context_extend.rb +138 -0
  29. data/lib/contrast/agent/response.rb +2 -73
  30. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +39 -16
  31. data/lib/contrast/agent/static_analysis.rb +1 -1
  32. data/lib/contrast/agent/telemetry.rb +15 -7
  33. data/lib/contrast/agent/telemetry_event.rb +8 -9
  34. data/lib/contrast/agent/thread_watcher.rb +31 -5
  35. data/lib/contrast/agent/version.rb +1 -1
  36. data/lib/contrast/agent.rb +15 -0
  37. data/lib/contrast/api/communication/connection_status.rb +10 -7
  38. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  39. data/lib/contrast/api/communication/response_processor.rb +15 -8
  40. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  41. data/lib/contrast/api/communication/socket.rb +6 -8
  42. data/lib/contrast/api/communication/socket_client.rb +29 -12
  43. data/lib/contrast/api/communication/speedracer.rb +37 -1
  44. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  45. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  46. data/lib/contrast/api/decorators/finding.rb +45 -0
  47. data/lib/contrast/components/api.rb +56 -0
  48. data/lib/contrast/components/app_context.rb +10 -65
  49. data/lib/contrast/components/app_context_extend.rb +78 -0
  50. data/lib/contrast/components/base.rb +23 -0
  51. data/lib/contrast/components/config.rb +8 -8
  52. data/lib/contrast/components/contrast_service.rb +5 -0
  53. data/lib/contrast/components/sampling.rb +2 -2
  54. data/lib/contrast/config/agent_configuration.rb +1 -1
  55. data/lib/contrast/config/api_configuration.rb +9 -4
  56. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  57. data/lib/contrast/config/application_configuration.rb +2 -3
  58. data/lib/contrast/config/assess_configuration.rb +3 -3
  59. data/lib/contrast/config/base_configuration.rb +17 -28
  60. data/lib/contrast/config/certification_configuration.rb +15 -0
  61. data/lib/contrast/config/env_variables.rb +2 -9
  62. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  63. data/lib/contrast/config/inventory_configuration.rb +1 -5
  64. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  65. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  66. data/lib/contrast/config/ruby_configuration.rb +6 -6
  67. data/lib/contrast/config/service_configuration.rb +1 -2
  68. data/lib/contrast/config.rb +0 -1
  69. data/lib/contrast/configuration.rb +1 -2
  70. data/lib/contrast/extension/assess/array.rb +5 -7
  71. data/lib/contrast/framework/manager.rb +8 -32
  72. data/lib/contrast/framework/manager_extend.rb +50 -0
  73. data/lib/contrast/framework/rails/railtie.rb +1 -1
  74. data/lib/contrast/framework/sinatra/support.rb +2 -1
  75. data/lib/contrast/logger/log.rb +8 -103
  76. data/lib/contrast/utils/assess/property/tagged_utils.rb +23 -0
  77. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  78. data/lib/contrast/utils/assess/trigger_method_utils.rb +1 -1
  79. data/lib/contrast/utils/class_util.rb +18 -14
  80. data/lib/contrast/utils/findings.rb +62 -0
  81. data/lib/contrast/utils/hash_digest.rb +10 -73
  82. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  83. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  84. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  85. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  86. data/lib/contrast/utils/io_util.rb +1 -1
  87. data/lib/contrast/utils/log_utils.rb +108 -0
  88. data/lib/contrast/utils/middleware_utils.rb +87 -0
  89. data/lib/contrast/utils/net_http_base.rb +158 -0
  90. data/lib/contrast/utils/object_share.rb +1 -0
  91. data/lib/contrast/utils/request_utils.rb +88 -0
  92. data/lib/contrast/utils/response_utils.rb +97 -0
  93. data/lib/contrast/utils/substitution_utils.rb +167 -0
  94. data/lib/contrast/utils/tag_util.rb +9 -9
  95. data/lib/contrast/utils/telemetry.rb +4 -2
  96. data/lib/contrast/utils/telemetry_client.rb +90 -0
  97. data/lib/contrast/utils/telemetry_identifier.rb +17 -24
  98. data/ruby-agent.gemspec +5 -5
  99. metadata +48 -23
  100. data/lib/contrast/config/default_value.rb +0 -17
  101. data/lib/contrast/utils/requests_client.rb +0 -150
@@ -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 the service.
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 the service. True after successfully
16
- # sending startup messages to service and reset to false if we lose connection
17
- # to the service.
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 service is active with a successful message sent
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 service may be in some sort of error state
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
- # Right now, there's one potential reaction, which is disabling the agent
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
- # This is the bridge between Contrast Service <-> Settings.
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
- # check if there's a zombie service that exists, and wait on it if so. currently, this only happens when trying
29
- # to initialize speedracer
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
- # the socket. Typically an encoded form of Contrast::Api::Dtm::Message.
15
- # @return [String] some marshalled form of data returned by the socket.
16
- # Typically an encoded form of Contrast::Api::Settings::AgentSettings.
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
- # service proxy and tracks the state of that proxy.
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
- # Service for processing
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
- '`agent.service.port` is not set. ' \
76
- 'Falling back to default TCP socket port.'
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
- '`agent.service.host` is not set. ' \
80
- 'Falling back to default TCP socket host.'
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
- 'Neither `agent.service.socket` nor the pair of `agent.service.host` and `agent.service.port` are set. '\
84
- 'Falling back to default TCP socket.'
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 Speedracer manage the connection
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 speedracer
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
- # (Speed Racer). Either it or the Unix Socket will be used, as determined
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
@@ -18,6 +18,7 @@ module Contrast
18
18
  @path = path
19
19
  end
20
20
 
21
+ # @return [::UNIXSocket]
21
22
  def new_socket
22
23
  ::UNIXSocket.new(path)
23
24
  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