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
@@ -14,6 +14,7 @@ require 'contrast/utils/telemetry'
14
14
  require 'contrast/agent/request_handler'
15
15
  require 'contrast/agent/static_analysis'
16
16
  require 'contrast/agent/startup_metrics_telemetry_event'
17
+ require 'contrast/utils/middleware_utils'
17
18
 
18
19
  require 'contrast/utils/timer'
19
20
 
@@ -25,6 +26,7 @@ module Contrast
25
26
  class Middleware
26
27
  include Contrast::Components::Logger::InstanceMethods
27
28
  include Contrast::Components::Scope::InstanceMethods
29
+ include Contrast::Utils::MiddlewareUtils
28
30
 
29
31
  attr_reader :app
30
32
 
@@ -66,22 +68,6 @@ module Contrast
66
68
 
67
69
  private
68
70
 
69
- def setup_agent
70
- ::Contrast::SETTINGS.reset_state
71
-
72
- inform_deprecations
73
- telemetry_disclaimer
74
-
75
- if ::Contrast::CONFIG.invalid?
76
- ::Contrast::AGENT.disable!
77
- logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
78
- elsif ::Contrast::AGENT.disabled?
79
- logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
80
- else
81
- ::Contrast::AGENT.enable!
82
- end
83
- end
84
-
85
71
  # Startup the Agent as part of the initialization process:
86
72
  # - start the service sending thread, responsible for sending and processing messages
87
73
  # - start the heartbeat thread, which triggers service startup
@@ -175,6 +161,9 @@ module Contrast
175
161
  with_contrast_scope do
176
162
  context.extract_after(response) # update context with final response information
177
163
 
164
+ # Build and report all collected findings prior response
165
+ Contrast::Agent::FINDINGS.report_collected_findings unless Contrast::Agent::FINDINGS.collection.empty?
166
+
178
167
  if Contrast::Agent.framework_manager.streaming?(env)
179
168
  context.reset_activity
180
169
  request_handler.stream_safe_postfilter
@@ -188,65 +177,6 @@ module Contrast
188
177
 
189
178
  logger.error('Unable to execute agent post_call', e)
190
179
  end
191
-
192
- def application_code env
193
- logger.trace_with_time('application') do
194
- app.call(env)
195
- end
196
- rescue Contrast::SecurityException => e
197
- logger.trace('Security Exception raised during application lifecycle to prevent an attack', e)
198
- raise e
199
- end
200
-
201
- SECURITY_EXCEPTION_MARKER = 'Contrast::SecurityException'
202
- # We're only going to suppress SecurityExceptions indicating a blocked attack. And, only if the
203
- # config.agent.ruby.exceptions.capture? is set
204
- def handle_exception exception
205
- if security_exception?(exception)
206
- exception_control = ::Contrast::AGENT.exception_control
207
- raise exception unless exception_control[:enable]
208
-
209
- [exception_control[:status], {}, [exception_control[:message]]]
210
- else
211
- logger.debug('Re-throwing original error', exception)
212
- raise exception
213
- end
214
- end
215
-
216
- # Is the given exception one raised by our Protect code?
217
- #
218
- # @param exception [Exception]
219
- # @return [Boolean]
220
- def security_exception? exception
221
- exception.is_a?(Contrast::SecurityException) || exception.message&.include?(SECURITY_EXCEPTION_MARKER)
222
- end
223
-
224
- # As we deprecate support to prepare to remove dead code, we need to inform our users still relying on the now
225
- # deprecated and soon to be removed functionality. This method handles doing that by leveraging the standard
226
- # Kernel#warn approach
227
- def inform_deprecations
228
- # Ruby 2.5 is currently in security maintenance, meaning int is only receiving updates for security issues. It
229
- # will move to eol on 31 March 2021. As such, we can remove support for it in Q3. We'll begin the deprecation
230
- # warnings now so that customers have time to reach out if they'll be impacted.
231
- # TODO: RUBY-715 remove this part of the method, leaving it empty if there are no other deprecations, when we
232
- # drop 2.5 support.
233
- return unless RUBY_VERSION < '2.6.0'
234
-
235
- Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
236
- 'Please contact Customer Support prior if you require continued support.')
237
- end
238
-
239
- # Displays Telemetry disclaimer if Telemetry is enabled.
240
- # if .telemetry file doesn't exist we create one and then show the disclaimer.
241
- # if the file already exists we do nothing.
242
- def telemetry_disclaimer
243
- return unless Contrast::Agent::Telemetry.enabled?
244
- return unless Contrast::Utils::Telemetry.create_telemetry_file
245
-
246
- logger.info Contrast::Utils::Telemetry.disclaimer
247
- $stdout.print Contrast::Utils::Telemetry.disclaimer
248
- true
249
- end
250
180
  end
251
181
  end
252
182
  end
@@ -1,12 +1,15 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'contrast/agent/patching/policy/method_policy_extend'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  module Patching
7
9
  module Policy
8
10
  # This class is used to map each method to the trigger node that applies to it
9
11
  class MethodPolicy
12
+ extend Contrast::Agent::Patching::Policy::MethodPolicyExtend
10
13
  attr_reader :deadzone_node, :inventory_node, :propagation_node, :protect_node, :trigger_node
11
14
  attr_accessor :source_node, :method_name, :method_visibility, :instance_method
12
15
 
@@ -60,95 +63,6 @@ module Contrast
60
63
  # defined by #nodes.
61
64
  @_method_scopes ||= nodes.flat_map(&:method_scope).tap(&:compact!).tap(&:uniq!)
62
65
  end
63
-
64
- class << self
65
- # Given a Contrast::Agent::Patching::Policy::ModulePolicy, parse
66
- # out its information for the given method in order to construct a
67
- # Contrast::Agent::Patching::Policy::MethodPolicy
68
- #
69
- # @param method_name [Symbol] the name of the method for this policy
70
- # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy]
71
- # the entire policy for this module
72
- # @param instance_method [Boolean] true if this method is an
73
- # instance method
74
- # @return [Contrast::Agent::Patching::Policy::MethodPolicy]
75
- def build_method_policy method_name, module_policy, instance_method
76
- source_node = find_method_node(module_policy.source_nodes, method_name, instance_method)
77
- propagation_node = find_method_node(module_policy.propagator_nodes, method_name, instance_method)
78
- trigger_node = find_method_node(module_policy.trigger_nodes, method_name, instance_method)
79
- protect_node = find_method_node(module_policy.protect_nodes, method_name, instance_method)
80
- inventory_node = find_method_node(module_policy.inventory_nodes, method_name, instance_method)
81
- deadzone_node = find_method_node(module_policy.deadzone_nodes, method_name, instance_method)
82
- method_visibility = find_visibility(source_node, propagation_node, trigger_node, protect_node,
83
- inventory_node, deadzone_node)
84
- method_policy = MethodPolicy.new(method_name: method_name,
85
- method_visibility: method_visibility,
86
- instance_method: instance_method,
87
- source_node: source_node,
88
- propagation_node: propagation_node,
89
- trigger_node: trigger_node,
90
- protect_node: protect_node,
91
- inventory_node: inventory_node,
92
- deadzone_node: deadzone_node)
93
-
94
- return method_policy unless check_method_policy_nodes_empty? source_node, propagation_node, trigger_node,
95
- protect_node, inventory_node, deadzone_node
96
-
97
- create_new_node(module_policy, method_policy) if module_policy.deadzone_nodes&.any?
98
- method_policy
99
- end
100
-
101
- def find_method_node nodes, method_name, is_instance_method
102
- return unless nodes
103
-
104
- nodes.find do |node|
105
- node.instance_method? == is_instance_method && node.method_name == method_name
106
- end
107
- end
108
-
109
- def find_visibility *nodes
110
- nodes.find { |node| node }&.method_visibility
111
- end
112
-
113
- def check_method_policy_nodes_empty?(source_node, propagation_node, trigger_node, protect_node,
114
- inventory_node, deadzone_node)
115
- return false unless source_node.nil? && propagation_node.nil? && trigger_node.nil? && protect_node.nil? &&
116
- inventory_node.nil? && deadzone_node.nil?
117
-
118
- true
119
- end
120
-
121
- private
122
-
123
- def create_new_node module_policy, method_policy
124
- return if module_policy.deadzone_nodes.empty?
125
-
126
- module_policy.deadzone_nodes.map do |node|
127
- next unless node.method_name.nil?
128
-
129
- klass = Module.cs__const_get(node.class_name)
130
- next unless it_defined? klass, method_policy.method_name
131
-
132
- new_node = {}
133
- new_node['instance_method'] = method_policy.instance_method
134
- new_node['method_visibility'] =
135
- klass.private_method_defined?(method_policy.method_name) ? 'private' : 'public'
136
- new_node['method_name'] = method_policy.method_name
137
- new_node['class_name'] = node.class_name
138
- new_node = Contrast::Agent::Deadzone::Policy::DeadzoneNode.new(new_node)
139
- method_policy.instance_variable_set(:@method_visibility, new_node.method_visibility)
140
- method_policy.instance_variable_set(:@deadzone_node, new_node)
141
- module_policy.deadzone_nodes << new_node
142
- break unless method_policy.deadzone_node.nil?
143
- end
144
- end
145
-
146
- def it_defined? klass, method_name
147
- klass.instance_methods(false).include?(method_name) ||
148
- klass.private_instance_methods(false).include?(method_name) ||
149
- klass.singleton_methods(false).include?(method_name)
150
- end
151
- end
152
66
  end
153
67
  end
154
68
  end
@@ -0,0 +1,111 @@
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
+ module Contrast
5
+ module Agent
6
+ module Patching
7
+ module Policy
8
+ # This class is used to map each method to the trigger node that applies to it
9
+ module MethodPolicyExtend
10
+ # Given a Contrast::Agent::Patching::Policy::ModulePolicy, parse
11
+ # out its information for the given method in order to construct a
12
+ # Contrast::Agent::Patching::Policy::MethodPolicy
13
+ #
14
+ # @param method_name [Symbol] the name of the method for this policy
15
+ # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy]
16
+ # the entire policy for this module
17
+ # @param instance_method [Boolean] true if this method is an
18
+ # instance method
19
+ # @return [Contrast::Agent::Patching::Policy::MethodPolicy]
20
+ def build_method_policy method_name, module_policy, instance_method
21
+ source_node = find_method_node(module_policy.source_nodes, method_name, instance_method)
22
+ propagation_node = find_method_node(module_policy.propagator_nodes, method_name, instance_method)
23
+ trigger_node = find_method_node(module_policy.trigger_nodes, method_name, instance_method)
24
+ protect_node = find_method_node(module_policy.protect_nodes, method_name, instance_method)
25
+ inventory_node = find_method_node(module_policy.inventory_nodes, method_name, instance_method)
26
+ deadzone_node = find_method_node(module_policy.deadzone_nodes, method_name, instance_method)
27
+ method_visibility = find_visibility(source_node, propagation_node, trigger_node, protect_node,
28
+ inventory_node, deadzone_node)
29
+ method_policy = MethodPolicy.new(method_name: method_name,
30
+ method_visibility: method_visibility,
31
+ instance_method: instance_method,
32
+ source_node: source_node,
33
+ propagation_node: propagation_node,
34
+ trigger_node: trigger_node,
35
+ protect_node: protect_node,
36
+ inventory_node: inventory_node,
37
+ deadzone_node: deadzone_node)
38
+
39
+ return method_policy unless check_method_policy_nodes_empty? source_node, propagation_node, trigger_node,
40
+ protect_node, inventory_node, deadzone_node
41
+
42
+ create_new_node(module_policy, method_policy) if module_policy.deadzone_nodes&.any?
43
+ method_policy
44
+ end
45
+
46
+ def find_method_node nodes, method_name, is_instance_method
47
+ return unless nodes
48
+
49
+ nodes.find do |node|
50
+ node.instance_method? == is_instance_method && node.method_name == method_name
51
+ end
52
+ end
53
+
54
+ def find_visibility *nodes
55
+ nodes.find { |node| node }&.method_visibility
56
+ end
57
+
58
+ def check_method_policy_nodes_empty?(source_node, propagation_node, trigger_node, protect_node,
59
+ inventory_node, deadzone_node)
60
+ return false unless source_node.nil? && propagation_node.nil? && trigger_node.nil? && protect_node.nil? &&
61
+ inventory_node.nil? && deadzone_node.nil?
62
+
63
+ true
64
+ end
65
+
66
+ private
67
+
68
+ def create_new_node module_policy, method_policy
69
+ return if module_policy.deadzone_nodes.empty?
70
+
71
+ module_policy.deadzone_nodes.map do |node|
72
+ next unless node.method_name.nil?
73
+
74
+ klass = Module.cs__const_get(node.class_name)
75
+ next unless it_defined? klass, method_policy.method_name
76
+
77
+ new_node = set_new_node method_policy, klass, node
78
+ method_policy.instance_variable_set(:@method_visibility, new_node.method_visibility)
79
+ method_policy.instance_variable_set(:@deadzone_node, node)
80
+ module_policy.deadzone_nodes << new_node
81
+ break unless method_policy.deadzone_node.nil?
82
+ end
83
+ end
84
+
85
+ # Helper method for creating new node
86
+ #
87
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
88
+ # used to map each method to the trigger node that applies to it
89
+ # @param klass [String] classname
90
+ # @param node [Contrast::Agent::Patching::Policy::PolicyNode]
91
+ # @return @_set_new_node [Contrast::Agent::Deadzone::Policy::DeadzoneNode]
92
+ def set_new_node method_policy, klass, node
93
+ new_node = {}
94
+ new_node['instance_method'] = method_policy.instance_method
95
+ new_node['method_visibility'] =
96
+ klass.private_method_defined?(method_policy.method_name) ? 'private' : 'public'
97
+ new_node['method_name'] = method_policy.method_name
98
+ new_node['class_name'] = node.class_name
99
+ @_set_new_node = Contrast::Agent::Deadzone::Policy::DeadzoneNode.new(new_node)
100
+ end
101
+
102
+ def it_defined? klass, method_name
103
+ klass.instance_methods(false).include?(method_name) ||
104
+ klass.private_instance_methods(false).include?(method_name) ||
105
+ klass.singleton_methods(false).include?(method_name)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -151,6 +151,18 @@ module Contrast
151
151
  return
152
152
  end
153
153
 
154
+ patch_methods status, module_data, module_policy
155
+ rescue StandardError => e
156
+ status&.failed_patch!
157
+ logger.warn('Patching failed', e, module: module_data.mod_name)
158
+ ensure
159
+ logger.trace('Patching complete',
160
+ module: module_data.mod_name,
161
+ result:
162
+ Contrast::Agent::Patching::Policy::PatchStatus.get_status(module_data.mod).patch_status)
163
+ end
164
+
165
+ def patch_methods status, module_data, module_policy
154
166
  status.patching!
155
167
  num_applied_patches = patch_into_instance_methods(module_data, module_policy)
156
168
  num_applied_patches += patch_into_singleton_methods(module_data, module_policy)
@@ -160,14 +172,6 @@ module Contrast
160
172
  else
161
173
  status.partial_patch!
162
174
  end
163
- rescue StandardError => e
164
- status&.failed_patch!
165
- logger.warn('Patching failed', e, module: module_data.mod_name)
166
- ensure
167
- logger.trace('Patching complete',
168
- module: module_data.mod_name,
169
- result:
170
- Contrast::Agent::Patching::Policy::PatchStatus.get_status(module_data.mod).patch_status)
171
175
  end
172
176
 
173
177
  # Get all of the instance methods on the given module, excluding
@@ -0,0 +1,21 @@
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
+ module Contrast
5
+ module Agent
6
+ # This is the base module for our reporting functionality. It is intended to
7
+ # facilitate all the needed report generating functionality and eventual
8
+ # report to RS.
9
+ # Any class under this namespace should be required here, providing a
10
+ # single point of require for this functionality.
11
+ module Reporting
12
+ end
13
+ end
14
+ end
15
+
16
+ require 'contrast/agent/reporting/reporter'
17
+ require 'contrast/agent/reporting/reporting_utilities/audit'
18
+ require 'contrast/agent/reporting/reporting_utilities/reporter_client'
19
+ require 'contrast/agent/reporting/reporting_events/finding'
20
+ require 'contrast/agent/reporting/reporting_events/preflight_message'
21
+ require 'contrast/agent/reporting/reporting_events/preflight'
@@ -0,0 +1,142 @@
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/agent/worker_thread'
5
+ require 'contrast/agent/reporting/report'
6
+ require 'contrast/components/logger'
7
+ require 'contrast/utils/object_share'
8
+ require 'contrast/agent/version'
9
+ require 'base64'
10
+
11
+ module Contrast
12
+ module Agent
13
+ # This module will hold everything essential to reporting to TeamServer
14
+ class Reporter < WorkerThread
15
+ include Contrast::Components::Logger::InstanceMethods
16
+ include Contrast::Utils::ObjectShare
17
+
18
+ class << self
19
+ include Contrast::Components::Logger::InstanceMethods
20
+
21
+ # check if we can report to TS
22
+ #
23
+ # @return[Boolean] true if bypass is enabled, or false if bypass disabled
24
+ def enabled?
25
+ @_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
26
+ @_enabled
27
+ end
28
+ end
29
+
30
+ def headers
31
+ @_headers ||= {
32
+ app_name: Base64.encode64(Contrast::APP_CONTEXT.app_name).chomp!,
33
+ api_key: Contrast::API.api_key,
34
+ agent_version: [RUBY, Contrast::Agent::VERSION].join(SPACE),
35
+ app_language: RUBY,
36
+ app_path: Base64.encode64(Contrast::APP_CONTEXT.path).chomp!,
37
+ app_version: Contrast::APP_CONTEXT.app_version,
38
+ authorization: Base64.encode64("#{ Contrast::API.username }:#{ Contrast::API.service_key }").chomp!,
39
+ server_name: Base64.encode64(Contrast::APP_CONTEXT.server_name).chomp!,
40
+ server_path: Base64.encode64(Contrast::APP_CONTEXT.server_path).chomp!,
41
+ server_type: Base64.encode64(Contrast::APP_CONTEXT.server_type).chomp!,
42
+ content_type: 'application/json',
43
+ encoding: 'base64'
44
+ }.cs__freeze
45
+ end
46
+
47
+ def client
48
+ @_client ||= Contrast::Utils::ReporterClient.new headers
49
+ end
50
+
51
+ def connection
52
+ @_connection ||= client.initialize_connection
53
+ end
54
+
55
+ def audit
56
+ @_audit ||= Contrast::Agent::Reporting::Audit.new
57
+ end
58
+
59
+ def attempt_to_start?
60
+ unless cs__class.enabled?
61
+ logger.warn('Reporter service is disabled!')
62
+ return false
63
+ end
64
+
65
+ logger.debug('Attempting to start Reporter thread') unless running?
66
+ true
67
+ end
68
+
69
+ def start_thread!
70
+ return if running?
71
+
72
+ @_thread = Contrast::Agent::Thread.new do
73
+ logger.debug('Starting background Reporter thread.')
74
+ event = queue.pop
75
+
76
+ begin
77
+ logger.debug('This is the current processed event', event)
78
+ client.send_event event, connection
79
+ rescue StandardError => e
80
+ logger.error('Could not send message to service from Reporter queue.', e)
81
+ end
82
+ end
83
+ end
84
+
85
+ def send_event event
86
+ if ::Contrast::AGENT.disabled?
87
+ logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
88
+ return
89
+ end
90
+
91
+ return unless cs__class.enabled?
92
+
93
+ logger.debug('Enqueued event for sending', event_type: event.cs__class)
94
+
95
+ result = queue << event if event
96
+ return result unless ::Contrast::API.request_audit_enable
97
+
98
+ audit&.audit_event(event)
99
+ result
100
+ end
101
+
102
+ # Use this to bypass the messaging queue and leave response processing to the caller
103
+ def send_event_immediately event
104
+ if ::Contrast::AGENT.disabled?
105
+ logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
106
+ return
107
+ end
108
+ response_data = client.send_event event, connection, true
109
+ if response_data.status == 200
110
+ # handle_response
111
+ client.send(:handle_response, event, response_data, connection)
112
+ end
113
+
114
+ return unless ::Contrast::API.request_audit_enable
115
+
116
+ audit&.audit_event(event, response_data)
117
+ response_data
118
+ rescue StandardError => e
119
+ logger.error('Could not send message to service from Reporter queue.', e)
120
+ end
121
+
122
+ def delete_queue!
123
+ @_queue&.clear
124
+ @_queue&.close
125
+ @_queue = nil
126
+ end
127
+
128
+ def stop!
129
+ return unless running?
130
+
131
+ super
132
+ delete_queue!
133
+ end
134
+
135
+ private
136
+
137
+ def queue
138
+ @_queue ||= Queue.new
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,90 @@
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 'json'
5
+ require 'contrast/components/logger'
6
+ require 'contrast/agent/reporting/reporting_events/reporting_event'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Reporting
11
+ # This is the new Findings class which will include all the needed information
12
+ # for the new reporting system
13
+ class Finding < Contrast::Agent::Reporting::ReportingEvent
14
+ attr_accessor :events, :properties, :request, :hash_code
15
+ attr_reader :rule_id
16
+
17
+ def initialize rule_id
18
+ super
19
+ @event_type = :report_vulnerability
20
+ @events = []
21
+ @platform = Contrast::Utils::ObjectShare::RUBY
22
+ @rule_id = Contrast::Utils::StringUtils.truncate(rule_id)
23
+ @properties = {}
24
+ end
25
+
26
+ def attach_data trigger_node, source, object, ret, request, *args
27
+ @events << Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret, args)
28
+ @request = request
29
+ from_properties source, @events
30
+ attach_routes request
31
+ end
32
+
33
+ def to_controlled_hash **_args
34
+ validate
35
+ {
36
+ platform: @platform,
37
+ ruleId: @rule_id,
38
+ request: request,
39
+ properties: properties,
40
+ events: events,
41
+ routes: routes
42
+ }
43
+ end
44
+
45
+ def validate
46
+ raise(ArgumentError, "#{ self } did not have a proper platform. Unable to continue.") unless @platform
47
+ raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
48
+ raise(ArgumentError, "#{ self } did not have proper events. Unable to continue.") if events.empty?
49
+ raise(ArgumentError, "#{ self } did not have proper properties. Unable to continue.") if properties.empty?
50
+ raise(ArgumentError, "#{ self } did not have a proper request. Unable to continue.") unless request
51
+
52
+ nil
53
+ end
54
+
55
+ private
56
+
57
+ def from_properties source, events
58
+ return unless source
59
+ return unless Contrast::Agent::Assess::Tracker.trackable?(source)
60
+
61
+ properties = Contrast::Agent::Assess::Tracker.properties(source)
62
+
63
+ build_events events, properties.event if properties.event
64
+ @properties = properties if properties
65
+ end
66
+
67
+ def build_events events, event
68
+ return unless event
69
+
70
+ event.parent_events&.each do |parent_event|
71
+ build_events(events, parent_event)
72
+ end
73
+
74
+ events << event
75
+ end
76
+
77
+ def attach_routes request
78
+ context = Contrast::Agent::REQUEST_TRACKER.current
79
+ if context
80
+ @routes << context.route if context.route
81
+ elsif request&.route
82
+ @routes << request.route
83
+ elsif request&.path
84
+ @routes << request.path
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,25 @@
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/agent/reporting/reporting_events/reporting_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Reporting
9
+ # This class here will hold the needed preflights we will send for certain trace/traces
10
+ class Preflight < Contrast::Agent::Reporting::ReportingEvent
11
+ attr_accessor :messages
12
+
13
+ def initialize
14
+ super
15
+ @event_type = :preflight
16
+ @messages = []
17
+ end
18
+
19
+ def to_controlled_hash **_args
20
+ { messages: @messages }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end