contrast-agent 6.7.0 → 6.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.simplecov +0 -1
  4. data/Rakefile +0 -1
  5. data/ext/cs__assess_array/cs__assess_array.c +41 -10
  6. data/ext/cs__assess_array/cs__assess_array.h +4 -1
  7. data/lib/contrast/agent/assess/policy/trigger_method.rb +2 -2
  8. data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +1 -1
  9. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  10. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  11. data/lib/contrast/agent/excluder.rb +52 -34
  12. data/lib/contrast/agent/exclusion_matcher.rb +21 -9
  13. data/lib/contrast/agent/middleware.rb +4 -4
  14. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +6 -0
  15. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +146 -127
  16. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +20 -0
  17. data/lib/contrast/agent/protect/policy/rule_applicator.rb +1 -1
  18. data/lib/contrast/agent/protect/rule/base.rb +45 -53
  19. data/lib/contrast/agent/protect/rule/base_service.rb +48 -24
  20. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +98 -0
  21. data/lib/contrast/agent/protect/rule/bot_blocker.rb +81 -0
  22. data/lib/contrast/agent/protect/rule/cmd_injection.rb +18 -1
  23. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +8 -5
  24. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +22 -22
  25. data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +69 -0
  26. data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +68 -0
  27. data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -58
  28. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -1
  29. data/lib/contrast/agent/protect/rule/deserialization.rb +3 -14
  30. data/lib/contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification.rb +2 -2
  31. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +0 -11
  32. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +29 -34
  33. data/lib/contrast/agent/protect/rule/no_sqli.rb +25 -18
  34. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +61 -0
  35. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +114 -0
  36. data/lib/contrast/agent/protect/rule/path_traversal.rb +38 -12
  37. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +33 -15
  38. data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +0 -14
  39. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -62
  40. data/lib/contrast/agent/protect/rule/sqli.rb +70 -0
  41. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +39 -63
  42. data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +6 -33
  43. data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +58 -0
  44. data/lib/contrast/agent/protect/rule/xss.rb +14 -20
  45. data/lib/contrast/agent/protect/rule/xxe.rb +4 -24
  46. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +18 -39
  47. data/lib/contrast/agent/reporting/attack_result/response_type.rb +9 -9
  48. data/lib/contrast/agent/reporting/details/ip_denylist_details.rb +10 -2
  49. data/lib/contrast/agent/reporting/details/virtual_patch_details.rb +8 -2
  50. data/lib/contrast/agent/reporting/input_analysis/details/bot_blocker_details.rb +27 -0
  51. data/lib/contrast/agent/reporting/input_analysis/details/protect_rule_details.rb +15 -0
  52. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +1 -2
  53. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +16 -2
  54. data/lib/contrast/agent/reporting/masker/masker.rb +2 -0
  55. data/lib/contrast/agent/reporting/reporter.rb +1 -14
  56. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +15 -12
  57. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +3 -3
  58. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -2
  59. data/lib/contrast/agent/reporting/reporting_events/application_update.rb +0 -2
  60. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +0 -1
  61. data/lib/contrast/agent/reporting/reporting_events/finding.rb +4 -4
  62. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +0 -5
  63. data/lib/contrast/agent/reporting/reporting_events/library_discovery.rb +0 -1
  64. data/lib/contrast/agent/reporting/reporting_events/poll.rb +1 -11
  65. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +0 -1
  66. data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +0 -1
  67. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +2 -2
  68. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +1 -1
  69. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +0 -3
  70. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -0
  71. data/lib/contrast/agent/reporting/settings/code_exclusion.rb +6 -1
  72. data/lib/contrast/agent/reporting/settings/exclusion_base.rb +18 -0
  73. data/lib/contrast/agent/reporting/settings/exclusions.rb +2 -1
  74. data/lib/contrast/agent/reporting/settings/input_exclusion.rb +9 -3
  75. data/lib/contrast/agent/reporting/settings/protect.rb +15 -15
  76. data/lib/contrast/agent/request.rb +2 -14
  77. data/lib/contrast/agent/request_context.rb +6 -9
  78. data/lib/contrast/agent/request_context_extend.rb +9 -148
  79. data/lib/contrast/agent/thread_watcher.rb +3 -18
  80. data/lib/contrast/agent/version.rb +1 -1
  81. data/lib/contrast/agent.rb +0 -11
  82. data/lib/contrast/agent_lib/api/command_injection.rb +46 -0
  83. data/lib/contrast/agent_lib/api/init.rb +101 -0
  84. data/lib/contrast/agent_lib/api/input_tracing.rb +267 -0
  85. data/lib/contrast/agent_lib/api/method_tempering.rb +29 -0
  86. data/lib/contrast/agent_lib/api/panic.rb +87 -0
  87. data/lib/contrast/agent_lib/api/path_semantic_file_security_bypass.rb +40 -0
  88. data/lib/contrast/agent_lib/interface.rb +260 -0
  89. data/lib/contrast/agent_lib/interface_base.rb +118 -0
  90. data/lib/contrast/agent_lib/return_types/eval_result.rb +44 -0
  91. data/lib/contrast/agent_lib/test.rb +29 -0
  92. data/lib/contrast/api/communication/connection_status.rb +5 -5
  93. data/lib/contrast/components/agent.rb +0 -14
  94. data/lib/contrast/components/app_context.rb +0 -2
  95. data/lib/contrast/components/app_context_extend.rb +0 -25
  96. data/lib/contrast/components/config.rb +1 -18
  97. data/lib/contrast/components/protect.rb +4 -1
  98. data/lib/contrast/components/ruby_component.rb +1 -1
  99. data/lib/contrast/components/settings.rb +37 -89
  100. data/lib/contrast/config/protect_rule_configuration.rb +7 -7
  101. data/lib/contrast/config/protect_rules_configuration.rb +20 -58
  102. data/lib/contrast/configuration.rb +1 -10
  103. data/lib/contrast/extension/assess/array.rb +9 -0
  104. data/lib/contrast/extension/delegator.rb +2 -0
  105. data/lib/contrast/framework/manager.rb +3 -1
  106. data/lib/contrast/framework/rails/railtie.rb +0 -1
  107. data/lib/contrast/framework/rails/support.rb +0 -1
  108. data/lib/contrast/tasks/config.rb +1 -8
  109. data/lib/contrast/utils/duck_utils.rb +1 -0
  110. data/lib/contrast/utils/input_classification_base.rb +156 -0
  111. data/lib/contrast/utils/os.rb +0 -20
  112. data/lib/contrast/utils/response_utils.rb +0 -16
  113. data/lib/contrast/utils/stack_trace_utils.rb +3 -15
  114. data/lib/contrast/utils/string_utils.rb +10 -7
  115. data/lib/contrast.rb +2 -3
  116. data/resources/protect/policy.json +1 -2
  117. data/ruby-agent.gemspec +2 -5
  118. metadata +42 -112
  119. data/exe/contrast_service +0 -23
  120. data/lib/contrast/agent/protect/rule/cmdi/cmdi_worth_watching.rb +0 -64
  121. data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +0 -118
  122. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_matcher.rb +0 -45
  123. data/lib/contrast/agent/reaction_processor.rb +0 -47
  124. data/lib/contrast/agent/service_heartbeat.rb +0 -35
  125. data/lib/contrast/api/communication/messaging_queue.rb +0 -128
  126. data/lib/contrast/api/communication/response_processor.rb +0 -90
  127. data/lib/contrast/api/communication/service_lifecycle.rb +0 -77
  128. data/lib/contrast/api/communication/socket.rb +0 -44
  129. data/lib/contrast/api/communication/socket_client.rb +0 -130
  130. data/lib/contrast/api/communication/speedracer.rb +0 -138
  131. data/lib/contrast/api/communication/tcp_socket.rb +0 -32
  132. data/lib/contrast/api/communication/unix_socket.rb +0 -28
  133. data/lib/contrast/api/communication.rb +0 -20
  134. data/lib/contrast/api/decorators/address.rb +0 -59
  135. data/lib/contrast/api/decorators/agent_startup.rb +0 -56
  136. data/lib/contrast/api/decorators/application_settings.rb +0 -43
  137. data/lib/contrast/api/decorators/application_startup.rb +0 -56
  138. data/lib/contrast/api/decorators/bot_blocker.rb +0 -37
  139. data/lib/contrast/api/decorators/http_request.rb +0 -137
  140. data/lib/contrast/api/decorators/input_analysis.rb +0 -18
  141. data/lib/contrast/api/decorators/instrumentation_mode.rb +0 -35
  142. data/lib/contrast/api/decorators/ip_denylist.rb +0 -37
  143. data/lib/contrast/api/decorators/message.rb +0 -67
  144. data/lib/contrast/api/decorators/rasp_rule_sample.rb +0 -52
  145. data/lib/contrast/api/decorators/response_type.rb +0 -17
  146. data/lib/contrast/api/decorators/server_features.rb +0 -25
  147. data/lib/contrast/api/decorators/user_input.rb +0 -51
  148. data/lib/contrast/api/decorators/virtual_patch.rb +0 -34
  149. data/lib/contrast/api/decorators.rb +0 -22
  150. data/lib/contrast/api/dtm.pb.rb +0 -363
  151. data/lib/contrast/api/settings.pb.rb +0 -500
  152. data/lib/contrast/api.rb +0 -16
  153. data/lib/contrast/components/contrast_service.rb +0 -88
  154. data/lib/contrast/components/service.rb +0 -55
  155. data/lib/contrast/tasks/service.rb +0 -84
  156. data/lib/contrast/utils/input_classification.rb +0 -73
  157. data/lib/protobuf/code_generator.rb +0 -129
  158. data/lib/protobuf/decoder.rb +0 -28
  159. data/lib/protobuf/deprecation.rb +0 -117
  160. data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +0 -79
  161. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +0 -360
  162. data/lib/protobuf/descriptors.rb +0 -3
  163. data/lib/protobuf/encoder.rb +0 -11
  164. data/lib/protobuf/enum.rb +0 -365
  165. data/lib/protobuf/exceptions.rb +0 -9
  166. data/lib/protobuf/field/base_field.rb +0 -380
  167. data/lib/protobuf/field/base_field_object_definitions.rb +0 -504
  168. data/lib/protobuf/field/bool_field.rb +0 -64
  169. data/lib/protobuf/field/bytes_field.rb +0 -67
  170. data/lib/protobuf/field/double_field.rb +0 -25
  171. data/lib/protobuf/field/enum_field.rb +0 -56
  172. data/lib/protobuf/field/field_array.rb +0 -102
  173. data/lib/protobuf/field/field_hash.rb +0 -122
  174. data/lib/protobuf/field/fixed32_field.rb +0 -25
  175. data/lib/protobuf/field/fixed64_field.rb +0 -28
  176. data/lib/protobuf/field/float_field.rb +0 -43
  177. data/lib/protobuf/field/int32_field.rb +0 -21
  178. data/lib/protobuf/field/int64_field.rb +0 -34
  179. data/lib/protobuf/field/integer_field.rb +0 -23
  180. data/lib/protobuf/field/message_field.rb +0 -51
  181. data/lib/protobuf/field/sfixed32_field.rb +0 -27
  182. data/lib/protobuf/field/sfixed64_field.rb +0 -28
  183. data/lib/protobuf/field/signed_integer_field.rb +0 -29
  184. data/lib/protobuf/field/sint32_field.rb +0 -21
  185. data/lib/protobuf/field/sint64_field.rb +0 -21
  186. data/lib/protobuf/field/string_field.rb +0 -51
  187. data/lib/protobuf/field/uint32_field.rb +0 -21
  188. data/lib/protobuf/field/uint64_field.rb +0 -21
  189. data/lib/protobuf/field/varint_field.rb +0 -77
  190. data/lib/protobuf/field.rb +0 -74
  191. data/lib/protobuf/generators/base.rb +0 -85
  192. data/lib/protobuf/generators/enum_generator.rb +0 -39
  193. data/lib/protobuf/generators/extension_generator.rb +0 -27
  194. data/lib/protobuf/generators/field_generator.rb +0 -193
  195. data/lib/protobuf/generators/file_generator.rb +0 -262
  196. data/lib/protobuf/generators/group_generator.rb +0 -122
  197. data/lib/protobuf/generators/message_generator.rb +0 -104
  198. data/lib/protobuf/generators/option_generator.rb +0 -17
  199. data/lib/protobuf/generators/printable.rb +0 -160
  200. data/lib/protobuf/generators/service_generator.rb +0 -50
  201. data/lib/protobuf/lifecycle.rb +0 -33
  202. data/lib/protobuf/logging.rb +0 -39
  203. data/lib/protobuf/message/fields.rb +0 -233
  204. data/lib/protobuf/message/serialization.rb +0 -85
  205. data/lib/protobuf/message.rb +0 -241
  206. data/lib/protobuf/optionable.rb +0 -72
  207. data/lib/protobuf/tasks/compile.rake +0 -80
  208. data/lib/protobuf/tasks.rb +0 -1
  209. data/lib/protobuf/varint.rb +0 -20
  210. data/lib/protobuf/varint_pure.rb +0 -31
  211. data/lib/protobuf/version.rb +0 -3
  212. data/lib/protobuf/wire_type.rb +0 -10
  213. data/lib/protobuf.rb +0 -91
  214. data/proto/dynamic_discovery.proto +0 -46
  215. data/proto/google/protobuf/compiler/plugin.proto +0 -183
  216. data/proto/google/protobuf/descriptor.proto +0 -911
  217. data/proto/rpc.proto +0 -71
  218. data/service_executables/.gitkeep +0 -0
  219. data/service_executables/VERSION +0 -1
  220. data/service_executables/linux/contrast-service +0 -0
  221. data/service_executables/mac/contrast-service +0 -0
@@ -1,128 +0,0 @@
1
- # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
- # frozen_string_literal: true
3
-
4
- require 'contrast/components/logger'
5
- require 'contrast/agent/assess/policy/trigger_method'
6
- require 'contrast/agent/worker_thread'
7
- require 'contrast/agent/reporting/reporting_utilities/audit'
8
- require 'contrast/api/dtm.pb'
9
-
10
- module Contrast
11
- module Api
12
- module Communication
13
- # Top level gateway to messaging with speedracer
14
- class MessagingQueue < Contrast::Agent::WorkerThread
15
- include Contrast::Components::Logger::InstanceMethods
16
-
17
- # @return [Contrast::Api::Communication::Speedracer, nil]
18
- attr_reader :speedracer
19
-
20
- def initialize
21
- return if ::Contrast::CONTRAST_SERVICE.unnecessary?
22
-
23
- @speedracer = Contrast::Api::Communication::Speedracer.new
24
- super
25
- end
26
-
27
- # Use this to bypass the messaging queue and leave response processing to the caller. We use this method for
28
- # those operations which are blocking, meaning they must complete for the Agent to continue operations. These
29
- # types of events include initial settings/ setup on startup and analysis required to complete before handing
30
- # execution from our pre-filter operations to the application code (i.e. Protect's input analysis).
31
- #
32
- # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of Contrast::Api::Dtm::Message
33
- # @return [Contrast::Api::Settings::AgentSettings,nil]
34
- def send_event_immediately event
35
- if ::Contrast::AGENT.disabled?
36
- logger.warn('Attempted to send event immediately with Agent disabled', caller: caller, event: event)
37
- return
38
- end
39
- return if ::Contrast::CONTRAST_SERVICE.unnecessary?
40
-
41
- speedracer.return_response(event)
42
- end
43
-
44
- # A simple Queue used to hold messages that are ready to be sent to SpeedRacer but for which we do not need an
45
- # immediate response
46
- #
47
- # @return [Queue, nil]
48
- def queue
49
- return if ::Contrast::CONTRAST_SERVICE.unnecessary?
50
-
51
- @_queue ||= Queue.new
52
- end
53
-
54
- # Use this to add a message to the queue and process the response internally. We use this method for those
55
- # operations which are non-blocking, meaning they don't need to complete for the Agent to continue operations.
56
- # These types of events include any post-request messaging that occurs after execution is returned from the
57
- # application to our post-filter operations as well as those that don't alter the application's execution (i.e.
58
- # Assess' vulnerability reporting).
59
- #
60
- # @param event [Contrast::Api::Dtm, Contrast::Api::Dtm::Poll] One of the DTMs valid for the event field of
61
- # Contrast::Api::Dtm::Message
62
- # @param force [Boolean] if we should always queue this event, even if the service isn't running, in case the
63
- # message may be ready before the service has initiated. usually for those events which happen in a separate
64
- # thread or which may occur during initialization.
65
- def send_event_eventually event, force: false
66
- if ::Contrast::AGENT.disabled?
67
- logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
68
- return
69
- end
70
- return if ::Contrast::CONTRAST_SERVICE.unnecessary?
71
- # If we're unable to start the Service, then we cannot afford to queue messages as this creates the potential
72
- # for a memory leak, especially if the thread responsible for de-queueing the messages is dead.
73
- return if !force && !(speedracer.status.connected? && running?)
74
-
75
- # If we're in direct communication mode, the only message we should queue is the heartbeat to keep the
76
- # service alive during Protect activities. All other messages should go through direct reporting.
77
- if ::Contrast::CONTRAST_SERVICE.use_agent_communication?
78
- return unless ::Contrast::PROTECT.enabled?
79
- return unless event.cs__is_a?(Contrast::Api::Dtm::Poll)
80
- end
81
-
82
- logger.debug('Enqueued event for sending', event_type: event.cs__class)
83
- queue << event if event
84
- end
85
-
86
- # Create the reporting thread, which will pull from the queue in order to send messages to SpeedRacer. If
87
- # SpeedRacer is not running and should be, meaning the Agent is configured to control it, then we will also
88
- # try to start that process.
89
- def start_thread!
90
- return if ::Contrast::CONTRAST_SERVICE.unnecessary?
91
-
92
- speedracer.ensure_startup!
93
- return if running?
94
-
95
- @_thread = Contrast::Agent::Thread.new do
96
- loop do
97
- event = queue.pop
98
- begin
99
- logger.debug('Dequeued event for sending', event_type: event.cs__class)
100
- speedracer.process_internally(event)
101
- rescue StandardError => e
102
- logger.error('Could not send message to service from messaging queue thread.', e)
103
- end
104
- end
105
- end
106
- logger.debug('Started background sending thread.')
107
- end
108
-
109
- # When the Agent shuts down, we terminate the message sending operations. This method clears, closes, and
110
- # destroys the queue.
111
- def delete_queue!
112
- @_queue&.clear
113
- @_queue&.close
114
- @_queue = nil
115
- end
116
-
117
- # When the Agent shuts down, we terminate the thread responsible for reporting. This method destroys that
118
- # thread and any data we would have sent with it.
119
- def stop!
120
- return unless running?
121
-
122
- super
123
- delete_queue!
124
- end
125
- end
126
- end
127
- end
128
- end
@@ -1,90 +0,0 @@
1
- # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
- # frozen_string_literal: true
3
-
4
- require 'contrast/agent/reaction_processor'
5
- require 'contrast/components/logger'
6
-
7
- module Contrast
8
- module Api
9
- module Communication
10
- # Handles processing deferred messages sent to SpeedRacer.
11
- class ResponseProcessor
12
- include Contrast::Components::Logger::InstanceMethods
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
- #
17
- # @param response [Contrast::Api::Settings::AgentSettings]
18
- def process response
19
- logger.debug('Received a response', sent_ms: response&.sent_ms)
20
-
21
- server_features = process_server_response(response)
22
- app_settings = process_application_response(response)
23
-
24
- # ReactionProcessor is a design pattern from TeamServer. Right now, there's one potential reaction, which is
25
- # disabling the agent
26
- Contrast::Agent::ReactionProcessor.process(response&.application_settings)
27
-
28
- Contrast::Logger::Log.instance.update(server_features&.log_file, server_features&.log_level)
29
- update_features(server_features, app_settings)
30
- logger.trace('Agent settings updated in response to Service', protect_on: ::Contrast::PROTECT.enabled?,
31
- assess_on: ::Contrast::ASSESS.enabled?)
32
- end
33
-
34
- private
35
-
36
- # Given some protobuf messages, update server features. This is the bridge between SpeedRacer <-> Settings.
37
- #
38
- # @param response [Contrast::Api::Settings::AgentSettings]
39
- # @return [Contrast::Api::Settings::ServerFeatures]
40
- def process_server_response response
41
- server_features = response&.server_features
42
- return unless server_features
43
-
44
- logger.trace('Agent: Received updated server features')
45
-
46
- ::Contrast::SETTINGS.update_from_server_features(server_features)
47
- server_features
48
- end
49
-
50
- # Given some protobuf messages, update application settings. This is the bridge between SpeedRacer <->
51
- # Settings.
52
- #
53
- # @param response [Contrast::Api::Settings::AgentSettings]
54
- # @return [Contrast::Api::Settings::ApplicationSettings]
55
- def process_application_response response
56
- logger.debug('[!] process_application_response', response: response)
57
- app_settings = response&.application_settings
58
- return unless app_settings
59
-
60
- logger.debug('[!] Agent: Received updated application settings', settings: app_settings)
61
- ::Contrast::SETTINGS.update_from_application_settings(app_settings)
62
- app_settings
63
- end
64
-
65
- # This can't go in the Settings component because protect and assess depend on settings
66
- # I don't think it should go into contrast_service because that only handles connection specific data.
67
- #
68
- # @param server_features
69
- # [Contrast::Api::Settings::AgentSettings, Contrast::Agent::Reporting::Settings::FeatureSettings]
70
- # @param app_settings
71
- # [Contrast::Api::Settings::ApplicationSettings, Contrast::Agent::Reporting::Settings::ApplicationSettings]
72
- def update_features server_features, app_settings
73
- logger.info('Updating features')
74
- return unless !!(server_features || app_settings)
75
- return unless ::Contrast::AGENT.enabled?
76
-
77
- logger.trace_with_time('Rebuilding rule modes') do
78
- ::Contrast::SETTINGS.build_protect_rules if ::Contrast::PROTECT.enabled?
79
- ::Contrast::AGENT.reset_ruleset
80
-
81
- logger.info('Current rule settings:')
82
-
83
- ::Contrast::PROTECT.defend_rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
84
- logger.info('Disabled Assess Rules', rules: ::Contrast::ASSESS.disabled_rules)
85
- end
86
- end
87
- end
88
- end
89
- end
90
- end
@@ -1,77 +0,0 @@
1
- # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
- # frozen_string_literal: true
3
-
4
- require 'contrast/components/logger'
5
-
6
- module Contrast
7
- module Api
8
- module Communication
9
- # Handles local service startup. As this should only ever be invoked by the Speedracer class, which includes
10
- # this, all methods here are private.
11
- module ServiceLifecycle
12
- include Contrast::Components::Logger::InstanceMethods
13
-
14
- private
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?
20
- def attempt_local_service_startup
21
- zombie_check
22
- service_starter_thread.join(5)
23
- is_service_started = Contrast::Utils::OS.running?
24
- if is_service_started
25
- logger.info('The bundled service was successfully started.')
26
- else
27
- logger.error('The bundled service could not be started. The agent will not function properly.')
28
- end
29
- is_service_started
30
- end
31
-
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.
34
- def zombie_check
35
- zombie_pid_list = Contrast::Utils::OS.zombie_pids
36
- zombie_pid_list.each do |pid|
37
- Process.wait(pid.to_i)
38
- rescue Errno::ECHILD => _e
39
- # Sometimes the zombie process dies between us finding it and killing it
40
- end
41
- end
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
47
- def determine_startup_options
48
- return { out: :out, err: :out } if ::Contrast::CONTRAST_SERVICE.logger_path == 'STDOUT'
49
- return { out: :err, err: :err } if ::Contrast::CONTRAST_SERVICE.logger_path == 'STDERR'
50
-
51
- { out: File::NULL, err: File::NULL }
52
- end
53
-
54
- # Create a new process for the SpeedRacer. This is a separate method so we can overwrite it globally in specs.
55
- def spawn_service
56
- options = determine_startup_options
57
- logger.debug('Spawning service')
58
- spawn('contrast_service', options)
59
- end
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.
63
- def service_starter_thread
64
- Contrast::Agent::Thread.new do
65
- # Always check to see if it already started
66
- unless Contrast::Utils::OS.running?
67
- # Spawn the service process
68
- spawn_service
69
- # Block until service is running
70
- sleep(0.1) until Contrast::Utils::OS.running?
71
- end
72
- end
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,44 +0,0 @@
1
- # Copyright (c) 2022 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 Api
6
- module Communication
7
- # Behavior common to all sockets used to communicate with the Contrast Service.
8
- module Socket
9
- SOCKET_MONITOR = Monitor.new
10
-
11
- # Send a message across the socket and read back the response.
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.
16
- def send_marshaled marshaled
17
- SOCKET_MONITOR.synchronize do
18
- socket = new_socket
19
-
20
- # write message length
21
- len = marshaled.length
22
- socket.write([len].pack(Contrast::Api::ENCODING_STRING))
23
-
24
- # write the entire message
25
- socket.write(marshaled)
26
- socket.close_write
27
-
28
- # read the response length
29
- len = socket.read(4).unpack1(Contrast::Api::ENCODING_STRING)
30
-
31
- # read expected response to end
32
- socket.read(len)
33
- end
34
- end
35
-
36
- # Override this method to return a socket. Should be interface compatible with TCPSocket, UNIXSocket, etc.
37
- # @raise[NoMethodError] abstract method, needs to be implemented
38
- def new_socket
39
- raise(NoMethodError, 'This is abstract, override it.')
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,130 +0,0 @@
1
- # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
- # frozen_string_literal: true
3
-
4
- require 'socket'
5
- require 'uri'
6
-
7
- require 'contrast/api/communication/tcp_socket'
8
- require 'contrast/api/communication/unix_socket'
9
- require 'contrast/components/logger'
10
-
11
- module Contrast
12
- module Api
13
- module Communication
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
- class SocketClient
17
- include Contrast::Components::Logger::InstanceMethods
18
-
19
- def initialize
20
- @socket = init_connection
21
- end
22
-
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.
26
- #
27
- # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
28
- # Contrast::Api::Dtm::Message
29
- # @return [Contrast::Api::Settings::AgentSettings, nil]
30
- def send_one event
31
- msg = Contrast::Api::Dtm::Message.build(event)
32
- send_message(msg)
33
- end
34
-
35
- private
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]
43
- def init_connection
44
- log_connection
45
- if ::Contrast::CONTRAST_SERVICE.use_tcp?
46
- Contrast::Api::Communication::TcpSocket.new(
47
- ::Contrast::CONTRAST_SERVICE.host, ::Contrast::CONTRAST_SERVICE.port)
48
- else
49
- Contrast::Api::Communication::UnixSocket.new(::Contrast::CONTRAST_SERVICE.socket_path)
50
- end
51
- end
52
-
53
- # Log information about the connection being used to communicate between the Agent and SpeedRacer.
54
- def log_connection
55
- # The socket is set,
56
- if ::Contrast::CONFIG.agent.service.socket
57
- logger.info('Connecting to the Contrast Service using a UnixSocket socket',
58
- socket: ::Contrast::CONTRAST_SERVICE.socket_path)
59
- return
60
- end
61
- # The host & port are set,
62
- if ::Contrast::CONFIG.agent.service.host && ::Contrast::CONFIG.agent.service.port
63
- logger.info('Connecting to the Contrast Service using a TCP socket',
64
- host: ::Contrast::CONTRAST_SERVICE.host,
65
- port: ::Contrast::CONTRAST_SERVICE.port)
66
- return
67
- end
68
-
69
- # Or something is not set.
70
- logger.warn(
71
- log_connection_error_msg,
72
- host: ::Contrast::CONTRAST_SERVICE.host,
73
- port: ::Contrast::CONTRAST_SERVICE.port)
74
- end
75
-
76
- # If our connection isn't built properly, we need to warn the user. This builds out the context specific
77
- # message to provide that warning
78
- #
79
- # @return [String]
80
- def log_connection_error_msg
81
- if ::Contrast::CONFIG.agent.service.host
82
- 'Missing a required connection value to the Contrast Service. ' \
83
- '`agent.service.port` is not set. ' \
84
- 'Falling back to default TCP socket port.'
85
- elsif ::Contrast::CONFIG.agent.service.port
86
- 'Missing a required connection value to the Contrast Service. ' \
87
- '`agent.service.host` is not set. ' \
88
- 'Falling back to default TCP socket host.'
89
- else
90
- 'Missing a required connection value to the Contrast Service. ' \
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.'
93
- end
94
- end
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
101
- def send_message msg
102
- return unless msg
103
-
104
- logger.debug('Sending message.', msg_id: msg.__id__, p_id: msg.pid, msg_count: msg.message_count)
105
- to_service = Contrast::Api::Dtm::Message.encode(msg)
106
- from_service = send_marshaled(to_service)
107
- response = Contrast::Api::Settings::AgentSettings.decode(from_service)
108
- logger.debug('RESPONSE FROM TS')
109
- logger.debug(from_service)
110
- logger.debug('RESPONSE FROM TS')
111
- logger.debug('Received response.', msg_id: msg.__id__, p_id: msg.pid, msg_count: msg.message_count,
112
- response_id: response&.__id__)
113
- response
114
- rescue StandardError => e
115
- logger.error('Sending failed for message.', e, msg_id: msg.__id__, p_id: msg.pid,
116
- msg_count: msg.message_count, response_id: response&.__id__)
117
- raise(e) # reraise to let SpeedRacer manage the connection
118
- end
119
-
120
- # Send the marshaled Contrast::Api::Dtm::Message across the socket used to talk to SpeedRacer
121
- #
122
- # @param marshaled [String]
123
- # @return [String] the marshaled from of Contrast::Api::Settings::AgentSettings
124
- def send_marshaled marshaled
125
- @socket.send_marshaled(marshaled)
126
- end
127
- end
128
- end
129
- end
130
- end
@@ -1,138 +0,0 @@
1
- # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
- # frozen_string_literal: true
3
-
4
- require 'contrast/components/logger'
5
-
6
- module Contrast
7
- module Api
8
- module Communication
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.
13
- class Speedracer
14
- include Contrast::Api::Communication::ServiceLifecycle
15
- include Contrast::Components::Logger::InstanceMethods
16
-
17
- attr_reader :status, :response_processor, :socket_client, :ensure_running
18
-
19
- def initialize
20
- @status = Contrast::Api::Communication::ConnectionStatus.new
21
- @socket_client = Contrast::Api::Communication::SocketClient.new
22
- @response_processor = Contrast::Api::Communication::ResponseProcessor.new
23
- @ensure_running = Mutex.new
24
- end
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.
34
- def ensure_startup!
35
- return if status.connected?
36
-
37
- ensure_running.synchronize do
38
- if ::Contrast::CONTRAST_SERVICE.use_bundled_service?
39
- logger.info('Attempting to start local service')
40
- unless attempt_local_service_startup
41
- logger.error('Failed to start local service')
42
- return
43
- end
44
- end
45
- unless status.startup_messages_sent? || Contrast::CONTRAST_SERVICE.unnecessary?
46
- startup_responses = send_initialization_messages
47
- return false unless startup_responses
48
-
49
- startup_responses.each { |response| response_processor.process(response) }
50
- end
51
- end
52
- end
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 Contrast::Api::Dtm::Message
59
- # @return [Contrast::Api::Settings::AgentSettings,nil]
60
- def return_response event
61
- send_to_speedracer(event) do |response|
62
- return response
63
- end
64
- end
65
-
66
- # Send the given Event to SpeedRacer and pass the result to our Contrast::Api::Communication::ResponseProcessor
67
- # as there is no immediate action required from sending this Event.
68
- #
69
- # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of Contrast::Api::Dtm::Message
70
- def process_internally event
71
- send_to_speedracer(event) do |response|
72
- response_processor.process(response)
73
- end
74
- end
75
-
76
- private
77
-
78
- # Ensure there is a running SpeedRacer and then send the given Event to it. It is necessary to ensure the
79
- # SpeedRacer is running as, if the process has crashed or restarted, we must rebuild our context there.
80
- #
81
- # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of Contrast::Api::Dtm::Message
82
- # @return [Contrast::Api::Settings::AgentSettings, nil]
83
- def send_to_speedracer event
84
- ensure_startup!
85
-
86
- logger.debug_with_time(event.cs__class.cs__name) do
87
- response = socket_client.send_one(event)
88
- status.success!
89
- yield(response)
90
- end
91
- rescue StandardError => e
92
- status.failure!
93
- logger.error('Unable to send message.', e, event_id: event.__id__, event_type: event.cs__class.cs__name)
94
- nil
95
- end
96
-
97
- # Send those messages which are required to build a context for this Agent process on SpeedRacer as well as
98
- # report server and application startup to TeamServer. With these messages, the SpeedRacer will be able to
99
- # retrieve settings from TeamServer and provide those for the Agent to complete its initialization.
100
- def send_initialization_messages
101
- agent_startup_msg = ::Contrast::APP_CONTEXT.build_agent_startup_message
102
-
103
- logger.debug('Preparing to send startup messages')
104
- # 1 initial attempt, + 3 potential retries.
105
- # The agent-service-api REQUIREMENTS.md spec mandates this #.
106
- 4.times do
107
- log_send_event(agent_startup_msg)
108
- next unless (agent_response = socket_client.send_one(agent_startup_msg))
109
-
110
- # Connection was successful; send app create with the resolved features.
111
- app_startup_msg = ::Contrast::APP_CONTEXT.build_app_startup_message
112
- log_send_event(app_startup_msg)
113
- app_response = socket_client.send_one(app_startup_msg)
114
-
115
- status.success!
116
- logger.debug('Successfully sent startup messages to service.')
117
- return [agent_response, app_response]
118
- end
119
-
120
- status.failure!
121
- logger.error('Could not send agent startup message context')
122
- nil
123
- rescue StandardError => e
124
- logger.error('Could not build service context', e)
125
- status.failure!
126
- nil
127
- end
128
-
129
- # Log the startup message we're sending to SpeedRacer
130
- #
131
- # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of Contrast::Api::Dtm::Message
132
- def log_send_event event
133
- logger.debug('Immediately sending event.', event_id: event.__id__, event_type: event.cs__class.cs__name)
134
- end
135
- end
136
- end
137
- end
138
- end
@@ -1,32 +0,0 @@
1
- # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
- # frozen_string_literal: true
3
-
4
- require 'contrast/api/communication/socket'
5
-
6
- module Contrast
7
- module Api
8
- module 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.
11
- class TcpSocket
12
- include Contrast::Api::Communication::Socket
13
-
14
- attr_reader :host, :port
15
-
16
- # Create the socket
17
- #
18
- # @param host [String] socket target hostname or IP address
19
- # @param port [String,Integer] socket target port
20
- def initialize host, port
21
- @host = host.to_s
22
- @port = port.to_i
23
- end
24
-
25
- # @return [::TCPSocket]
26
- def new_socket
27
- ::TCPSocket.new(host, port)
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,28 +0,0 @@
1
- # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
- # frozen_string_literal: true
3
-
4
- require 'contrast/api/communication/socket'
5
-
6
- module Contrast
7
- module Api
8
- module Communication
9
- # Implements a UNIX domain socket to connect to the Contrast Service.
10
- class UnixSocket
11
- include Contrast::Api::Communication::Socket
12
-
13
- attr_reader :path
14
-
15
- # Create the socket
16
- # @param path [String] file path to a UNIX domain socket
17
- def initialize path
18
- @path = path
19
- end
20
-
21
- # @return [::UNIXSocket]
22
- def new_socket
23
- ::UNIXSocket.new(path)
24
- end
25
- end
26
- end
27
- end
28
- end