contrast-agent 4.7.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -1
  3. data/.rspec +0 -1
  4. data/.rspec_parallel +6 -0
  5. data/.simplecov +1 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +0 -1
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +0 -2
  8. data/lib/contrast/agent/assess/contrast_event.rb +1 -5
  9. data/lib/contrast/agent/assess/finalizers/hash.rb +2 -5
  10. data/lib/contrast/agent/assess/policy/patcher.rb +5 -4
  11. data/lib/contrast/agent/assess/policy/policy.rb +1 -1
  12. data/lib/contrast/agent/assess/policy/policy_scanner.rb +2 -6
  13. data/lib/contrast/agent/assess/policy/preshift.rb +11 -8
  14. data/lib/contrast/agent/assess/policy/propagation_method.rb +102 -59
  15. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -7
  16. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  17. data/lib/contrast/agent/assess/policy/propagator/rack_protection.rb +73 -0
  18. data/lib/contrast/agent/assess/policy/propagator/split.rb +10 -6
  19. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +3 -3
  20. data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
  21. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +6 -7
  22. data/lib/contrast/agent/assess/policy/source_method.rb +18 -22
  23. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -4
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +61 -86
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +1 -1
  26. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  27. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -4
  28. data/lib/contrast/agent/at_exit_hook.rb +3 -3
  29. data/lib/contrast/agent/class_reopener.rb +6 -5
  30. data/lib/contrast/agent/disable_reaction.rb +4 -5
  31. data/lib/contrast/agent/exclusion_matcher.rb +2 -7
  32. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  33. data/lib/contrast/agent/inventory/dependency_analysis.rb +2 -6
  34. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +8 -9
  35. data/lib/contrast/agent/inventory/policy/datastores.rb +5 -6
  36. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  37. data/lib/contrast/agent/middleware.rb +15 -13
  38. data/lib/contrast/agent/patching/policy/after_load_patch.rb +6 -3
  39. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +21 -16
  40. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  41. data/lib/contrast/agent/patching/policy/patch.rb +13 -8
  42. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  43. data/lib/contrast/agent/patching/policy/patcher.rb +14 -14
  44. data/lib/contrast/agent/patching/policy/policy.rb +2 -4
  45. data/lib/contrast/agent/patching/policy/policy_node.rb +2 -3
  46. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  47. data/lib/contrast/agent/protect/policy/policy.rb +1 -1
  48. data/lib/contrast/agent/protect/policy/rule_applicator.rb +3 -5
  49. data/lib/contrast/agent/protect/rule/base.rb +10 -10
  50. data/lib/contrast/agent/protect/rule/cmd_injection.rb +4 -5
  51. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  52. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -5
  53. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  54. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  55. data/lib/contrast/agent/reaction_processor.rb +3 -4
  56. data/lib/contrast/agent/request.rb +9 -5
  57. data/lib/contrast/agent/request_context.rb +28 -31
  58. data/lib/contrast/agent/request_handler.rb +5 -3
  59. data/lib/contrast/agent/response.rb +2 -3
  60. data/lib/contrast/agent/rewriter.rb +4 -3
  61. data/lib/contrast/agent/rule_set.rb +5 -4
  62. data/lib/contrast/agent/service_heartbeat.rb +2 -3
  63. data/lib/contrast/agent/static_analysis.rb +7 -6
  64. data/lib/contrast/agent/thread.rb +2 -4
  65. data/lib/contrast/agent/thread_watcher.rb +3 -4
  66. data/lib/contrast/agent/tracepoint_hook.rb +10 -5
  67. data/lib/contrast/agent/version.rb +1 -1
  68. data/lib/contrast/api/communication/messaging_queue.rb +16 -11
  69. data/lib/contrast/api/communication/response_processor.rb +11 -11
  70. data/lib/contrast/api/communication/service_lifecycle.rb +9 -5
  71. data/lib/contrast/api/communication/socket_client.rb +18 -14
  72. data/lib/contrast/api/communication/speedracer.rb +5 -6
  73. data/lib/contrast/api/decorators/address.rb +2 -3
  74. data/lib/contrast/api/decorators/agent_startup.rb +7 -9
  75. data/lib/contrast/api/decorators/application_startup.rb +9 -10
  76. data/lib/contrast/api/decorators/application_update.rb +0 -4
  77. data/lib/contrast/api/decorators/http_request.rb +3 -7
  78. data/lib/contrast/api/decorators/instrumentation_mode.rb +3 -5
  79. data/lib/contrast/api/decorators/message.rb +7 -7
  80. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  81. data/lib/contrast/api/decorators/trace_event_object.rb +2 -3
  82. data/lib/contrast/components/agent.rb +13 -15
  83. data/lib/contrast/components/app_context.rb +7 -11
  84. data/lib/contrast/components/assess.rb +19 -16
  85. data/lib/contrast/components/base.rb +40 -0
  86. data/lib/contrast/components/config.rb +1 -2
  87. data/lib/contrast/components/contrast_service.rb +8 -11
  88. data/lib/contrast/components/heap_dump.rb +5 -4
  89. data/lib/contrast/components/inventory.rb +2 -7
  90. data/lib/contrast/components/logger.rb +14 -10
  91. data/lib/contrast/components/protect.rb +10 -13
  92. data/lib/contrast/components/sampling.rb +5 -5
  93. data/lib/contrast/components/scope.rb +9 -32
  94. data/lib/contrast/components/settings.rb +1 -5
  95. data/lib/contrast/config/base_configuration.rb +14 -6
  96. data/lib/contrast/configuration.rb +22 -19
  97. data/lib/contrast/extension/assess/array.rb +3 -15
  98. data/lib/contrast/extension/assess/eval_trigger.rb +2 -23
  99. data/lib/contrast/extension/assess/fiber.rb +6 -16
  100. data/lib/contrast/extension/assess/hash.rb +3 -13
  101. data/lib/contrast/extension/assess/kernel.rb +3 -14
  102. data/lib/contrast/extension/assess/marshal.rb +6 -14
  103. data/lib/contrast/extension/assess/regexp.rb +5 -15
  104. data/lib/contrast/extension/assess/string.rb +6 -31
  105. data/lib/contrast/extension/extension.rb +61 -0
  106. data/lib/contrast/extension/kernel.rb +2 -4
  107. data/lib/contrast/extension/protect/kernel.rb +0 -15
  108. data/lib/contrast/framework/grape/support.rb +174 -0
  109. data/lib/contrast/framework/manager.rb +44 -9
  110. data/lib/contrast/framework/rack/patch/session_cookie.rb +6 -6
  111. data/lib/contrast/framework/rack/support.rb +1 -1
  112. data/lib/contrast/framework/rails/patch/assess_configuration.rb +5 -8
  113. data/lib/contrast/framework/rails/patch/support.rb +44 -37
  114. data/lib/contrast/framework/rails/railtie.rb +34 -0
  115. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +4 -4
  116. data/lib/contrast/framework/rails/support.rb +60 -13
  117. data/lib/contrast/framework/sinatra/support.rb +1 -1
  118. data/lib/contrast/funchook/funchook.rb +4 -3
  119. data/lib/contrast/logger/application.rb +1 -6
  120. data/lib/contrast/logger/log.rb +103 -13
  121. data/lib/contrast/logger/request.rb +0 -4
  122. data/lib/contrast/tasks/config.rb +0 -1
  123. data/lib/contrast/tasks/service.rb +1 -6
  124. data/lib/contrast/utils/assess/sampling_util.rb +2 -3
  125. data/lib/contrast/utils/assess/tracking_util.rb +2 -4
  126. data/lib/contrast/utils/heap_dump_util.rb +5 -3
  127. data/lib/contrast/utils/invalid_configuration_util.rb +4 -3
  128. data/lib/contrast/utils/io_util.rb +3 -5
  129. data/lib/contrast/utils/job_servers_running.rb +4 -3
  130. data/lib/contrast/utils/os.rb +2 -3
  131. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  132. data/lib/contrast/utils/string_utils.rb +2 -3
  133. data/lib/contrast/utils/tag_util.rb +26 -19
  134. data/lib/contrast.rb +24 -14
  135. data/resources/assess/policy.json +252 -2
  136. data/resources/deadzone/policy.json +10 -0
  137. data/ruby-agent.gemspec +14 -3
  138. data/service_executables/VERSION +1 -1
  139. data/service_executables/linux/contrast-service +0 -0
  140. data/service_executables/mac/contrast-service +0 -0
  141. metadata +104 -24
  142. data/lib/contrast/agent/railtie.rb +0 -31
  143. data/lib/contrast/components/interface.rb +0 -196
  144. data/lib/contrast/delegators/input_analysis.rb +0 -12
  145. data/lib/contrast/utils/inventory_util.rb +0 -114
@@ -1,7 +1,8 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
4
+ require 'contrast/components/logger'
5
+ require 'contrast/components/scope'
5
6
 
6
7
  module Contrast
7
8
  module Framework
@@ -11,9 +12,8 @@ module Contrast
11
12
  # runtime detection of insecure configurations on individual cookies
12
13
  # within the application
13
14
  class SessionCookie
14
- include Contrast::Components::Interface
15
-
16
- access_component :agent, :analysis, :logging, :scope
15
+ extend Contrast::Components::Logger::InstanceMethods
16
+ extend Contrast::Components::Scope::InstanceMethods
17
17
 
18
18
  CS__SECURE_RULE_NAME = 'secure-flag-missing'
19
19
  CS__HTTPONLY_NAME = 'rails-http-only-disabled'
@@ -36,8 +36,8 @@ module Contrast
36
36
  end
37
37
 
38
38
  def analyze options
39
- return unless AGENT.enabled?
40
- return if ASSESS.forcibly_disabled?
39
+ return unless ::Contrast::AGENT.enabled?
40
+ return if ::Contrast::ASSESS.forcibly_disabled?
41
41
 
42
42
  apply_session_timeout(options)
43
43
  apply_httponly(options)
@@ -14,7 +14,7 @@ module Contrast
14
14
  extend Contrast::Framework::Rack::Patch::Support
15
15
  class << self
16
16
  def detection_class
17
- 'don\'t let me be detected'
17
+ 'rack -- don\'t let me be detected'
18
18
  end
19
19
  end
20
20
  end
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
5
4
  require 'contrast/utils/invalid_configuration_util'
6
5
 
7
6
  module Contrast
@@ -10,9 +9,7 @@ module Contrast
10
9
  module Patch
11
10
  # This module is used to analyze rails session storage configuration for assess vulnerabilities
12
11
  module AssessConfiguration
13
- include Contrast::Components::Interface
14
-
15
- access_component :agent, :analysis, :logging
12
+ include Contrast::Components::Logger::InstanceMethods
16
13
 
17
14
  CS__SESSION_TIMEOUT_NAME = 'session-timeout'
18
15
  SAFE_SESSION_TIMEOUT = (30 * 60 * 1000)
@@ -23,7 +20,7 @@ module Contrast
23
20
  include Contrast::Utils::InvalidConfigurationUtil
24
21
 
25
22
  def analyze_session_store *args
26
- return if ASSESS.forcibly_disabled?
23
+ return if ::Contrast::ASSESS.forcibly_disabled?
27
24
 
28
25
  apply_httponly_disabled(*args)
29
26
  apply_secure_cookie_disabled(*args)
@@ -52,7 +49,7 @@ module Contrast
52
49
  end
53
50
 
54
51
  def apply_session_timeout *args
55
- return if ASSESS.rule_disabled? CS__SESSION_TIMEOUT_NAME
52
+ return if ::Contrast::ASSESS.rule_disabled? CS__SESSION_TIMEOUT_NAME
56
53
  return unless vulnerable_setting?(:expire_after, SAFE_SESSION_TIMEOUT, args,
57
54
  comparison_type: :greater_than, safe_default: false)
58
55
 
@@ -67,7 +64,7 @@ module Contrast
67
64
  end
68
65
 
69
66
  def apply_secure_cookie_disabled *args
70
- return if ASSESS.rule_disabled? CS__SECURE_RULE_NAME
67
+ return if ::Contrast::ASSESS.rule_disabled? CS__SECURE_RULE_NAME
71
68
  return unless vulnerable_setting?(:secure, true, args)
72
69
 
73
70
  rails_session_settings = args[1]
@@ -81,7 +78,7 @@ module Contrast
81
78
  end
82
79
 
83
80
  def apply_httponly_disabled *args
84
- return if ASSESS.rule_disabled? CS__HTTPONLY_RULE_NAME
81
+ return if ::Contrast::ASSESS.rule_disabled? CS__HTTPONLY_RULE_NAME
85
82
  return unless vulnerable_setting?(:httponly, true, args)
86
83
 
87
84
  rails_session_settings = args[1]
@@ -20,47 +20,54 @@ module Contrast
20
20
  # (i.e., where we normally patch) we will miss the configuration
21
21
  # and will never be able to report session misconfiguration rules.
22
22
  Contrast::Framework::Rails::Patch::RailsApplicationConfiguration.instrument
23
- require 'contrast/agent/railtie' if ::Rails::VERSION::MAJOR.to_i >= 3
23
+ require 'contrast/framework/rails/railtie' if ::Rails::VERSION::MAJOR.to_i >= 3
24
24
  end
25
25
 
26
26
  # (See BaseSupport#after_load_patches)
27
27
  def after_load_patches
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: 'Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer'),
33
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
34
- 'Rails::Application::Configuration',
35
- 'contrast/framework/rails/patch/rails_application_configuration',
36
- method_to_instrument: :session_store,
37
- instrumenting_module: 'Contrast::Framework::Rails::Patch::RailsApplicationConfiguration'),
38
-
39
- # TODO: RUBY-714 remove w/ EOL of 2.5
40
- #
41
- # @deprecated Everything past here is used for Rewriting and can
42
- # be removed once we no longer support 2.5.
43
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
44
- 'ActionController::Railties::Helper::ClassMethods',
45
- 'contrast/framework/rails/rewrite/action_controller_railties_helper_inherited',
46
- method_to_instrument: :inherited,
47
- instrumenting_module:
48
- 'Contrast::Framework::Rails::Rewrite::ActionControllerRailtiesHelperInherited'),
49
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
50
- 'ActiveRecord::AttributeMethods::Read::ClassMethods',
51
- 'contrast/framework/rails/rewrite/active_record_attribute_methods_read',
52
- instrumenting_module:
53
- 'Contrast::Framework::Rails::Rewrite::ActiveRecordAttributeMethodsRead'),
54
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
55
- 'ActiveRecord::Scoping::Named::ClassMethods',
56
- 'contrast/framework/rails/rewrite/active_record_named',
57
- instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordNamed'),
58
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
59
- 'ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods',
60
- 'contrast/framework/rails/rewrite/active_record_time_zone_inherited',
61
- method_to_instrument: :inherited,
62
- instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordTimeZoneInherited')
63
- ])
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
+ if RUBY_VERSION < '2.6.0'
42
+ patches.merge([
43
+ # TODO: RUBY-714 remove w/ EOL of 2.5
44
+ #
45
+ # @deprecated Everything past here is used for Rewriting and can
46
+ # be removed once we no longer support 2.5.
47
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
48
+ 'ActionController::Railties::Helper::ClassMethods',
49
+ 'contrast/framework/rails/rewrite/action_controller_railties_helper_inherited',
50
+ method_to_instrument: :inherited,
51
+ instrumenting_module:
52
+ 'Contrast::Framework::Rails::Rewrite::ActionControllerRailtiesHelperInherited'),
53
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
54
+ 'ActiveRecord::AttributeMethods::Read::ClassMethods',
55
+ 'contrast/framework/rails/rewrite/active_record_attribute_methods_read',
56
+ instrumenting_module:
57
+ 'Contrast::Framework::Rails::Rewrite::ActiveRecordAttributeMethodsRead'),
58
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
59
+ 'ActiveRecord::Scoping::Named::ClassMethods',
60
+ 'contrast/framework/rails/rewrite/active_record_named',
61
+ instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordNamed'),
62
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
63
+ 'ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods',
64
+ 'contrast/framework/rails/rewrite/active_record_time_zone_inherited',
65
+ method_to_instrument: :inherited,
66
+ instrumenting_module:
67
+ 'Contrast::Framework::Rails::Rewrite::ActiveRecordTimeZoneInherited')
68
+ ])
69
+ end
70
+ patches
64
71
  end
65
72
  end
66
73
  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/job_servers_running'
5
+ require 'contrast/components/logger'
6
+
7
+ module Contrast
8
+ module Framework
9
+ module Rails
10
+ # A Railtie to allow for the automatic hooking of the Agent into a Rails application.
11
+ class Railtie < ::Rails::Railtie
12
+ include Contrast::Components::Logger::InstanceMethods
13
+
14
+ initializer 'Contrast Ruby Agent Initializer' do |app|
15
+ log_rails = defined?(Rails) && defined?(Rails.logger)
16
+
17
+ Rails.logger.debug("In railtie ::#{ app.middleware.inspect }") if log_rails
18
+
19
+ if ::Contrast::APP_CONTEXT.instrument_middleware_stack?
20
+ ::Contrast::AGENT.insert_middleware(app)
21
+ else
22
+ Rails.logger.debug('Detected a running job server, skipping Contrast middleware insertion.') if log_rails
23
+ logger.debug('Disabling Contrast for process', p_id: Process.pid)
24
+ end
25
+ end
26
+
27
+ rake_tasks do
28
+ load 'contrast/tasks/service.rb'
29
+ load 'contrast/tasks/config.rb'
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -3,7 +3,7 @@
3
3
 
4
4
  return unless RUBY_VERSION < '2.6.0' # TODO: RUBY-714 remove guard w/ EOL of 2.5
5
5
 
6
- require 'contrast/components/interface'
6
+ require 'contrast/components/logger'
7
7
 
8
8
  module Contrast
9
9
  module Framework
@@ -17,12 +17,12 @@ module Contrast
17
17
  # @deprecated Changes to this class are discouraged as this approach is
18
18
  # being phased out with support for those language versions.
19
19
  class ActiveRecordNamed
20
- include Contrast::Components::Interface
21
- access_component :agent, :logging
20
+ include Contrast::Components::Logger::InstanceMethods
21
+ extend Contrast::Components::Logger::InstanceMethods
22
22
 
23
23
  class << self
24
24
  def rewrite mod, method_name, body
25
- return body unless AGENT.rewrite_interpolation?
25
+ return body unless ::Contrast::AGENT.rewrite_interpolation?
26
26
  return body unless body.is_a?(Proc)
27
27
 
28
28
  location = body.source_location
@@ -13,6 +13,8 @@ module Contrast
13
13
  class Support
14
14
  extend Contrast::Framework::BaseSupport
15
15
  extend Contrast::Framework::Rails::Patch::Support
16
+ include Contrast::Components::Logger::InstanceMethods
17
+ extend Contrast::Components::Logger::InstanceMethods
16
18
 
17
19
  class << self
18
20
  RAILS_MODULE_NAME_VERSION = Gem::Version.new('6.0.0')
@@ -49,31 +51,36 @@ module Contrast
49
51
  # Find the current route, based on the provided Request wrapper
50
52
  #
51
53
  # @param request[Contrast::Agent::Request]
52
- # @return [Contrast::Api::Dtm::RouteCoverage]
54
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil]
53
55
  def current_route request
54
56
  return unless ::Rails.cs__respond_to?(:application)
55
57
 
58
+ # ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
56
59
  match, _params, route, path = get_full_route(request.rack_request)
60
+ unless route
61
+ logger.warn('Unable to determine the current route of this request')
62
+ return
63
+ end
57
64
 
58
65
  original_url = request.rack_request.path_info
59
-
66
+ mounted_app = route&.app&.app
60
67
  # Route is either the final rails route, or a router that points to a Sinatra controller.
61
- if Contrast::Framework::Sinatra::Support.sinatra_controller?(route.app.app)
62
- # Create a request copied from current request, but with the base path removed from path_info.
63
- new_req = ::ActionDispatch::Request.new(request.env)
64
- new_req.path_info = new_req.path_info.gsub((path << match).join, '')
65
-
66
- return Contrast::Framework::Sinatra::Support.current_route(new_req, route.app.app, original_url)
68
+ if mounted_app && Contrast::Framework::Sinatra::Support.sinatra_controller?(mounted_app)
69
+ return mounted_sinatra_route(request, match, path, route, original_url)
70
+ end
71
+ if mounted_app && Contrast::Framework::Grape::Support.grape_controller?(mounted_app)
72
+ return mounted_grape_route(request, match, path, route, original_url)
67
73
  end
68
74
 
69
75
  Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route, original_url)
70
- rescue StandardError => _e
76
+ rescue StandardError => e
77
+ logger.warn('Unable to determine the current route of this request', e)
71
78
  nil
72
79
  end
73
80
 
74
81
  # Copy a request for modification.
75
82
  #
76
- # @param [::ActionDispatch::Request] original env.
83
+ # @param env [::ActionDispatch::Request] original env.
77
84
  # @return [::ActionDispatch::Request] a copy of original env with rails env merged.
78
85
  def retrieve_request env
79
86
  rails_env = ::Rails.application.env_config.merge(env)
@@ -91,18 +98,21 @@ module Contrast
91
98
 
92
99
  # Determine if route is a Rails engine route.
93
100
  #
94
- # @param [Object] app or route that points to a ::Rails::Engine
101
+ # @param route [Object] app or route that points to a ::Rails::Engine
95
102
  # @return [bool] whether the router is an engine or not.
96
103
  def engine_route? route
104
+ return false unless route&.app&.app
105
+
97
106
  route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) && route.app.app < ::Rails::Engine
98
107
  end
99
108
 
100
109
  # Recursively get final route traversing engines as required.
101
110
  #
102
111
  # @param request [::Rack::Request] the rack request as will be handed to rails controller.
103
- # @param top_router [::ActionDispatch::Journer::Router] the current router relative to the previous.
112
+ # @param top_router [::ActionDispatch::Journey::Router] the current router relative to the previous.
104
113
  # @param path [Array<String>] the chunks of path that have been seen.
105
- # @return [Array<array>] the final set of rails route classes.
114
+ # @return [Array<Object>] the final set of rails route classes.
115
+ # ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
106
116
  def get_full_route request, top_router = ::Rails.application.routes.router, path = []
107
117
  return if (route_matches = top_router.send(:find_routes, request)).empty?
108
118
 
@@ -132,6 +142,43 @@ module Contrast
132
142
  end
133
143
  route_list
134
144
  end
145
+
146
+ # @param request[Contrast::Agent::Request]
147
+ # @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
148
+ # @param path [Array<String>] the path of this request, built out from each nested
149
+ # ActionDispatch::Journey::Path::Pattern::MatchData
150
+ # @param route [::ActionDispatch::Journey::Route]
151
+ # @param original_url [String] the full url of this request, including the mount
152
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil]
153
+ def mounted_sinatra_route request, match, path, route, original_url
154
+ new_req = unmounted_route(request, match, path)
155
+ Contrast::Framework::Sinatra::Support.current_route(new_req, route.app.app, original_url)
156
+ end
157
+
158
+ # @param request[Contrast::Agent::Request]
159
+ # @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
160
+ # @param path [Array<String>] the path of this request, built out from each nested
161
+ # ActionDispatch::Journey::Path::Pattern::MatchData
162
+ # @param route [::ActionDispatch::Journey::Route]
163
+ # @param original_url [String] the full url of this request, including the mount
164
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil]
165
+ def mounted_grape_route request, match, path, route, original_url
166
+ new_req = unmounted_route(request, match, path)
167
+ Contrast::Framework::Grape::Support.current_route(new_req, route.app.app, original_url)
168
+ end
169
+
170
+ # Create a request copied from current request, but with the base path removed from path_info, as that's
171
+ # the mount.
172
+ #
173
+ # @param request[Contrast::Agent::Request]
174
+ # @param match []
175
+ # @param path [String] the path of this request
176
+ # @return [::ActionDispatch::Request]
177
+ def unmounted_route request, match, path
178
+ new_req = ::ActionDispatch::Request.new(request.env)
179
+ new_req.path_info = new_req.path_info.gsub((path << match).join, '')
180
+ new_req
181
+ end
135
182
  end
136
183
  end
137
184
  end
@@ -106,7 +106,7 @@ module Contrast
106
106
  def _route_recurse controller, method, route
107
107
  return if controller.nil? || controller.cs__class == NilClass
108
108
 
109
- route_patterns = controller.routes.fetch(method, []).map(&:first)
109
+ route_patterns = controller.routes.fetch(method) { [] }.map(&:first)
110
110
  route_pattern = route_patterns&.find do |matcher|
111
111
  matcher.params(route) # ::Mustermann::Sinatra match.
112
112
  end
@@ -1,11 +1,12 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
4
+ require 'contrast/components/logger'
5
+
5
6
  # This module is used to find funchook library and determine availability
6
7
  module Funchook
7
- include Contrast::Components::Interface
8
- access_component :logging
8
+ extend Contrast::Components::Logger::InstanceMethods
9
+
9
10
  attr_accessor :path
10
11
 
11
12
  # Possible platform library files
@@ -1,16 +1,11 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
5
-
6
4
  module Contrast
7
5
  module Logger
8
6
  # Our decorator for the Ougai logger allowing for the logging of the
9
7
  # application environment, used to provide context during troubleshooting.
10
8
  module Application
11
- include Contrast::Components::Interface
12
- access_component :config
13
-
14
9
  ENV_KEYS = %w[HOME PWD RACK_ENV RAILS_ENV RUBY_VERSION GEM_HOME GEM_PATH].cs__freeze
15
10
  # Utility method to log some current ruby and rails information from environment
16
11
  def application_environment
@@ -31,7 +26,7 @@ module Contrast
31
26
  def application_configuration
32
27
  return unless info?
33
28
 
34
- loggable = CONFIG.loggable
29
+ loggable = ::Contrast::CONFIG.loggable
35
30
  info('Current configuration', configuration: loggable)
36
31
  env_keys = ENV.keys.select do |env_key|
37
32
  env_key&.to_s&.start_with?(Contrast::Components::Config::CONTRAST_ENV_MARKER)
@@ -5,21 +5,106 @@ require 'logger'
5
5
  require 'ougai'
6
6
  require 'singleton'
7
7
 
8
- require 'contrast/components/interface'
9
8
  require 'contrast/extension/module'
10
9
  require 'contrast/logger/application'
11
10
  require 'contrast/logger/format'
12
11
  require 'contrast/logger/request'
13
12
  require 'contrast/logger/time'
13
+ require 'contrast/components/config'
14
14
 
15
15
  module Contrast
16
+ # This module allows us to dynamically weave timing into our code, so that only when the time is actually needed do
17
+ # we pay the penalty for that timing block
18
+ module TraceTiming
19
+ def methods_to_time
20
+ @_methods_to_time ||= []
21
+ end
22
+
23
+ # Store info about methods for later patching.
24
+ METHOD_INFO = Struct.new(:clazz, :method_name, :custom_msg, :aliased)
25
+
26
+ # Add a method to the list of methods to be trace timed if logger set to TRACE. Enables trace timing after if
27
+ # logger set to TRACE.
28
+ #
29
+ # @params: clazz [Class] the class of the method to time.
30
+ # @params: method [Symbol] the method to time.
31
+ # @params: method [String] optional custom logging message.
32
+ def add_method_to_trace_timing clazz, method, msg = nil
33
+ methods_to_time.append(METHOD_INFO.new(clazz, method, msg, false))
34
+ enable_trace_timing if logger.level == ::Ougai::Logging::TRACE
35
+ end
36
+
37
+ # Add a method to the list of methods to be trace timed if logger set to TRACE. Enables trace timing after if
38
+ # logger set to TRACE.
39
+ #
40
+ # @params: method_spec [METHOD_INFO] specs about the method to be timed.
41
+ # @params: class_method [Boolean] whether this is or isn't a class/module method.
42
+ def trace_time_class_method meth_spec, class_method
43
+ untimed_func_symbol = "untimed_#{ meth_spec.method_name }".to_sym
44
+ send_to = class_method ? meth_spec.clazz.cs__singleton_class : meth_spec.clazz
45
+ meth_spec.clazz.class_eval do
46
+ include Contrast::Components::Logger::InstanceMethods
47
+ extend Contrast::Components::Logger::InstanceMethods
48
+
49
+ send_to.send(:alias_method, untimed_func_symbol, meth_spec.method_name)
50
+ meth_spec.aliased = true
51
+
52
+ log_message = "Elapsed time for #{ meth_spec.method_name }."
53
+ log_message = meth_spec.custom_message if meth_spec.custom_msg
54
+
55
+ send_to.send(:define_method, meth_spec.method_name) do |*args, **kwargs, &block| # rubocop:disable Performance/Kernel/DefineMethod
56
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
+ rv = if kwargs.empty?
58
+ send(untimed_func_symbol, *args, &block)
59
+ else
60
+ send(untimed_func_symbol, *args, **kwargs, &block)
61
+ end
62
+ delta = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
63
+ logger.trace(log_message, elapsed: delta * 1000)
64
+ rv
65
+ end
66
+ end
67
+ end
68
+
69
+ # Enable trace timing of methods specified in @_methods_to_time via aliasing.
70
+ def enable_trace_timing
71
+ methods_to_time.each do |meth_spec|
72
+ next if meth_spec.aliased
73
+
74
+ is_class_method = meth_spec.clazz.singleton_methods(false).include?(meth_spec.method_name)
75
+ trace_time_class_method meth_spec, is_class_method
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ module Contrast
82
+ # Used as a wrapper around our logging. The module option specifically adds in a new method for error that raises the
83
+ # logged exception, used in testing so that we can see if anything unexpected happens without it being swallowed
84
+ # while still providing safe options for customers.
16
85
  module Logger
17
- # This class functions to serve as a wrapper around our logging, as we need
18
- # to be able to dynamically update level based on updates to TeamServer.
86
+ # For development set following env var to raise logged exceptions instead of just logging.
87
+ if ENV['CONTRAST__AGENT__RUBY_MORE_COWBELL']
88
+ Ougai::Logger.class_eval do
89
+ alias_method :cs__error, :error
90
+ alias_method :cs__warn, :warn
91
+
92
+ def error *args, **kwargs
93
+ if kwargs.empty?
94
+ cs__error(*args)
95
+ else
96
+ cs__error(*args, **kwargs)
97
+ end
98
+ args.each { |arg| raise arg if arg && arg.cs__class < Exception }
99
+ end
100
+ end
101
+ end
102
+
103
+ # This class functions to serve as a wrapper around our logging, as we need to be able to dynamically update
104
+ # level based on updates to TeamServer.
19
105
  class Log
20
106
  include Singleton
21
- include Contrast::Components::Interface
22
- access_component :config
107
+ include ::Contrast::TraceTiming
23
108
 
24
109
  DEFAULT_NAME = 'contrast.log'
25
110
  DEFAULT_LEVEL = ::Ougai::Logging::Severity::INFO
@@ -33,8 +118,8 @@ module Contrast
33
118
  update
34
119
  end
35
120
 
36
- # Given new settings from TeamServer, update our logging to use the new
37
- # file and level, assuming they weren't set by local configuration.
121
+ # Given new settings from TeamServer, update our logging to use the new file and level, assuming they weren't
122
+ # set by local configuration.
38
123
  #
39
124
  # @param log_file [String] the file to which to log, as provided by TeamServer settings
40
125
  # @param log_level [String] the level at which to log, as provided by TeamServer settings
@@ -51,6 +136,8 @@ module Contrast
51
136
  @previous_path = current_path
52
137
  @previous_level = current_level_const
53
138
 
139
+ enable_trace_timing if current_level_const == ::Ougai::Logging::TRACE
140
+
54
141
  @_logger = build(path: current_path, level_const: current_level_const)
55
142
  # If we're logging to a new path, then let's start it w/ our helpful
56
143
  # data gathering messages
@@ -60,6 +147,9 @@ module Contrast
60
147
  logger.error('Unable to process update to LoggerManager.', e)
61
148
  else
62
149
  puts 'Unable to process update to LoggerManager.'
150
+ raise e if ENV['CONTRAST__AGENT__RUBY_MORE_COWBELL']
151
+
152
+ puts e.message
63
153
  puts e.backtrace.join("\n")
64
154
  end
65
155
  end
@@ -107,8 +197,8 @@ module Contrast
107
197
  # TeamServer.
108
198
  # @return [String] the path to which to log or STDOUT / STDERR if one of those values provided.
109
199
  def find_valid_path log_file
110
- config = CONFIG.root.agent.logger
111
- config_path = config.path&.length.to_i.positive? ? config.path : nil
200
+ config = ::Contrast::CONFIG.root.agent.logger
201
+ config_path = config&.path&.length.to_i.positive? ? config.path : nil
112
202
  valid_path(config_path || log_file)
113
203
  end
114
204
 
@@ -141,8 +231,9 @@ module Contrast
141
231
  # TeamServer.
142
232
  # @return [::Ougai::Logging::Severity] the level at which to log
143
233
  def find_valid_level log_level
144
- config = CONFIG.root.agent.logger
145
- config_level = config.level&.length&.positive? ? config.level : nil
234
+ config = ::Contrast::CONFIG.root.agent.logger
235
+ config_level = config&.level&.length&.positive? ? config.level : nil
236
+
146
237
  valid_level(config_level || log_level)
147
238
  end
148
239
 
@@ -158,8 +249,7 @@ module Contrast
158
249
  DEFAULT_LEVEL
159
250
  end
160
251
 
161
- # Log that the Agent log has changed and include some default information
162
- # at the start of the log.
252
+ # Log that the Agent log has changed and include some default information at the start of the log.
163
253
  def log_update
164
254
  logger.debug('Initialized new contrast agent logger')
165
255
  logger.debug_with_time('middleware: log environment') do