contrast-agent 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_regexp/cs__assess_regexp.c +15 -2
  3. data/ext/cs__assess_regexp/cs__assess_regexp.h +2 -0
  4. data/ext/cs__assess_string/cs__assess_string.c +8 -0
  5. data/ext/cs__assess_test/cs__assess_test.h +9 -0
  6. data/ext/cs__assess_test/cs__assess_tests.c +22 -0
  7. data/ext/cs__assess_test/extconf.rb +5 -0
  8. data/ext/cs__common/cs__common.c +101 -0
  9. data/ext/cs__common/cs__common.h +29 -5
  10. data/ext/cs__contrast_patch/cs__contrast_patch.c +1 -1
  11. data/ext/cs__tests/cs__tests.c +12 -0
  12. data/ext/cs__tests/cs__tests.h +3 -0
  13. data/ext/cs__tests/extconf.rb +5 -0
  14. data/lib/contrast/agent/assess/contrast_object.rb +16 -16
  15. data/lib/contrast/agent/assess/events/source_event.rb +17 -19
  16. data/lib/contrast/agent/assess/policy/policy_scanner.rb +2 -16
  17. data/lib/contrast/agent/assess/policy/propagator/split.rb +15 -19
  18. data/lib/contrast/agent/assess/policy/trigger_method.rb +3 -11
  19. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +7 -2
  20. data/lib/contrast/agent/assess/rule/response/base_rule.rb +11 -3
  21. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +60 -36
  22. data/lib/contrast/agent/at_exit_hook.rb +1 -1
  23. data/lib/contrast/agent/inventory/database_config.rb +10 -3
  24. data/lib/contrast/agent/middleware.rb +3 -3
  25. data/lib/contrast/agent/patching/policy/after_load_patch.rb +0 -2
  26. data/lib/contrast/agent/patching/policy/patch.rb +13 -12
  27. data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
  28. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +6 -2
  29. data/lib/contrast/agent/reporting/masker/masker.rb +8 -11
  30. data/lib/contrast/agent/reporting/masker/masker_utils.rb +8 -4
  31. data/lib/contrast/agent/reporting/reporter.rb +11 -16
  32. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +49 -0
  33. data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +6 -2
  34. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +53 -0
  35. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +48 -0
  36. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +64 -0
  37. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +70 -0
  38. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +57 -0
  39. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +56 -0
  40. data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +5 -1
  41. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +58 -0
  42. data/lib/contrast/agent/reporting/reporting_events/application_reporting_event.rb +27 -0
  43. data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +20 -10
  44. data/lib/contrast/agent/reporting/reporting_events/application_update.rb +7 -12
  45. data/lib/contrast/agent/reporting/reporting_events/finding.rb +9 -3
  46. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +2 -4
  47. data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +3 -3
  48. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +6 -2
  49. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +7 -3
  50. data/lib/contrast/agent/reporting/reporting_events/poll.rb +6 -2
  51. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +10 -8
  52. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +6 -10
  53. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +12 -20
  54. data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +27 -0
  55. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +17 -27
  56. data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +38 -0
  57. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +8 -0
  58. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
  59. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +9 -4
  60. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +54 -67
  61. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +17 -7
  62. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +8 -5
  63. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +10 -10
  64. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +32 -17
  65. data/lib/contrast/agent/reporting/settings/protect.rb +1 -1
  66. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +1 -1
  67. data/lib/contrast/agent/request.rb +3 -3
  68. data/lib/contrast/agent/request_context_extend.rb +1 -1
  69. data/lib/contrast/agent/request_handler.rb +3 -3
  70. data/lib/contrast/agent/response.rb +2 -0
  71. data/lib/contrast/agent/service_heartbeat.rb +6 -48
  72. data/lib/contrast/agent/static_analysis.rb +1 -1
  73. data/lib/contrast/agent/telemetry/base.rb +151 -0
  74. data/lib/contrast/agent/telemetry/events/event.rb +35 -0
  75. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +44 -36
  76. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +29 -21
  77. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +91 -73
  78. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +62 -44
  79. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +50 -33
  80. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +20 -0
  81. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +32 -0
  82. data/lib/contrast/agent/telemetry/events/metric_event.rb +28 -0
  83. data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +123 -0
  84. data/lib/contrast/agent/thread_watcher.rb +52 -68
  85. data/lib/contrast/agent/version.rb +1 -1
  86. data/lib/contrast/agent/worker_thread.rb +8 -0
  87. data/lib/contrast/agent.rb +1 -3
  88. data/lib/contrast/api/communication/messaging_queue.rb +28 -11
  89. data/lib/contrast/api/communication/response_processor.rb +7 -10
  90. data/lib/contrast/api/communication/speedracer.rb +1 -1
  91. data/lib/contrast/api/decorators/activity.rb +33 -0
  92. data/lib/contrast/api/decorators/http_request.rb +1 -1
  93. data/lib/contrast/components/config.rb +13 -22
  94. data/lib/contrast/components/contrast_service.rb +9 -0
  95. data/lib/contrast/components/settings.rb +10 -0
  96. data/lib/contrast/config/agent_configuration.rb +21 -11
  97. data/lib/contrast/config/api_configuration.rb +12 -8
  98. data/lib/contrast/config/api_proxy_configuration.rb +7 -3
  99. data/lib/contrast/config/application_configuration.rb +15 -11
  100. data/lib/contrast/config/assess_configuration.rb +13 -9
  101. data/lib/contrast/config/assess_rules_configuration.rb +5 -1
  102. data/lib/contrast/config/base_configuration.rb +3 -35
  103. data/lib/contrast/config/certification_configuration.rb +9 -5
  104. data/lib/contrast/config/exception_configuration.rb +10 -7
  105. data/lib/contrast/config/heap_dump_configuration.rb +13 -9
  106. data/lib/contrast/config/inventory_configuration.rb +9 -6
  107. data/lib/contrast/config/logger_configuration.rb +9 -6
  108. data/lib/contrast/config/protect_configuration.rb +9 -6
  109. data/lib/contrast/config/protect_rule_configuration.rb +12 -8
  110. data/lib/contrast/config/protect_rules_configuration.rb +18 -17
  111. data/lib/contrast/config/request_audit_configuration.rb +10 -7
  112. data/lib/contrast/config/root_configuration.rb +28 -11
  113. data/lib/contrast/config/ruby_configuration.rb +14 -11
  114. data/lib/contrast/config/sampling_configuration.rb +11 -8
  115. data/lib/contrast/config/server_configuration.rb +13 -9
  116. data/lib/contrast/config/service_configuration.rb +14 -11
  117. data/lib/contrast/configuration.rb +19 -10
  118. data/lib/contrast/framework/rails/patch/support.rb +13 -45
  119. data/lib/contrast/logger/aliased_logging.rb +87 -0
  120. data/lib/contrast/logger/application.rb +0 -4
  121. data/lib/contrast/tasks/config.rb +22 -13
  122. data/lib/contrast/utils/class_util.rb +2 -6
  123. data/lib/contrast/utils/invalid_configuration_util.rb +1 -1
  124. data/lib/contrast/utils/log_utils.rb +2 -0
  125. data/lib/contrast/utils/middleware_utils.rb +1 -1
  126. data/lib/contrast/utils/object_share.rb +1 -1
  127. data/lib/contrast/utils/telemetry.rb +20 -2
  128. data/lib/contrast/utils/telemetry_client.rb +22 -10
  129. data/lib/contrast/utils/telemetry_hash.rb +41 -0
  130. data/lib/contrast/utils/telemetry_identifier.rb +16 -1
  131. data/lib/contrast.rb +9 -0
  132. data/ruby-agent.gemspec +1 -1
  133. data/service_executables/VERSION +1 -1
  134. data/service_executables/linux/contrast-service +0 -0
  135. data/service_executables/mac/contrast-service +0 -0
  136. metadata +39 -16
  137. data/lib/contrast/agent/telemetry/events/metric_telemetry_event.rb +0 -26
  138. data/lib/contrast/agent/telemetry/events/startup_metrics_telemetry_event.rb +0 -121
  139. data/lib/contrast/agent/telemetry/events/telemetry_event.rb +0 -33
  140. data/lib/contrast/agent/telemetry/telemetry.rb +0 -150
  141. data/lib/contrast/utils/exclude_key.rb +0 -20
@@ -7,7 +7,6 @@ require 'fileutils'
7
7
  require 'contrast/config'
8
8
  require 'contrast/utils/object_share'
9
9
  require 'contrast/components/scope'
10
- require 'contrast/utils/exclude_key'
11
10
 
12
11
  module Contrast
13
12
  # This is how we read in the local settings for the Agent, both ENV/ CMD line
@@ -27,15 +26,17 @@ module Contrast
27
26
  MILLISECOND_MARKER = '_ms'
28
27
  CONVERSION = { 'agent.service.enable' => 'agent.start_bundled_service' }.cs__freeze
29
28
  CONFIG_BASE_PATHS = ['', 'config/', '/etc/contrast/ruby/', '/etc/contrast/', '/etc/'].cs__freeze
29
+ KEYS_TO_REDACT = %i[api_key url service_key user_name].cs__freeze
30
+ REDACTED = '**REDACTED**'
30
31
 
31
32
  def initialize cli_options = nil, default_name = DEFAULT_YAML_PATH
32
33
  @default_name = default_name
33
34
 
34
35
  # Load config_kv from file
35
- config_kv = deep_stringify_all_keys(load_config)
36
+ config_kv = deep_symbolize_all_keys(load_config)
36
37
 
37
38
  # Overlay CLI options - they take precedence over config file
38
- cli_options = deep_stringify_all_keys(cli_options)
39
+ cli_options = deep_symbolize_all_keys(cli_options)
39
40
  config_kv = deep_merge(cli_options, config_kv) if cli_options
40
41
 
41
42
  # Some in-flight rewrites to maintain backwards compatibility
@@ -105,7 +106,7 @@ module Contrast
105
106
  def update_prop_keys config
106
107
  CONVERSION.each_pair do |old_method, new_method|
107
108
  # See if the old value was set and needs to be translated
108
- deprecated_keys = old_method.split('.')
109
+ deprecated_keys = old_method.split('.').map(&:to_sym)
109
110
  old_value = config
110
111
  deprecated_keys.each do |key|
111
112
  old_value = old_value[key]
@@ -114,7 +115,7 @@ module Contrast
114
115
  next if old_value.nil? # have to account for literal false
115
116
 
116
117
  log_deprecated_property(old_method, new_method)
117
- new_keys = new_method.split('.')
118
+ new_keys = new_method.split('.').map(&:to_sym)
118
119
  # We changed the seconds values into ms values. Multiply them accordingly
119
120
  old_value = old_value.to_i * 1000 if new_method.end_with?(MILLISECOND_MARKER)
120
121
  new_value = config
@@ -147,12 +148,12 @@ module Contrast
147
148
  end
148
149
  end
149
150
 
150
- def deep_stringify_all_keys hash
151
+ def deep_symbolize_all_keys hash
151
152
  return if hash.nil?
152
153
 
153
154
  new_hash = {}
154
155
  hash.each do |key, value|
155
- new_hash[key.to_s] = value.is_a?(Hash) ? deep_stringify_all_keys(value) : value
156
+ new_hash[key.to_sym] = value.is_a?(Hash) ? deep_symbolize_all_keys(value) : value
156
157
  end
157
158
  new_hash
158
159
  end
@@ -219,10 +220,9 @@ module Contrast
219
220
  when Contrast::Config::BaseConfiguration
220
221
  # to_hash returns @configuration_map
221
222
  convert.to_hash.each_key do |key|
222
- next if Contrast::Utils::ExcludeKey.excludable?(key.to_s)
223
-
224
223
  # change '-' to '_' for ProtectRulesConfiguration
225
224
  hash[key] = convert_to_hash(convert.send(key.tr('-', '_').to_sym), {})
225
+ hash[key] = REDACTED if redactable?(key)
226
226
  end
227
227
  hash
228
228
  else
@@ -234,7 +234,7 @@ module Contrast
234
234
  idx = 0
235
235
  end_idx = new_keys.length - 1
236
236
  while idx < new_keys.length
237
- new_key = new_keys[idx]
237
+ new_key = new_keys[idx].to_sym
238
238
  if idx == end_idx
239
239
  new_value[new_key] = old_value if new_value[new_key].nil?
240
240
  else
@@ -245,5 +245,14 @@ module Contrast
245
245
  idx += 1
246
246
  end
247
247
  end
248
+
249
+ # Check if keys with sensitive data needs to be
250
+ # redacted.
251
+ #
252
+ # @param key [Symbol] key to check
253
+ # @return[Boolean] true | false
254
+ def redactable? key
255
+ KEYS_TO_REDACT.include?(key.to_sym)
256
+ end
248
257
  end
249
258
  end
@@ -25,51 +25,19 @@ module Contrast
25
25
 
26
26
  # (See BaseSupport#after_load_patches)
27
27
  def after_load_patches
28
- patches = Set.new([
29
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
30
- 'ActionController::Live::Buffer',
31
- 'contrast/framework/rails/patch/action_controller_live_buffer',
32
- instrumenting_module:
33
- 'Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer'),
34
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
35
- 'Rails::Application::Configuration',
36
- 'contrast/framework/rails/patch/rails_application_configuration',
37
- method_to_instrument: :session_store,
38
- instrumenting_module:
39
- 'Contrast::Framework::Rails::Patch::RailsApplicationConfiguration')
40
- ])
41
- patches.merge(special_after_load_patches) if RUBY_VERSION < '2.6.0'
42
- patches
43
- end
44
-
45
- def special_after_load_patches
46
- [
47
- # TODO: RUBY-714 remove w/ EOL of 2.5
48
- #
49
- # @deprecated Everything past here is used for Rewriting and can
50
- # be removed once we no longer support 2.5.
51
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
52
- 'ActionController::Railties::Helper::ClassMethods',
53
- 'contrast/framework/rails/rewrite/action_controller_railties_helper_inherited',
54
- method_to_instrument: :inherited,
55
- instrumenting_module:
56
- 'Contrast::Framework::Rails::Rewrite::ActionControllerRailtiesHelperInherited'),
57
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
58
- 'ActiveRecord::AttributeMethods::Read::ClassMethods',
59
- 'contrast/framework/rails/rewrite/active_record_attribute_methods_read',
60
- instrumenting_module:
61
- 'Contrast::Framework::Rails::Rewrite::ActiveRecordAttributeMethodsRead'),
62
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
63
- 'ActiveRecord::Scoping::Named::ClassMethods',
64
- 'contrast/framework/rails/rewrite/active_record_named',
65
- instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordNamed'),
66
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
67
- 'ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods',
68
- 'contrast/framework/rails/rewrite/active_record_time_zone_inherited',
69
- method_to_instrument: :inherited,
70
- instrumenting_module:
71
- 'Contrast::Framework::Rails::Rewrite::ActiveRecordTimeZoneInherited')
72
- ]
28
+ Set.new([
29
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
30
+ 'ActionController::Live::Buffer',
31
+ 'contrast/framework/rails/patch/action_controller_live_buffer',
32
+ instrumenting_module:
33
+ 'Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer'),
34
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
35
+ 'Rails::Application::Configuration',
36
+ 'contrast/framework/rails/patch/rails_application_configuration',
37
+ method_to_instrument: :session_store,
38
+ instrumenting_module:
39
+ 'Contrast::Framework::Rails::Patch::RailsApplicationConfiguration')
40
+ ])
73
41
  end
74
42
  end
75
43
  end
@@ -0,0 +1,87 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/telemetry/events/exceptions/telemetry_exceptions'
5
+
6
+ module Contrast
7
+ module Logger
8
+ # Our decorator for the Ougai logger allowing for the catching, creating and saving Telemetry exceptions
9
+ module AliasedLogging
10
+ ALIASED_WARN = 'warn'.cs__freeze
11
+ ALIASED_ERROR = 'error'.cs__freeze
12
+ ALIASED_FATAL = 'fatal'.cs__freeze
13
+
14
+ # @param message [String] The message to log. Use default_message if not specified.
15
+ # @param exception [Exception] The exception or the error
16
+ # @param data [Object] Any structured data
17
+ def warn message = nil, exception = nil, data = nil, &block
18
+ # build Telemetry Exclusion
19
+ build_exclusion(ALIASED_WARN, message, exception, data)
20
+ super(message, exception, data, &block)
21
+ end
22
+
23
+ # @param message [String] The message to log. Use default_message if not specified.
24
+ # @param exception [Exception] The exception or the error
25
+ # @param data [Object] Any structured data
26
+ def error message = nil, exception = nil, data = nil, &block
27
+ # build Telemetry Exclusion
28
+ build_exclusion(ALIASED_ERROR, message, exception, data)
29
+ super(message, exception, data, &block)
30
+ end
31
+
32
+ # @param message [String] The message to log. Use default_message if not specified.
33
+ # @param exception [Exception] The exception or the error
34
+ # @param data [Object] Any structured data
35
+ def fatal message = nil, exception = nil, data = nil, &block
36
+ # build Telemetry Exclusion
37
+ build_exclusion(ALIASED_FATAL, message, exception, data)
38
+ super(message, exception, data, &block)
39
+ end
40
+
41
+ private
42
+
43
+ def build_exclusion type, message = nil, exception = nil, data = nil
44
+ start = caller_locations&.find_index { |stack| stack.to_s.include?(type) }
45
+ stack_trace = start ? caller_locations(start + 1, 20) : caller_locations(20, 20)
46
+ stack_frame_type = stack_trace[1].path.delete_prefix(Dir.pwd)
47
+ message_exception_type = exception ? exception.cs__class.to_s : stack_frame_type.split('/').last
48
+ stack_frame_function = stack_trace[1].label
49
+ key = "#{ stack_frame_type }|#{ stack_frame_function }|#{ message }"
50
+ if TELEMETRY_EXCEPTIONS[key]
51
+ TELEMETRY_EXCEPTIONS.increment key
52
+ return
53
+ end
54
+
55
+ event_message = create_message(stack_frame_function, stack_frame_type, message_exception_type, data, exception,
56
+ message)
57
+ TELEMETRY_EXCEPTIONS[key] = event_message
58
+ rescue StandardError => e
59
+ debug('Unable to report exception to telemetry', e)
60
+ end
61
+
62
+ def create_message stack_frame_function, stack_frame_type, message_exception_type, data, exception, message
63
+ message_for_exception = if exception
64
+ exception.cs__respond_to?(:message) ? exception.message : exception
65
+ else
66
+ message
67
+ end
68
+ module_name = exception ? exception.cs__class.to_s.split('::').first : nil
69
+ stack_frame = Contrast::Agent::Telemetry::TelemetryException::StackFrame.build stack_frame_function,
70
+ stack_frame_type,
71
+ module_name
72
+ message_exception = Contrast::Agent::Telemetry::TelemetryException::MessageException.build(
73
+ message_exception_type,
74
+ message_for_exception,
75
+ module_name,
76
+ stack_frame)
77
+ tags = if data
78
+ data
79
+ else
80
+ exception.cs__is_a?(Hash) ? exception : {}
81
+ end
82
+ message = Contrast::Agent::Telemetry::TelemetryException::Message.build tags, [message_exception]
83
+ Contrast::Agent::Telemetry::TelemetryException::Event.new message
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,8 +1,6 @@
1
1
  # Copyright (c) 2022 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/utils/exclude_key'
5
-
6
4
  module Contrast
7
5
  module Logger
8
6
  # Our decorator for the Ougai logger allowing for the logging of the
@@ -31,8 +29,6 @@ module Contrast
31
29
  loggable = ::Contrast::CONFIG.loggable
32
30
  info('Current configuration', configuration: loggable)
33
31
  env_keys = ENV.keys.select do |env_key|
34
- next if Contrast::Utils::ExcludeKey.excludable? env_key.to_s
35
-
36
32
  env_key&.to_s&.start_with?(Contrast::Components::Config::CONTRAST_ENV_MARKER)
37
33
  end
38
34
  env_items = env_keys.map { |env_key| Contrast::Utils::EnvConfigurationItem.new(env_key, nil) }
@@ -7,7 +7,7 @@ require 'contrast/agent/reporting/reporter'
7
7
 
8
8
  module Contrast
9
9
  # A Rake task to generate a contrast_security.yaml file with some basic settings
10
- module Config
10
+ module Config # rubocop:disable Metrics/ModuleLength
11
11
  extend Rake::DSL
12
12
  DEFAULT_CONFIG = {
13
13
  'api' => {
@@ -32,6 +32,7 @@ module Contrast
32
32
  }.cs__freeze
33
33
 
34
34
  SKIP_LOG = %w[service_key api_key].cs__freeze
35
+ REQUIRED = %i[url api_key service_key user_name].cs__freeze
35
36
 
36
37
  namespace :contrast do
37
38
  namespace :config do
@@ -62,20 +63,19 @@ module Contrast
62
63
  puts 'Validating Contrast Reporter Headers...'
63
64
  reporter = Contrast::Config.validate_headers
64
65
  puts '...done!'
65
- puts 'Testing Client Connection...'
66
+ puts 'Testing Reporter Client Connection...'
66
67
  Contrast::Config.test_connection(reporter) if reporter
67
68
  puts '...done!'
68
69
  end
69
70
  end
70
- def self.validate_config # rubocop:disable Metrics/PerceivedComplexity
71
+
72
+ def self.validate_config
71
73
  config = Contrast::Configuration.new
72
74
  abort('Unable to Build Config') unless config
73
-
74
- required = %i[url api_key service_key user_name]
75
-
76
75
  missing = []
77
- config.root.api.each do |key, value|
78
- puts "#{ key }::#{ value }" unless value.is_a?(Contrast::Config::BaseConfiguration) || SKIP_LOG.includes?(key)
76
+ api_hash = config.root.api.to_hash
77
+ api_hash.each_key do |key|
78
+ value = mask_keys api_hash, key
79
79
  if value.is_a?(Contrast::Config::ApiProxyConfiguration)
80
80
  Contrast::Config.validate_proxy(value)
81
81
  elsif value.is_a?(Contrast::Config::CertificationConfiguration)
@@ -84,7 +84,7 @@ module Contrast
84
84
  elsif value.is_a?(Contrast::Config::RequestAuditConfiguration)
85
85
  Contrast::Config.validate_audit(value)
86
86
  next
87
- elsif value == Contrast::Config::BaseConfiguration::EMPTY_VALUE && required.includes?(key.to_sym)
87
+ elsif value.nil? && REQUIRED.includes?(key.to_sym)
88
88
  missing << key
89
89
  end
90
90
  end
@@ -123,8 +123,9 @@ module Contrast
123
123
  def self.validate_headers
124
124
  missing = []
125
125
  reporter = Contrast::Agent::Reporter.new
126
- reporter.client.headers.to_hash.each_pair do |key, value|
127
- puts "#{ key }::#{ value }"
126
+ reporter_headers = reporter.client.headers.to_hash
127
+ reporter_headers.each_key do |key|
128
+ value = mask_keys reporter_headers, key
128
129
  missing << key if value.nil?
129
130
  end
130
131
  abort("Missing required header values: #{ missing.join(', ') }") unless missing.empty?
@@ -132,8 +133,16 @@ module Contrast
132
133
  end
133
134
 
134
135
  def self.test_connection reporter
135
- abort('Failed to Initialize Connection please check error logs for details') unless reporter.connection
136
- abort('Failed to Start Client please check error logs for details') unless reporter.client.startup!
136
+ connection = reporter.connection
137
+ abort('Failed to Initialize Connection please check error logs for details') unless connection
138
+ abort('Failed to Start Client please check error logs for details') unless reporter.client.startup! connection
139
+ end
140
+
141
+ def self.mask_keys hash, key
142
+ value = hash[key]
143
+ redacted_value = Contrast::Configuration::REDACTED if SKIP_LOG.include?(key.to_s)
144
+ puts "#{ key }::#{ redacted_value || value }" unless value.is_a?(Contrast::Config::BaseConfiguration)
145
+ value
137
146
  end
138
147
  end
139
148
  end
@@ -86,13 +86,9 @@ module Contrast
86
86
  end
87
87
  end
88
88
 
89
- # The method const_defined? can cause autoload, which is bad for us. The method autoload? doesn't traverse
90
- # namespaces. This method lets us provide a constant, as a String, and parse it to determine if it has been
91
- # truly truly defined, meaning it existed before this method was invoked, not as a result of it.
89
+ # The method Module.const_defined? can raise an exception if the constant is poorly named. As such, we need to
90
+ # handle the case where that exception is raised.
92
91
  #
93
- # TODO: RUBY-1326
94
- # This is required to handle a bug in Ruby prior to 2.7.0. When we drop support for 2.6.X, we should remove
95
- # this code. https://bugs.ruby-lang.org/issues/10741
96
92
  # @param name [String] the name of the constant to look up
97
93
  # @return [Boolean]
98
94
  def truly_defined? name
@@ -53,7 +53,7 @@ module Contrast
53
53
  ruby_finding = Contrast::Agent::Reporting::Finding.new rule_id
54
54
  ruby_finding.hash_code = hash_code
55
55
  set_new_finding_properties(ruby_finding, user_provided_options, call_location)
56
- Contrast::Agent.reporter&.send_event_immediately(new_preflight)
56
+ Contrast::Agent.reporter&.send_event(new_preflight)
57
57
  Contrast::Agent::Reporting::ReportingStorage[hash_code] = ruby_finding
58
58
  end
59
59
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'socket'
5
5
  require 'contrast/agent/version'
6
+ require 'contrast/logger/aliased_logging'
6
7
 
7
8
  module Contrast
8
9
  module Utils
@@ -37,6 +38,7 @@ module Contrast
37
38
  logger.extend(Contrast::Logger::Application)
38
39
  logger.extend(Contrast::Logger::Request)
39
40
  logger.extend(Contrast::Logger::Time)
41
+ logger.extend(Contrast::Logger::AliasedLogging) if Contrast::Utils::Telemetry.exceptions_enabled?
40
42
  end
41
43
 
42
44
  # Determine the valid path to which to log, given the precedence of config > settings > default.
@@ -66,7 +66,7 @@ module Contrast
66
66
  # if .telemetry file doesn't exist we create one and then show the disclaimer.
67
67
  # if the file already exists we do nothing.
68
68
  def telemetry_disclaimer
69
- return unless Contrast::Agent::Telemetry.enabled?
69
+ return unless Contrast::Agent::Telemetry::Base.enabled?
70
70
  return unless Contrast::Utils::Telemetry.create_telemetry_file
71
71
 
72
72
  logger.info Contrast::Utils::Telemetry.disclaimer
@@ -10,7 +10,6 @@ module Contrast
10
10
  module ObjectShare
11
11
  # Strings
12
12
  ASTERISK = '*'
13
- AMPERSAND = '&'
14
13
  BACK_SLASH = '\\'
15
14
  EMPTY_STRING = ''
16
15
  COLON = ':'
@@ -24,6 +23,7 @@ module Contrast
24
23
  HTTPS_START = 'https:'
25
24
  NEW_LINE = "\n"
26
25
  NIL_STRING = 'nil'
26
+ NIL_64_STRING = 'bmls'
27
27
  PERIOD = '.'
28
28
  POUND_SIGN = '#'
29
29
  QUESTION_MARK = '?'
@@ -1,8 +1,9 @@
1
1
  # Copyright (c) 2022 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/telemetry/telemetry'
4
+ require 'contrast/agent/telemetry/base'
5
5
  require 'contrast/utils/telemetry_identifier'
6
+ require 'contrast/config/env_variables'
6
7
 
7
8
  module Contrast
8
9
  module Utils
@@ -37,7 +38,17 @@ module Contrast
37
38
  @_disclaimer = MESSAGE[:disclaimer]
38
39
  end
39
40
 
41
+ def self.exceptions_enabled?
42
+ # TODO: RUBY-1643 Obfuscate path
43
+ # Enabled this once the masking of stack frames is done.
44
+ # @_exceptions_enabled = telemetry_exceptions_enabled? if @_exceptions_enabled.nil?
45
+ @_exceptions_enabled = false if @_exceptions_enabled.nil?
46
+ @_exceptions_enabled
47
+ end
48
+
40
49
  class << self
50
+ include Contrast::Config::EnvVariables
51
+
41
52
  private
42
53
 
43
54
  # Create the mark file
@@ -48,7 +59,7 @@ module Contrast
48
59
  # @return[Boolean, nil] true if file is created, false if file already exist
49
60
  # and nil if Telemetry is disabled or on unsupported OS.
50
61
  def write_mark_file dir, file, config_dir
51
- return unless Contrast::Agent::Telemetry.enabled?
62
+ return unless Contrast::Agent::Telemetry::Base.enabled?
52
63
  return if Contrast::Utils::OS.windows?
53
64
 
54
65
  @dir = dir
@@ -73,6 +84,13 @@ module Contrast
73
84
  rescue StandardError => _e
74
85
  false
75
86
  end
87
+
88
+ def telemetry_exceptions_enabled?
89
+ opts_out_telemetry = return_value(:telemetry_opt_outs).to_s
90
+ return false if opts_out_telemetry.casecmp?('true') || opts_out_telemetry == '1'
91
+
92
+ true
93
+ end
76
94
  end
77
95
  end
78
96
  end
@@ -13,6 +13,7 @@ module Contrast
13
13
  # This module creates a Net::HTTP client and initiates a connection to the provided result
14
14
  class TelemetryClient < NetHttpBase
15
15
  ENDPOINT = 'api/v1/telemetry/metrics' # /TelemetryEvent.path
16
+ EXCEPTIONS = 'api/v1/telemetry/exceptions' # /TelemetryExceptions::Event.path
16
17
  SERVICE_NAME = 'Telemetry'
17
18
  include Contrast::Components::Logger::InstanceMethods
18
19
  # This method initializes the Net::HTTP client we'll need. it will validate
@@ -28,23 +29,28 @@ module Contrast
28
29
  # This method will be responsible for building the request. Because the telemetry collector expects to receive
29
30
  # multiple events in a single request, we must always wrap the event in an array, even if there is only one.
30
31
  #
31
- # @param event [Contrast::Agent::TelemetryEvent,Contrast::Agent::StartupMetricsTelemetryEvent]
32
+ # @param event [Contrast::Agent::Telemetry::Event, Array<Contrast::Agent::Telemetry::TelemetryException::Event>]
32
33
  # @return [Net::HTTP::Post]
33
34
  def build_request event
34
35
  return unless valid_event? event
35
36
 
36
- string_body = [event.to_hash].to_json
37
+ string_body = if Array(event).all?(Contrast::Agent::Telemetry::TelemetryException::Event)
38
+ event.map(&:to_controlled_hash).flatten!
39
+ else
40
+ [event.to_hash]
41
+ end
42
+
37
43
  header = {
38
44
  'User-Agent' => "<#{ Contrast::Utils::ObjectShare::RUBY }>-<#{ Contrast::Agent::VERSION }>",
39
45
  'Content-Type' => 'application/json'
40
46
  }
41
- request = Net::HTTP::Post.new(build_path(event.path), header)
42
- request.body = string_body
47
+ request = Net::HTTP::Post.new(build_path(event), header)
48
+ request.body = string_body.to_json
43
49
  request
44
50
  end
45
51
 
46
52
  # This method will create the actual request and send it
47
- # @param event[Contrast::Agent::TelemetryEvent]
53
+ # @param event[Contrast::Agent::Telemetry::Event]
48
54
  # @param connection[Net::HTTP]
49
55
  def send_request event, connection
50
56
  return if connection.nil? || event.nil?
@@ -68,10 +74,13 @@ module Contrast
68
74
  end
69
75
 
70
76
  # This method will be responsible for validating the event
71
- # @param event[Contrast::Agent::TelemetryEvent,Contrast::Agent::StartupMetricsTelemetryEvent]
77
+ # @param event[Contrast::Agent::Telemetry::Event,Contrast::Agent::Telemetry::StartupMetricsEvent,
78
+ # array<Contrast::Agent::Telemetry::TelemetryException::Event>]
72
79
  def valid_event? event
73
- return true if event.cs__is_a?(Contrast::Agent::TelemetryEvent)
74
- return true if event.cs__is_a?(Contrast::Agent::StartupMetricsTelemetryEvent)
80
+ return true if event.cs__is_a?(Contrast::Agent::Telemetry::Event)
81
+ return true if event.cs__is_a?(Contrast::Agent::Telemetry::StartupMetricsEvent)
82
+ # Batch
83
+ return true if Array(event).all?(Contrast::Agent::Telemetry::TelemetryException::Event)
75
84
 
76
85
  false
77
86
  end
@@ -81,9 +90,12 @@ module Contrast
81
90
  # The telemetry instance accepts any path to #{ Contrast::Agent::Telemetry::URL }#{ ENDPOINT }, using the
82
91
  # remainder of the path to segregate messages.
83
92
  #
93
+ # @param event [Contrast::Agent::Telemetry::Event, Contrast::Agent::Telemetry::TelemetryException::Event]
84
94
  # @return [String] the fully qualified path to send the request
85
- def build_path event_path
86
- "#{ Contrast::Agent::Telemetry::URL }#{ ENDPOINT }#{ event_path }"
95
+ def build_path event
96
+ endpoint = Array(event).all?(Contrast::Agent::Telemetry::TelemetryException::Event) ? EXCEPTIONS : ENDPOINT
97
+ path = endpoint == EXCEPTIONS ? Contrast::Agent::Telemetry::TelemetryException::Event.path : event.path
98
+ "#{ Contrast::Agent::Telemetry::Base::URL }#{ endpoint }#{ path }"
87
99
  end
88
100
  end
89
101
  end
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/components/logger'
5
+
6
+ module Contrast
7
+ module Utils
8
+ # This is the TelemetryHash, which will take data type, so we can push freely, without worrying about
9
+ # validating the event before that
10
+ class TelemetryHash < Hash
11
+ include Contrast::Components::Logger::InstanceMethods
12
+
13
+ attr_reader :data_type
14
+
15
+ def initialize data_type, *_several_variants
16
+ super()
17
+ @data_type = data_type
18
+ end
19
+
20
+ def []= key, value
21
+ return unless valid_value? value
22
+
23
+ super(key, value)
24
+ end
25
+
26
+ def increment key
27
+ self[key].exceptions[0].increment_occurrences
28
+ :incremented!
29
+ end
30
+
31
+ def valid_value? value
32
+ unless value.cs__is_a?(data_type)
33
+ logger.debug('The following key will be omitted', value: value)
34
+ return false
35
+ end
36
+
37
+ true
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,8 +1,8 @@
1
1
  # Copyright (c) 2022 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/telemetry/telemetry'
5
4
  require 'contrast/utils/os'
5
+ require 'digest'
6
6
  require 'socket'
7
7
 
8
8
  module Contrast
@@ -38,6 +38,21 @@ module Contrast
38
38
  @_mac = find_mac(primary) || find_mac
39
39
  end
40
40
 
41
+ def self.application_id
42
+ @_application_id ||= begin
43
+ id = nil
44
+ mac = Contrast::Utils::Telemetry::Identifier.mac
45
+ app_name = Contrast::Utils::Telemetry::Identifier.app_name
46
+ id = mac + app_name if mac && app_name
47
+ Digest::SHA2.new(256).hexdigest(id || "_#{ SecureRandom.uuid }")
48
+ end
49
+ end
50
+
51
+ def self.instance_id
52
+ @_instance_id ||= Digest::SHA2.new(256).hexdigest(Contrast::Utils::Telemetry::Identifier.mac ||
53
+ "_#{ SecureRandom.uuid }")
54
+ end
55
+
41
56
  class << self
42
57
  private
43
58
 
data/lib/contrast.rb CHANGED
@@ -47,6 +47,9 @@ require 'contrast/components/protect'
47
47
  require 'contrast/components/sampling'
48
48
  require 'contrast/components/scope'
49
49
  require 'contrast/components/settings'
50
+ require 'contrast/utils/telemetry_hash'
51
+ require 'contrast/utils/telemetry'
52
+ require 'contrast/agent/telemetry/events/exceptions/telemetry_exception_event'
50
53
 
51
54
  module Contrast
52
55
  API = Contrast::Components::Api::Interface.new
@@ -62,6 +65,12 @@ module Contrast
62
65
  APP_CONTEXT = Contrast::Components::AppContext::Interface.new
63
66
  end
64
67
 
68
+ module Contrast
69
+ TELEMETRY_EXCEPTIONS = if Contrast::Utils::Telemetry.exceptions_enabled?
70
+ Contrast::Utils::TelemetryHash.new(Contrast::Agent::Telemetry::TelemetryException::Event)
71
+ end
72
+ end
73
+
65
74
  # This needs to be required very early, after component interfaces, and before instrumentation attempts
66
75
  require 'contrast/funchook/funchook'
67
76
 
data/ruby-agent.gemspec CHANGED
@@ -156,7 +156,7 @@ def self.add_files spec
156
156
  end
157
157
 
158
158
  def self.add_metadata spec
159
- spec.metadata['changelog_uri'] = 'https://docs.contrastsecurity.com/release.html'
159
+ spec.metadata['changelog_uri'] = 'https://docs.contrastsecurity.com/en/ruby-agent-release-notes-and-archive.html'
160
160
  spec.metadata['support_uri'] = 'https://support.contrastsecurity.com'
161
161
  spec.metadata['trouble_shooting_uri'] = 'https://support.contrastsecurity.com/hc/en-us/search?utf8=%E2%9C%93&query=Ruby'
162
162
  spec.metadata['wiki_uri'] = 'https://docs.contrastsecurity.com/'
@@ -1 +1 @@
1
- 2.28.19
1
+ 2.28.20