datadog 2.12.2 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -14
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +3 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.c +69 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.h +7 -0
- data/ext/datadog_profiling_native_extension/http_transport.c +25 -32
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +22 -21
- data/ext/libdatadog_api/datadog_ruby_common.h +3 -0
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +89 -0
- data/lib/datadog/appsec/actions_handler.rb +22 -1
- data/lib/datadog/appsec/anonymizer.rb +16 -0
- data/lib/datadog/appsec/assets/waf_rules/README.md +50 -5
- data/lib/datadog/appsec/assets/waf_rules/processors.json +239 -10
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +0 -1344
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +926 -17
- data/lib/datadog/appsec/assets/waf_rules/strict.json +0 -1344
- data/lib/datadog/appsec/component.rb +19 -17
- data/lib/datadog/appsec/compressed_json.rb +40 -0
- data/lib/datadog/appsec/configuration/settings.rb +62 -10
- data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/configuration.rb +7 -31
- data/lib/datadog/appsec/contrib/devise/data_extractor.rb +79 -0
- data/lib/datadog/appsec/contrib/devise/ext.rb +21 -0
- data/lib/datadog/appsec/contrib/devise/integration.rb +0 -1
- data/lib/datadog/appsec/contrib/devise/patcher.rb +36 -23
- data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +102 -0
- data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +69 -0
- data/lib/datadog/appsec/contrib/devise/{patcher/rememberable_patch.rb → patches/skip_signin_tracking_patch.rb} +2 -2
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +93 -0
- data/lib/datadog/appsec/contrib/rack/ext.rb +14 -0
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +10 -3
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +0 -2
- data/lib/datadog/appsec/event.rb +22 -51
- data/lib/datadog/appsec/ext.rb +4 -2
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +4 -2
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -3
- data/lib/datadog/appsec/remote.rb +4 -0
- data/lib/datadog/appsec/security_engine/runner.rb +2 -2
- data/lib/datadog/appsec/utils.rb +0 -2
- data/lib/datadog/core/configuration/components.rb +2 -1
- data/lib/datadog/core/configuration/ext.rb +4 -0
- data/lib/datadog/core/configuration/options.rb +2 -2
- data/lib/datadog/core/configuration/settings.rb +53 -30
- data/lib/datadog/core/diagnostics/environment_logger.rb +1 -1
- data/lib/datadog/core/environment/agent_info.rb +4 -3
- data/lib/datadog/core/metrics/client.rb +1 -1
- data/lib/datadog/core/remote/client.rb +1 -1
- data/lib/datadog/core/remote/component.rb +3 -6
- data/lib/datadog/core/remote/configuration/repository.rb +2 -1
- data/lib/datadog/core/remote/negotiation.rb +9 -9
- data/lib/datadog/core/remote/transport/config.rb +4 -3
- data/lib/datadog/core/remote/transport/http/client.rb +4 -3
- data/lib/datadog/core/remote/transport/http/config.rb +6 -32
- data/lib/datadog/core/remote/transport/http/negotiation.rb +6 -32
- data/lib/datadog/core/remote/transport/http.rb +22 -57
- data/lib/datadog/core/remote/transport/negotiation.rb +4 -3
- data/lib/datadog/core/runtime/metrics.rb +8 -1
- data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
- data/lib/datadog/core/telemetry/metric.rb +5 -5
- data/lib/datadog/core/telemetry/request.rb +1 -1
- data/lib/datadog/core/transport/http/api/instance.rb +17 -0
- data/lib/datadog/core/transport/http/api/spec.rb +17 -0
- data/lib/datadog/core/transport/http/builder.rb +5 -3
- data/lib/datadog/core/transport/http.rb +39 -2
- data/lib/datadog/di/component.rb +0 -2
- data/lib/datadog/di/probe_notification_builder.rb +1 -1
- data/lib/datadog/di/probe_notifier_worker.rb +16 -16
- data/lib/datadog/di/transport/diagnostics.rb +4 -3
- data/lib/datadog/di/transport/http/api.rb +2 -12
- data/lib/datadog/di/transport/http/client.rb +4 -3
- data/lib/datadog/di/transport/http/diagnostics.rb +7 -34
- data/lib/datadog/di/transport/http/input.rb +7 -34
- data/lib/datadog/di/transport/http.rb +14 -62
- data/lib/datadog/di/transport/input.rb +4 -3
- data/lib/datadog/di/utils.rb +5 -0
- data/lib/datadog/kit/appsec/events.rb +12 -0
- data/lib/datadog/kit/identity.rb +5 -1
- data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
- data/lib/datadog/opentelemetry/api/baggage.rbs +26 -0
- data/lib/datadog/opentelemetry/api/context.rb +16 -2
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +1 -1
- data/lib/datadog/opentelemetry.rb +2 -1
- data/lib/datadog/profiling/collectors/info.rb +3 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +1 -1
- data/lib/datadog/profiling/encoded_profile.rb +11 -0
- data/lib/datadog/profiling/exporter.rb +2 -3
- data/lib/datadog/profiling/ext.rb +0 -1
- data/lib/datadog/profiling/flush.rb +4 -7
- data/lib/datadog/profiling/http_transport.rb +10 -59
- data/lib/datadog/profiling/stack_recorder.rb +4 -4
- data/lib/datadog/profiling.rb +6 -2
- data/lib/datadog/tracing/component.rb +15 -12
- data/lib/datadog/tracing/configuration/ext.rb +7 -1
- data/lib/datadog/tracing/configuration/settings.rb +18 -2
- data/lib/datadog/tracing/context_provider.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -5
- data/lib/datadog/tracing/contrib/excon/middleware.rb +5 -3
- data/lib/datadog/tracing/contrib/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +5 -3
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +7 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +0 -15
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +4 -1
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +5 -5
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +5 -11
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +6 -10
- data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +27 -0
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +46 -0
- data/lib/datadog/tracing/contrib/karafka/ext.rb +27 -0
- data/lib/datadog/tracing/contrib/karafka/integration.rb +45 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +66 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +71 -0
- data/lib/datadog/tracing/contrib/karafka.rb +37 -0
- data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +17 -0
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +9 -0
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +5 -3
- data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +6 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
- data/lib/datadog/tracing/contrib.rb +1 -0
- data/lib/datadog/tracing/correlation.rb +9 -2
- data/lib/datadog/tracing/distributed/baggage.rb +131 -0
- data/lib/datadog/tracing/distributed/datadog.rb +2 -0
- data/lib/datadog/tracing/distributed/propagation.rb +25 -4
- data/lib/datadog/tracing/distributed/propagation_policy.rb +42 -0
- data/lib/datadog/tracing/metadata/ext.rb +5 -0
- data/lib/datadog/tracing/sampling/span/rule.rb +0 -1
- data/lib/datadog/tracing/span_event.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +2 -1
- data/lib/datadog/tracing/sync_writer.rb +1 -2
- data/lib/datadog/tracing/trace_digest.rb +9 -2
- data/lib/datadog/tracing/trace_operation.rb +29 -17
- data/lib/datadog/tracing/trace_segment.rb +6 -4
- data/lib/datadog/tracing/tracer.rb +38 -2
- data/lib/datadog/tracing/transport/http/api.rb +2 -10
- data/lib/datadog/tracing/transport/http/client.rb +5 -4
- data/lib/datadog/tracing/transport/http/traces.rb +13 -41
- data/lib/datadog/tracing/transport/http.rb +11 -44
- data/lib/datadog/tracing/transport/trace_formatter.rb +7 -0
- data/lib/datadog/tracing/transport/traces.rb +26 -9
- data/lib/datadog/tracing/workers/trace_writer.rb +2 -6
- data/lib/datadog/tracing/writer.rb +2 -6
- data/lib/datadog/tracing.rb +16 -3
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +1 -1
- metadata +28 -13
- data/lib/datadog/appsec/contrib/devise/event.rb +0 -54
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +0 -72
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +0 -47
- data/lib/datadog/appsec/contrib/devise/resource.rb +0 -35
- data/lib/datadog/appsec/contrib/devise/tracking.rb +0 -57
- data/lib/datadog/appsec/utils/trace_operation.rb +0 -15
@@ -12,7 +12,25 @@ module Datadog
|
|
12
12
|
class << self
|
13
13
|
def build_appsec_component(settings, telemetry:)
|
14
14
|
return if !settings.respond_to?(:appsec) || !settings.appsec.enabled
|
15
|
-
|
15
|
+
|
16
|
+
ffi_version = Gem.loaded_specs['ffi'] && Gem.loaded_specs['ffi'].version
|
17
|
+
unless ffi_version
|
18
|
+
Datadog.logger.warn('FFI gem is not loaded, AppSec will be disabled.')
|
19
|
+
telemetry.error('AppSec: Component not loaded, due to missing FFI gem')
|
20
|
+
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3') && ffi_version < Gem::Version.new('1.16.0')
|
25
|
+
Datadog.logger.warn(
|
26
|
+
'AppSec is not supported in Ruby versions above 3.3.0 when using `ffi` versions older than 1.16.0, ' \
|
27
|
+
'and will be forcibly disabled due to a memory leak in `ffi`. ' \
|
28
|
+
'Please upgrade your `ffi` version to 1.16.0 or higher.'
|
29
|
+
)
|
30
|
+
telemetry.error('AppSec: Component not loaded, ffi version is leaky with ruby > 3.3.0')
|
31
|
+
|
32
|
+
return
|
33
|
+
end
|
16
34
|
|
17
35
|
processor = create_processor(settings, telemetry)
|
18
36
|
|
@@ -29,22 +47,6 @@ module Datadog
|
|
29
47
|
|
30
48
|
private
|
31
49
|
|
32
|
-
def incompatible_ffi_version?
|
33
|
-
ffi_version = Gem.loaded_specs['ffi'] && Gem.loaded_specs['ffi'].version
|
34
|
-
return true unless ffi_version
|
35
|
-
|
36
|
-
return false unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3') &&
|
37
|
-
ffi_version < Gem::Version.new('1.16.0')
|
38
|
-
|
39
|
-
Datadog.logger.warn(
|
40
|
-
'AppSec is not supported in Ruby versions above 3.3.0 when using `ffi` versions older than 1.16.0, ' \
|
41
|
-
'and will be forcibly disabled due to a memory leak in `ffi`. ' \
|
42
|
-
'Please upgrade your `ffi` version to 1.16.0 or higher.'
|
43
|
-
)
|
44
|
-
|
45
|
-
true
|
46
|
-
end
|
47
|
-
|
48
50
|
def create_processor(settings, telemetry)
|
49
51
|
rules = AppSec::Processor::RuleLoader.load_rules(
|
50
52
|
telemetry: telemetry,
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'zlib'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
require_relative '../core/utils/base64'
|
8
|
+
|
9
|
+
module Datadog
|
10
|
+
module AppSec
|
11
|
+
# Converts derivative schema payloads into JSON and compresses them into a
|
12
|
+
# base64 encoded string if the payload is worth compressing.
|
13
|
+
#
|
14
|
+
# See: https://github.com/DataDog/dd-trace-rb/pull/3177#issuecomment-1747221082
|
15
|
+
module CompressedJson
|
16
|
+
MIN_SIZE_FOR_COMPRESSION = 260
|
17
|
+
|
18
|
+
def self.dump(payload)
|
19
|
+
value = JSON.dump(payload)
|
20
|
+
return value if value.bytesize < MIN_SIZE_FOR_COMPRESSION
|
21
|
+
|
22
|
+
compress_and_encode(value)
|
23
|
+
rescue ArgumentError, Encoding::UndefinedConversionError, JSON::JSONError => e
|
24
|
+
AppSec.telemetry.report(e, description: 'AppSec: Failed to convert value into JSON')
|
25
|
+
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private_class_method def self.compress_and_encode(payload)
|
30
|
+
Core::Utils::Base64.strict_encode64(
|
31
|
+
Zlib.gzip(payload, level: Zlib::BEST_SPEED, strategy: Zlib::DEFAULT_STRATEGY)
|
32
|
+
)
|
33
|
+
rescue Zlib::Error, TypeError => e
|
34
|
+
AppSec.telemetry.report(e, description: 'AppSec: Failed to compress and encode value')
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -164,6 +164,66 @@ module Datadog
|
|
164
164
|
end
|
165
165
|
end
|
166
166
|
|
167
|
+
settings :stack_trace do
|
168
|
+
option :enabled do |o|
|
169
|
+
o.type :bool
|
170
|
+
o.env 'DD_APPSEC_STACK_TRACE_ENABLED'
|
171
|
+
o.default true
|
172
|
+
end
|
173
|
+
|
174
|
+
# The maximum number of stack trace frames to collect for each stack trace.
|
175
|
+
#
|
176
|
+
# If the stack trace exceeds this limit, the frames are dropped from the middle of the stack trace:
|
177
|
+
# 75% of the frames are kept from the top of the stack trace and 25% from the bottom
|
178
|
+
# (this percentage is also configurable).
|
179
|
+
#
|
180
|
+
# Minimum value is 10.
|
181
|
+
# Set to zero if you don't want any frames to be dropped.
|
182
|
+
#
|
183
|
+
# Default value is 32
|
184
|
+
option :max_depth do |o|
|
185
|
+
o.type :int
|
186
|
+
o.env 'DD_APPSEC_MAX_STACK_TRACE_DEPTH'
|
187
|
+
o.default 32
|
188
|
+
|
189
|
+
o.setter do |value|
|
190
|
+
value = 0 if value < 0
|
191
|
+
value
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# The percentage of frames to keep from the top of the stack trace.
|
196
|
+
#
|
197
|
+
# Default value is 75
|
198
|
+
option :top_percentage do |o|
|
199
|
+
o.type :int
|
200
|
+
o.env 'DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT'
|
201
|
+
o.default 75
|
202
|
+
|
203
|
+
o.setter do |value|
|
204
|
+
value = 100 if value > 100
|
205
|
+
value = 0 if value.negative?
|
206
|
+
value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Maximum number of stack traces to collect per span.
|
211
|
+
#
|
212
|
+
# Set to zero if you want to collect all stack traces.
|
213
|
+
#
|
214
|
+
# Default value is 2
|
215
|
+
option :max_stack_traces do |o|
|
216
|
+
o.type :int
|
217
|
+
o.env 'DD_APPSEC_MAX_STACK_TRACES'
|
218
|
+
o.default 2
|
219
|
+
|
220
|
+
o.setter do |value|
|
221
|
+
value = 0 if value < 0
|
222
|
+
value
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
167
227
|
settings :auto_user_instrumentation do
|
168
228
|
define_method(:enabled?) { get_option(:mode) != DISABLED_AUTO_USER_INSTRUMENTATION_MODE }
|
169
229
|
|
@@ -178,10 +238,10 @@ module Datadog
|
|
178
238
|
Datadog.logger.warn(
|
179
239
|
'The appsec.auto_user_instrumentation.mode value provided is not supported. ' \
|
180
240
|
"Supported values are: #{AUTO_USER_INSTRUMENTATION_MODES.join(' | ')}. " \
|
181
|
-
"Using
|
241
|
+
"Using value: #{DISABLED_AUTO_USER_INSTRUMENTATION_MODE}."
|
182
242
|
)
|
183
243
|
|
184
|
-
|
244
|
+
DISABLED_AUTO_USER_INSTRUMENTATION_MODE
|
185
245
|
end
|
186
246
|
end
|
187
247
|
end
|
@@ -259,14 +319,6 @@ module Datadog
|
|
259
319
|
o.type :bool, nilable: true
|
260
320
|
o.env 'DD_APPSEC_SCA_ENABLED'
|
261
321
|
end
|
262
|
-
|
263
|
-
settings :standalone do
|
264
|
-
option :enabled do |o|
|
265
|
-
o.type :bool
|
266
|
-
o.env 'DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED'
|
267
|
-
o.default false
|
268
|
-
end
|
269
|
-
end
|
270
322
|
end
|
271
323
|
end
|
272
324
|
end
|
@@ -13,7 +13,7 @@ module Datadog
|
|
13
13
|
|
14
14
|
MINIMUM_VERSION = Gem::Version.new('4')
|
15
15
|
|
16
|
-
register_as :active_record, auto_patch:
|
16
|
+
register_as :active_record, auto_patch: true
|
17
17
|
|
18
18
|
def self.version
|
19
19
|
Gem.loaded_specs['activerecord'] && Gem.loaded_specs['activerecord'].version
|
@@ -9,7 +9,7 @@ module Datadog
|
|
9
9
|
def self.patch_all
|
10
10
|
integrations = []
|
11
11
|
|
12
|
-
Datadog::AppSec::Contrib::Integration.registry.
|
12
|
+
Datadog::AppSec::Contrib::Integration.registry.each_value do |integration|
|
13
13
|
next unless integration.klass.auto_instrument?
|
14
14
|
|
15
15
|
integrations << integration.name
|
@@ -7,19 +7,11 @@ module Datadog
|
|
7
7
|
# A temporary configuration module to accomodate new RFC changes.
|
8
8
|
# NOTE: DEV-3 Remove module
|
9
9
|
module Configuration
|
10
|
-
|
11
|
-
|
12
|
-
AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE =>
|
10
|
+
TRACK_USER_EVENTS_CONVERSION_RULES = {
|
11
|
+
AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE =>
|
13
12
|
AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE,
|
14
|
-
|
13
|
+
AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE =>
|
15
14
|
AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
|
16
|
-
}.freeze,
|
17
|
-
auto_instrumentation_to_track_user: {
|
18
|
-
AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE =>
|
19
|
-
AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE,
|
20
|
-
AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE =>
|
21
|
-
AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE
|
22
|
-
}.freeze
|
23
15
|
}.freeze
|
24
16
|
|
25
17
|
module_function
|
@@ -44,30 +36,14 @@ module Datadog
|
|
44
36
|
appsec.auto_user_instrumentation.mode
|
45
37
|
appsec.track_user_events.mode
|
46
38
|
|
47
|
-
if !appsec.
|
48
|
-
appsec.
|
49
|
-
return
|
50
|
-
end
|
51
|
-
|
52
|
-
if appsec.auto_user_instrumentation.options[:mode].default_precedence?
|
53
|
-
return MODES_CONVERSION_RULES[:track_user_to_auto_instrumentation].fetch(
|
39
|
+
if !appsec.track_user_events.options[:mode].default_precedence? &&
|
40
|
+
appsec.auto_user_instrumentation.options[:mode].default_precedence?
|
41
|
+
return TRACK_USER_EVENTS_CONVERSION_RULES.fetch(
|
54
42
|
appsec.track_user_events.mode, appsec.auto_user_instrumentation.mode
|
55
43
|
)
|
56
44
|
end
|
57
45
|
|
58
|
-
|
59
|
-
if appsec.auto_user_instrumentation.mode == identification_mode ||
|
60
|
-
appsec.track_user_events.mode == AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE
|
61
|
-
return identification_mode
|
62
|
-
end
|
63
|
-
|
64
|
-
AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE
|
65
|
-
end
|
66
|
-
|
67
|
-
# NOTE: Remove in next version of tracking
|
68
|
-
def track_user_events_mode
|
69
|
-
MODES_CONVERSION_RULES[:auto_instrumentation_to_track_user]
|
70
|
-
.fetch(auto_user_instrumentation_mode, Datadog.configuration.appsec.track_user_events.mode)
|
46
|
+
appsec.auto_user_instrumentation.mode
|
71
47
|
end
|
72
48
|
end
|
73
49
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../anonymizer'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module AppSec
|
7
|
+
module Contrib
|
8
|
+
module Devise
|
9
|
+
# Extracts user identification data from Devise resources.
|
10
|
+
# Supports both regular and anonymized data extraction modes.
|
11
|
+
class DataExtractor
|
12
|
+
PRIORITY_ORDERED_ID_KEYS = [:id, 'id', :uuid, 'uuid'].freeze
|
13
|
+
PRIORITY_ORDERED_LOGIN_KEYS = [:email, 'email', :username, 'username', :login, 'login'].freeze
|
14
|
+
|
15
|
+
def initialize(mode:)
|
16
|
+
@mode = mode
|
17
|
+
@devise_scopes = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_id(object)
|
21
|
+
return if object.nil?
|
22
|
+
|
23
|
+
if object.respond_to?(:[])
|
24
|
+
id = object[PRIORITY_ORDERED_ID_KEYS.find { |key| object[key] }]
|
25
|
+
scope = find_devise_scope(object)
|
26
|
+
|
27
|
+
id = "#{scope}:#{id}" if id && scope
|
28
|
+
return transform(id)
|
29
|
+
end
|
30
|
+
|
31
|
+
id = object.id if object.respond_to?(:id)
|
32
|
+
id ||= object.uuid if object.respond_to?(:uuid)
|
33
|
+
|
34
|
+
scope = find_devise_scope(object)
|
35
|
+
id = "#{scope}:#{id}" if id && scope
|
36
|
+
|
37
|
+
transform(id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_login(object)
|
41
|
+
return if object.nil?
|
42
|
+
|
43
|
+
if object.respond_to?(:[])
|
44
|
+
login = object[PRIORITY_ORDERED_LOGIN_KEYS.find { |key| object[key] }]
|
45
|
+
return transform(login)
|
46
|
+
end
|
47
|
+
|
48
|
+
login = object.email if object.respond_to?(:email)
|
49
|
+
login ||= object.username if object.respond_to?(:username)
|
50
|
+
login ||= object.login if object.respond_to?(:login)
|
51
|
+
|
52
|
+
transform(login)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def find_devise_scope(object)
|
58
|
+
return if ::Devise.mappings.count == 1
|
59
|
+
|
60
|
+
@devise_scopes[object.class.name] ||= begin
|
61
|
+
::Devise.mappings.each_value.find { |mapping| mapping.class_name == object.class.name }&.name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def transform(value)
|
66
|
+
return if value.nil?
|
67
|
+
return value.to_s unless anonymize?
|
68
|
+
|
69
|
+
Anonymizer.anonymize(value.to_s)
|
70
|
+
end
|
71
|
+
|
72
|
+
def anonymize?
|
73
|
+
@mode == AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -6,6 +6,27 @@ module Datadog
|
|
6
6
|
module Devise
|
7
7
|
# Devise integration constants
|
8
8
|
module Ext
|
9
|
+
EVENT_LOGIN_SUCCESS = 'users.login.success'
|
10
|
+
EVENT_LOGIN_FAILURE = 'users.login.failure'
|
11
|
+
EVENT_SIGNUP = 'users.signup'
|
12
|
+
|
13
|
+
TAG_DD_USR_ID = '_dd.appsec.usr.id'
|
14
|
+
TAG_DD_USR_LOGIN = '_dd.appsec.usr.login'
|
15
|
+
TAG_DD_SIGNUP_MODE = '_dd.appsec.events.users.signup.auto.mode'
|
16
|
+
TAG_DD_COLLECTION_MODE = '_dd.appsec.user.collection_mode'
|
17
|
+
TAG_DD_LOGIN_SUCCESS_MODE = '_dd.appsec.events.users.login.success.auto.mode'
|
18
|
+
TAG_DD_LOGIN_FAILURE_MODE = '_dd.appsec.events.users.login.failure.auto.mode'
|
19
|
+
|
20
|
+
TAG_USR_ID = 'usr.id'
|
21
|
+
TAG_SIGNUP_TRACK = 'appsec.events.users.signup.track'
|
22
|
+
TAG_SIGNUP_USR_ID = 'appsec.events.users.signup.usr.id'
|
23
|
+
TAG_SIGNUP_USR_LOGIN = 'appsec.events.users.signup.usr.login'
|
24
|
+
TAG_LOGIN_FAILURE_TRACK = 'appsec.events.users.login.failure.track'
|
25
|
+
TAG_LOGIN_FAILURE_USR_ID = 'appsec.events.users.login.failure.usr.id'
|
26
|
+
TAG_LOGIN_FAILURE_USR_LOGIN = 'appsec.events.users.login.failure.usr.login'
|
27
|
+
TAG_LOGIN_FAILURE_USR_EXISTS = 'appsec.events.users.login.failure.usr.exists'
|
28
|
+
TAG_LOGIN_SUCCESS_TRACK = 'appsec.events.users.login.success.track'
|
29
|
+
TAG_LOGIN_SUCCESS_USR_LOGIN = 'appsec.events.users.login.success.usr.login'
|
9
30
|
end
|
10
31
|
end
|
11
32
|
end
|
@@ -1,15 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
|
5
|
-
require_relative '
|
3
|
+
require_relative '../../../core/utils/only_once'
|
4
|
+
|
5
|
+
require_relative 'tracking_middleware'
|
6
|
+
require_relative 'patches/signup_tracking_patch'
|
7
|
+
require_relative 'patches/signin_tracking_patch'
|
8
|
+
require_relative 'patches/skip_signin_tracking_patch'
|
6
9
|
|
7
10
|
module Datadog
|
8
11
|
module AppSec
|
9
12
|
module Contrib
|
10
13
|
module Devise
|
11
|
-
#
|
14
|
+
# Devise patcher
|
12
15
|
module Patcher
|
16
|
+
GUARD_ONCE_PER_APP = Hash.new do |hash, key|
|
17
|
+
hash[key] = Datadog::Core::Utils::OnlyOnce.new
|
18
|
+
end
|
19
|
+
|
13
20
|
module_function
|
14
21
|
|
15
22
|
def patched?
|
@@ -21,29 +28,35 @@ module Datadog
|
|
21
28
|
end
|
22
29
|
|
23
30
|
def patch
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
31
|
+
::ActiveSupport.on_load(:before_initialize) do |app|
|
32
|
+
GUARD_ONCE_PER_APP[app].run do
|
33
|
+
begin
|
34
|
+
app.middleware.insert_after(Warden::Manager, TrackingMiddleware)
|
35
|
+
rescue RuntimeError
|
36
|
+
AppSec.telemetry.error('AppSec: unable to insert Devise TrackingMiddleware')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
34
40
|
|
35
|
-
|
36
|
-
|
41
|
+
::ActiveSupport.on_load(:after_initialize) do
|
42
|
+
if ::Devise::RegistrationsController.descendants.empty?
|
43
|
+
::Devise::RegistrationsController.prepend(Patches::SignupTrackingPatch)
|
44
|
+
else
|
45
|
+
::Devise::RegistrationsController.descendants.each do |controller|
|
46
|
+
controller.prepend(Patches::SignupTrackingPatch)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
37
50
|
|
38
|
-
|
39
|
-
::Devise::Models::Rememberable # rubocop:disable Lint/Void
|
40
|
-
::Devise::Strategies::Rememberable.prepend(RememberablePatch)
|
41
|
-
end
|
51
|
+
::Devise::Strategies::Authenticatable.prepend(Patches::SigninTrackingPatch)
|
42
52
|
|
43
|
-
|
44
|
-
|
45
|
-
|
53
|
+
if ::Devise::STRATEGIES.include?(:rememberable)
|
54
|
+
# Rememberable strategy is required in autoloaded Rememberable model
|
55
|
+
require 'devise/models/rememberable'
|
56
|
+
::Devise::Strategies::Rememberable.prepend(Patches::SkipSigninTrackingPatch)
|
46
57
|
end
|
58
|
+
|
59
|
+
Patcher.instance_variable_set(:@patched, true)
|
47
60
|
end
|
48
61
|
end
|
49
62
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../ext'
|
4
|
+
require_relative '../configuration'
|
5
|
+
require_relative '../data_extractor'
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module AppSec
|
9
|
+
module Contrib
|
10
|
+
module Devise
|
11
|
+
module Patches
|
12
|
+
# A patch for Devise::Authenticatable strategy with tracking functionality
|
13
|
+
module SigninTrackingPatch
|
14
|
+
def validate(resource, &block)
|
15
|
+
result = super
|
16
|
+
|
17
|
+
return result unless AppSec.enabled?
|
18
|
+
return result if @_datadog_appsec_skip_track_login_event
|
19
|
+
return result unless Configuration.auto_user_instrumentation_enabled?
|
20
|
+
return result unless AppSec.active_context
|
21
|
+
|
22
|
+
context = AppSec.active_context
|
23
|
+
if context.trace.nil? || context.span.nil?
|
24
|
+
Datadog.logger.debug { 'AppSec: unable to track signin events, due to missing trace or span' }
|
25
|
+
return result
|
26
|
+
end
|
27
|
+
|
28
|
+
context.trace.keep!
|
29
|
+
|
30
|
+
if result
|
31
|
+
record_successful_signin(context, resource)
|
32
|
+
Instrumentation.gateway.push('appsec.events.user_lifecycle', Ext::EVENT_LOGIN_SUCCESS)
|
33
|
+
|
34
|
+
return result
|
35
|
+
end
|
36
|
+
|
37
|
+
record_failed_signin(context, resource)
|
38
|
+
Instrumentation.gateway.push('appsec.events.user_lifecycle', Ext::EVENT_LOGIN_FAILURE)
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def record_successful_signin(context, resource)
|
46
|
+
extractor = DataExtractor.new(mode: Configuration.auto_user_instrumentation_mode)
|
47
|
+
|
48
|
+
id = extractor.extract_id(resource)
|
49
|
+
login = extractor.extract_login(authentication_hash) || extractor.extract_login(resource)
|
50
|
+
|
51
|
+
if id
|
52
|
+
context.span[Ext::TAG_USR_ID] ||= id
|
53
|
+
context.span[Ext::TAG_DD_USR_ID] = id
|
54
|
+
end
|
55
|
+
|
56
|
+
context.span[Ext::TAG_LOGIN_SUCCESS_USR_LOGIN] ||= login
|
57
|
+
context.span[Ext::TAG_LOGIN_SUCCESS_TRACK] = 'true'
|
58
|
+
context.span[Ext::TAG_DD_USR_LOGIN] = login
|
59
|
+
context.span[Ext::TAG_DD_LOGIN_SUCCESS_MODE] = Configuration.auto_user_instrumentation_mode
|
60
|
+
|
61
|
+
# NOTE: We don't have a way to make one-shot receivers for events,
|
62
|
+
# and because of that we will trigger an additional event even
|
63
|
+
# if it was already done via the SDK
|
64
|
+
AppSec::Instrumentation.gateway.push(
|
65
|
+
'identity.set_user', AppSec::Instrumentation::Gateway::User.new(id, login)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def record_failed_signin(context, resource)
|
70
|
+
extractor = DataExtractor.new(mode: Configuration.auto_user_instrumentation_mode)
|
71
|
+
|
72
|
+
context.span[Ext::TAG_LOGIN_FAILURE_TRACK] = 'true'
|
73
|
+
context.span[Ext::TAG_DD_LOGIN_FAILURE_MODE] = Configuration.auto_user_instrumentation_mode
|
74
|
+
|
75
|
+
unless resource
|
76
|
+
login = extractor.extract_login(authentication_hash)
|
77
|
+
|
78
|
+
context.span[Ext::TAG_DD_USR_LOGIN] = login
|
79
|
+
context.span[Ext::TAG_LOGIN_FAILURE_USR_LOGIN] ||= login
|
80
|
+
context.span[Ext::TAG_LOGIN_FAILURE_USR_EXISTS] ||= 'false'
|
81
|
+
|
82
|
+
return
|
83
|
+
end
|
84
|
+
|
85
|
+
id = extractor.extract_id(resource)
|
86
|
+
login = extractor.extract_login(authentication_hash) || extractor.extract_login(resource)
|
87
|
+
|
88
|
+
if id
|
89
|
+
context.span[Ext::TAG_DD_USR_ID] = id
|
90
|
+
context.span[Ext::TAG_LOGIN_FAILURE_USR_ID] ||= id
|
91
|
+
end
|
92
|
+
|
93
|
+
context.span[Ext::TAG_DD_USR_LOGIN] = login
|
94
|
+
context.span[Ext::TAG_LOGIN_FAILURE_USR_LOGIN] ||= login
|
95
|
+
context.span[Ext::TAG_LOGIN_FAILURE_USR_EXISTS] ||= 'true'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../ext'
|
4
|
+
require_relative '../configuration'
|
5
|
+
require_relative '../data_extractor'
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module AppSec
|
9
|
+
module Contrib
|
10
|
+
module Devise
|
11
|
+
module Patches
|
12
|
+
# A patch for Devise::RegistrationsController with tracking functionality
|
13
|
+
module SignupTrackingPatch
|
14
|
+
def create
|
15
|
+
return super unless AppSec.enabled?
|
16
|
+
return super unless Configuration.auto_user_instrumentation_enabled?
|
17
|
+
return super unless AppSec.active_context
|
18
|
+
|
19
|
+
super do |resource|
|
20
|
+
context = AppSec.active_context
|
21
|
+
|
22
|
+
if context.trace.nil? || context.span.nil?
|
23
|
+
Datadog.logger.debug { 'AppSec: unable to track signup events, due to missing trace or span' }
|
24
|
+
next yield(resource) if block_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
next yield(resource) if resource.new_record? && block_given?
|
28
|
+
|
29
|
+
context.trace.keep!
|
30
|
+
record_successful_signup(context, resource)
|
31
|
+
Instrumentation.gateway.push('appsec.events.user_lifecycle', Ext::EVENT_SIGNUP)
|
32
|
+
|
33
|
+
yield(resource) if block_given?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def record_successful_signup(context, resource)
|
40
|
+
extractor = DataExtractor.new(mode: Configuration.auto_user_instrumentation_mode)
|
41
|
+
|
42
|
+
id = extractor.extract_id(resource)
|
43
|
+
login = extractor.extract_login(resource_params) || extractor.extract_login(resource)
|
44
|
+
|
45
|
+
context.span[Ext::TAG_SIGNUP_TRACK] = 'true'
|
46
|
+
context.span[Ext::TAG_DD_USR_LOGIN] = login
|
47
|
+
context.span[Ext::TAG_SIGNUP_USR_LOGIN] ||= login
|
48
|
+
context.span[Ext::TAG_DD_SIGNUP_MODE] = Configuration.auto_user_instrumentation_mode
|
49
|
+
|
50
|
+
if id
|
51
|
+
context.span[Ext::TAG_DD_USR_ID] = id
|
52
|
+
|
53
|
+
id_tag = resource.active_for_authentication? ? Ext::TAG_USR_ID : Ext::TAG_SIGNUP_USR_ID
|
54
|
+
context.span[id_tag] ||= id
|
55
|
+
end
|
56
|
+
|
57
|
+
# NOTE: We don't have a way to make one-shot receivers for events,
|
58
|
+
# and because of that we will trigger an additional event even
|
59
|
+
# if it was already done via the SDK
|
60
|
+
AppSec::Instrumentation.gateway.push(
|
61
|
+
'identity.set_user', AppSec::Instrumentation::Gateway::User.new(id, login)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -4,10 +4,10 @@ module Datadog
|
|
4
4
|
module AppSec
|
5
5
|
module Contrib
|
6
6
|
module Devise
|
7
|
-
module
|
7
|
+
module Patches
|
8
8
|
# To avoid tracking new sessions that are created by
|
9
9
|
# Rememberable strategy as Login Success events.
|
10
|
-
module
|
10
|
+
module SkipSigninTrackingPatch
|
11
11
|
def validate(*args)
|
12
12
|
@_datadog_appsec_skip_track_login_event = true
|
13
13
|
|