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.
- checksums.yaml +4 -4
- data/.simplecov +1 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
- data/lib/contrast/agent/assess/policy/trigger_method.rb +44 -7
- data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/tagged.rb +51 -57
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
- data/lib/contrast/agent/metric_telemetry_event.rb +2 -2
- data/lib/contrast/agent/middleware.rb +5 -75
- data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
- data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
- data/lib/contrast/agent/patching/policy/patcher.rb +12 -8
- data/lib/contrast/agent/reporting/report.rb +21 -0
- data/lib/contrast/agent/reporting/reporter.rb +142 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
- data/lib/contrast/agent/request.rb +2 -81
- data/lib/contrast/agent/request_context.rb +4 -128
- data/lib/contrast/agent/request_context_extend.rb +138 -0
- data/lib/contrast/agent/response.rb +2 -73
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +39 -16
- data/lib/contrast/agent/static_analysis.rb +1 -1
- data/lib/contrast/agent/telemetry.rb +15 -7
- data/lib/contrast/agent/telemetry_event.rb +8 -9
- data/lib/contrast/agent/thread_watcher.rb +31 -5
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +15 -0
- data/lib/contrast/api/communication/connection_status.rb +10 -7
- data/lib/contrast/api/communication/messaging_queue.rb +37 -3
- data/lib/contrast/api/communication/response_processor.rb +15 -8
- data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
- data/lib/contrast/api/communication/socket.rb +6 -8
- data/lib/contrast/api/communication/socket_client.rb +29 -12
- data/lib/contrast/api/communication/speedracer.rb +37 -1
- data/lib/contrast/api/communication/tcp_socket.rb +4 -3
- data/lib/contrast/api/communication/unix_socket.rb +1 -0
- data/lib/contrast/api/decorators/finding.rb +45 -0
- data/lib/contrast/components/api.rb +56 -0
- data/lib/contrast/components/app_context.rb +10 -65
- data/lib/contrast/components/app_context_extend.rb +78 -0
- data/lib/contrast/components/base.rb +23 -0
- data/lib/contrast/components/config.rb +8 -8
- data/lib/contrast/components/contrast_service.rb +5 -0
- data/lib/contrast/components/sampling.rb +2 -2
- data/lib/contrast/config/agent_configuration.rb +1 -1
- data/lib/contrast/config/api_configuration.rb +9 -4
- data/lib/contrast/config/api_proxy_configuration.rb +14 -0
- data/lib/contrast/config/application_configuration.rb +2 -3
- data/lib/contrast/config/assess_configuration.rb +3 -3
- data/lib/contrast/config/base_configuration.rb +17 -28
- data/lib/contrast/config/certification_configuration.rb +15 -0
- data/lib/contrast/config/env_variables.rb +2 -9
- data/lib/contrast/config/heap_dump_configuration.rb +6 -6
- data/lib/contrast/config/inventory_configuration.rb +1 -5
- data/lib/contrast/config/protect_rule_configuration.rb +1 -1
- data/lib/contrast/config/request_audit_configuration.rb +18 -0
- data/lib/contrast/config/ruby_configuration.rb +6 -6
- data/lib/contrast/config/service_configuration.rb +1 -2
- data/lib/contrast/config.rb +0 -1
- data/lib/contrast/configuration.rb +1 -2
- data/lib/contrast/extension/assess/array.rb +5 -7
- data/lib/contrast/framework/manager.rb +8 -32
- data/lib/contrast/framework/manager_extend.rb +50 -0
- data/lib/contrast/framework/rails/railtie.rb +1 -1
- data/lib/contrast/framework/sinatra/support.rb +2 -1
- data/lib/contrast/logger/log.rb +8 -103
- data/lib/contrast/utils/assess/property/tagged_utils.rb +23 -0
- data/lib/contrast/utils/assess/tracking_util.rb +20 -15
- data/lib/contrast/utils/assess/trigger_method_utils.rb +1 -1
- data/lib/contrast/utils/class_util.rb +18 -14
- data/lib/contrast/utils/findings.rb +62 -0
- data/lib/contrast/utils/hash_digest.rb +10 -73
- data/lib/contrast/utils/hash_digest_extend.rb +86 -0
- data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
- data/lib/contrast/utils/heap_dump_util.rb +2 -65
- data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
- data/lib/contrast/utils/io_util.rb +1 -1
- data/lib/contrast/utils/log_utils.rb +108 -0
- data/lib/contrast/utils/middleware_utils.rb +87 -0
- data/lib/contrast/utils/net_http_base.rb +158 -0
- data/lib/contrast/utils/object_share.rb +1 -0
- data/lib/contrast/utils/request_utils.rb +88 -0
- data/lib/contrast/utils/response_utils.rb +97 -0
- data/lib/contrast/utils/substitution_utils.rb +167 -0
- data/lib/contrast/utils/tag_util.rb +9 -9
- data/lib/contrast/utils/telemetry.rb +4 -2
- data/lib/contrast/utils/telemetry_client.rb +90 -0
- data/lib/contrast/utils/telemetry_identifier.rb +17 -24
- data/ruby-agent.gemspec +5 -5
- metadata +48 -23
- data/lib/contrast/config/default_value.rb +0 -17
- 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
|