contrast-agent 6.2.0 → 6.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -3
  3. data/.simplecov +1 -0
  4. data/Rakefile +0 -27
  5. data/ext/cs__assess_basic_object/cs__assess_basic_object.c +7 -5
  6. data/ext/cs__assess_kernel/cs__assess_kernel.c +14 -3
  7. data/ext/cs__assess_kernel/cs__assess_kernel.h +2 -0
  8. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +10 -3
  9. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +2 -1
  10. data/ext/cs__assess_regexp/cs__assess_regexp.c +9 -7
  11. data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.c → cs__assess_string_interpolation/cs__assess_string_interpolation.c} +14 -3
  12. data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.h → cs__assess_string_interpolation/cs__assess_string_interpolation.h} +1 -1
  13. data/ext/{cs__assess_string_interpolation26 → cs__assess_string_interpolation}/extconf.rb +0 -0
  14. data/ext/cs__common/cs__common.c +5 -4
  15. data/ext/cs__contrast_patch/cs__contrast_patch.c +17 -11
  16. data/lib/contrast/agent/assess/events/source_event.rb +16 -12
  17. data/lib/contrast/agent/assess/finalizers/hash.rb +1 -0
  18. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -0
  19. data/lib/contrast/agent/assess/policy/propagation_method.rb +8 -42
  20. data/lib/contrast/agent/assess/policy/propagation_node.rb +8 -0
  21. data/lib/contrast/agent/assess/policy/propagator/base.rb +2 -0
  22. data/lib/contrast/agent/assess/policy/propagator/custom.rb +4 -0
  23. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -0
  24. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -0
  25. data/lib/contrast/agent/assess/policy/source_method.rb +7 -47
  26. data/lib/contrast/agent/assess/policy/source_node.rb +1 -0
  27. data/lib/contrast/agent/assess/policy/trigger_method.rb +9 -3
  28. data/lib/contrast/agent/assess/policy/trigger_node.rb +8 -0
  29. data/lib/contrast/agent/assess/property/evented.rb +4 -18
  30. data/lib/contrast/agent/assess/tag.rb +19 -0
  31. data/lib/contrast/agent/assess/tracker.rb +12 -0
  32. data/lib/contrast/agent/at_exit_hook.rb +8 -8
  33. data/lib/contrast/agent/inventory/database_config.rb +6 -3
  34. data/lib/contrast/agent/inventory/dependency_analysis.rb +5 -4
  35. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +11 -11
  36. data/lib/contrast/agent/inventory/policy/datastores.rb +1 -1
  37. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  38. data/lib/contrast/agent/middleware.rb +4 -0
  39. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +27 -2
  40. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -3
  41. data/lib/contrast/agent/patching/policy/policy.rb +5 -0
  42. data/lib/contrast/agent/patching/policy/policy_node.rb +6 -0
  43. data/lib/contrast/agent/patching/policy/trigger_node.rb +3 -0
  44. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +3 -4
  45. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +1 -0
  46. data/lib/contrast/agent/protect/policy/rule_applicator.rb +2 -2
  47. data/lib/contrast/agent/protect/rule/base.rb +1 -0
  48. data/lib/contrast/agent/protect/rule/no_sqli.rb +2 -0
  49. data/lib/contrast/agent/reporting/reporter.rb +32 -7
  50. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +22 -18
  51. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +17 -21
  52. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -1
  53. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +26 -3
  54. data/lib/contrast/agent/reporting/reporting_events/application_update.rb +5 -24
  55. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +8 -1
  56. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +8 -1
  57. data/lib/contrast/agent/reporting/reporting_events/finding.rb +7 -1
  58. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +10 -1
  59. data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +11 -1
  60. data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +11 -1
  61. data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +12 -1
  62. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -1
  63. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +11 -1
  64. data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +11 -1
  65. data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +11 -1
  66. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +11 -1
  67. data/lib/contrast/agent/reporting/reporting_events/library_discovery.rb +29 -32
  68. data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +13 -1
  69. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +11 -8
  70. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -5
  71. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +8 -1
  72. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +9 -1
  73. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +10 -1
  74. data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +11 -4
  75. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -8
  76. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -4
  77. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -22
  78. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -3
  79. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -11
  80. data/lib/contrast/agent/request.rb +5 -7
  81. data/lib/contrast/agent/request_context.rb +16 -17
  82. data/lib/contrast/agent/request_context_extend.rb +8 -9
  83. data/lib/contrast/agent/request_handler.rb +9 -38
  84. data/lib/contrast/agent/rule_set.rb +4 -0
  85. data/lib/contrast/agent/service_heartbeat.rb +3 -4
  86. data/lib/contrast/agent/static_analysis.rb +7 -12
  87. data/lib/contrast/agent/telemetry/base.rb +35 -35
  88. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +2 -0
  89. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +2 -0
  90. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +5 -2
  91. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +3 -0
  92. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +3 -0
  93. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +0 -1
  94. data/lib/contrast/agent/thread_watcher.rb +1 -4
  95. data/lib/contrast/agent/version.rb +1 -1
  96. data/lib/contrast/agent/worker_thread.rb +10 -0
  97. data/lib/contrast/api/communication/socket.rb +1 -0
  98. data/lib/contrast/api/decorators/message.rb +0 -6
  99. data/lib/contrast/api/decorators.rb +0 -2
  100. data/lib/contrast/api/dtm.pb.rb +1 -1
  101. data/lib/contrast/api/settings.pb.rb +1 -1
  102. data/lib/contrast/components/agent.rb +51 -13
  103. data/lib/contrast/components/assess.rb +16 -6
  104. data/lib/contrast/components/config.rb +18 -2
  105. data/lib/contrast/components/contrast_service.rb +1 -1
  106. data/lib/contrast/components/heap_dump.rb +51 -1
  107. data/lib/contrast/components/inventory.rb +19 -13
  108. data/lib/contrast/components/logger.rb +18 -0
  109. data/lib/contrast/config/assess_configuration.rb +28 -0
  110. data/lib/contrast/config/base_configuration.rb +8 -15
  111. data/lib/contrast/config/root_configuration.rb +12 -8
  112. data/lib/contrast/config/ruby_configuration.rb +2 -9
  113. data/lib/contrast/config/service_configuration.rb +4 -4
  114. data/lib/contrast/config.rb +0 -6
  115. data/lib/contrast/configuration.rb +0 -2
  116. data/lib/contrast/extension/assess/eval_trigger.rb +0 -4
  117. data/lib/contrast/extension/assess/hash.rb +3 -2
  118. data/lib/contrast/extension/assess/kernel.rb +22 -0
  119. data/lib/contrast/extension/assess/marshal.rb +16 -0
  120. data/lib/contrast/extension/assess/string.rb +21 -20
  121. data/lib/contrast/extension/object.rb +19 -0
  122. data/lib/contrast/framework/base_support.rb +8 -0
  123. data/lib/contrast/framework/manager.rb +6 -20
  124. data/lib/contrast/framework/manager_extend.rb +0 -1
  125. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +11 -16
  126. data/lib/contrast/framework/rails/support.rb +4 -1
  127. data/lib/contrast/logger/aliased_logging.rb +2 -0
  128. data/lib/contrast/logger/log.rb +2 -1
  129. data/lib/contrast/utils/assess/event_limit_utils.rb +96 -0
  130. data/lib/contrast/utils/assess/propagation_method_utils.rb +27 -7
  131. data/lib/contrast/utils/assess/source_method_utils.rb +0 -9
  132. data/lib/contrast/utils/log_utils.rb +2 -2
  133. data/lib/contrast/utils/lru_cache.rb +3 -0
  134. data/lib/contrast/utils/middleware_utils.rb +2 -0
  135. data/lib/contrast/utils/patching/policy/patch_utils.rb +6 -23
  136. data/lib/contrast/utils/telemetry_client.rb +7 -7
  137. data/lib/contrast.rb +37 -18
  138. data/lib/protobuf/code_generator.rb +129 -0
  139. data/lib/protobuf/decoder.rb +28 -0
  140. data/lib/protobuf/deprecation.rb +117 -0
  141. data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +79 -0
  142. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +360 -0
  143. data/lib/protobuf/descriptors.rb +3 -0
  144. data/lib/protobuf/encoder.rb +11 -0
  145. data/lib/protobuf/enum.rb +365 -0
  146. data/lib/protobuf/exceptions.rb +9 -0
  147. data/lib/protobuf/field/base_field.rb +380 -0
  148. data/lib/protobuf/field/base_field_object_definitions.rb +504 -0
  149. data/lib/protobuf/field/bool_field.rb +64 -0
  150. data/lib/protobuf/field/bytes_field.rb +67 -0
  151. data/lib/protobuf/field/double_field.rb +25 -0
  152. data/lib/protobuf/field/enum_field.rb +56 -0
  153. data/lib/protobuf/field/field_array.rb +102 -0
  154. data/lib/protobuf/field/field_hash.rb +122 -0
  155. data/lib/protobuf/field/fixed32_field.rb +25 -0
  156. data/lib/protobuf/field/fixed64_field.rb +28 -0
  157. data/lib/protobuf/field/float_field.rb +43 -0
  158. data/lib/protobuf/field/int32_field.rb +21 -0
  159. data/lib/protobuf/field/int64_field.rb +34 -0
  160. data/lib/protobuf/field/integer_field.rb +23 -0
  161. data/lib/protobuf/field/message_field.rb +51 -0
  162. data/lib/protobuf/field/sfixed32_field.rb +27 -0
  163. data/lib/protobuf/field/sfixed64_field.rb +28 -0
  164. data/lib/protobuf/field/signed_integer_field.rb +29 -0
  165. data/lib/protobuf/field/sint32_field.rb +21 -0
  166. data/lib/protobuf/field/sint64_field.rb +21 -0
  167. data/lib/protobuf/field/string_field.rb +51 -0
  168. data/lib/protobuf/field/uint32_field.rb +21 -0
  169. data/lib/protobuf/field/uint64_field.rb +21 -0
  170. data/lib/protobuf/field/varint_field.rb +77 -0
  171. data/lib/protobuf/field.rb +74 -0
  172. data/lib/protobuf/generators/base.rb +85 -0
  173. data/lib/protobuf/generators/enum_generator.rb +39 -0
  174. data/lib/protobuf/generators/extension_generator.rb +27 -0
  175. data/lib/protobuf/generators/field_generator.rb +193 -0
  176. data/lib/protobuf/generators/file_generator.rb +262 -0
  177. data/lib/protobuf/generators/group_generator.rb +122 -0
  178. data/lib/protobuf/generators/message_generator.rb +104 -0
  179. data/lib/protobuf/generators/option_generator.rb +17 -0
  180. data/lib/protobuf/generators/printable.rb +160 -0
  181. data/lib/protobuf/generators/service_generator.rb +50 -0
  182. data/lib/protobuf/lifecycle.rb +33 -0
  183. data/lib/protobuf/logging.rb +39 -0
  184. data/lib/protobuf/message/fields.rb +233 -0
  185. data/lib/protobuf/message/serialization.rb +85 -0
  186. data/lib/protobuf/message.rb +241 -0
  187. data/lib/protobuf/optionable.rb +72 -0
  188. data/lib/protobuf/tasks/compile.rake +80 -0
  189. data/lib/protobuf/tasks.rb +1 -0
  190. data/lib/protobuf/varint.rb +20 -0
  191. data/lib/protobuf/varint_pure.rb +31 -0
  192. data/lib/protobuf/version.rb +3 -0
  193. data/lib/protobuf/wire_type.rb +10 -0
  194. data/lib/protobuf.rb +91 -0
  195. data/proto/dynamic_discovery.proto +46 -0
  196. data/proto/google/protobuf/compiler/plugin.proto +183 -0
  197. data/proto/google/protobuf/descriptor.proto +911 -0
  198. data/proto/rpc.proto +71 -0
  199. data/resources/assess/policy.json +6 -23
  200. data/ruby-agent.gemspec +4 -2
  201. metadata +122 -33
  202. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +0 -30
  203. data/lib/contrast/api/decorators/application_update.rb +0 -44
  204. data/lib/contrast/api/decorators/library.rb +0 -56
  205. data/lib/contrast/config/agent_configuration.rb +0 -63
  206. data/lib/contrast/config/heap_dump_configuration.rb +0 -59
  207. data/lib/contrast/config/inventory_configuration.rb +0 -33
  208. data/lib/contrast/config/logger_configuration.rb +0 -26
  209. data/lib/contrast/framework/platform_version.rb +0 -22
@@ -47,10 +47,14 @@ module Contrast
47
47
  # Scope of the method parsed from our JSON policy.
48
48
  attr_reader :method_scope
49
49
 
50
+ # @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
51
+ # that method implemented, but is being called on.
50
52
  def node_class
51
53
  raise(NoMethodError, 'specify the type of the feature for which this node patches')
52
54
  end
53
55
 
56
+ # @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
57
+ # that method implemented, but is being called on.
54
58
  def feature
55
59
  raise(NoMethodError, 'specify the name of the feature for which this node patches')
56
60
  end
@@ -62,6 +66,8 @@ module Contrast
62
66
  # Don't let nodes be created that will be missing things we need
63
67
  # later on. Really, if they don't have these things, they couldn't have
64
68
  # done their jobs anyway.
69
+ #
70
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
65
71
  def validate
66
72
  unless class_name
67
73
  raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
@@ -41,6 +41,7 @@ module Contrast
41
41
  NODE
42
42
  end
43
43
 
44
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
44
45
  def validate
45
46
  super
46
47
  unless applicator.public_methods(false).any?(applicator_method)
@@ -52,6 +53,7 @@ module Contrast
52
53
  validate_rule
53
54
  end
54
55
 
56
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
55
57
  def validate_properties
56
58
  if (required_properties & optional_properties).any?
57
59
  raise(ArgumentError,
@@ -66,6 +68,7 @@ module Contrast
66
68
  raise(ArgumentError, "#{ id } did not have a required property. Unable to create.")
67
69
  end
68
70
 
71
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
69
72
  def validate_rule
70
73
  raise(ArgumentError, 'Unknown rule did not have a proper name. Unable to create.') unless rule_id
71
74
  raise(ArgumentError, "#{ id } did not have a proper applicator. Unable to create.") unless applicator
@@ -31,13 +31,12 @@ module Contrast
31
31
  # was invoked
32
32
  # @param args [Array<Object>] the arguments passed to the triggering
33
33
  # method at invocation
34
- # @raise [Contrast::SecurityException] on block, will pass the
35
- # exception from the rule
36
34
  def invoke _method, _exception, _properties, _object, args
37
35
  return unless valid_input?(args)
38
36
  return if skip_analysis?
39
37
 
40
38
  rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, args[0])
39
+ # add rescue here
41
40
  end
42
41
 
43
42
  # Calls the actual rule for this applicator, if required, when the
@@ -46,13 +45,12 @@ module Contrast
46
45
  #
47
46
  # @param arg [Object] the argument passed to the triggering method
48
47
  # at invocation
49
- # @raise [Contrast::SecurityException] on block, will pass the
50
- # exception from the rule
51
48
  def prepended_invoke arg
52
49
  return unless arg&.cs__is_a?(String)
53
50
  return if skip_analysis?
54
51
 
55
52
  rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, arg)
53
+ # add rescue here
56
54
  end
57
55
 
58
56
  # Allow the rule to check if the given input is an attempt to
@@ -67,6 +65,7 @@ module Contrast
67
65
  return if skip_analysis?
68
66
 
69
67
  rule.check_command_scope(command)
68
+ # add rescue here
70
69
  end
71
70
 
72
71
  protected
@@ -64,6 +64,7 @@ module Contrast
64
64
  write_marker && possible_write?(write_marker)
65
65
  end
66
66
 
67
+ # @raise [Contrast::SecurityException] re-raises if some attack is blocked and raised from the infilter
67
68
  def path_traversal_rule path, possible_write, object, method
68
69
  return unless applies_to?(path, possible_write: possible_write)
69
70
 
@@ -61,8 +61,7 @@ module Contrast
61
61
  # was invoked
62
62
  # @param _args [Array<Object>] the arguments passed to the triggering
63
63
  # method at invocation
64
- # @raise [Contrast::SecurityException] on block, will pass the
65
- # exception from the rule
64
+ # @raise [NoMethodError] This is abstract method
66
65
  def invoke _method, _exception, _properties, _object, _args
67
66
  raise(NoMethodError, 'This is abstract, override it.')
68
67
  end
@@ -70,6 +69,7 @@ module Contrast
70
69
  # The name of the rule, as expected by the Contrast Service and Contrast UI.
71
70
  #
72
71
  # @return [String]
72
+ # @raise [NoMethodError] This is abstract method
73
73
  def rule_name
74
74
  raise(NoMethodError, 'This is abstract, override it.')
75
75
  end
@@ -230,6 +230,7 @@ module Contrast
230
230
  # the rule and matched the attack detection logic
231
231
  # @param _kwargs [Hash] key-value pairs used by the rule to build a
232
232
  # report.
233
+ # @raise[NoMethodError] raises if subclass did not implement this method on extend
233
234
  def find_attacker _context, _potential_attack_string, **_kwargs
234
235
  raise(NoMethodError, "Rule #{ rule_name } did not implement find_attack")
235
236
  end
@@ -40,6 +40,8 @@ module Contrast
40
40
  BLOCK_MESSAGE
41
41
  end
42
42
 
43
+ # @raise [Contrast::SecurityException] if the attack is blocked
44
+ # raised with BLOCK_MESSAGE
43
45
  def infilter context, database, query_string
44
46
  return unless infilter?(context)
45
47
 
@@ -38,14 +38,12 @@ module Contrast
38
38
  @_thread = Contrast::Agent::Thread.new do
39
39
  logger.debug('Starting background Reporter thread.')
40
40
  loop do
41
- # TODO: RUBY-99999
42
- # The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
43
- # to figure out why that is and lock it so that it isn't.
44
- next unless client && connection
41
+ next unless connected?
42
+ next unless app_create_complete?
45
43
 
46
44
  process_event(queue.pop)
47
45
  rescue StandardError => e
48
- logger.debug('Reporter thread could not process because of:', error: e)
46
+ logger.debug('Reporter thread could not process because of:', e)
49
47
  end
50
48
  end
51
49
  end
@@ -71,7 +69,6 @@ module Contrast
71
69
  end
72
70
  return unless event
73
71
 
74
- logger.debug('Enqueued event for sending', event_type: event.cs__class)
75
72
  queue << event
76
73
  end
77
74
 
@@ -84,7 +81,9 @@ module Contrast
84
81
  logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
85
82
  return
86
83
  end
87
- client.send_event(event, connection, send_immediately: true)
84
+ return unless event
85
+
86
+ client.send_event(event, connection)
88
87
  rescue StandardError => e
89
88
  logger.error('Could not send message to TeamServer from Reporter queue.', e)
90
89
  end
@@ -108,6 +107,32 @@ module Contrast
108
107
  @_queue ||= Queue.new
109
108
  end
110
109
 
110
+ # TODO: RUBY-99999
111
+ # The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
112
+ # to figure out why that is and lock it so that it isn't.
113
+ #
114
+ # @return [Boolean]
115
+ def connected?
116
+ return true if client && connection
117
+
118
+ logger.debug('No client/connection; sleeping', client: client, connection: connection)
119
+ sleep(5)
120
+ false
121
+ end
122
+
123
+ # Unless we're in bypass mode, we need to make sure the service has started and built the application on
124
+ # TeamServer since we're doing a split style here.
125
+ #
126
+ # @return [Boolean]
127
+ def app_create_complete?
128
+ return true if Contrast::CONTRAST_SERVICE.use_agent_communication?
129
+ return true if Contrast::Agent.messaging_queue&.speedracer&.status&.startup_messages_sent?
130
+
131
+ logger.debug('Service startup incomplete; Application may not be created; sleeping')
132
+ sleep(5)
133
+ false
134
+ end
135
+
111
136
  # @param event [Contrast::Agent::Reporting::ReportingEvent]
112
137
  def process_event event
113
138
  client.send_event(event, connection)
@@ -3,47 +3,51 @@
3
3
 
4
4
  require 'contrast/agent/worker_thread'
5
5
  require 'contrast/agent/reporting/report'
6
- require 'contrast/components/logger'
7
- require 'contrast/agent/reporting/reporting_events/agent_startup'
6
+ require 'contrast/agent/inventory/dependency_usage_analysis'
7
+ require 'contrast/agent/reporting/reporting_events/poll'
8
+ require 'contrast/agent/reporting/reporting_events/server_activity'
8
9
 
9
10
  module Contrast
10
11
  module Agent
11
12
  # The ReporterHeartbeat will make sure that the process remains marked alive by TeamServer and that we periodically
12
- # reach out to get the latest settings for this application.
13
+ # reach out to get the latest settings for this application. It also sends out those messages which do not need to
14
+ # be associated directly with a request, such as Server Activity and Library Observation.
13
15
  class ReporterHeartbeat < WorkerThread
14
- include Contrast::Components::Logger::InstanceMethods
15
-
16
16
  # TeamServer will mark an application offline after 5 minutes. Sending this every one should be more than enough
17
17
  # to satisfy our goals.
18
18
  REFRESH_INTERVAL_SEC = 60
19
19
 
20
- # check if we can report to TS
21
- #
22
- # @return[Boolean] true if bypass is enabled, or false if bypass disabled
23
- def enabled?
24
- @_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
25
- @_enabled
26
- end
27
-
28
- def connection
29
- @_connection ||= client.initialize_connection
30
- end
31
-
32
20
  def start_thread!
33
21
  return if running?
34
22
 
35
23
  @_thread = Contrast::Agent::Thread.new do
36
24
  logger.info('Starting heartbeat thread.')
37
25
  loop do
38
- Contrast::Agent.reporter&.send_event(poll_message)
26
+ polling_events.each do |event|
27
+ Contrast::Agent.reporter&.send_event(event)
28
+ end
29
+ clean_properties
39
30
  sleep(REFRESH_INTERVAL_SEC)
40
31
  end
41
32
  end
42
33
  end
43
34
 
35
+ private
36
+
44
37
  def poll_message
45
38
  @_poll_message ||= Contrast::Agent::Reporting::Poll.new
46
39
  end
40
+
41
+ # Those events which should be sent periodically, rather than on event or request.
42
+ #
43
+ # @return [Array<Contrast::Agent::Reporting::ReportingEvent>]
44
+ def polling_events
45
+ [
46
+ Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage,
47
+ Contrast::Agent::Reporting::ServerActivity.new,
48
+ poll_message
49
+ ].compact
50
+ end
47
51
  end
48
52
  end
49
53
  end
@@ -56,35 +56,31 @@ module Contrast
56
56
  # @param attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
57
57
  # @param rule [String]
58
58
  def attach_existing existing_attacker_activity, attacker_activity, rule
59
- # TODO: RUBY-1663 figure out attack time mapping
60
59
  new_violation = attacker_activity.protection_rules[rule]
60
+ sample_activity = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity
61
61
  if (previously_violated = existing_attacker_activity.protection_rules[rule])
62
- if (new_blocked = new_violation.blocked&.samples)
63
- unless previously_violated.blocked
64
- previously_violated.blocked = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
65
- end
66
- previously_violated.blocked.samples.concat(new_blocked)
62
+ if (new_blocked = new_violation.blocked)
63
+ previously_violated.blocked ||= sample_activity.new
64
+ previously_violated.blocked.samples.concat(new_blocked.samples) if new_blocked.samples
65
+ previously_violated.blocked.merge_time_maps(new_blocked.time_map)
67
66
  end
68
67
 
69
- if (new_exploited = new_violation.exploited&.samples)
70
- unless previously_violated.exploited
71
- previously_violated.exploited = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
72
- end
73
- previously_violated.exploited.samples.concat(new_exploited)
68
+ if (new_exploited = new_violation.exploited)
69
+ previously_violated.exploited ||= sample_activity.new
70
+ previously_violated.exploited.samples.concat(new_exploited.samples) if new_exploited.samples
71
+ previously_violated.exploited.merge_time_maps(new_exploited.time_map)
74
72
  end
75
73
 
76
- if (new_ineffective = new_violation.ineffective&.samples)
77
- unless previously_violated.ineffective
78
- previously_violated.ineffective = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
79
- end
80
- previously_violated.ineffective.samples.concat(new_ineffective)
74
+ if (new_ineffective = new_violation.ineffective)
75
+ previously_violated.ineffective ||= sample_activity.new
76
+ previously_violated.ineffective.samples.concat(new_ineffective.samples) if new_ineffective.samples
77
+ previously_violated.ineffective.merge_time_maps(new_ineffective.time_map)
81
78
  end
82
79
 
83
- if (new_suspicious = new_violation.suspicious&.samples)
84
- unless previously_violated.suspicious
85
- previously_violated.suspicious = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
86
- end
87
- previously_violated.suspicious.samples.concat(new_suspicious)
80
+ if (new_suspicious = new_violation.suspicious)
81
+ previously_violated.suspicious ||= sample_activity.new
82
+ previously_violated.suspicious.samples.concat(new_suspicious.samples) if new_suspicious.samples
83
+ previously_violated.suspicious.merge_time_maps(new_suspicious.time_map)
88
84
  end
89
85
  else
90
86
  existing_attacker_activity.protection_rules[rule] = new_violation
@@ -16,7 +16,7 @@ module Contrast
16
16
  class ApplicationDefendAttackSample
17
17
  include Contrast::Agent::Reporting::InputType
18
18
 
19
- # @return [Hash]
19
+ # @return [Hash] => start: ms, elapsed: ms
20
20
  attr_reader :time_stamp
21
21
 
22
22
  class << self
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/logger'
5
+ require 'contrast/utils/timer'
5
6
  require 'contrast/agent/reporting/reporting_events/application_defend_attack_sample'
6
7
 
7
8
  module Contrast
@@ -26,7 +27,7 @@ module Contrast
26
27
 
27
28
  def initialize
28
29
  @samples = []
29
- @start_time = (Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0) / 1000
30
+ @start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0 # in ms
30
31
  @event_type = :application_defend_attack_sample_activity
31
32
  @time_map = Hash.new { |h, k| h[k] = 0 }
32
33
  super
@@ -36,7 +37,7 @@ module Contrast
36
37
  {
37
38
  attackTimeMap: time_map,
38
39
  samples: samples.map(&:to_controlled_hash),
39
- startTime: @start_time,
40
+ startTime: @start_time, # Start time in ms.
40
41
  total: 1 # there will only ever be 1 attack sample, until batching is done
41
42
  }
42
43
  end
@@ -44,12 +45,34 @@ module Contrast
44
45
  # @param attack_result [Contrast::Api::Dtm::AttackResult]
45
46
  def attach_data attack_result
46
47
  attack_result.samples.each do |attack_sample|
48
+ base_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0
49
+ dmt_time = attack_sample.timestamp_ms.to_i
47
50
  converted = Contrast::Agent::Reporting::ApplicationDefendAttackSample.convert(attack_result, attack_sample)
48
51
  samples << converted
49
- attack_second = converted.time_stamp[:elapsed] / 1000
52
+ @start_time = if dmt_time.zero?
53
+ @start_time
54
+ else
55
+ dmt_time
56
+ end
57
+ attack_second = (@start_time - base_time) / 1000 # in seconds
50
58
  time_map[attack_second] += 1
51
59
  end
52
60
  end
61
+
62
+ # This method will merge time_maps of attack samples with same
63
+ # type.
64
+ #
65
+ # @param map [Hash<Integer,Integer>] TimeMap to append to previously_violated rule
66
+ # samples.
67
+ # @return time_map [Hash<Integer,Integer>] merged time map with updated occurrences.
68
+ def merge_time_maps map
69
+ # If the second is the same (key) if we just merge there won't be a new entry,
70
+ # so just increase the attack count.
71
+ map.each_key do |key|
72
+ @time_map[key] = @time_map.fetch(key, 0) + map[key]
73
+ end
74
+ @time_map
75
+ end
53
76
  end
54
77
  end
55
78
  end
@@ -17,23 +17,16 @@ module Contrast
17
17
  # system. Contains data used by TeamServer to render the Flow Map and SCA features.
18
18
  #
19
19
  # @attr_reader components [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
20
- # @attr_reader libraries [Array<Contrast::Agent::Reporting::LibraryDiscovery>]
20
+ # @attr_accessor libraries [Array<Contrast::Agent::Reporting::LibraryDiscovery>]
21
21
  class ApplicationUpdate < Contrast::Agent::Reporting::ApplicationReportingEvent
22
- attr_reader :components, :libraries
23
-
24
- class << self
25
- # @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
26
- # @return [Contrast::Agent::Reporting::ApplicationUpdate]
27
- def convert app_update_dtm
28
- report = new
29
- report.attach_data(app_update_dtm)
30
- report
31
- end
32
- end
22
+ attr_reader :components
23
+ attr_accessor :libraries
33
24
 
34
25
  def initialize
35
26
  @event_method = :PUT
36
27
  @event_endpoint = "#{ Contrast::API.api_url }/api/ng/update/application"
28
+ @components = []
29
+ @libraries = []
37
30
  super
38
31
  end
39
32
 
@@ -41,18 +34,6 @@ module Contrast
41
34
  'update-application'
42
35
  end
43
36
 
44
- # Attach the data from the protobuf models to this reporter so that it can be sent to TeamServer directly.
45
- #
46
- # @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
47
- def attach_data app_update_dtm
48
- @components = app_update_dtm.components.map do |component|
49
- Contrast::Agent::Reporting::ArchitectureComponent.convert(component)
50
- end
51
- @libraries = app_update_dtm.libraries.values.map do |library|
52
- Contrast::Agent::Reporting::LibraryDiscovery.convert(library)
53
- end
54
- end
55
-
56
37
  # Convert the instance variables on the class, and other information, into the identifiers required for
57
38
  # TeamServer to process the JSON form of this message.
58
39
  #
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/api/dtm.pb'
5
+ require 'contrast/components/logger'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -17,6 +18,7 @@ module Contrast
17
18
  # @attr_reader url [String] the url used to connect to the component. Required for reporting.
18
19
  # @attr_reader vendor [String] the publisher of the component, like MySQL.
19
20
  class ArchitectureComponent
21
+ include Contrast::Components::Logger::InstanceMethods
20
22
  # required attributes
21
23
  attr_reader :type, :url
22
24
  # optional attributes
@@ -55,7 +57,12 @@ module Contrast
55
57
  # @return [Hash]
56
58
  # @raise [ArgumentError]
57
59
  def to_controlled_hash
58
- validate
60
+ begin
61
+ validate
62
+ rescue ArgumentError => e
63
+ logger.error('ArchitectureComponent validation failed with: ', e)
64
+ return
65
+ end
59
66
  {
60
67
  remoteHost: remote_host,
61
68
  remotePort: remote_port,
@@ -16,6 +16,8 @@ module Contrast
16
16
  # includes the literal URL and HTTP Verb used to invoke them, as they must have been called at this point to be
17
17
  # recorded.
18
18
  class DiscoveredRoute < Contrast::Agent::Reporting::ObservedRoute
19
+ include Contrast::Components::Logger::InstanceMethods
20
+
19
21
  class << self
20
22
  # @param obj [Regexp, Object]
21
23
  # @return [String]
@@ -104,7 +106,12 @@ module Contrast
104
106
  end
105
107
 
106
108
  def to_controlled_hash
107
- validate
109
+ begin
110
+ validate
111
+ rescue ArgumentError => e
112
+ logger.error('DiscoveredRoute validation failed with: ', e)
113
+ return
114
+ end
108
115
  { session_id: ::Contrast::ASSESS.session_id, signature: @signature, verb: @verb, url: @url }.compact
109
116
  end
110
117
 
@@ -130,7 +130,13 @@ module Contrast
130
130
  # @return [Hash]
131
131
  # @raise [ArgumentError]
132
132
  def to_controlled_hash
133
- validate
133
+ begin
134
+ validate
135
+ rescue ArgumentError => e
136
+ logger.error('Finding event validation failed with: ', e)
137
+ return
138
+ end
139
+
134
140
  hsh = {
135
141
  created: created,
136
142
  hash: hash_code.to_s,
@@ -8,6 +8,7 @@ require 'contrast/agent/reporting/reporting_events/finding_event_signature'
8
8
  require 'contrast/agent/reporting/reporting_events/finding_event_source'
9
9
  require 'contrast/agent/reporting/reporting_events/finding_event_stack'
10
10
  require 'contrast/agent/reporting/reporting_events/finding_event_taint_range'
11
+ require 'contrast/components/logger'
11
12
 
12
13
  module Contrast
13
14
  module Agent
@@ -17,6 +18,8 @@ module Contrast
17
18
  # construct the vulnerability information for the assess feature. They represent the operation the application
18
19
  # underwent that transformed data during the dataflow.
19
20
  class FindingEvent
21
+ include Contrast::Components::Logger::InstanceMethods
22
+
20
23
  # @return [Symbol] what the event did; CREATION, A2O, A2P, A2A, A2R, O2A, O2O, O2P, O2R, P2A, P2O, P2P, P2R,
21
24
  # TAG, TRIGGER.
22
25
  attr_reader :action
@@ -120,7 +123,13 @@ module Contrast
120
123
  # @return [Hash]
121
124
  # @raise [ArgumentError]
122
125
  def to_controlled_hash # rubocop:disable Metrics/AbcSize
123
- validate
126
+ begin
127
+ validate
128
+ rescue ArgumentError => e
129
+ logger.error('FindingEvent validation failed with: ', e)
130
+ return
131
+ end
132
+
124
133
  {
125
134
  action: action,
126
135
  args: args.map(&:to_controlled_hash),
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'base64'
5
5
  require 'contrast/agent/assess/contrast_object'
6
+ require 'contrast/components/logger'
6
7
 
7
8
  module Contrast
8
9
  module Agent
@@ -12,6 +13,8 @@ module Contrast
12
13
  # TeamServer to construct the vulnerability information for the assess feature. They represent those parts of the
13
14
  # objects that were acted on in a Dataflow Finding.
14
15
  class FindingEventObject
16
+ include Contrast::Components::Logger::InstanceMethods
17
+
15
18
  # @return [Integer] the id of the Object this represents.
16
19
  attr_reader :hash
17
20
  # @return [Boolean] if the Object is tracked or not
@@ -52,7 +55,13 @@ module Contrast
52
55
  # @return [Hash]
53
56
  # @raise [ArgumentError]
54
57
  def to_controlled_hash
55
- validate
58
+ begin
59
+ validate
60
+ rescue ArgumentError => e
61
+ logger.error('FindingEventObject validation failed with: ', e)
62
+ return
63
+ end
64
+
56
65
  {
57
66
  hash: hash,
58
67
  tracked: tracked,
@@ -60,6 +69,7 @@ module Contrast
60
69
  }
61
70
  end
62
71
 
72
+ # @raise [ArgumentError]
63
73
  def validate
64
74
  raise(ArgumentError, "#{ self } did not have a proper hash. Unable to continue.") unless hash
65
75
  raise(ArgumentError, "#{ self } did not have a proper tracked. Unable to continue.") if tracked.nil?
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'base64'
5
+ require 'contrast/components/logger'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -11,6 +12,8 @@ module Contrast
11
12
  # used by TeamServer to relate this event to those that came previously. They represent the events that directly
12
13
  # preceding the FindingEvent generated.
13
14
  class FindingEventParentObject
15
+ include Contrast::Components::Logger::InstanceMethods
16
+
14
17
  # @return [Integer] the Id of the parent event
15
18
  attr_reader :id
16
19
 
@@ -24,12 +27,19 @@ module Contrast
24
27
  # @return [Hash]
25
28
  # @raise [ArgumentError]
26
29
  def to_controlled_hash
27
- validate
30
+ begin
31
+ validate
32
+ rescue ArgumentError => e
33
+ logger.error('FindingEventParentObject validation failed with: ', e)
34
+ return
35
+ end
36
+
28
37
  {
29
38
  id: id
30
39
  }
31
40
  end
32
41
 
42
+ # @raise [ArgumentError]
33
43
  def validate
34
44
  raise(ArgumentError, "#{ self } did not have a proper id. Unable to continue.") unless id
35
45
  end
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'contrast/components/logger'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  module Reporting
@@ -8,6 +10,8 @@ module Contrast
8
10
  # system to relay this information in the Finding/Trace messages. Events have properties on them which are held
9
11
  # as an array of key-value pairs.
10
12
  class FindingEventProperty
13
+ include Contrast::Components::Logger::InstanceMethods
14
+
11
15
  # @return [String] the key of the property
12
16
  attr_reader :key
13
17
  # @return [String] the value of the source
@@ -24,13 +28,20 @@ module Contrast
24
28
  # @return [Hash]
25
29
  # @raise [ArgumentError]
26
30
  def to_controlled_hash
27
- validate
31
+ begin
32
+ validate
33
+ rescue ArgumentError => e
34
+ logger.error('FindingEventProperty validation failed with: ', e)
35
+ return
36
+ end
37
+
28
38
  {
29
39
  key: key,
30
40
  value: value
31
41
  }
32
42
  end
33
43
 
44
+ # @raise [ArgumentError]
34
45
  def validate
35
46
  raise(ArgumentError, "#{ self } did not have a proper key. Unable to continue.") unless key && !key.empty?
36
47
  end