contrast-agent 6.15.1 → 6.15.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da127ad5e9e6a680c97012fff322627848cafb812164a7c9eb2b4200790342a8
4
- data.tar.gz: 222870b966c8ebc16dc5cc0fc4452e3a50326b9669c06606dd2e3c798fec1bf9
3
+ metadata.gz: a2f94b8a7a87febf8c10b58cf5133081a6dc3183c158ad59c202921efc23753e
4
+ data.tar.gz: 2e3fff601596655f725a4bea7a8b6f309a5f702557cfb009ad82b1d424da8d40
5
5
  SHA512:
6
- metadata.gz: 46014984fee94a0a0244ab9374e4699b7e09737720384edd3e584d0c6c19fa3741f2ae34c961b8aea9ba7d4bea9cabab7ed406979ff878b61ffd9db9b4993556
7
- data.tar.gz: d164ae2ab0cad83e3369f8e330a0a8b98d6ca9c36dca768134202e5c9bf1f5aa3130a7e45488f9994ce3044e33805f49c72a79eae4dded097d4f3fd13986ae5a
6
+ metadata.gz: cd0a28ee1a7331401a4709e1aec63a44b272d9d93ba9e6dcebd47270078165b42e3955fe8d938311b10cae4ec60cfaa92d9bee088b3a6056a2461fb00c6a4ab8
7
+ data.tar.gz: ba7ab7bebd769fd3057533da348a260e208bb622fe50aab3d0e68f8709413579bac258588680f04a65aa68ff4f5dbf0a2038d56fde7fd2ef2892a095e0e8e388
@@ -7,12 +7,13 @@ require 'contrast/agent/telemetry/client'
7
7
  require 'contrast/agent/thread/worker_thread'
8
8
  require 'contrast/agent/telemetry/telemetry'
9
9
  require 'contrast/agent/telemetry/exception'
10
+ require 'contrast/utils/job_servers_running'
10
11
 
11
12
  module Contrast
12
13
  module Agent
13
14
  module Telemetry
14
15
  # This class will initialize and hold everything needed for the telemetry
15
- class Base < WorkerThread
16
+ class Base < WorkerThread # rubocop:disable Metrics/ClassLength
16
17
  include Contrast::Components::Logger::InstanceMethods
17
18
  # this is where we will send the data from the agents
18
19
  URL = 'https://telemetry.ruby.contrastsecurity.com/'
@@ -48,6 +49,8 @@ module Contrast
48
49
  private
49
50
 
50
51
  def telemetry_enabled?
52
+ return false if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
53
+
51
54
  opt_out_telemetry = return_value(:telemetry_opt_outs).to_s
52
55
  return false if opt_out_telemetry.casecmp?('true') || opt_out_telemetry == '1'
53
56
 
@@ -56,7 +59,9 @@ module Contrast
56
59
  @_client = Contrast::Agent::Telemetry::Client.new
57
60
  ip_opt_out_telemetry = @_client.initialize_connection(URL)
58
61
  if ip_opt_out_telemetry.nil?
59
- logger.warn("[Telemetry] Connection was not established properly!!! \n
62
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
63
+ # an infinite loop w/ telemetry
64
+ logger.debug("[Telemetry] Connection was not established properly!!! \n
60
65
  Telemetry reporting will be disabled!")
61
66
  return false
62
67
  end
@@ -74,6 +79,8 @@ module Contrast
74
79
  end
75
80
 
76
81
  def attempt_to_start?
82
+ return unless super
83
+
77
84
  unless cs__class.enabled?
78
85
  logger.info('[Telemetry] Telemetry service is disabled!')
79
86
  return false
@@ -92,7 +99,7 @@ module Contrast
92
99
 
93
100
  def send_event event
94
101
  if ::Contrast::AGENT.disabled?
95
- logger.warn('[Telemetry] Attempted to queue event with Agent disabled', caller: caller, event: event)
102
+ logger.debug('[Telemetry] Attempted to queue event with Agent disabled', caller: caller, event: event)
96
103
  return
97
104
  end
98
105
 
@@ -148,7 +155,9 @@ module Contrast
148
155
  sleep(retry_sleep_time) unless retry_sleep_time.nil?
149
156
  end
150
157
  rescue StandardError => e
151
- logger.error('[Telemetry] Could not send message to service from telemetry queue.', e)
158
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
159
+ # an infinite loop w/ telemetry
160
+ logger.debug('[Telemetry] Could not send message to service from telemetry queue.', e)
152
161
  stop!
153
162
  end
154
163
  end
@@ -100,7 +100,9 @@ module Contrast
100
100
  def get_event_json event
101
101
  Array(event.to_controlled_hash).to_json
102
102
  rescue Exception => e # rubocop:disable Lint/RescueException
103
- logger.error('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
103
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
104
+ # an infinite loop w/ telemetry
105
+ logger.debug('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
104
106
  raise(e)
105
107
  end
106
108
  end
@@ -94,6 +94,12 @@ module Contrast
94
94
  def telemetry_exceptions_enabled?
95
95
  opts_out_telemetry = return_value(:telemetry_opt_outs).to_s
96
96
  return false if opts_out_telemetry.casecmp?('true') || opts_out_telemetry == '1'
97
+ # Double check if telemetry is enabled, this includes a check for Agent enable setting in the
98
+ # config. In case of disabled Agent the queue won't be created and the Telemetry would not
99
+ # be enabled. This check is here to prevent a loop of error creations that could no be added
100
+ # to a queue ( because the client cannot be initialized if the Agent is disabled or settings are
101
+ # missing).
102
+ return false unless Contrast::Agent::Telemetry::Base.enabled?
97
103
 
98
104
  true
99
105
  end
@@ -7,6 +7,7 @@ require 'contrast/agent/reporting/reporting_workers/reporting_workers'
7
7
  require 'contrast/agent/telemetry/base'
8
8
  require 'contrast/agent/protect/input_analyzer/worth_watching_analyzer'
9
9
  require 'contrast/config/diagnostics'
10
+ require 'contrast/utils/job_servers_running'
10
11
 
11
12
  module Contrast
12
13
  module Agent
@@ -28,6 +29,11 @@ module Contrast
28
29
  attr_reader :reporter_app_settings_worker
29
30
 
30
31
  def initialize
32
+ if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
33
+ logger.info('Agent Disabled, shutting down all threads...')
34
+ return
35
+ end
36
+
31
37
  @heapdump_util = Contrast::Utils::HeapDumpUtil.new
32
38
  @reporter = Contrast::Agent::Reporter.new
33
39
  @reporter_heartbeat = Contrast::Agent::ReportingWorkers::ReporterHeartbeat.new
@@ -37,7 +43,7 @@ module Contrast
37
43
  @worth_watching_analyzer = Contrast::Agent::Protect::WorthWatchingInputAnalyzer.new unless protect_disabled?
38
44
  end
39
45
 
40
- # @return [Hash, nil] map of process to thread startup status
46
+ # @return [Array<Contrast::Agent::WorkerThread>] map of process to thread startup status
41
47
  def startup!
42
48
  check_before_start
43
49
 
@@ -29,7 +29,7 @@ module Contrast
29
29
  #
30
30
  # @return [Boolean]
31
31
  def attempt_to_start?
32
- true
32
+ Contrast::AGENT.enabled?
33
33
  end
34
34
 
35
35
  def clean_properties
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '6.15.1'
6
+ VERSION = '6.15.2'
7
7
  end
8
8
  end
@@ -9,6 +9,7 @@ require 'contrast/components/security_logger'
9
9
  require 'contrast/components/heap_dump'
10
10
  require 'contrast/components/ruby_component'
11
11
  require 'contrast/components/polling'
12
+ require 'contrast/agent/hooks/tracepoint_hook'
12
13
 
13
14
  module Contrast
14
15
  module Components
@@ -23,6 +24,8 @@ module Contrast
23
24
  attr_reader :canon_name
24
25
  # @return [Array]
25
26
  attr_reader :config_values
27
+ # @return [Boolean]
28
+ attr_accessor :enable
26
29
 
27
30
  CANON_NAME = 'agent'
28
31
  CONFIG_VALUES = %w[enabled? omit_body?].cs__freeze
@@ -53,6 +53,7 @@ module Contrast
53
53
  # @return [String,nil]
54
54
  attr_reader :config_file
55
55
 
56
+ CONTRAST_ENV_MARKER = 'CONTRAST__'
56
57
  DEFAULT_YAML_PATH = 'contrast_security.yaml'
57
58
  MILLISECOND_MARKER = '_ms'
58
59
  CONVERSION = {}.cs__freeze
@@ -67,6 +68,14 @@ module Contrast
67
68
  config_kv = deep_symbolize_all_keys(load_config)
68
69
  config_sources = assign_source_to(config_kv, Contrast::Components::Config::Sources::YAML)
69
70
 
71
+ unless cli_options
72
+ cli_options = {}
73
+ ENV.each do |key, value|
74
+ next unless key.to_s.start_with?(CONTRAST_ENV_MARKER)
75
+
76
+ cli_options[key] = value
77
+ end
78
+ end
70
79
  # Overlay CLI options - they take precedence over config file
71
80
  cli_options = deep_symbolize_all_keys(cli_options)
72
81
  if cli_options
@@ -11,6 +11,7 @@ require 'contrast/framework/rack/support'
11
11
  require 'contrast/framework/rails/support'
12
12
  require 'contrast/framework/sinatra/support'
13
13
  require 'contrast/utils/class_util'
14
+ require 'contrast/utils/job_servers_running'
14
15
 
15
16
  module Contrast
16
17
  module Framework
@@ -28,6 +29,8 @@ module Contrast
28
29
  ].cs__freeze
29
30
 
30
31
  def initialize
32
+ return if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
33
+
31
34
  @_frameworks = SUPPORTED_FRAMEWORKS.map do |framework_klass|
32
35
  next unless enable_framework_support?(framework_klass.detection_class)
33
36
 
@@ -116,8 +119,8 @@ module Contrast
116
119
  # @param request [Contrast::Agent::Request] the current request.
117
120
  # @return [Contrast::Agent::Reporting::RouteCoverage] the current route as a Dtm.
118
121
  def get_route_information request
119
- @_frameworks.lazy.map { |framework_support| framework_support.current_route_coverage(request) }.
120
- reject(&:nil?).first # rubocop:disable Style/CollectionCompact
122
+ @_frameworks&.lazy&.map { |framework_support| framework_support.current_route_coverage(request) }&.
123
+ reject(&:nil?)&.first
121
124
  end
122
125
 
123
126
  # Sometimes the framework we want to instrument is loaded after our agent code. To catch that case, we'll detect
@@ -28,7 +28,6 @@ module Funchook
28
28
  logger.warn('Unable to find funchook')
29
29
  else
30
30
  @path = absolute_path(actual_path_segments)
31
- logger.info('Funchook found', path: @path)
32
31
  end
33
32
  @path
34
33
  end
@@ -40,12 +40,38 @@ module Contrast
40
40
 
41
41
  private
42
42
 
43
+ # There's a weird circular import in our code that we don't have time to untangle. This is 100% bad code and
44
+ # I'm sorry to the future team, but this is all we got.
45
+ #
46
+ # If the configuration we need is not available, we'll skip out for now - it means the agent isn't ready. We
47
+ # won't get telemetry exceptions at this point, but that means the agent hasn't initialized and there's a
48
+ # chance we're not supposed to. Essentially, we fail closed.
49
+ #
50
+ # Once we have the agent initialized, we'll use the value to check. Since it cannot change once set, we'll use
51
+ # the saved value.
52
+ #
53
+ # - HM
54
+ #
55
+ # @return [Boolean]
56
+ def buildable?
57
+ if @_buildable.nil?
58
+ return false unless defined?(Contrast) &&
59
+ defined?(Contrast::Agent) &&
60
+ defined?(Contrast::Agent::Telemetry)
61
+
62
+ @_buildable = Contrast::Agent::Telemetry.exceptions_enabled?
63
+ end
64
+ @_buildable
65
+ end
66
+
43
67
  # @param type [ALIASED_FATAL, ALIASED_ERROR, ALIASED_WARN] the type of error, used to indicate the function used
44
68
  # for logging
45
69
  # @param message [String] the exception message
46
70
  # @param exception [Exception] The exception or error
47
71
  # @param data [Object] Any structured data
48
72
  def build_exception type, message = nil, exception = nil, data = nil
73
+ return unless buildable?
74
+
49
75
  stack_trace = wrapped_caller_locations
50
76
  caller_idx = stack_trace&.find_index { |stack| stack.to_s.include?(type) } || 0
51
77
  # The caller_stack is the method in which the error occurred, so has to be above this method
@@ -1,15 +1,13 @@
1
1
  # Copyright (c) 2023 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/components/logger'
4
+ require 'contrast/components/ruby_component'
5
5
 
6
6
  module Contrast
7
7
  module Utils
8
8
  # A module that detects whether any job servers attached to
9
9
  # the application are running
10
10
  module JobServersRunning
11
- extend Contrast::Components::Logger::InstanceMethods
12
-
13
11
  class << self
14
12
  def job_servers_running?
15
13
  sidekiq_running? || rake_running?
@@ -20,7 +18,6 @@ module Contrast
20
18
  def sidekiq_running?
21
19
  return unless defined?(Sidekiq) && Sidekiq.cs__respond_to?(:server?) && Sidekiq.server?
22
20
 
23
- logger.trace('Detected the spawn of a Sidekiq process')
24
21
  true
25
22
  end
26
23
 
@@ -32,13 +29,18 @@ module Contrast
32
29
  return
33
30
  end
34
31
 
35
- disabled_rake_tasks = ::Contrast::APP_CONTEXT.disabled_agent_rake_tasks
32
+ # This might be called before component even exist, so we backup to
33
+ # default disabled rake tasks.
34
+ disabled_rake_tasks = if Contrast.const_defined?(:APP_CONTEXT) # rubocop:disable Security/Module/ConstDefined
35
+ Contrast::APP_CONTEXT.disabled_agent_rake_tasks
36
+ else
37
+ Contrast::Components::Ruby::Interface::DISABLED_RAKE_TASK_LIST
38
+ end
36
39
  has_disabled_task = Rake.application.top_level_tasks.any? do |top_level_task|
37
40
  disabled_rake_tasks.include?(top_level_task)
38
41
  end
39
42
  return false unless has_disabled_task
40
43
 
41
- logger.trace('Detected startup within Rake task')
42
44
  true
43
45
  end
44
46
  end
@@ -51,7 +51,7 @@ module Contrast
51
51
  logger.extend(Contrast::Logger::Application)
52
52
  logger.extend(Contrast::Logger::Request)
53
53
  logger.extend(Contrast::Logger::Time)
54
- logger.extend(Contrast::Logger::AliasedLogging) if Contrast::Agent::Telemetry.exceptions_enabled?
54
+ logger.extend(Contrast::Logger::AliasedLogging)
55
55
  end
56
56
 
57
57
  # Determine the valid path to which to log, given the precedence of config > settings > default.
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/config/yaml_file'
5
+ require 'contrast/utils/job_servers_running'
5
6
 
6
7
  module Contrast
7
8
  module Utils
@@ -30,6 +31,9 @@ module Contrast
30
31
  logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
31
32
  elsif ::Contrast::AGENT.disabled?
32
33
  logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
34
+ elsif Contrast::Utils::JobServersRunning.job_servers_running?
35
+ logger.info('Server job detected disabling Agent...')
36
+ ::Contrast::AGENT.disable!
33
37
  else
34
38
  ::Contrast::AGENT.enable!
35
39
  end
@@ -43,7 +43,9 @@ module Contrast
43
43
  logger.debug('Client verified', service: service_name, url: url)
44
44
  net_http_client
45
45
  rescue StandardError => e
46
- logger.error('Connection failed', e, service: service_name, url: url)
46
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
47
+ # an infinite loop w/ telemetry
48
+ logger.debug('Connection failed', e, service: service_name, url: url)
47
49
  nil
48
50
  end
49
51
 
@@ -72,7 +74,9 @@ module Contrast
72
74
  Errno::ETIMEDOUT, Errno::ESHUTDOWN, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::EISCONN,
73
75
  Errno::ECONNABORTED, Errno::ENETRESET, Errno::ENETUNREACH => e
74
76
 
75
- logger.warn("#{ service_name } connection failed", e.message)
77
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
78
+ # an infinite loop w/ telemetry
79
+ logger.debug("#{ service_name } connection failed", e.message)
76
80
  false
77
81
  end
78
82
 
@@ -110,7 +114,9 @@ module Contrast
110
114
  client.key = OpenSSL::PKey::RSA.new(File.read(Contrast::API.certification_key_file)).to_s
111
115
  end
112
116
  rescue Errno::ENOENT => e
113
- logger.error('Custom certificates failed', e.message)
117
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
118
+ # an infinite loop w/ telemetry
119
+ logger.debug('Custom certificates failed', e.message)
114
120
  end
115
121
 
116
122
  # sets default setting for client validation of certificates and
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrast-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.15.1
4
+ version: 6.15.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - galen.palmer@contrastsecurity.com
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: exe
15
15
  cert_chain: []
16
- date: 2023-02-16 00:00:00.000000000 Z
16
+ date: 2023-02-22 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -678,22 +678,22 @@ email:
678
678
  executables: []
679
679
  extensions:
680
680
  - ext/cs__common/extconf.rb
681
- - ext/cs__assess_basic_object/extconf.rb
682
- - ext/cs__assess_hash/extconf.rb
681
+ - ext/cs__assess_marshal_module/extconf.rb
682
+ - ext/cs__assess_yield_track/extconf.rb
683
+ - ext/cs__scope/extconf.rb
683
684
  - ext/cs__assess_kernel/extconf.rb
684
- - ext/cs__assess_string_interpolation/extconf.rb
685
- - ext/cs__contrast_patch/extconf.rb
685
+ - ext/cs__assess_array/extconf.rb
686
+ - ext/cs__os_information/extconf.rb
686
687
  - ext/cs__assess_string/extconf.rb
688
+ - ext/cs__assess_hash/extconf.rb
687
689
  - ext/cs__assess_regexp/extconf.rb
688
- - ext/cs__tests/extconf.rb
689
690
  - ext/cs__assess_module/extconf.rb
690
- - ext/cs__assess_yield_track/extconf.rb
691
- - ext/cs__assess_fiber_track/extconf.rb
692
- - ext/cs__scope/extconf.rb
691
+ - ext/cs__assess_string_interpolation/extconf.rb
692
+ - ext/cs__tests/extconf.rb
693
693
  - ext/cs__assess_test/extconf.rb
694
- - ext/cs__os_information/extconf.rb
695
- - ext/cs__assess_marshal_module/extconf.rb
696
- - ext/cs__assess_array/extconf.rb
694
+ - ext/cs__assess_fiber_track/extconf.rb
695
+ - ext/cs__assess_basic_object/extconf.rb
696
+ - ext/cs__contrast_patch/extconf.rb
697
697
  extra_rdoc_files: []
698
698
  files:
699
699
  - ".clang-format"