contrast-agent 3.9.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.simplecov +5 -2
- data/ext/build_funchook.rb +14 -9
- data/ext/cs__assess_active_record_named/cs__active_record_named.c +5 -12
- data/ext/cs__assess_active_record_named/extconf.rb +3 -0
- data/ext/cs__assess_array/cs__assess_array.c +3 -5
- data/ext/cs__assess_array/extconf.rb +3 -0
- data/ext/cs__assess_basic_object/cs__assess_basic_object.c +10 -4
- data/ext/cs__assess_basic_object/extconf.rb +3 -0
- data/ext/cs__assess_fiber_track/cs__assess_fiber_track.c +4 -3
- data/ext/cs__assess_fiber_track/cs__assess_fiber_track.h +3 -3
- data/ext/cs__assess_fiber_track/extconf.rb +3 -0
- data/ext/cs__assess_hash/cs__assess_hash.c +40 -17
- data/ext/cs__assess_hash/cs__assess_hash.h +4 -6
- data/ext/cs__assess_hash/extconf.rb +3 -0
- data/ext/cs__assess_kernel/cs__assess_kernel.c +10 -8
- data/ext/cs__assess_kernel/cs__assess_kernel.h +1 -0
- data/ext/cs__assess_kernel/extconf.rb +3 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +3 -6
- data/ext/cs__assess_marshal_module/extconf.rb +3 -0
- data/ext/cs__assess_module/cs__assess_module.c +13 -9
- data/ext/cs__assess_module/extconf.rb +3 -0
- data/ext/cs__assess_regexp/cs__assess_regexp.c +13 -9
- data/ext/cs__assess_regexp/cs__assess_regexp.h +1 -0
- data/ext/cs__assess_regexp/extconf.rb +3 -0
- data/ext/cs__assess_string/cs__assess_string.c +5 -8
- data/ext/cs__assess_string/cs__assess_string.h +2 -1
- data/ext/cs__assess_string/extconf.rb +3 -0
- data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.c +2 -2
- data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.h +3 -3
- data/ext/cs__assess_string_interpolation26/extconf.rb +3 -0
- data/ext/cs__assess_yield_track/cs__assess_yield_track.h +1 -1
- data/ext/cs__assess_yield_track/extconf.rb +3 -0
- data/ext/cs__common/cs__common.c +79 -0
- data/ext/cs__common/cs__common.h +34 -0
- data/ext/cs__common/extconf.rb +9 -8
- data/ext/cs__contrast_patch/cs__contrast_patch.h +1 -6
- data/ext/cs__contrast_patch/extconf.rb +3 -0
- data/ext/cs__protect_kernel/cs__protect_kernel.c +20 -11
- data/ext/cs__protect_kernel/extconf.rb +3 -0
- data/ext/extconf_common.rb +10 -8
- data/funchook/autom4te.cache/output.0 +1 -13
- data/funchook/autom4te.cache/requests +44 -45
- data/funchook/autom4te.cache/traces.0 +0 -3
- data/funchook/config.log +378 -217
- data/funchook/config.status +23 -24
- data/funchook/configure +1 -13
- data/funchook/src/Makefile +7 -7
- data/funchook/src/config.h +2 -2
- data/funchook/src/decoder.o +0 -0
- data/funchook/src/distorm.o +0 -0
- data/funchook/src/funchook.o +0 -0
- data/funchook/src/funchook_io.o +0 -0
- data/funchook/src/funchook_syscall.o +0 -0
- data/funchook/src/funchook_unix.o +0 -0
- data/funchook/src/funchook_x86.o +0 -0
- data/funchook/src/instructions.o +0 -0
- data/funchook/src/insts.o +0 -0
- data/funchook/src/libfunchook.dylib +0 -0
- data/funchook/src/mnemonics.o +0 -0
- data/funchook/src/operands.o +0 -0
- data/funchook/src/os_func.o +0 -0
- data/funchook/src/os_func_unix.o +0 -0
- data/funchook/src/prefix.o +0 -0
- data/funchook/src/printf_base.o +0 -0
- data/funchook/src/textdefs.o +0 -0
- data/funchook/src/wstring.o +0 -0
- data/funchook/test/Makefile +2 -2
- data/funchook/test/funchook_test +0 -0
- data/funchook/test/libfunchook_test.so +0 -0
- data/funchook/test/libfunchook_test.so.dSYM/Contents/Info.plist +20 -0
- data/funchook/test/libfunchook_test.so.dSYM/Contents/Resources/DWARF/libfunchook_test.so +0 -0
- data/funchook/test/test_main.o +0 -0
- data/funchook/test/x86_64_test.o +0 -0
- data/lib/contrast.rb +1 -0
- data/lib/contrast/agent.rb +21 -15
- data/lib/contrast/agent/assess.rb +1 -2
- data/lib/contrast/agent/assess/adjusted_span.rb +3 -1
- data/lib/contrast/agent/assess/contrast_event.rb +20 -68
- data/lib/contrast/agent/assess/events/event_factory.rb +25 -0
- data/lib/contrast/agent/assess/events/source_event.rb +83 -0
- data/lib/contrast/agent/assess/insulator.rb +0 -4
- data/lib/contrast/agent/assess/policy/patcher.rb +5 -2
- data/lib/contrast/agent/assess/policy/policy_node.rb +0 -7
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +1 -1
- data/lib/contrast/agent/assess/policy/preshift.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagation_method.rb +65 -33
- data/lib/contrast/agent/assess/policy/propagation_node.rb +2 -1
- data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +80 -0
- data/lib/contrast/agent/assess/policy/propagator/select.rb +35 -22
- data/lib/contrast/agent/assess/policy/propagator/split.rb +26 -6
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -0
- data/lib/contrast/agent/assess/policy/rewriter_patch.rb +40 -27
- data/lib/contrast/agent/assess/policy/source_method.rb +20 -20
- data/lib/contrast/agent/assess/policy/source_node.rb +0 -15
- data/lib/contrast/agent/assess/policy/trigger_method.rb +29 -40
- data/lib/contrast/agent/assess/policy/trigger_node.rb +3 -6
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -31
- data/lib/contrast/agent/assess/properties.rb +5 -3
- data/lib/contrast/agent/assess/rule/base.rb +1 -5
- data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +2 -22
- data/lib/contrast/agent/assess/rule/csrf/csrf_watcher.rb +5 -1
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +2 -2
- data/lib/contrast/agent/assess/rule/redos.rb +4 -4
- data/lib/contrast/agent/assess/tag.rb +24 -14
- data/lib/contrast/agent/at_exit_hook.rb +16 -13
- data/lib/contrast/agent/class_reopener.rb +15 -6
- data/lib/contrast/agent/deadzone/policy/policy.rb +2 -2
- data/lib/contrast/agent/disable_reaction.rb +3 -4
- data/lib/contrast/agent/exclusion_matcher.rb +8 -48
- data/lib/contrast/agent/feature_state.rb +45 -78
- data/lib/contrast/agent/logger.rb +173 -0
- data/lib/contrast/agent/middleware.rb +87 -250
- data/lib/contrast/agent/module_data.rb +2 -1
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +2 -1
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +21 -4
- data/lib/contrast/agent/patching/policy/method_policy.rb +3 -3
- data/lib/contrast/agent/patching/policy/module_policy.rb +0 -25
- data/lib/contrast/agent/patching/policy/patch.rb +96 -23
- data/lib/contrast/agent/patching/policy/patcher.rb +19 -19
- data/lib/contrast/agent/patching/policy/policy.rb +7 -7
- data/lib/contrast/agent/patching/policy/policy_node.rb +2 -10
- data/lib/contrast/agent/patching/policy/trigger_node.rb +1 -4
- data/lib/contrast/agent/protect/rule/base.rb +14 -33
- data/lib/contrast/agent/protect/rule/base_service.rb +3 -1
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +10 -13
- data/lib/contrast/agent/protect/rule/csrf.rb +2 -1
- data/lib/contrast/agent/protect/rule/csrf/csrf_evaluator.rb +11 -14
- data/lib/contrast/agent/protect/rule/default_scanner.rb +0 -13
- data/lib/contrast/agent/protect/rule/deserialization.rb +2 -0
- data/lib/contrast/agent/protect/rule/http_method_tampering.rb +2 -2
- data/lib/contrast/agent/protect/rule/no_sqli.rb +4 -4
- data/lib/contrast/agent/protect/rule/path_traversal.rb +5 -4
- data/lib/contrast/agent/protect/rule/sqli.rb +1 -0
- data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +2 -0
- data/lib/contrast/agent/protect/rule/xss.rb +2 -0
- data/lib/contrast/agent/protect/rule/xxe.rb +10 -4
- data/lib/contrast/agent/railtie.rb +2 -8
- data/lib/contrast/agent/reaction_processor.rb +5 -5
- data/lib/contrast/agent/request.rb +9 -12
- data/lib/contrast/agent/request_context.rb +12 -14
- data/lib/contrast/agent/request_handler.rb +35 -0
- data/lib/contrast/agent/response.rb +52 -30
- data/lib/contrast/agent/rewriter.rb +25 -11
- data/lib/contrast/agent/rule_set.rb +49 -0
- data/lib/contrast/agent/scope.rb +4 -12
- data/lib/contrast/agent/service_heartbeat.rb +1 -2
- data/lib/contrast/agent/settings_state.rb +10 -74
- data/lib/contrast/agent/socket_client.rb +17 -11
- data/lib/contrast/agent/static_analysis.rb +42 -0
- data/lib/contrast/agent/thread.rb +1 -1
- data/lib/contrast/agent/tracepoint_hook.rb +1 -5
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api.rb +1 -1
- data/lib/contrast/api/decorators.rb +14 -0
- data/lib/contrast/api/decorators/application_settings.rb +37 -0
- data/lib/contrast/api/decorators/application_update.rb +66 -0
- data/lib/contrast/api/decorators/exclusion.rb +20 -0
- data/lib/contrast/api/decorators/input_analysis.rb +17 -0
- data/lib/contrast/api/decorators/server_features.rb +24 -0
- data/lib/contrast/api/speedracer.rb +31 -29
- data/lib/contrast/api/tcp_socket.rb +0 -2
- data/lib/contrast/components/agent.rb +27 -22
- data/lib/contrast/components/app_context.rb +18 -43
- data/lib/contrast/components/config.rb +7 -5
- data/lib/contrast/components/contrast_service.rb +0 -4
- data/lib/contrast/components/heap_dump.rb +12 -8
- data/lib/contrast/components/interface.rb +11 -10
- data/lib/contrast/components/logger.rb +3 -68
- data/lib/contrast/components/sampling.rb +22 -11
- data/lib/contrast/components/settings.rb +18 -5
- data/lib/contrast/config/base_configuration.rb +1 -0
- data/lib/contrast/config/default_value.rb +1 -0
- data/lib/contrast/config/protect_rule_configuration.rb +0 -14
- data/lib/contrast/configuration.rb +3 -5
- data/lib/contrast/extensions/framework/rails/action_controller_inheritance.rb +39 -0
- data/lib/contrast/extensions/framework/rails/active_record_named.rb +13 -8
- data/lib/contrast/extensions/ruby_core/assess.rb +1 -2
- data/lib/contrast/extensions/ruby_core/assess/assess_extension.rb +27 -22
- data/lib/contrast/extensions/ruby_core/assess/erb.rb +0 -8
- data/lib/contrast/extensions/ruby_core/assess/exec_trigger.rb +6 -8
- data/lib/contrast/extensions/ruby_core/assess/fiber.rb +88 -100
- data/lib/contrast/extensions/ruby_core/assess/hash.rb +32 -15
- data/lib/contrast/extensions/ruby_core/assess/kernel.rb +28 -27
- data/lib/contrast/extensions/ruby_core/assess/regexp.rb +74 -196
- data/lib/contrast/extensions/ruby_core/assess/string.rb +15 -7
- data/lib/contrast/extensions/ruby_core/assess/tilt_template_trigger.rb +29 -24
- data/lib/contrast/extensions/ruby_core/assess/xpath_library_trigger.rb +2 -2
- data/lib/contrast/extensions/ruby_core/eval_trigger.rb +0 -1
- data/lib/contrast/extensions/ruby_core/inventory/datastores.rb +2 -2
- data/lib/contrast/extensions/ruby_core/protect/applies_command_injection_rule.rb +9 -20
- data/lib/contrast/extensions/ruby_core/protect/applies_deserialization_rule.rb +9 -19
- data/lib/contrast/extensions/ruby_core/protect/applies_no_sqli_rule.rb +10 -27
- data/lib/contrast/extensions/ruby_core/protect/applies_path_traversal_rule.rb +13 -21
- data/lib/contrast/extensions/ruby_core/protect/applies_sqli_rule.rb +11 -23
- data/lib/contrast/extensions/ruby_core/protect/applies_xxe_rule.rb +62 -78
- data/lib/contrast/extensions/ruby_core/protect/rule_applicator.rb +50 -0
- data/lib/contrast/framework/base_support.rb +10 -0
- data/lib/contrast/framework/manager.rb +28 -2
- data/lib/contrast/framework/platform_version.rb +1 -0
- data/lib/contrast/framework/rails_support.rb +16 -0
- data/lib/contrast/framework/sinatra_support.rb +12 -2
- data/lib/contrast/framework/view_technologies_descriptor.rb +1 -0
- data/lib/contrast/tasks/service.rb +2 -8
- data/lib/contrast/utils/assess/sampling_util.rb +4 -9
- data/lib/contrast/utils/assess/tracking_util.rb +7 -1
- data/lib/contrast/utils/boolean_util.rb +2 -5
- data/lib/contrast/utils/cache.rb +0 -11
- data/lib/contrast/utils/class_util.rb +20 -1
- data/lib/contrast/utils/gemfile_reader.rb +5 -3
- data/lib/contrast/utils/hash_digest.rb +0 -4
- data/lib/contrast/utils/heap_dump_util.rb +12 -11
- data/lib/contrast/utils/invalid_configuration_util.rb +1 -1
- data/lib/contrast/utils/inventory_util.rb +2 -2
- data/lib/contrast/utils/io_util.rb +1 -11
- data/lib/contrast/utils/job_servers_running.rb +2 -2
- data/lib/contrast/utils/object_share.rb +1 -37
- data/lib/contrast/utils/os.rb +1 -25
- data/lib/contrast/utils/rack_assess_session_cookie.rb +3 -3
- data/lib/contrast/utils/rails_assess_configuration.rb +3 -3
- data/lib/contrast/utils/ruby_ast_rewriter.rb +5 -1
- data/lib/contrast/utils/service_response_util.rb +27 -53
- data/lib/contrast/utils/service_sender_util.rb +9 -5
- data/lib/contrast/utils/sinatra_helper.rb +0 -6
- data/lib/contrast/utils/stack_trace_utils.rb +86 -182
- data/lib/contrast/utils/string_utils.rb +18 -2
- data/lib/contrast/utils/tag_util.rb +11 -1
- data/lib/contrast/utils/thread_tracker.rb +2 -2
- data/lib/contrast/utils/timer.rb +0 -40
- data/resources/assess/policy.json +33 -21
- data/resources/protect/policy.json +9 -9
- data/ruby-agent.gemspec +7 -4
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +79 -54
- data/ext/cs__assess_regexp_track/cs__assess_regexp_track.c +0 -63
- data/ext/cs__assess_regexp_track/cs__assess_regexp_track.h +0 -29
- data/ext/cs__assess_regexp_track/extconf.rb +0 -2
- data/funchook/src/libfunchook.so +0 -0
- data/lib/contrast/agent/assess/frozen_properties.rb +0 -41
- data/lib/contrast/agent/logger_manager.rb +0 -116
- data/lib/contrast/delegators.rb +0 -9
- data/lib/contrast/delegators/application_update.rb +0 -32
- data/lib/contrast/utils/comment_range.rb +0 -19
- data/lib/contrast/utils/environment_util.rb +0 -82
- data/lib/contrast/utils/performs_logging.rb +0 -152
- data/resources/factory-bot-spec/spec_helper.rb +0 -30
- data/resources/rubocops/kernel/catch_cop.rb +0 -37
- data/resources/rubocops/kernel/require_cop.rb +0 -37
- data/resources/rubocops/kernel/require_relative_cop.rb +0 -33
- data/resources/rubocops/module/autoload_cop.rb +0 -37
- data/resources/rubocops/module/const_defined_cop.rb +0 -37
- data/resources/rubocops/module/const_get_cop.rb +0 -37
- data/resources/rubocops/module/const_set_cop.rb +0 -37
- data/resources/rubocops/module/constants_cop.rb +0 -37
- data/resources/rubocops/module/name_cop.rb +0 -37
- data/resources/rubocops/object/class_cop.rb +0 -37
- data/resources/rubocops/object/freeze_cop.rb +0 -37
- data/resources/rubocops/object/frozen_cop.rb +0 -37
- data/resources/rubocops/object/is_a_cop.rb +0 -37
- data/resources/rubocops/object/method_cop.rb +0 -37
- data/resources/rubocops/object/respond_to_cop.rb +0 -37
- data/resources/rubocops/object/singleton_class_cop.rb +0 -37
- data/resources/rubocops/regexp/spelling_cop.rb +0 -44
- data/resources/rubocops/thread/new_cop.rb +0 -39
- data/resources/ruby-spec/ancestors_spec.rb +0 -70
- data/resources/ruby-spec/modulo_spec.rb +0 -831
- data/resources/ruby-spec/parameters_spec.rb +0 -261
- data/resources/ruby-spec/ruby_spec_spec_helper.rb +0 -35
@@ -2,7 +2,6 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
cs__scoped_require 'contrast/agent/settings_state'
|
5
|
-
cs__scoped_require 'contrast/extensions/ruby_core/module'
|
6
5
|
cs__scoped_require 'contrast/utils/boolean_util'
|
7
6
|
|
8
7
|
module Contrast
|
@@ -26,9 +25,6 @@ module Contrast
|
|
26
25
|
include Contrast::Components::Interface
|
27
26
|
access_component :config
|
28
27
|
|
29
|
-
# Ruby 2.4 does not nicely compare to nil, so we have to include
|
30
|
-
# these wrapper methods. RUBY-179 has the task to update this on
|
31
|
-
# EOL of 2.4 support
|
32
28
|
def false? config
|
33
29
|
Contrast::Utils::BooleanUtil.false?(config)
|
34
30
|
end
|
@@ -38,57 +34,69 @@ module Contrast
|
|
38
34
|
end
|
39
35
|
|
40
36
|
def inventory_enabled?
|
41
|
-
!false?(CONFIG.root.inventory.enable)
|
37
|
+
@_inventory_enabled = !false?(CONFIG.root.inventory.enable) if @_inventory_enabled.nil?
|
38
|
+
@_inventory_enabled
|
42
39
|
end
|
43
40
|
|
44
41
|
def protect_forcibly_enabled?
|
45
|
-
true?(CONFIG.root.protect.enable)
|
42
|
+
@_protect_forcibly_enabled = true?(CONFIG.root.protect.enable) if @_protect_forcibly_enabled.nil?
|
43
|
+
@_protect_forcibly_enabled
|
46
44
|
end
|
47
45
|
|
48
46
|
def protect_forcibly_disabled?
|
49
|
-
false?(CONFIG.root.protect.enable)
|
47
|
+
@_protect_forcibly_disabled = false?(CONFIG.root.protect.enable) if @_protect_forcibly_disabled.nil?
|
48
|
+
@_protect_forcibly_disabled
|
50
49
|
end
|
51
50
|
|
52
51
|
def assess_forcibly_enabled?
|
53
|
-
true?(CONFIG.root.assess.enable)
|
52
|
+
@_assess_forcibly_enabled = true?(CONFIG.root.assess.enable) if @_assess_forcibly_enabled.nil?
|
53
|
+
@_assess_forcibly_enabled
|
54
54
|
end
|
55
55
|
|
56
56
|
def assess_forcibly_disabled?
|
57
|
-
false?(CONFIG.root.assess.enable)
|
57
|
+
@_assess_forcibly_disabled = false?(CONFIG.root.assess.enable) if @_assess_forcibly_disabled.nil?
|
58
|
+
@_assess_forcibly_disabled
|
58
59
|
end
|
59
60
|
|
60
61
|
def assess_track_frozen_sources?
|
61
|
-
!false?(CONFIG.root.agent.ruby.track_frozen_sources)
|
62
|
+
@_assess_track_frozen_sources = !false?(CONFIG.root.agent.ruby.track_frozen_sources) if @_assess_track_frozen_sources.nil?
|
63
|
+
@_assess_track_frozen_sources
|
62
64
|
end
|
63
65
|
|
64
66
|
def scan_response?
|
65
|
-
|
67
|
+
@_scan_response = !false?(CONFIG.root.assess.enable_scan_response) if @_scan_response.nil?
|
68
|
+
@_scan_response
|
66
69
|
end
|
67
70
|
|
68
71
|
def omit_body?
|
69
|
-
true?(CONFIG.root.agent.omit_body)
|
72
|
+
@_omit_body = true?(CONFIG.root.agent.omit_body) if @_omit_body.nil?
|
73
|
+
@_omit_body
|
70
74
|
end
|
71
75
|
|
72
76
|
def require_scanning_enabled?
|
73
|
-
!false?(CONFIG.root.agent.ruby.require_scan)
|
74
|
-
|
75
|
-
|
76
|
-
def heap_dump_enabled?
|
77
|
-
true?(CONFIG.root.agent.heap_dump.enable)
|
77
|
+
@_require_scanning_enabled = !false?(CONFIG.root.agent.ruby.require_scan) if @_require_scanning_enabled.nil?
|
78
|
+
@_require_scanning_enabled
|
78
79
|
end
|
79
80
|
|
80
81
|
def service_forcibly_disabled?
|
81
|
-
false?(CONFIG.root.agent.start_bundled_service)
|
82
|
+
@_service_forcibly_disabled = false?(CONFIG.root.agent.start_bundled_service) if @_service_forcibly_disabled.nil?
|
83
|
+
@_service_forcibly_disabled
|
82
84
|
end
|
83
85
|
|
84
86
|
def report_any_command_execution?
|
85
|
-
|
86
|
-
|
87
|
+
if @_report_any_command_execution.nil?
|
88
|
+
ctrl = protect_rule_config[Contrast::Agent::Protect::Rule::CmdInjection::NAME]
|
89
|
+
@_report_any_command_execution = true?(ctrl.disable_system_commands)
|
90
|
+
end
|
91
|
+
@_report_any_command_execution
|
87
92
|
end
|
88
93
|
|
89
94
|
def report_custom_code_sysfile_access?
|
90
|
-
|
91
|
-
|
95
|
+
if @_report_custom_code_sysfile_access.nil?
|
96
|
+
ctrl = protect_rule_config[Contrast::Agent::Protect::Rule::PathTraversal::NAME]
|
97
|
+
@_report_custom_code_sysfile_access = true?(ctrl.detect_custom_code_accessing_system_files)
|
98
|
+
end
|
99
|
+
@_report_custom_code_sysfile_access
|
92
100
|
end
|
93
101
|
|
94
102
|
# Determines if the Process we're currently in matches that of the
|
@@ -141,14 +149,6 @@ module Contrast
|
|
141
149
|
SETTINGS.exclusion_matchers
|
142
150
|
end
|
143
151
|
|
144
|
-
def url_exclusions
|
145
|
-
exclusions.select(&:url?)
|
146
|
-
end
|
147
|
-
|
148
|
-
def input_exclusions
|
149
|
-
exclusions.select(&:input?)
|
150
|
-
end
|
151
|
-
|
152
152
|
def code_exclusions
|
153
153
|
exclusions.select(&:code?)
|
154
154
|
end
|
@@ -238,36 +238,9 @@ module Contrast
|
|
238
238
|
include Contrast::Components::Interface
|
239
239
|
access_component :config
|
240
240
|
|
241
|
-
def heap_dump_control
|
242
|
-
{
|
243
|
-
path: File.absolute_path(CONFIG.root.agent.heap_dump.path),
|
244
|
-
count: CONFIG.root.agent.heap_dump.count.to_i,
|
245
|
-
window: CONFIG.root.agent.heap_dump.window_ms.to_f / 1000,
|
246
|
-
delay: CONFIG.root.agent.heap_dump.delay_ms.to_f / 1000,
|
247
|
-
clean: true?(CONFIG.root.agent.heap_dump.clean)
|
248
|
-
}
|
249
|
-
end
|
250
|
-
|
251
|
-
DEFAULT_SAMPLING_ENABLED = false
|
252
|
-
DEFAULT_SAMPLING_BASELINE = 5
|
253
|
-
DEFAULT_SAMPLING_REQUEST_FREQUENCY = 5
|
254
|
-
DEFAULT_SAMPLING_RESPONSE_FREQUENCY = 25
|
255
|
-
DEFAULT_SAMPLING_WINDOW_MS = 180_000 # 3 minutes, arbitrary value from Java agent
|
256
|
-
def sampling_control settings = @sampling_features
|
257
|
-
cas = CONFIG.root.assess&.sampling
|
258
|
-
|
259
|
-
{
|
260
|
-
enabled: [cas&.enable, settings&.enabled, DEFAULT_SAMPLING_ENABLED] .reject(&:nil?).first,
|
261
|
-
baseline: [cas&.baseline, settings&.baseline, DEFAULT_SAMPLING_BASELINE] .map(&:to_i).find(&:positive?),
|
262
|
-
request_frequency: [cas&.request_frequency, settings&.request_frequency, DEFAULT_SAMPLING_REQUEST_FREQUENCY] .map(&:to_i).find(&:positive?),
|
263
|
-
response_frequency: [cas&.response_frequency, settings&.response_frequency, DEFAULT_SAMPLING_RESPONSE_FREQUENCY].map(&:to_i).find(&:positive?),
|
264
|
-
window: [cas&.window_ms, settings&.window_ms, DEFAULT_SAMPLING_WINDOW_MS] .map(&:to_i).find(&:positive?)
|
265
|
-
}
|
266
|
-
end
|
267
|
-
|
268
241
|
DEFAULT_LOG_FILENAME = 'contrast_service.log'
|
269
242
|
def service_control
|
270
|
-
{
|
243
|
+
@_service_control ||= {
|
271
244
|
host: CONFIG.root.agent.service.host || Contrast::Configuration::DEFAULT_HOST,
|
272
245
|
port: CONFIG.root.agent.service.port || Contrast::Configuration::DEFAULT_PORT,
|
273
246
|
socket_path: CONFIG.root.agent.service.socket,
|
@@ -276,7 +249,7 @@ module Contrast
|
|
276
249
|
end
|
277
250
|
|
278
251
|
def exception_control
|
279
|
-
{
|
252
|
+
@_exception_control ||= {
|
280
253
|
enable: true?(CONFIG.root.agent.ruby.exceptions.capture),
|
281
254
|
status: CONFIG.root.agent.ruby.exceptions.override_status || 403,
|
282
255
|
message: CONFIG.root.agent.ruby.exceptions.override_message || Contrast::Utils::ObjectShare::OVERRIDE_MESSAGE
|
@@ -301,35 +274,25 @@ module Contrast
|
|
301
274
|
# methods for logging state.
|
302
275
|
module Logging
|
303
276
|
include Contrast::Components::Interface
|
304
|
-
access_component :config
|
305
|
-
|
306
|
-
FALLBACK = STDOUT
|
307
|
-
LOG_INFO_RULE_SETTINGS = 'Current rule settings:'
|
308
|
-
# Utility method to log the current mode of all the rules
|
309
|
-
def log_rule_modes
|
310
|
-
return unless logger&.info?
|
311
|
-
|
312
|
-
logger.info(LOG_INFO_RULE_SETTINGS)
|
313
|
-
PROTECT.rules.each { |k, v| logger.info(".. protect:\t#{ k } (#{ v.mode })") }
|
314
|
-
ASSESS.rules.each { |k, v| logger.info(".. assess: \t#{ k } (#{ v.enabled? })") }
|
315
|
-
end
|
277
|
+
access_component :config, :logging
|
316
278
|
|
317
279
|
ENV_KEYS = %w[HOME PWD RACK_ENV RAILS_ENV RUBY_VERSION GEM_HOME GEM_PATH].cs__freeze
|
318
280
|
# Utility method to log some current ruby and rails information from environment
|
319
281
|
def log_environment
|
320
282
|
return unless logger.info?
|
321
283
|
|
322
|
-
logger.info
|
284
|
+
logger.info('Process environment information',
|
285
|
+
p_id: Process.pid,
|
286
|
+
pp_id: Process.ppid,
|
287
|
+
agent_version: Contrast::Agent::VERSION)
|
323
288
|
ENV.each do |env_key, env_value|
|
324
289
|
env_key = env_key.to_s
|
325
290
|
next unless ENV_KEYS.include?(env_key) ||
|
326
291
|
(env_key.start_with?(Contrast::Components::Config::CONTRAST_ENV_MARKER) &&
|
327
292
|
!env_key.start_with?(Contrast::Components::Config::CONTRAST_ENV_MARKER + 'API'))
|
328
293
|
|
329
|
-
logger.info
|
294
|
+
logger.info('Environment settings', key: env_key, value: env_value)
|
330
295
|
end
|
331
|
-
logger.info "PARENT_PROCESS_ID=#{ Process.ppid }"
|
332
|
-
logger.info "PROCESS_ID=#{ Process.pid }"
|
333
296
|
end
|
334
297
|
|
335
298
|
def log_configuration
|
@@ -337,7 +300,7 @@ module Contrast
|
|
337
300
|
|
338
301
|
loggable = CONFIG.raw.send(:load_config)
|
339
302
|
loggable.delete('api')
|
340
|
-
logger.info
|
303
|
+
logger.info('Current configuration', configruation: JSON.pretty_generate(loggable))
|
341
304
|
end
|
342
305
|
|
343
306
|
FRAMEWORKS = %w[rails sinatra grape].cs__freeze
|
@@ -353,7 +316,9 @@ module Contrast
|
|
353
316
|
return unless logger.debug?
|
354
317
|
|
355
318
|
Gem.loaded_specs.each_pair do |_name, gem_spec|
|
356
|
-
logger.debug
|
319
|
+
logger.debug('Gem loaded',
|
320
|
+
gem_name: gem_spec.name,
|
321
|
+
gem_version: gem_spec.version.to_s)
|
357
322
|
end
|
358
323
|
end
|
359
324
|
|
@@ -361,7 +326,9 @@ module Contrast
|
|
361
326
|
gem_spec = Gem.loaded_specs[gem_name]
|
362
327
|
return unless gem_spec
|
363
328
|
|
364
|
-
logger.info
|
329
|
+
logger.info('Gem loaded',
|
330
|
+
gem_name: gem_spec.name,
|
331
|
+
gem_version: gem_spec.version.to_s)
|
365
332
|
end
|
366
333
|
end
|
367
334
|
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
cs__scoped_require 'logger'
|
5
|
+
cs__scoped_require 'ougai'
|
6
|
+
cs__scoped_require 'singleton'
|
7
|
+
|
8
|
+
cs__scoped_require 'contrast/extensions/ruby_core/module'
|
9
|
+
cs__scoped_require 'contrast/components/interface'
|
10
|
+
|
11
|
+
module Contrast
|
12
|
+
module Ougai
|
13
|
+
# Our decorator for the Ougai logger allowing for timing and with_level
|
14
|
+
# methods.
|
15
|
+
module Logger
|
16
|
+
# Log the message at the given level.
|
17
|
+
#
|
18
|
+
# @param level [String] the name of the method to use. Should be one of
|
19
|
+
# trace, debug, info, warn, error
|
20
|
+
# @param message [String] the message to log
|
21
|
+
def with_level level, message
|
22
|
+
send(level.to_sym, message)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Log, at the debug level, the action with a message including the time
|
26
|
+
# it took for the wrapped function to complete.
|
27
|
+
#
|
28
|
+
# @param msgs [Array<Object>] the arguments to pass to the logger.
|
29
|
+
# msgs[0] will be modified to include the elapsed time.
|
30
|
+
# @param block [Block, Proc] the block to execute
|
31
|
+
def debug_with_time *msgs, &block
|
32
|
+
log_with_time(:debug, *msgs, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Log, at the trace level, the action with a message including the time
|
36
|
+
# it took for the wrapped function to complete.
|
37
|
+
#
|
38
|
+
# @param msgs [Array<Object>] the arguments to pass to the logger.
|
39
|
+
# msgs[0] will be modified to include the elapsed time.
|
40
|
+
# @param block [Block, Proc] the block to execute
|
41
|
+
def trace_with_time *msgs, &block
|
42
|
+
log_with_time(:trace, *msgs, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def log_with_time level, *msgs
|
48
|
+
a = Contrast::Utils::Timer.now_ms
|
49
|
+
ret = yield if block_given?
|
50
|
+
z = Contrast::Utils::Timer.now_ms
|
51
|
+
msgs[0] = "#{ msgs[0] }: pid=#{ Process.pid }, elapsed=#{ z - a }ms"
|
52
|
+
send(level, *msgs)
|
53
|
+
ret
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module Contrast
|
60
|
+
module Agent
|
61
|
+
# This class functions to serve as a wrapper around our logging, as we need
|
62
|
+
# to be able to dynamically update level based on updates to TeamServer.
|
63
|
+
class Logger
|
64
|
+
include Singleton
|
65
|
+
include Contrast::Components::Interface
|
66
|
+
access_component :config
|
67
|
+
|
68
|
+
DEFAULT_NAME = 'contrast.log'
|
69
|
+
DEFAULT_LEVEL = ::Ougai::Logging::Severity::INFO
|
70
|
+
VALID_LEVELS = ::Ougai::Logging::Severity::SEV_LABEL
|
71
|
+
STDOUT_STR = 'STDOUT'
|
72
|
+
STDERR_STR = 'STDERR'
|
73
|
+
|
74
|
+
attr_reader :previous_path,
|
75
|
+
:previous_level
|
76
|
+
|
77
|
+
def initialize
|
78
|
+
update
|
79
|
+
rescue StandardError => e
|
80
|
+
logger.error('Unable to initialize regular logger in LoggerManager.', e)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Given new settings from TeamServer, update our logging to use the new
|
84
|
+
# file and level, assuming they weren't set by local configuration.
|
85
|
+
#
|
86
|
+
# @param log_file [String] the file to which to log
|
87
|
+
# @param log_level [String] the level at which to log
|
88
|
+
def update log_file = nil, log_level = nil
|
89
|
+
config = CONFIG.root.agent.logger
|
90
|
+
|
91
|
+
config_path = config.path&.length.to_i.positive? ? config.path : nil
|
92
|
+
config_level = config.level&.length&.positive? ? config.level : nil
|
93
|
+
|
94
|
+
# config > settings > default
|
95
|
+
path = valid_path(config_path || log_file)
|
96
|
+
level_const = valid_level(config_level || log_level)
|
97
|
+
|
98
|
+
# don't needlessly recreate logger
|
99
|
+
return if @_logger && (path == previous_path) && (level_const == previous_level)
|
100
|
+
|
101
|
+
@previous_path = path
|
102
|
+
@previous_level = level_const
|
103
|
+
|
104
|
+
@_logger = build(path: path, level_const: level_const)
|
105
|
+
|
106
|
+
logger.debug('Initialized new contrast agent logger')
|
107
|
+
rescue StandardError => e
|
108
|
+
logger.error('Unable to process update to LoggerManager.', e)
|
109
|
+
end
|
110
|
+
|
111
|
+
def logger
|
112
|
+
@_logger
|
113
|
+
end
|
114
|
+
|
115
|
+
# StringIO is a valid path because it logs directly to a string buffer
|
116
|
+
def write_permission? path
|
117
|
+
return false if path.nil?
|
118
|
+
return true if path.is_a?(StringIO)
|
119
|
+
return File.writable?(path) if File.exist?(path)
|
120
|
+
|
121
|
+
dir_name = File.dirname(File.absolute_path(path))
|
122
|
+
File.writable?(dir_name)
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def build path: STDOUT_STR, level_const: DEFAULT_LEVEL
|
128
|
+
logger = case path
|
129
|
+
when STDOUT_STR, STDERR_STR
|
130
|
+
::Ougai::Logger.new(Object.cs__const_get(path))
|
131
|
+
else
|
132
|
+
::Ougai::Logger.new(path)
|
133
|
+
end
|
134
|
+
logger.extend(Contrast::Ougai::Logger)
|
135
|
+
logger.progname = 'Contrast Agent'
|
136
|
+
logger.level = level_const
|
137
|
+
logger.formatter.datetime_format = '%Y-%m-%dT%H:%M:%S.%L%z'
|
138
|
+
logger
|
139
|
+
end
|
140
|
+
|
141
|
+
def valid_path path
|
142
|
+
path = path.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : path
|
143
|
+
return path if path == STDOUT_STR
|
144
|
+
return path if path == STDERR_STR
|
145
|
+
|
146
|
+
path = DEFAULT_NAME if path.empty?
|
147
|
+
if write_permission?(path)
|
148
|
+
path
|
149
|
+
else
|
150
|
+
if File.directory?(path)
|
151
|
+
logger.error('Unable to create log in directory. Writing to standard out instead', path: path)
|
152
|
+
else
|
153
|
+
logger.error('Unable to write to log file. Writing to standard out instead', path: path)
|
154
|
+
end
|
155
|
+
|
156
|
+
STDOUT_STR
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def valid_level level
|
161
|
+
level ||= DEFAULT_LEVEL
|
162
|
+
level = level.upcase
|
163
|
+
if VALID_LEVELS.include?(level)
|
164
|
+
Object.cs__const_get("::Ougai::Logging::Severity::#{ level }")
|
165
|
+
else
|
166
|
+
DEFAULT_LEVEL
|
167
|
+
end
|
168
|
+
rescue StandardError
|
169
|
+
DEFAULT_LEVEL
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -7,10 +7,11 @@ cs__scoped_require 'rack'
|
|
7
7
|
|
8
8
|
cs__scoped_require 'contrast/security_exception'
|
9
9
|
cs__scoped_require 'contrast/utils/object_share'
|
10
|
-
cs__scoped_require 'contrast/utils/gemfile_reader'
|
11
10
|
cs__scoped_require 'contrast/agent/service_heartbeat'
|
12
11
|
cs__scoped_require 'contrast/components/interface'
|
13
12
|
cs__scoped_require 'contrast/utils/heap_dump_util'
|
13
|
+
cs__scoped_require 'contrast/agent/request_handler'
|
14
|
+
cs__scoped_require 'contrast/agent/static_analysis'
|
14
15
|
|
15
16
|
cs__scoped_require 'contrast/utils/timer'
|
16
17
|
cs__scoped_require 'contrast/utils/freeze_util'
|
@@ -19,331 +20,167 @@ cs__scoped_require 'contrast/utils/service_response_util'
|
|
19
20
|
|
20
21
|
module Contrast
|
21
22
|
module Agent
|
22
|
-
# This class allows the Agent to plug into the Rack middleware stack
|
23
|
-
#
|
24
|
-
#
|
23
|
+
# This class allows the Agent to plug into the Rack middleware stack. When the
|
24
|
+
# application is first started, we initialize ourselves as a rack middleware
|
25
|
+
# inside of #initialize. Afterwards, we process each http request and response
|
26
|
+
# as it goes through the middleware stack inside of #call.
|
25
27
|
class Middleware
|
26
28
|
include Contrast::Components::Interface
|
27
|
-
access_component :
|
29
|
+
access_component :agent, :logging, :scope
|
28
30
|
|
29
31
|
attr_reader :app
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
33
|
+
# Allows the Agent to function as a middleware. We perform all our one-time whole-app routines in here
|
34
|
+
# since we're only going to be initialized a single time. Our initializatoin order is:
|
35
|
+
# - log environment, configuration, and all libraries
|
36
|
+
# - start the service sending thread, responsible for sending and processing messages
|
37
|
+
# - start the heartbeat thread, which triggers service startup
|
38
|
+
# - start instrumenting libraries and do a 'catchup' patch for everything we didn't see get loaded
|
39
|
+
# - enable TracePoint, which handles all class loads and required instrumentation going forward
|
37
40
|
#
|
38
41
|
# @param app [Rack::Application] the application to be instrumented
|
39
42
|
# @param _legacy_param [nil] was a flag we no longer need, but Sinatra may call it
|
40
|
-
def initialize app, _legacy_param = nil
|
41
|
-
@app = app
|
42
43
|
|
44
|
+
def initialize app, _legacy_param = nil
|
45
|
+
@app = app # THIS MUST BE FIRST AND ALWAYS SET!
|
43
46
|
# TODO: RUBY-545 nomenclature here needs to be updated.
|
44
|
-
# enabled/initialized are really only reflective of whether we were
|
45
|
-
#
|
46
|
-
# to do anything.
|
47
|
+
# enabled/initialized are really only reflective of whether we were able to parse the config,
|
48
|
+
# which is the bare minimum for the agent to do anything.
|
47
49
|
unless AGENT.enabled?
|
48
|
-
logger.error(
|
49
|
-
|
50
|
-
AGENT.disable!
|
50
|
+
logger.error(
|
51
|
+
'Contrast middleware initializer detected an early-stage setup failure (likely config parse). Disabling.')
|
52
|
+
AGENT.disable! # ensure the agent is disabled (probably redundant)
|
51
53
|
return
|
52
54
|
end
|
55
|
+
agent_startup_routine
|
56
|
+
end
|
53
57
|
|
54
|
-
|
58
|
+
def agent_startup_routine
|
59
|
+
logger.debug_with_time('middleware: log environment') do
|
55
60
|
settings.log_environment
|
56
61
|
settings.log_configuration
|
57
62
|
settings.log_specific_libraries
|
58
63
|
settings.log_all_libraries
|
59
64
|
end
|
60
65
|
|
61
|
-
|
62
|
-
|
63
|
-
# - start heartbeat thread, which triggers service startup
|
64
|
-
# - start patching to achieve instrumentation
|
65
|
-
|
66
|
-
logger.debug_with_time(LOG_DEBUG_MIDDLEWARE_SERVICE) do
|
67
|
-
# get threads ready to poll for messages on the queue
|
68
|
-
run_service_sender_thread
|
69
|
-
|
70
|
-
# sends first message to service, which triggers service startup
|
71
|
-
run_service_heartbeat_thread
|
66
|
+
logger.debug_with_time('middleware: starting service') do
|
67
|
+
run_service_threads
|
72
68
|
end
|
73
69
|
|
74
|
-
|
75
|
-
|
76
|
-
AGENT.run_instrumentation
|
70
|
+
logger.debug_with_time('middleware: instrument shared libraries and patch') do
|
71
|
+
instrument_and_patch
|
77
72
|
end
|
78
73
|
|
74
|
+
AGENT.enable_tracepoint
|
79
75
|
Contrast::Agent::AtExitHook.exit_hook
|
80
76
|
end
|
81
77
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
#
|
78
|
+
# This is where we're hooked into the middleware stack. If the agent is enabled, we're ready
|
79
|
+
# to do some processing on a per request basis. If not, we just pass the request along to the
|
80
|
+
# next middleware in the stack.
|
81
|
+
#
|
82
|
+
# @param env [Hash] the various variables stored by this and other Middlewares to know the state
|
83
|
+
# and values of this Request
|
84
|
+
# @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back
|
85
|
+
# to the user up the Rack framework.
|
88
86
|
def call env
|
89
87
|
Contrast::Utils::HeapDumpUtil.run
|
90
88
|
|
91
89
|
if AGENT.enabled?
|
92
|
-
|
93
|
-
|
94
|
-
response
|
90
|
+
Contrast::Agent::StaticAnalysis.catchup
|
91
|
+
call_with_agent(env)
|
95
92
|
else
|
96
|
-
|
93
|
+
app.call(env)
|
97
94
|
end
|
98
95
|
end
|
99
96
|
|
100
|
-
|
101
|
-
app.call(env)
|
102
|
-
end
|
97
|
+
private
|
103
98
|
|
104
|
-
|
105
|
-
|
99
|
+
# This is where we process each request we intercept as a middleware. We make the request context
|
100
|
+
# available globally so that it can be accessed from anywhere. A RequestHandler object is made
|
101
|
+
# for each request, which handles prefilter and postfilter operations.
|
106
102
|
def call_with_agent env
|
107
|
-
|
103
|
+
framework_request = Contrast::Agent.framework_manager.retrieve_request(env)
|
104
|
+
context = Contrast::Agent::RequestContext.new(framework_request)
|
105
|
+
response = nil
|
108
106
|
|
109
|
-
context = Contrast::Agent::RequestContext.new(rack_request, true) # 'true' here is legacy, by the time we're here, we're ready
|
110
|
-
|
111
|
-
# default response
|
112
|
-
streaming = false
|
113
107
|
# make the context available for the lifecycle of this request
|
114
108
|
Contrast::Agent::REQUEST_TRACKER.lifespan(context) do
|
115
|
-
|
116
|
-
|
117
|
-
|
109
|
+
request_handler = Contrast::Agent::RequestHandler.new(context)
|
110
|
+
|
111
|
+
logger.debug_with_time('HTTP request cycle') do
|
112
|
+
# prefilter sequence
|
118
113
|
with_contrast_scope do
|
119
114
|
context.service_extract_request
|
120
|
-
prefilter
|
115
|
+
request_handler.ruleset.prefilter
|
121
116
|
end
|
122
117
|
|
123
|
-
|
124
|
-
# the original env
|
125
|
-
response = application_code(env)
|
118
|
+
response = application_code(env) # pass request down the Rack chain with original env
|
126
119
|
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
120
|
+
# postfilter sequence
|
121
|
+
with_contrast_scope do
|
122
|
+
context.extract_after(response) # update context with final response information
|
123
|
+
|
124
|
+
if Contrast::Agent.framework_manager.streaming?(env)
|
125
|
+
context.reset_activity
|
126
|
+
request_handler.stream_safe_postfilter
|
127
|
+
else
|
128
|
+
request_handler.ruleset.postfilter
|
129
|
+
# return response stored in the context in case any postfilter rules updated the response data
|
130
|
+
response = context.response&.rack_response || response
|
131
|
+
request_handler.send_activity_messages
|
132
|
+
end
|
132
133
|
end
|
133
|
-
|
134
|
-
# update context with final response information
|
135
|
-
context.extract_after(response)
|
136
|
-
|
137
|
-
# process filters that look at response headers and body
|
138
|
-
with_contrast_scope { postfilter(context) }
|
139
134
|
end
|
140
135
|
end
|
141
136
|
|
142
|
-
|
143
|
-
# updated the response data
|
144
|
-
context&.response&.rack_response || response
|
145
|
-
|
146
|
-
# handle security exception
|
137
|
+
response
|
147
138
|
rescue StandardError => e
|
148
139
|
handle_exception(e)
|
149
|
-
ensure
|
150
|
-
begin
|
151
|
-
handle_ensure(context, streaming)
|
152
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
153
|
-
logger.error(e, 'Exception raised while flushing messages to Contrast service!')
|
154
|
-
raise
|
155
|
-
end
|
156
140
|
end
|
157
141
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
# supported frameworks are defined OR we cannot determine the framework
|
162
|
-
# currently in use or there is an exception during request generation, we
|
163
|
-
# will fall back on Rack::Request object
|
164
|
-
def generate_request env
|
165
|
-
rails_defined = defined?(Rails)
|
166
|
-
sinatra_defined = defined?(Sinatra) && defined?(Sinatra::Request)
|
167
|
-
|
168
|
-
if rails_defined && !sinatra_defined
|
169
|
-
# code from /lib/action_cable/connection/base
|
170
|
-
environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
|
171
|
-
ActionDispatch::Request.new(environment || env)
|
172
|
-
# !defined? currently redundant, won't be if we have more frameworks
|
173
|
-
elsif sinatra_defined && !rails_defined
|
174
|
-
Sinatra::Request.new(env)
|
175
|
-
else # many OR none
|
176
|
-
Rack::Request.new(env)
|
177
|
-
end
|
178
|
-
rescue StandardError => e
|
179
|
-
logger.warn(e, LOG_WARN_FRAMEWORK_PARSE)
|
180
|
-
Rack::Request.new(env)
|
181
|
-
end
|
182
|
-
|
183
|
-
LOG_ERROR_STREAM_CHECK = 'Unable to check for streaming'
|
184
|
-
ACTION_CONTROLLER_INSTANCE = 'action_controller.instance'
|
185
|
-
# First check to see if an action could potentially be streaming a response
|
186
|
-
def possibly_streaming? env
|
187
|
-
return false unless defined?(ActionController::Live)
|
188
|
-
|
189
|
-
env[ACTION_CONTROLLER_INSTANCE].cs__class.included_modules.include?(ActionController::Live)
|
190
|
-
rescue StandardError => e
|
191
|
-
logger.warn(LOG_ERROR_STREAM_CHECK, e)
|
192
|
-
end
|
193
|
-
|
194
|
-
LOG_ERROR_ENSURE = 'Context not defined in middleware ensure.'
|
195
|
-
# Send a server activity message and an application activity message
|
196
|
-
# and return response
|
197
|
-
def handle_ensure context, streaming
|
198
|
-
if context
|
199
|
-
logger.debug('Middleware request lifecycle complete; flushing context activity to Contrast service.')
|
200
|
-
Contrast::Utils::GemfileReader.instance.generate_library_usage(context.activity)
|
201
|
-
[context.server_activity, context.activity, context.observed_route].each do |message|
|
202
|
-
CONTRAST_SERVICE.send_message message
|
203
|
-
end
|
204
|
-
else
|
205
|
-
logger.error(LOG_ERROR_ENSURE)
|
142
|
+
def application_code env
|
143
|
+
logger.trace_with_time('application') do
|
144
|
+
app.call(env)
|
206
145
|
end
|
207
|
-
|
208
|
-
|
209
|
-
|
146
|
+
rescue Contrast::SecurityException => e
|
147
|
+
logger.trace('Security Exception raised during application lifecycle to prevent an attack', e)
|
148
|
+
raise e
|
210
149
|
end
|
211
150
|
|
212
151
|
SECURITY_EXCEPTION_MARKER = 'Contrast::SecurityException'
|
213
|
-
LOG_RETHROW_ERROR = 'Re-throwing original error'
|
214
152
|
# We're only going to suppress SecurityExceptions indicating a blocked attack.
|
215
153
|
# And, only if the config.agent.ruby.exceptions.capture? is set
|
216
154
|
def handle_exception exception
|
217
155
|
if exception.is_a?(Contrast::SecurityException) ||
|
218
156
|
exception.message&.include?(SECURITY_EXCEPTION_MARKER)
|
219
157
|
|
220
|
-
exception_control =
|
158
|
+
exception_control = settings.exception_control
|
221
159
|
raise exception unless exception_control[:enable]
|
222
160
|
|
223
161
|
[exception_control[:status], {}, [exception_control[:message]]]
|
224
162
|
else
|
225
|
-
|
226
|
-
logger.debug(exception, LOG_RETHROW_ERROR)
|
163
|
+
logger.debug('Re-throwing original error', exception)
|
227
164
|
raise exception
|
228
165
|
end
|
229
166
|
end
|
230
167
|
|
231
|
-
|
232
|
-
|
233
|
-
# Iterate through rules that only depend upon the request object.
|
234
|
-
def prefilter context
|
235
|
-
return unless context.app_loaded?
|
236
|
-
|
237
|
-
logger.debug_with_time(LOG_DEBUG_PREFILTER) do
|
238
|
-
prefilter_assess(context) if ASSESS.enabled? && context.analyze_request?
|
239
|
-
prefilter_protect(context) if PROTECT.enabled?
|
240
|
-
end
|
241
|
-
rescue Contrast::SecurityException => e
|
242
|
-
logger.warn("RASP threw security exception in prefilter: exception=#{ e.message }")
|
243
|
-
raise e
|
244
|
-
rescue StandardError => e
|
245
|
-
logger.error(LOG_ERROR_PREFILTER, e)
|
246
|
-
end
|
247
|
-
|
248
|
-
def prefilter_assess context
|
249
|
-
rules = ASSESS.rules
|
250
|
-
logger.debug("Assess: Running #{ rules.length } rules in prefilter.")
|
251
|
-
prefilter_rules(rules, context)
|
252
|
-
end
|
253
|
-
|
254
|
-
def prefilter_protect context
|
255
|
-
rules = PROTECT.rules
|
256
|
-
logger.debug("Protect: Running #{ rules.length } rules in prefilter.")
|
257
|
-
prefilter_rules(rules, context)
|
258
|
-
end
|
259
|
-
|
260
|
-
def prefilter_rules rules, context
|
261
|
-
rules.each do |_, rule|
|
262
|
-
rule.prefilter(context)
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
LOG_DEBUG_APPLICATION = 'application'
|
267
|
-
# Run the next level of the Rack stack as normal
|
268
|
-
def application_code env
|
269
|
-
logger.debug_with_time(LOG_DEBUG_APPLICATION) do
|
270
|
-
app.call(env)
|
271
|
-
end
|
272
|
-
rescue Contrast::SecurityException => e
|
273
|
-
logger.info("RASP threw security exception in application code: exception=#{ e.message }")
|
274
|
-
raise e
|
275
|
-
end
|
276
|
-
|
277
|
-
LOG_DEBUG_POSTFILTER = 'postfilter'
|
278
|
-
LOG_ERROR_POSTFILTER = 'Unexpected exception during postfilter'
|
279
|
-
# Iterate through rules that depend on the full response object
|
280
|
-
def postfilter context, streaming = false
|
281
|
-
return unless context.app_loaded?
|
282
|
-
|
283
|
-
logger.debug_with_time(LOG_DEBUG_POSTFILTER) do
|
284
|
-
if ASSESS.enabled? && context.analyze_response?
|
285
|
-
logger.debug("Assess:\tRunning #{ ASSESS.rules.length } rules in postfilter.")
|
286
|
-
postfilter_rules(ASSESS.rules, context, streaming)
|
287
|
-
end
|
288
|
-
|
289
|
-
if PROTECT.enabled?
|
290
|
-
logger.debug("Protect:\tRunning #{ PROTECT.rules.length } rules in postfilter.")
|
291
|
-
postfilter_rules(PROTECT.rules, context, streaming)
|
292
|
-
end
|
293
|
-
end
|
294
|
-
rescue Contrast::SecurityException => e
|
295
|
-
logger.warn("RASP threw security exception: exception=#{ e.message }")
|
296
|
-
raise e
|
297
|
-
rescue StandardError => e
|
298
|
-
logger.error(LOG_ERROR_POSTFILTER, e)
|
299
|
-
end
|
300
|
-
|
301
|
-
# Iterate through a list of rules with the current context and the full response body
|
302
|
-
def postfilter_rules rules, context, streaming
|
303
|
-
rules.each do |_, rule|
|
304
|
-
if !streaming || rule.stream_safe?
|
305
|
-
rule.postfilter(context)
|
306
|
-
else
|
307
|
-
logger.debug("Skipping rule: #{ rule.name } in streamed response.")
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
def run_service_heartbeat_thread
|
313
|
-
# Rspec stubs over this method for simplicity's sake in testing.
|
314
|
-
# Take care if you refactor this back into #initialize.
|
315
|
-
Contrast::Agent::ServiceHeartbeat.new.start
|
168
|
+
def settings
|
169
|
+
Contrast::Agent::FeatureState.instance
|
316
170
|
end
|
317
171
|
|
318
|
-
|
319
|
-
|
320
|
-
# Take care if you refactor this back into #initialize.
|
172
|
+
# Rspec stubs over these methods for simplicity's sake in testing
|
173
|
+
def run_service_threads
|
321
174
|
Contrast::Utils::ServiceSenderUtil.start
|
175
|
+
Contrast::Agent::ServiceHeartbeat.new.start
|
322
176
|
end
|
323
177
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
# Everything in here should be asynchronous, as we need to return
|
331
|
-
# the HTTP response from middleware ASAP.
|
332
|
-
|
333
|
-
# Review already-loaded inventory
|
334
|
-
logger.debug_with_time('initializer: report loaded gemset') do
|
335
|
-
Contrast::Utils::GemfileReader.instance.map_loaded_classes
|
336
|
-
end
|
337
|
-
|
338
|
-
# Report already-loaded inventory
|
339
|
-
logger.debug_with_time(LOG_DEBUG_MW_INV) do
|
340
|
-
settings.send_inventory_message
|
341
|
-
end
|
342
|
-
|
343
|
-
true
|
344
|
-
end
|
345
|
-
rescue StandardError => e
|
346
|
-
logger.warn(LOG_WARN_STATIC_ANALYSIS, e)
|
178
|
+
# All this method chain does anymore is load the Thread patch that
|
179
|
+
# propagates request contexts. That can go away RUBY-700.
|
180
|
+
SHARED_LIBRARIES = %w[contrast/extensions/ruby_core/thread].cs__freeze
|
181
|
+
def instrument_and_patch
|
182
|
+
SHARED_LIBRARIES.each { |lib| settings.instrument(lib) }
|
183
|
+
Contrast::Agent::Patching::Policy::Patcher.patch
|
347
184
|
end
|
348
185
|
end
|
349
186
|
end
|