contrast-agent 4.10.0 → 4.13.1
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/ext/cs__assess_module/cs__assess_module.c +48 -0
- data/ext/cs__assess_module/cs__assess_module.h +7 -0
- data/ext/cs__common/cs__common.c +24 -7
- data/ext/cs__common/cs__common.h +12 -2
- data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -11
- data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
- data/ext/cs__os_information/cs__os_information.c +31 -0
- data/ext/cs__os_information/cs__os_information.h +7 -0
- data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
- data/lib/contrast/agent/assess/contrast_event.rb +1 -1
- data/lib/contrast/agent/assess/contrast_object.rb +1 -4
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
- data/lib/contrast/agent/assess/policy/preshift.rb +25 -11
- data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +4 -4
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
- data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -107
- data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
- data/lib/contrast/agent/assess/property/tagged.rb +15 -132
- data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +2 -1
- data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
- data/lib/contrast/agent/middleware.rb +22 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
- data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
- data/lib/contrast/agent/patching/policy/patch.rb +37 -238
- data/lib/contrast/agent/patching/policy/patcher.rb +3 -42
- data/lib/contrast/agent/request.rb +5 -3
- data/lib/contrast/agent/request_context.rb +32 -11
- data/lib/contrast/agent/request_handler.rb +7 -3
- data/lib/contrast/agent/rule_set.rb +2 -4
- data/lib/contrast/agent/scope.rb +32 -20
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
- data/lib/contrast/agent/static_analysis.rb +4 -2
- data/lib/contrast/agent/telemetry.rb +129 -0
- data/lib/contrast/agent/telemetry_event.rb +34 -0
- data/lib/contrast/agent/thread_watcher.rb +43 -14
- data/lib/contrast/agent/tracepoint_hook.rb +11 -3
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -1
- data/lib/contrast/components/api.rb +34 -0
- data/lib/contrast/components/app_context.rb +24 -0
- data/lib/contrast/components/assess.rb +7 -0
- data/lib/contrast/components/config.rb +90 -11
- data/lib/contrast/components/contrast_service.rb +6 -0
- data/lib/contrast/config/api_configuration.rb +22 -0
- data/lib/contrast/config/assess_configuration.rb +1 -0
- data/lib/contrast/config/env_variables.rb +25 -0
- data/lib/contrast/config/root_configuration.rb +1 -0
- data/lib/contrast/config/service_configuration.rb +2 -1
- data/lib/contrast/config.rb +1 -0
- data/lib/contrast/configuration.rb +3 -0
- data/lib/contrast/framework/manager.rb +14 -12
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
- data/lib/contrast/framework/rails/patch/support.rb +31 -29
- data/lib/contrast/logger/application.rb +4 -0
- data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
- data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
- data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
- data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
- data/lib/contrast/utils/class_util.rb +58 -44
- data/lib/contrast/utils/exclude_key.rb +20 -0
- data/lib/contrast/utils/io_util.rb +42 -34
- data/lib/contrast/utils/lru_cache.rb +45 -0
- data/lib/contrast/utils/metrics_hash.rb +59 -0
- data/lib/contrast/utils/os.rb +23 -0
- data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
- data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
- data/lib/contrast/utils/requests_client.rb +150 -0
- data/lib/contrast/utils/ruby_ast_rewriter.rb +1 -1
- data/lib/contrast/utils/telemetry.rb +77 -0
- data/lib/contrast/utils/telemetry_identifier.rb +137 -0
- data/lib/contrast.rb +19 -1
- data/resources/assess/policy.json +12 -6
- data/resources/deadzone/policy.json +86 -5
- data/ruby-agent.gemspec +2 -1
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +32 -14
- data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
- data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
- data/lib/contrast/extension/protect/kernel.rb +0 -29
@@ -6,19 +6,23 @@ require 'contrast/components/scope'
|
|
6
6
|
|
7
7
|
module Contrast
|
8
8
|
module Agent
|
9
|
-
# This class is instantiated when we receive a request and the agent is enabled to process
|
10
|
-
#
|
11
|
-
# prefilter and postfilter).
|
9
|
+
# This class is instantiated when we receive a request and the agent is enabled to process that request. It holds
|
10
|
+
# the ruleset that we perform filtering operations on (currently prefilter and postfilter).
|
12
11
|
class RequestHandler
|
13
12
|
include Contrast::Components::Logger::InstanceMethods
|
14
13
|
|
15
14
|
attr_reader :ruleset, :context
|
16
15
|
|
16
|
+
# @param context [Contrast::Agent::RequestContext] the context of the request for which this handler applies
|
17
17
|
def initialize context
|
18
18
|
@context = context
|
19
19
|
@ruleset = ::Contrast::AGENT.ruleset
|
20
20
|
end
|
21
21
|
|
22
|
+
# TODO: RUBY-1353
|
23
|
+
# TODO: RUBY-1355
|
24
|
+
# TODO: RUBY-1357
|
25
|
+
# TODO: RUBY-1358
|
22
26
|
def send_activity_messages
|
23
27
|
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage(context.activity)
|
24
28
|
[context.server_activity, context.activity, context.observed_route].each do |message|
|
@@ -16,8 +16,7 @@ module Contrast
|
|
16
16
|
# terminate requests on attack detection if set to block at perimeter
|
17
17
|
def prefilter
|
18
18
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
19
|
-
|
20
|
-
return unless context&.analyze_request? || ::Contrast::PROTECT.enabled?
|
19
|
+
return unless context&.analyze_request?
|
21
20
|
|
22
21
|
logger.trace_with_time('Running prefilter...') do
|
23
22
|
map { |rule| rule.prefilter(context) }
|
@@ -33,8 +32,7 @@ module Contrast
|
|
33
32
|
# has been created. The main actions here are analyzing the response for unsafe state or actions.
|
34
33
|
def postfilter
|
35
34
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
36
|
-
|
37
|
-
return unless context&.analyze_response? || ::Contrast::PROTECT.enabled?
|
35
|
+
return unless context&.analyze_response?
|
38
36
|
|
39
37
|
logger.trace_with_time('Running postfilter...') do
|
40
38
|
map { |rule| rule.postfilter(context) }
|
data/lib/contrast/agent/scope.rb
CHANGED
@@ -104,39 +104,51 @@ module Contrast
|
|
104
104
|
exit_split_scope!
|
105
105
|
end
|
106
106
|
|
107
|
-
#
|
108
|
-
#
|
109
|
-
# Prefer the static methods if you know what scope you need at the call site.
|
107
|
+
# Static methods to be used, the cases are defined by the usage from the above methods
|
108
|
+
# if more methods are added - please extend the case statements as they are no longed dynamic
|
110
109
|
def in_scope? name
|
111
|
-
|
112
|
-
|
113
|
-
|
110
|
+
case name
|
111
|
+
when :contrast
|
112
|
+
in_contrast_scope?
|
113
|
+
when :deserialization
|
114
|
+
in_deserialization_scope?
|
115
|
+
when :split
|
116
|
+
in_split_scope?
|
117
|
+
else
|
118
|
+
raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
|
119
|
+
end
|
114
120
|
end
|
115
121
|
|
116
122
|
def enter_scope! name
|
117
|
-
|
118
|
-
|
119
|
-
|
123
|
+
case name
|
124
|
+
when :contrast
|
125
|
+
enter_contrast_scope!
|
126
|
+
when :deserialization
|
127
|
+
enter_deserialization_scope!
|
128
|
+
when :split
|
129
|
+
enter_split_scope!
|
130
|
+
else
|
131
|
+
raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
|
132
|
+
end
|
120
133
|
end
|
121
134
|
|
122
135
|
def exit_scope! name
|
123
|
-
|
124
|
-
|
125
|
-
|
136
|
+
case name
|
137
|
+
when :contrast
|
138
|
+
exit_contrast_scope!
|
139
|
+
when :deserialization
|
140
|
+
exit_deserialization_scope!
|
141
|
+
when :split
|
142
|
+
exit_split_scope!
|
143
|
+
else
|
144
|
+
raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
|
145
|
+
end
|
126
146
|
end
|
127
147
|
|
128
148
|
class << self
|
129
149
|
def valid_scope? scope_sym
|
130
150
|
Contrast::Agent::Scope::SCOPE_LIST.include? scope_sym
|
131
151
|
end
|
132
|
-
|
133
|
-
def ensure_valid_scope! scope_sym
|
134
|
-
unless valid_scope? scope_sym # rubocop:disable Style/GuardClause
|
135
|
-
with_contrast_scope do
|
136
|
-
raise NoMethodError, "Scope '#{ scope_sym.inspect }' is not registered as a scope."
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
152
|
end
|
141
153
|
end
|
142
154
|
end
|
@@ -0,0 +1,71 @@
|
|
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/utils/metrics_hash'
|
5
|
+
require 'contrast/agent/metric_telemetry_event'
|
6
|
+
require 'contrast/agent/version'
|
7
|
+
require 'contrast/utils/os'
|
8
|
+
|
9
|
+
module Contrast
|
10
|
+
module Agent
|
11
|
+
# This class will hold the Startup Metrics Telemetry Event
|
12
|
+
# The class will include initialization of the agent version, language version
|
13
|
+
# os type, arch and version
|
14
|
+
# application framework and version and server framework
|
15
|
+
# It will be initialized and send in Middleware#agent_startup_routine
|
16
|
+
class StartupMetricsTelemetryEvent < Contrast::Agent::MetricTelemetryEvent
|
17
|
+
include Contrast::Utils::OS
|
18
|
+
|
19
|
+
APP_AND_SERVER_DATA = ::Contrast::APP_CONTEXT.app_and_server_information.cs__freeze
|
20
|
+
SAAS_DEFAULT = { addr: 'app.contrastsecuirty.com', type: 'SAAS_DEFAULT' }.cs__freeze
|
21
|
+
SAAS_CE = { addr: 'ce.contrastsecurity.com', type: 'SAAS_CE' }.cs__freeze
|
22
|
+
SAAS_CUSTOM = { addr: 'contrastsecurite.com', type: 'SAAS_CUSTOM' }.cs__freeze
|
23
|
+
SAAS_POV = { addr: 'eval.contrastsecuirty.com', type: 'SAAS_POV' }.cs__freeze
|
24
|
+
EOP = 'EOP'
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
super
|
28
|
+
add_tags
|
29
|
+
end
|
30
|
+
|
31
|
+
def path
|
32
|
+
'/startup'
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_tags
|
36
|
+
@tags['teamserver'] = teamserver_type
|
37
|
+
@tags['agent_version'] = VERSION
|
38
|
+
@tags['ruby_version'] = RUBY_VERSION
|
39
|
+
@tags['os_type'] = sys_info['os_type'] == 'Darwin' ? 'MacOS' : 'Linux'
|
40
|
+
@tags['os_arch'] = sys_info['os_arch']
|
41
|
+
@tags['os_version'] = sys_info['os_version']
|
42
|
+
@tags['app_framework_and_version'] = APP_AND_SERVER_DATA[:application_info].to_s
|
43
|
+
@tags['server_framework_and_version'] = APP_AND_SERVER_DATA[:server_info].to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def sys_info
|
47
|
+
@sys_info ||= get_system_information if @sys_info.nil?
|
48
|
+
@sys_info
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Here we extract the Teamserver url type
|
54
|
+
#
|
55
|
+
# @return[String] type, it could be SAAS_DEFAULT, SAAS_POV, SAAS_CE, SAAS_CUSTOM, or EOP
|
56
|
+
def teamserver_type
|
57
|
+
@_teamserver_type ||= if Contrast::API.api_url.include?(SAAS_DEFAULT[:addr])
|
58
|
+
SAAS_DEFAULT[:type]
|
59
|
+
elsif Contrast::API.api_url.include?(SAAS_POV[:addr])
|
60
|
+
SAAS_POV[:type]
|
61
|
+
elsif Contrast::API.api_url.include?(SAAS_CE[:addr])
|
62
|
+
SAAS_CE[:type]
|
63
|
+
elsif Contrast::API.api_url.end_with? SAAS_CUSTOM[:addr]
|
64
|
+
SAAS_CUSTOM[:type]
|
65
|
+
else
|
66
|
+
EOP
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -14,8 +14,8 @@ module Contrast
|
|
14
14
|
include Contrast::Components::Scope::InstanceMethods
|
15
15
|
|
16
16
|
class << self
|
17
|
-
# After the first request is complete, we do a one-time manual catchup to review and
|
18
|
-
#
|
17
|
+
# After the first request is complete, we do a one-time manual catchup to review and report the already-loaded
|
18
|
+
# gems.
|
19
19
|
def catchup
|
20
20
|
@_catchup ||= begin
|
21
21
|
threaded_analysis!
|
@@ -23,6 +23,8 @@ module Contrast
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
# TODO: RUBY-1354
|
27
|
+
# TODO: RUBY-1356
|
26
28
|
def send_inventory_message
|
27
29
|
return unless ::Contrast::INVENTORY.enabled?
|
28
30
|
|
@@ -0,0 +1,129 @@
|
|
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/config/env_variables'
|
5
|
+
require 'contrast/components/logger'
|
6
|
+
require 'contrast/utils/requests_client'
|
7
|
+
require 'contrast/agent/worker_thread'
|
8
|
+
require 'contrast/utils/telemetry'
|
9
|
+
|
10
|
+
module Contrast
|
11
|
+
module Agent
|
12
|
+
# This class will initialize and hold everything needed for the telemetry
|
13
|
+
class Telemetry < WorkerThread
|
14
|
+
include Contrast::Utils::RequestsClient
|
15
|
+
include Contrast::Components::Logger::InstanceMethods
|
16
|
+
# this is where we will send the data from the agents
|
17
|
+
URL = 'https://telemetry.ruby.contrastsecurity.com/'
|
18
|
+
# Suggested timeout after each send is to be 3 hours (10800 seconds)
|
19
|
+
SUGGESTED_TIMEOUT = 10_800
|
20
|
+
|
21
|
+
class << self
|
22
|
+
include Contrast::Components::Logger::InstanceMethods
|
23
|
+
include Contrast::Config::EnvVariables
|
24
|
+
|
25
|
+
def application_id
|
26
|
+
@_application_id ||= begin
|
27
|
+
id = nil
|
28
|
+
mac = Contrast::Utils::Telemetry::Identifier.mac
|
29
|
+
app_name = Contrast::Utils::Telemetry::Identifier.app_name
|
30
|
+
id = mac + app_name if mac && app_name
|
31
|
+
Digest::SHA2.new(256).hexdigest(id || '_' + SecureRandom.uuid)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def instance_id
|
36
|
+
@_instance_id ||= Digest::SHA2.new(256).hexdigest(Contrast::Utils::Telemetry::Identifier.mac ||
|
37
|
+
'_' + SecureRandom.uuid)
|
38
|
+
end
|
39
|
+
|
40
|
+
def enabled?
|
41
|
+
@_enabled = telemetry_enabled? if @_enabled.nil?
|
42
|
+
@_enabled
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def telemetry_enabled?
|
48
|
+
opt_out_telemetry = return_value(:telemetry_opt_outs)
|
49
|
+
return false if opt_out_telemetry.to_s.casecmp('true').zero?
|
50
|
+
return false if opt_out_telemetry.to_s.casecmp('1').zero?
|
51
|
+
|
52
|
+
# In case of connection error, do not create the background thread or queue,
|
53
|
+
# as if the opt-out env var was set
|
54
|
+
ip_opt_out_telemetry = Contrast::Utils::RequestsClient.initialize_connection(URL)
|
55
|
+
if ip_opt_out_telemetry.nil?
|
56
|
+
logger.warn('Connection was not established properly!!!')
|
57
|
+
logger.warn('THE SERVICE IS GOING TO BE TERMINATED!!')
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def attempt_to_start?
|
66
|
+
unless cs__class.enabled?
|
67
|
+
logger.warn('Telemetry service is disabled!')
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
|
71
|
+
logger.debug('Attempting to start telemetry thread') unless running?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def start_thread!
|
76
|
+
return if running?
|
77
|
+
|
78
|
+
# It is recommended that implementations send a single payload of
|
79
|
+
# general metrics every 3 hours, starting from implementation startup.
|
80
|
+
@_thread = Contrast::Agent::Thread.new do
|
81
|
+
logger.debug('Starting background telemetry thread.')
|
82
|
+
event = queue.pop
|
83
|
+
|
84
|
+
begin
|
85
|
+
logger.debug('This is the current processed event', event)
|
86
|
+
res = Contrast::Utils::RequestsClient.send_request event, connection
|
87
|
+
sleep_time = Contrast::Utils::RequestsClient.handle_response res
|
88
|
+
sleep(sleep_time) unless sleep_time.nil?
|
89
|
+
rescue StandardError => e
|
90
|
+
logger.error('Could not send message to service from telemetry queue.', e)
|
91
|
+
end
|
92
|
+
sleep(SUGGESTED_TIMEOUT)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def send_event event
|
97
|
+
if ::Contrast::AGENT.disabled?
|
98
|
+
logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
|
99
|
+
return
|
100
|
+
end
|
101
|
+
|
102
|
+
return unless cs__class.enabled?
|
103
|
+
|
104
|
+
logger.debug('Enqueued event for sending', event_type: event.cs__class)
|
105
|
+
|
106
|
+
queue << event if event
|
107
|
+
end
|
108
|
+
|
109
|
+
def delete_queue!
|
110
|
+
@_queue&.clear
|
111
|
+
@_queue&.close
|
112
|
+
@_queue = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def stop!
|
116
|
+
return unless running?
|
117
|
+
|
118
|
+
super
|
119
|
+
delete_queue!
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def queue
|
125
|
+
@_queue ||= Queue.new
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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/utils/metrics_hash'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Agent
|
8
|
+
# This class will hold the basic information for a Telemetry Event
|
9
|
+
class TelemetryEvent
|
10
|
+
include Contrast::Utils
|
11
|
+
|
12
|
+
attr_reader :timestamp, :tags
|
13
|
+
|
14
|
+
# The instance_id comes from the Telemetry Instance
|
15
|
+
def initialize
|
16
|
+
@tags = MetricsHash.new(String)
|
17
|
+
@timestamp = format_date
|
18
|
+
@instance_id = Contrast::Agent.telemetry_queue.__id__
|
19
|
+
end
|
20
|
+
|
21
|
+
def path
|
22
|
+
''
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json **_args
|
26
|
+
{ tags: @tags, timestamp: @timestamp, instance_id: @instance_id }
|
27
|
+
end
|
28
|
+
|
29
|
+
def format_date
|
30
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S.%L')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -4,6 +4,7 @@
|
|
4
4
|
require 'contrast/components/logger'
|
5
5
|
require 'contrast/agent/service_heartbeat'
|
6
6
|
require 'contrast/api/communication/messaging_queue'
|
7
|
+
require 'contrast/agent/telemetry'
|
7
8
|
|
8
9
|
module Contrast
|
9
10
|
module Agent
|
@@ -22,28 +23,22 @@ module Contrast
|
|
22
23
|
@heapdump_util = Contrast::Utils::HeapDumpUtil.new
|
23
24
|
@heartbeat = Contrast::Agent::ServiceHeartbeat.new
|
24
25
|
@messaging_queue = Contrast::Api::Communication::MessagingQueue.new
|
26
|
+
@telemetry = Contrast::Agent::Telemetry.new if Contrast::Agent::Telemetry.enabled?
|
25
27
|
end
|
26
28
|
|
27
29
|
def startup!
|
28
30
|
return unless ::Contrast::AGENT.enabled?
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
heartbeat_result = heartbeat.running?
|
35
|
-
logger.debug('Heartbeat thread status', alive: heartbeat_result)
|
36
|
-
|
37
|
-
unless messaging_queue.running?
|
38
|
-
logger.debug('Attempting to start messaging queue thread')
|
39
|
-
messaging_queue.start_thread!
|
40
|
-
end
|
41
|
-
messaging_result = messaging_queue.running?
|
42
|
-
logger.debug('Messaging thread status', alive: messaging_result)
|
32
|
+
telemetry_thread_status = telemetry_thread_init
|
33
|
+
heartbeat_thread_status = heartbeat_thread_init
|
34
|
+
messaging_thread_status = messaging_thread_init
|
43
35
|
|
44
36
|
logger.debug('ThreadWatcher started threads')
|
45
37
|
|
46
|
-
@pids[Process.pid] =
|
38
|
+
@pids[Process.pid] = messaging_thread_status && heartbeat_thread_status
|
39
|
+
return @pids unless Contrast::Agent::Telemetry.enabled?
|
40
|
+
|
41
|
+
@pids[Process.pid] = messaging_thread_status && heartbeat_thread_status && telemetry_thread_status
|
47
42
|
end
|
48
43
|
|
49
44
|
def ensure_running?
|
@@ -57,6 +52,40 @@ module Contrast
|
|
57
52
|
heartbeat.stop!
|
58
53
|
messaging_queue.stop!
|
59
54
|
heapdump_util.stop!
|
55
|
+
telemetry_queue&.stop!
|
56
|
+
end
|
57
|
+
|
58
|
+
def heartbeat_thread_init
|
59
|
+
unless heartbeat.running?
|
60
|
+
logger.debug('Attempting to start heartbeat thread')
|
61
|
+
heartbeat.start_thread!
|
62
|
+
end
|
63
|
+
heartbeat_result = heartbeat.running?
|
64
|
+
logger.debug('Heartbeat thread status', alive: heartbeat_result)
|
65
|
+
heartbeat_result
|
66
|
+
end
|
67
|
+
|
68
|
+
def telemetry_thread_init
|
69
|
+
@telemetry.start_thread! if @telemetry&.attempt_to_start?
|
70
|
+
telemetry_result = @telemetry&.running?
|
71
|
+
logger.debug('Telemetry thread status', alive: telemetry_result)
|
72
|
+
telemetry_result
|
73
|
+
end
|
74
|
+
|
75
|
+
def messaging_thread_init
|
76
|
+
unless messaging_queue.running?
|
77
|
+
logger.debug('Attempting to start messaging queue thread')
|
78
|
+
messaging_queue.start_thread!
|
79
|
+
end
|
80
|
+
messaging_result = messaging_queue.running?
|
81
|
+
logger.debug('Messaging thread status', alive: messaging_result)
|
82
|
+
messaging_result
|
83
|
+
end
|
84
|
+
|
85
|
+
def telemetry_queue
|
86
|
+
return if @telemetry.nil?
|
87
|
+
|
88
|
+
@telemetry
|
60
89
|
end
|
61
90
|
end
|
62
91
|
end
|
@@ -27,14 +27,22 @@ module Contrast
|
|
27
27
|
|
28
28
|
private
|
29
29
|
|
30
|
+
# Use the TracePoint from the :end event, meaning the completion of a definition of a Class or Module (or
|
31
|
+
# really the completion of that piece of a definition, as determined by an `end` statement since there could be
|
32
|
+
# definitions across multiple files) to carry out actions required on definition. This typically involves
|
33
|
+
# patching and usage analysis
|
34
|
+
#
|
35
|
+
# @param tracepoint_event [TracePoint] the TracePoint from the :end
|
30
36
|
def process tracepoint_event
|
31
37
|
with_contrast_scope do
|
32
|
-
|
33
|
-
|
38
|
+
# the Module or Class that was loaded during this event
|
34
39
|
loaded_module = tracepoint_event.self
|
40
|
+
# the file being loaded that contained this definition
|
35
41
|
path = tracepoint_event.path
|
36
42
|
return if path&.include?('contrast')
|
37
43
|
|
44
|
+
logger.trace('Received TracePoint end event', module: loaded_module, path: path)
|
45
|
+
|
38
46
|
Contrast::Agent.framework_manager.register_late_framework(loaded_module)
|
39
47
|
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.associate_file(path) if path
|
40
48
|
Contrast::Agent::Patching::Policy::Patcher.patch_specific_module(loaded_module)
|
@@ -43,7 +51,7 @@ module Contrast
|
|
43
51
|
end
|
44
52
|
Contrast::Agent::Assess::Policy::PolicyScanner.scan(tracepoint_event)
|
45
53
|
rescue StandardError => e
|
46
|
-
logger.error('Unable to complete TracePoint analysis', e, module: loaded_module)
|
54
|
+
logger.error('Unable to complete TracePoint analysis', e, module: loaded_module, path: path)
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
data/lib/contrast/agent.rb
CHANGED
@@ -20,7 +20,6 @@ require 'contrast/extension/delegator'
|
|
20
20
|
require 'contrast/extension/inventory'
|
21
21
|
require 'contrast/extension/module'
|
22
22
|
require 'contrast/extension/protect'
|
23
|
-
require 'contrast/extension/protect/kernel'
|
24
23
|
|
25
24
|
require 'contrast/utils/object_share'
|
26
25
|
require 'contrast/utils/string_utils'
|
@@ -62,6 +61,12 @@ module Contrast
|
|
62
61
|
thread_watcher.messaging_queue
|
63
62
|
end
|
64
63
|
|
64
|
+
def self.telemetry_queue
|
65
|
+
return unless thread_watcher.telemetry_queue
|
66
|
+
|
67
|
+
thread_watcher.telemetry_queue
|
68
|
+
end
|
69
|
+
|
65
70
|
def self.thread_watcher
|
66
71
|
@_thread_watcher ||= Contrast::Agent::ThreadWatcher.new
|
67
72
|
end
|
@@ -0,0 +1,34 @@
|
|
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/components/base'
|
5
|
+
require 'contrast/components/config'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Components
|
9
|
+
module Api
|
10
|
+
# A wrapper build around the Common Agent Configuration project to allow
|
11
|
+
# for Api keys to be mapped with their values contained in their
|
12
|
+
# parent_configuration_spec.yaml.
|
13
|
+
class Interface
|
14
|
+
include Contrast::Components::ComponentBase
|
15
|
+
|
16
|
+
def api_url
|
17
|
+
@_api_url ||= ::Contrast::CONFIG.root.api.url
|
18
|
+
end
|
19
|
+
|
20
|
+
def api_key
|
21
|
+
@_api_key ||= ::Contrast::CONFIG.root.api.api_key
|
22
|
+
end
|
23
|
+
|
24
|
+
def service_key
|
25
|
+
@_service_key ||= ::Contrast::CONFIG.root.api.service_key
|
26
|
+
end
|
27
|
+
|
28
|
+
def username
|
29
|
+
@_username ||= ::Contrast::CONFIG.root.api.user_name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -23,6 +23,10 @@ module Contrast
|
|
23
23
|
DEFAULT_SERVER_NAME = 'localhost'
|
24
24
|
DEFAULT_SERVER_PATH = '/'
|
25
25
|
|
26
|
+
SUPPORTED_FRAMEWORKS = %w[rails sinatra grape rack].cs__freeze
|
27
|
+
|
28
|
+
SUPPORTED_SERVERS = %w[passenger puma thin unicorn].cs__freeze
|
29
|
+
|
26
30
|
def initialize
|
27
31
|
original_pid
|
28
32
|
end
|
@@ -109,6 +113,26 @@ module Contrast
|
|
109
113
|
@_client_id ||= [app_name, pgid].join('-')
|
110
114
|
end
|
111
115
|
|
116
|
+
def app_and_server_information
|
117
|
+
{
|
118
|
+
application_info: find_gem_information(SUPPORTED_FRAMEWORKS),
|
119
|
+
server_info: find_gem_information(SUPPORTED_SERVERS)
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def find_gem_information arr
|
124
|
+
arr.each do |framework|
|
125
|
+
next unless Gem.loaded_specs.key?(framework)
|
126
|
+
|
127
|
+
loaded = Gem.loaded_specs[framework]
|
128
|
+
next unless loaded
|
129
|
+
|
130
|
+
name = loaded.instance_variable_get(:@name)
|
131
|
+
version = loaded.instance_variable_get(:@version).to_s
|
132
|
+
return [name, version].join(' ')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
112
136
|
def instrument_middleware_stack?
|
113
137
|
!Contrast::Utils::JobServersRunning.job_servers_running?
|
114
138
|
end
|
@@ -88,6 +88,13 @@ module Contrast
|
|
88
88
|
@_require_scan
|
89
89
|
end
|
90
90
|
|
91
|
+
def require_dynamic_sources?
|
92
|
+
if @_require_dynamic_sources.nil?
|
93
|
+
@_require_dynamic_sources = !false?(::Contrast::CONFIG.root.assess.enable_dynamic_sources)
|
94
|
+
end
|
95
|
+
@_require_dynamic_sources
|
96
|
+
end
|
97
|
+
|
91
98
|
def tags
|
92
99
|
::Contrast::CONFIG.root.assess&.tags
|
93
100
|
end
|