contrast-agent 4.12.0 → 4.13.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/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 +5 -0
- data/ext/cs__common/cs__common.h +8 -0
- data/ext/cs__contrast_patch/cs__contrast_patch.c +16 -1
- 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__os_information/extconf.rb +5 -0
- 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/source_method.rb +2 -71
- data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -106
- data/lib/contrast/agent/assess/property/tagged.rb +2 -128
- data/lib/contrast/agent/deadzone/policy/policy.rb +1 -1
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
- data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
- data/lib/contrast/agent/middleware.rb +22 -0
- data/lib/contrast/agent/patching/policy/patch.rb +28 -235
- data/lib/contrast/agent/patching/policy/patcher.rb +2 -41
- data/lib/contrast/agent/request_handler.rb +7 -3
- 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/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -0
- data/lib/contrast/components/api.rb +34 -0
- data/lib/contrast/components/app_context.rb +24 -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/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/exclude_key.rb +20 -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/telemetry.rb +78 -0
- data/lib/contrast/utils/telemetry_identifier.rb +137 -0
- data/lib/contrast.rb +18 -0
- 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 -10
@@ -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
|
data/lib/contrast/agent.rb
CHANGED
@@ -61,6 +61,12 @@ module Contrast
|
|
61
61
|
thread_watcher.messaging_queue
|
62
62
|
end
|
63
63
|
|
64
|
+
def self.telemetry_queue
|
65
|
+
return unless thread_watcher.telemetry_queue
|
66
|
+
|
67
|
+
thread_watcher.telemetry_queue
|
68
|
+
end
|
69
|
+
|
64
70
|
def self.thread_watcher
|
65
71
|
@_thread_watcher ||= Contrast::Agent::ThreadWatcher.new
|
66
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
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/utils/env_configuration_item'
|
5
|
+
require 'ougai'
|
5
6
|
require 'contrast/configuration'
|
6
7
|
|
7
8
|
module Contrast
|
@@ -23,17 +24,33 @@ module Contrast
|
|
23
24
|
# time than to silently fail to deliver functionality.
|
24
25
|
module Config
|
25
26
|
CONTRAST_ENV_MARKER = 'CONTRAST__'
|
27
|
+
CONTRAST_LOG = 'contrast_agent.log'
|
28
|
+
CONTRAST_NAME = 'Contrast Agent'
|
26
29
|
|
27
30
|
class Interface # :nodoc:
|
28
31
|
def initialize
|
29
32
|
build
|
30
33
|
end
|
31
34
|
|
32
|
-
|
35
|
+
# Basic logger for handling configuration validation logging
|
36
|
+
# the file to log is determined by the default one or set
|
37
|
+
# by the config file, if that configuration is found
|
38
|
+
def proto_logger
|
39
|
+
@_proto_logger ||= begin
|
40
|
+
@_proto_logger = ::Ougai::Logger.new(logger_path || CONTRAST_LOG)
|
41
|
+
@_proto_logger.progname = CONTRAST_NAME
|
42
|
+
@_proto_logger.level = ::Ougai::Logging::Severity::WARN
|
43
|
+
@_proto_logger.formatter = Contrast::Logger::Format.new
|
44
|
+
@_proto_logger.formatter.datetime_format = '%Y-%m-%dT%H:%M:%S.%L%z'
|
45
|
+
@_proto_logger
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def build
|
33
50
|
@_valid = nil
|
34
51
|
@config = Contrast::Configuration.new
|
35
52
|
env_overrides
|
36
|
-
validate
|
53
|
+
validate
|
37
54
|
end
|
38
55
|
alias_method :rebuild, :build
|
39
56
|
|
@@ -43,7 +60,7 @@ module Contrast
|
|
43
60
|
end
|
44
61
|
|
45
62
|
def valid?
|
46
|
-
@_valid = validate
|
63
|
+
@_valid = validate if @_valid.nil?
|
47
64
|
@_valid
|
48
65
|
end
|
49
66
|
|
@@ -59,20 +76,28 @@ module Contrast
|
|
59
76
|
|
60
77
|
SESSION_VARIABLES = 'Invalid configuration. '\
|
61
78
|
"Setting both application.session_id and application.session_metadata is not allowed.\n"
|
62
|
-
|
79
|
+
API_URL = "Invalid configuration. Missing a required connection value 'url' is not set."
|
80
|
+
API_KEY = "Invalid configuration. Missing a required connection value 'api_key' is not set."
|
81
|
+
API_SERVICE_KEY = "Invalid configuration. Missing a required connection value 'service_tag' is not set."
|
82
|
+
API_USERNAME = "Invalid configuration. Missing a required connection value 'user_name' is not set."
|
83
|
+
def validate
|
63
84
|
# The config has information about how to construct the logger.
|
64
85
|
# If the config is invalid, and you want to know about it, then
|
65
86
|
# you have a circular dependency if you try to log it,
|
66
|
-
#
|
87
|
+
# so we use basic proto_logger to do this job.
|
67
88
|
if !session_id.empty? && !session_metadata.empty?
|
68
|
-
|
69
|
-
cs__class.log_error(SESSION_VARIABLES)
|
70
|
-
else
|
71
|
-
puts SESSION_VARIABLES
|
72
|
-
end
|
89
|
+
proto_logger.error(SESSION_VARIABLES)
|
73
90
|
return false
|
74
91
|
end
|
75
|
-
|
92
|
+
if bypass
|
93
|
+
msg = []
|
94
|
+
msg << API_URL unless api_url
|
95
|
+
msg << API_KEY unless api_key
|
96
|
+
msg << API_SERVICE_KEY unless api_service_key
|
97
|
+
msg << API_USERNAME unless api_username
|
98
|
+
msg.any? { |m| proto_logger.error(m) }
|
99
|
+
return false unless msg.empty?
|
100
|
+
end
|
76
101
|
true
|
77
102
|
end
|
78
103
|
|
@@ -108,6 +133,60 @@ module Contrast
|
|
108
133
|
def session_metadata
|
109
134
|
@config.application.session_metadata
|
110
135
|
end
|
136
|
+
|
137
|
+
# Typically, the following values would be accessed through Contrast::Components::AppContext
|
138
|
+
# and Contrast::Components::API, but we're too early in the initialization of the Agent to use
|
139
|
+
# that mechanism, so we look it up directly for ourselves.
|
140
|
+
#
|
141
|
+
# @return [String, nil]
|
142
|
+
def api_url
|
143
|
+
@config.api.url
|
144
|
+
end
|
145
|
+
|
146
|
+
# Typically, the following values would be accessed through Contrast::Components::AppContext
|
147
|
+
# and Contrast::Components::API, but we're too early in the initialization of the Agent to use
|
148
|
+
# that mechanism, so we look it up directly for ourselves.
|
149
|
+
#
|
150
|
+
# @return [String, nil]
|
151
|
+
def api_key
|
152
|
+
@config.api.api_key
|
153
|
+
end
|
154
|
+
|
155
|
+
# Typically, the following values would be accessed through Contrast::Components::AppContext
|
156
|
+
# and Contrast::Components::API, but we're too early in the initialization of the Agent to use
|
157
|
+
# that mechanism, so we look it up directly for ourselves.
|
158
|
+
#
|
159
|
+
# @return [String, nil]
|
160
|
+
def api_service_key
|
161
|
+
@config.api.service_key
|
162
|
+
end
|
163
|
+
|
164
|
+
# Typically, the following values would be accessed through Contrast::Components::AppContext
|
165
|
+
# and Contrast::Components::API, but we're too early in the initialization of the Agent to use
|
166
|
+
# that mechanism, so we look it up directly for ourselves.
|
167
|
+
#
|
168
|
+
# @return [String, nil]
|
169
|
+
def api_username
|
170
|
+
@config.api.user_name
|
171
|
+
end
|
172
|
+
|
173
|
+
# Typically, the following values would be accessed through Contrast::Components::AppContext
|
174
|
+
# and Contrast::Components::API, but we're too early in the initialization of the Agent to use
|
175
|
+
# that mechanism, so we look it up directly for ourselves.
|
176
|
+
#
|
177
|
+
# @return [String, nil]
|
178
|
+
def bypass
|
179
|
+
@config.root.agent.service.bypass
|
180
|
+
end
|
181
|
+
|
182
|
+
# Typically, the following values would be accessed through Contrast::Components::AppContext
|
183
|
+
# and Contrast::Components::Logger, but we're too early in the initialization of the Agent to use
|
184
|
+
# that mechanism, so we look it up directly for ourselves.
|
185
|
+
#
|
186
|
+
# @return [String, nil]
|
187
|
+
def logger_path
|
188
|
+
@config.root.agent.logger.path
|
189
|
+
end
|
111
190
|
end
|
112
191
|
end
|
113
192
|
end
|
@@ -31,6 +31,12 @@ module Contrast
|
|
31
31
|
(LOCALHOST.match?(host) || !!socket_path)
|
32
32
|
end
|
33
33
|
|
34
|
+
def use_agent_communication?
|
35
|
+
return @_use_agent_communication unless @_use_agent_communication.nil?
|
36
|
+
|
37
|
+
@_use_agent_communication = true?(::Contrast::CONFIG.root.agent.service.bypass)
|
38
|
+
end
|
39
|
+
|
34
40
|
def host
|
35
41
|
@_host ||=
|
36
42
|
(::Contrast::CONFIG.root.agent.service.host || Contrast::Config::ServiceConfiguration::DEFAULT_HOST).to_s
|
@@ -0,0 +1,22 @@
|
|
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/default_value'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Config
|
8
|
+
# Api keys configuration
|
9
|
+
class ApiConfiguration < BaseConfiguration
|
10
|
+
URL = 'https://app.contrastsecurity.com/Contrast'
|
11
|
+
KEYS = {
|
12
|
+
api_key: EMPTY_VALUE,
|
13
|
+
url: Contrast::Config::DefaultValue.new(URL),
|
14
|
+
user_name: EMPTY_VALUE,
|
15
|
+
service_key: EMPTY_VALUE
|
16
|
+
}.cs__freeze
|
17
|
+
def initialize hsh
|
18
|
+
super(hsh, KEYS)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
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
|
+
module Contrast
|
5
|
+
module Config
|
6
|
+
# This module is holding all the Env Variables that we could use through the agent lifecycle
|
7
|
+
module EnvVariables
|
8
|
+
ENV_VARIABLES = {
|
9
|
+
telemetry_opt_outs: ENV['CONTRAST_AGENT_TELEMETRY_OPTOUT'].to_s || Contrast::Config::DefaultValue.new('false')
|
10
|
+
}.cs__freeze
|
11
|
+
|
12
|
+
def return_value key
|
13
|
+
return unless ENV_VARIABLES.key?(key.to_sym)
|
14
|
+
|
15
|
+
sym_key = key.downcase.to_sym
|
16
|
+
return_val = ENV_VARIABLES[sym_key]
|
17
|
+
if return_val.is_a?(Contrast::Config::DefaultValue)
|
18
|
+
return_val.value
|
19
|
+
else
|
20
|
+
return_val
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -6,6 +6,7 @@ module Contrast
|
|
6
6
|
# The base of the Common Configuration settings.
|
7
7
|
class RootConfiguration < BaseConfiguration
|
8
8
|
KEYS = {
|
9
|
+
api: Contrast::Config::ApiConfiguration,
|
9
10
|
enable: BaseConfiguration::EMPTY_VALUE,
|
10
11
|
agent: Contrast::Config::AgentConfiguration,
|
11
12
|
application: Contrast::Config::ApplicationConfiguration,
|
@@ -19,7 +19,8 @@ module Contrast
|
|
19
19
|
host: EMPTY_VALUE,
|
20
20
|
port: EMPTY_VALUE,
|
21
21
|
socket: EMPTY_VALUE,
|
22
|
-
logger: Contrast::Config::LoggerConfiguration
|
22
|
+
logger: Contrast::Config::LoggerConfiguration,
|
23
|
+
bypass: Contrast::Config::DefaultValue.new(false)
|
23
24
|
}.cs__freeze
|
24
25
|
|
25
26
|
def initialize hsh
|
data/lib/contrast/config.rb
CHANGED
@@ -24,6 +24,7 @@ require 'contrast/config/protect_rules_configuration'
|
|
24
24
|
require 'contrast/config/sampling_configuration'
|
25
25
|
|
26
26
|
require 'contrast/config/ruby_configuration'
|
27
|
+
require 'contrast/config/api_configuration'
|
27
28
|
require 'contrast/config/agent_configuration'
|
28
29
|
require 'contrast/config/application_configuration'
|
29
30
|
require 'contrast/config/server_configuration'
|