contrast-agent 4.13.1 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
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