datadog 2.14.0 → 2.16.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.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -1
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +7 -6
  4. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +1 -4
  5. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
  6. data/ext/datadog_profiling_native_extension/encoded_profile.c +69 -0
  7. data/ext/datadog_profiling_native_extension/encoded_profile.h +7 -0
  8. data/ext/datadog_profiling_native_extension/extconf.rb +3 -0
  9. data/ext/datadog_profiling_native_extension/heap_recorder.c +8 -1
  10. data/ext/datadog_profiling_native_extension/http_transport.c +25 -32
  11. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  12. data/ext/datadog_profiling_native_extension/stack_recorder.c +22 -21
  13. data/ext/libdatadog_api/crashtracker.c +1 -9
  14. data/ext/libdatadog_api/crashtracker.h +5 -0
  15. data/ext/libdatadog_api/datadog_ruby_common.c +1 -4
  16. data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
  17. data/ext/libdatadog_api/init.c +15 -0
  18. data/ext/libdatadog_api/library_config.c +122 -0
  19. data/ext/libdatadog_api/library_config.h +19 -0
  20. data/ext/libdatadog_api/process_discovery.c +117 -0
  21. data/ext/libdatadog_api/process_discovery.h +5 -0
  22. data/lib/datadog/appsec/actions_handler.rb +3 -2
  23. data/lib/datadog/appsec/assets/waf_rules/README.md +50 -5
  24. data/lib/datadog/appsec/assets/waf_rules/processors.json +239 -10
  25. data/lib/datadog/appsec/assets/waf_rules/scanners.json +926 -17
  26. data/lib/datadog/appsec/autoload.rb +1 -1
  27. data/lib/datadog/appsec/component.rb +29 -20
  28. data/lib/datadog/appsec/compressed_json.rb +40 -0
  29. data/lib/datadog/appsec/configuration/settings.rb +31 -18
  30. data/lib/datadog/appsec/context.rb +1 -1
  31. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +10 -12
  32. data/lib/datadog/appsec/contrib/active_record/integration.rb +2 -2
  33. data/lib/datadog/appsec/contrib/active_record/patcher.rb +22 -22
  34. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +2 -3
  35. data/lib/datadog/appsec/contrib/devise/ext.rb +1 -0
  36. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/devise/patcher.rb +3 -5
  38. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +17 -4
  39. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  40. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +9 -10
  41. data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
  42. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +8 -9
  43. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +8 -9
  44. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
  45. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +22 -32
  46. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  47. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +16 -16
  48. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +11 -13
  49. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  50. data/lib/datadog/appsec/contrib/rails/patcher.rb +21 -21
  51. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  52. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +10 -11
  53. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +17 -23
  54. data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
  55. data/lib/datadog/appsec/event.rb +95 -134
  56. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +5 -2
  57. data/lib/datadog/appsec/metrics/telemetry.rb +1 -1
  58. data/lib/datadog/appsec/monitor/gateway/watcher.rb +42 -12
  59. data/lib/datadog/appsec/processor/rule_loader.rb +26 -28
  60. data/lib/datadog/appsec/processor/rule_merger.rb +5 -5
  61. data/lib/datadog/appsec/processor.rb +1 -1
  62. data/lib/datadog/appsec/remote.rb +16 -11
  63. data/lib/datadog/appsec/response.rb +6 -6
  64. data/lib/datadog/appsec/security_engine/runner.rb +1 -1
  65. data/lib/datadog/appsec/security_event.rb +39 -0
  66. data/lib/datadog/appsec.rb +1 -1
  67. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
  68. data/lib/datadog/core/configuration/components.rb +19 -10
  69. data/lib/datadog/core/configuration/option.rb +61 -25
  70. data/lib/datadog/core/configuration/settings.rb +10 -0
  71. data/lib/datadog/core/configuration/stable_config.rb +23 -0
  72. data/lib/datadog/core/configuration.rb +24 -0
  73. data/lib/datadog/core/crashtracking/component.rb +1 -9
  74. data/lib/datadog/core/diagnostics/environment_logger.rb +1 -1
  75. data/lib/datadog/core/environment/git.rb +1 -0
  76. data/lib/datadog/core/environment/variable_helpers.rb +1 -1
  77. data/lib/datadog/core/metrics/client.rb +8 -7
  78. data/lib/datadog/core/process_discovery.rb +32 -0
  79. data/lib/datadog/core/remote/client.rb +7 -0
  80. data/lib/datadog/core/runtime/metrics.rb +1 -1
  81. data/lib/datadog/core/telemetry/component.rb +60 -50
  82. data/lib/datadog/core/telemetry/emitter.rb +17 -11
  83. data/lib/datadog/core/telemetry/event.rb +7 -4
  84. data/lib/datadog/core/telemetry/http/adapters/net.rb +12 -97
  85. data/lib/datadog/core/telemetry/metric.rb +5 -5
  86. data/lib/datadog/core/telemetry/request.rb +4 -4
  87. data/lib/datadog/core/telemetry/transport/http/api.rb +43 -0
  88. data/lib/datadog/core/telemetry/transport/http/client.rb +49 -0
  89. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +92 -0
  90. data/lib/datadog/core/telemetry/transport/http.rb +63 -0
  91. data/lib/datadog/core/telemetry/transport/telemetry.rb +52 -0
  92. data/lib/datadog/core/telemetry/worker.rb +45 -0
  93. data/lib/datadog/core/utils/time.rb +12 -0
  94. data/lib/datadog/core/workers/async.rb +20 -2
  95. data/lib/datadog/core/workers/interval_loop.rb +12 -1
  96. data/lib/datadog/core/workers/runtime_metrics.rb +2 -2
  97. data/lib/datadog/core.rb +8 -0
  98. data/lib/datadog/di/boot.rb +34 -0
  99. data/lib/datadog/di/probe_notification_builder.rb +1 -1
  100. data/lib/datadog/di/remote.rb +2 -0
  101. data/lib/datadog/di/transport/http/diagnostics.rb +0 -1
  102. data/lib/datadog/di/transport/http/input.rb +0 -1
  103. data/lib/datadog/di/transport/http.rb +0 -6
  104. data/lib/datadog/di.rb +5 -32
  105. data/lib/datadog/error_tracking/collector.rb +87 -0
  106. data/lib/datadog/error_tracking/component.rb +167 -0
  107. data/lib/datadog/error_tracking/configuration/settings.rb +63 -0
  108. data/lib/datadog/error_tracking/configuration.rb +11 -0
  109. data/lib/datadog/error_tracking/ext.rb +18 -0
  110. data/lib/datadog/error_tracking/extensions.rb +16 -0
  111. data/lib/datadog/error_tracking/filters.rb +77 -0
  112. data/lib/datadog/error_tracking.rb +18 -0
  113. data/lib/datadog/kit/identity.rb +1 -1
  114. data/lib/datadog/profiling/collectors/info.rb +3 -0
  115. data/lib/datadog/profiling/encoded_profile.rb +11 -0
  116. data/lib/datadog/profiling/exporter.rb +3 -4
  117. data/lib/datadog/profiling/ext.rb +0 -1
  118. data/lib/datadog/profiling/flush.rb +4 -7
  119. data/lib/datadog/profiling/http_transport.rb +10 -59
  120. data/lib/datadog/profiling/stack_recorder.rb +4 -4
  121. data/lib/datadog/profiling.rb +1 -0
  122. data/lib/datadog/tracing/analytics.rb +1 -1
  123. data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
  124. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
  125. data/lib/datadog/tracing/contrib/karafka/monitor.rb +1 -1
  126. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +8 -0
  127. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -0
  128. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +18 -1
  129. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +17 -0
  130. data/lib/datadog/tracing/contrib/opensearch/ext.rb +9 -0
  131. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +5 -1
  132. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
  133. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
  134. data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
  135. data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
  136. data/lib/datadog/tracing/distributed/datadog.rb +2 -2
  137. data/lib/datadog/tracing/sampling/rate_sampler.rb +2 -1
  138. data/lib/datadog/tracing/span_event.rb +1 -1
  139. data/lib/datadog/tracing/span_operation.rb +38 -14
  140. data/lib/datadog/tracing/trace_operation.rb +15 -7
  141. data/lib/datadog/tracing/tracer.rb +7 -3
  142. data/lib/datadog/tracing/utils.rb +1 -1
  143. data/lib/datadog/version.rb +1 -1
  144. data/lib/datadog.rb +2 -3
  145. metadata +40 -10
  146. data/lib/datadog/core/telemetry/http/env.rb +0 -20
  147. data/lib/datadog/core/telemetry/http/ext.rb +0 -28
  148. data/lib/datadog/core/telemetry/http/response.rb +0 -70
  149. data/lib/datadog/core/telemetry/http/transport.rb +0 -90
@@ -4,7 +4,7 @@ if %w[1 true].include?((ENV['DD_APPSEC_ENABLED'] || '').downcase)
4
4
  begin
5
5
  require_relative 'contrib/auto_instrument'
6
6
  Datadog::AppSec::Contrib::AutoInstrument.patch_all
7
- rescue StandardError => e
7
+ rescue => e
8
8
  Kernel.warn(
9
9
  '[datadog] AppSec failed to instrument. No security check will be performed. error: ' \
10
10
  " #{e.class.name} #{e.message}"
@@ -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
- return if incompatible_ffi_version?
15
+
16
+ ffi_version = 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,
@@ -59,9 +61,16 @@ module Datadog
59
61
 
60
62
  exclusions = AppSec::Processor::RuleLoader.load_exclusions(ip_passlist: settings.appsec.ip_passlist)
61
63
 
64
+ # NOTE: This is a temporary solution before the RuleMerger refactoring
65
+ # with new RemoteConfig setup
66
+ processors = rules['processors']
67
+ scanners = rules['scanners']
68
+
62
69
  ruleset = AppSec::Processor::RuleMerger.merge(
63
70
  rules: [rules],
64
71
  data: data,
72
+ scanners: scanners,
73
+ processors: processors,
65
74
  exclusions: exclusions,
66
75
  telemetry: telemetry
67
76
  )
@@ -86,13 +95,13 @@ module Datadog
86
95
  @mutex.synchronize do
87
96
  new_processor = Processor.new(ruleset: ruleset, telemetry: telemetry)
88
97
 
89
- if new_processor && new_processor.ready?
98
+ if new_processor&.ready?
90
99
  old_processor = @processor
91
100
 
92
101
  @telemetry = telemetry
93
102
  @processor = new_processor
94
103
 
95
- old_processor.finalize if old_processor
104
+ old_processor&.finalize
96
105
  end
97
106
  end
98
107
  end
@@ -103,7 +112,7 @@ module Datadog
103
112
 
104
113
  def shutdown!
105
114
  @mutex.synchronize do
106
- if processor && processor.ready?
115
+ if processor&.ready?
107
116
  processor.finalize
108
117
  @processor = nil
109
118
  end
@@ -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
@@ -131,9 +131,12 @@ module Datadog
131
131
  o.type :string, nilable: true
132
132
  o.setter do |value|
133
133
  if value
134
- raise(ArgumentError, "appsec.templates.html: file not found: #{value}") unless File.exist?(value)
134
+ unless File.exist?(value)
135
+ raise(ArgumentError,
136
+ "appsec.templates.html: file not found: #{value}")
137
+ end
135
138
 
136
- File.open(value, 'rb', &:read) || ''
139
+ File.binread(value) || ''
137
140
  end
138
141
  end
139
142
  end
@@ -143,9 +146,12 @@ module Datadog
143
146
  o.type :string, nilable: true
144
147
  o.setter do |value|
145
148
  if value
146
- raise(ArgumentError, "appsec.templates.json: file not found: #{value}") unless File.exist?(value)
149
+ unless File.exist?(value)
150
+ raise(ArgumentError,
151
+ "appsec.templates.json: file not found: #{value}")
152
+ end
147
153
 
148
- File.open(value, 'rb', &:read) || ''
154
+ File.binread(value) || ''
149
155
  end
150
156
  end
151
157
  end
@@ -155,9 +161,12 @@ module Datadog
155
161
  o.type :string, nilable: true
156
162
  o.setter do |value|
157
163
  if value
158
- raise(ArgumentError, "appsec.templates.text: file not found: #{value}") unless File.exist?(value)
164
+ unless File.exist?(value)
165
+ raise(ArgumentError,
166
+ "appsec.templates.text: file not found: #{value}")
167
+ end
159
168
 
160
- File.open(value, 'rb', &:read) || ''
169
+ File.binread(value) || ''
161
170
  end
162
171
  end
163
172
  end
@@ -237,7 +246,7 @@ module Datadog
237
246
 
238
247
  Datadog.logger.warn(
239
248
  'The appsec.auto_user_instrumentation.mode value provided is not supported. ' \
240
- "Supported values are: #{AUTO_USER_INSTRUMENTATION_MODES.join(' | ')}. " \
249
+ "Supported values are: #{AUTO_USER_INSTRUMENTATION_MODES.join(" | ")}. " \
241
250
  "Using value: #{DISABLED_AUTO_USER_INSTRUMENTATION_MODE}."
242
251
  )
243
252
 
@@ -259,11 +268,13 @@ module Datadog
259
268
  APPSEC_VALID_TRACK_USER_EVENTS_ENABLED_VALUES.include?(env_value.strip.downcase)
260
269
  end
261
270
  end
262
- o.after_set do
263
- Core.log_deprecation(key: :appsec_track_user_events_enabled) do
264
- 'The appsec.track_user_events.enabled setting has been deprecated for removal. ' \
265
- 'Please remove it from your Datadog.configure block and use ' \
266
- 'appsec.auto_user_instrumentation.mode instead.'
271
+ o.after_set do |_, _, precedence|
272
+ unless precedence == Datadog::Core::Configuration::Option::Precedence::DEFAULT
273
+ Core.log_deprecation(key: :appsec_track_user_events_enabled) do
274
+ 'The appsec.track_user_events.enabled setting is deprecated. ' \
275
+ 'Please remove it from your Datadog.configure block and use ' \
276
+ 'appsec.auto_user_instrumentation.mode instead.'
277
+ end
267
278
  end
268
279
  end
269
280
  end
@@ -280,18 +291,20 @@ module Datadog
280
291
  else
281
292
  Datadog.logger.warn(
282
293
  'The appsec.track_user_events.mode value provided is not supported.' \
283
- "Supported values are: #{APPSEC_VALID_TRACK_USER_EVENTS_MODE.join(' | ')}." \
294
+ "Supported values are: #{APPSEC_VALID_TRACK_USER_EVENTS_MODE.join(" | ")}." \
284
295
  "Using default value: #{SAFE_TRACK_USER_EVENTS_MODE}."
285
296
  )
286
297
 
287
298
  SAFE_TRACK_USER_EVENTS_MODE
288
299
  end
289
300
  end
290
- o.after_set do
291
- Core.log_deprecation(key: :appsec_track_user_events_mode) do
292
- 'The appsec.track_user_events.mode setting has been deprecated for removal. ' \
293
- 'Please remove it from your Datadog.configure block and use ' \
294
- 'appsec.auto_user_instrumentation.mode instead.'
301
+ o.after_set do |_, _, precedence|
302
+ unless precedence == Datadog::Core::Configuration::Option::Precedence::DEFAULT
303
+ Core.log_deprecation(key: :appsec_track_user_events_mode) do
304
+ 'The appsec.track_user_events.mode setting is deprecated. ' \
305
+ 'Please remove it from your Datadog.configure block and use ' \
306
+ 'appsec.auto_user_instrumentation.mode instead.'
307
+ end
295
308
  end
296
309
  end
297
310
  end
@@ -56,7 +56,7 @@ module Datadog
56
56
  end
57
57
 
58
58
  def extract_schema
59
- @waf_runner.run({ 'waf.context.processor' => { 'extract-schema' => true } }, {})
59
+ @waf_runner.run({'waf.context.processor' => {'extract-schema' => true}}, {})
60
60
  end
61
61
 
62
62
  def export_metrics
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../event'
4
+ require_relative '../../security_event'
5
+
3
6
  module Datadog
4
7
  module AppSec
5
8
  module Contrib
@@ -28,18 +31,13 @@ module Datadog
28
31
  result = context.run_rasp(Ext::RASP_SQLI, {}, ephemeral_data, waf_timeout)
29
32
 
30
33
  if result.match?
31
- Datadog::AppSec::Event.tag_and_keep!(context, result)
32
-
33
- event = {
34
- waf_result: result,
35
- trace: context.trace,
36
- span: context.span,
37
- sql: sql,
38
- actions: result.actions
39
- }
40
- context.events << event
41
-
42
- ActionsHandler.handle(result.actions)
34
+ AppSec::Event.tag_and_keep!(context, result)
35
+
36
+ context.events.push(
37
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
38
+ )
39
+
40
+ AppSec::ActionsHandler.handle(result.actions)
43
41
  end
44
42
  end
45
43
 
@@ -13,10 +13,10 @@ module Datadog
13
13
 
14
14
  MINIMUM_VERSION = Gem::Version.new('4')
15
15
 
16
- register_as :active_record, auto_patch: false
16
+ register_as :active_record, auto_patch: true
17
17
 
18
18
  def self.version
19
- Gem.loaded_specs['activerecord'] && Gem.loaded_specs['activerecord'].version
19
+ Gem.loaded_specs['activerecord']&.version
20
20
  end
21
21
 
22
22
  def self.loaded?
@@ -53,43 +53,43 @@ module Datadog
53
53
 
54
54
  def patch_sqlite3_adapter
55
55
  instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
56
- Instrumentation::InternalExecQueryAdapterPatch
57
- elsif ::ActiveRecord.gem_version.segments.first == 4
58
- Instrumentation::Rails4ExecQueryAdapterPatch
59
- else
60
- Instrumentation::ExecQueryAdapterPatch
61
- end
56
+ Instrumentation::InternalExecQueryAdapterPatch
57
+ elsif ::ActiveRecord.gem_version.segments.first == 4
58
+ Instrumentation::Rails4ExecQueryAdapterPatch
59
+ else
60
+ Instrumentation::ExecQueryAdapterPatch
61
+ end
62
62
 
63
63
  ::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
64
64
  end
65
65
 
66
66
  def patch_mysql2_adapter
67
67
  instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
68
- Instrumentation::InternalExecQueryAdapterPatch
69
- elsif ::ActiveRecord.gem_version.segments.first == 4
70
- Instrumentation::Rails4ExecQueryAdapterPatch
71
- else
72
- Instrumentation::ExecQueryAdapterPatch
73
- end
68
+ Instrumentation::InternalExecQueryAdapterPatch
69
+ elsif ::ActiveRecord.gem_version.segments.first == 4
70
+ Instrumentation::Rails4ExecQueryAdapterPatch
71
+ else
72
+ Instrumentation::ExecQueryAdapterPatch
73
+ end
74
74
 
75
75
  ::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
76
76
  end
77
77
 
78
78
  def patch_postgresql_adapter
79
79
  instrumentation_module = if ::ActiveRecord.gem_version.segments.first == 4
80
- Instrumentation::Rails4ExecuteAndClearAdapterPatch
81
- else
82
- Instrumentation::ExecuteAndClearAdapterPatch
83
- end
80
+ Instrumentation::Rails4ExecuteAndClearAdapterPatch
81
+ else
82
+ Instrumentation::ExecuteAndClearAdapterPatch
83
+ end
84
84
 
85
85
  if defined?(::ActiveRecord::ConnectionAdapters::JdbcAdapter)
86
86
  instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
87
- Instrumentation::InternalExecQueryAdapterPatch
88
- elsif ::ActiveRecord.gem_version.segments.first == 4
89
- Instrumentation::Rails4ExecQueryAdapterPatch
90
- else
91
- Instrumentation::ExecQueryAdapterPatch
92
- end
87
+ Instrumentation::InternalExecQueryAdapterPatch
88
+ elsif ::ActiveRecord.gem_version.segments.first == 4
89
+ Instrumentation::Rails4ExecQueryAdapterPatch
90
+ else
91
+ Instrumentation::ExecQueryAdapterPatch
92
+ end
93
93
  end
94
94
 
95
95
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
@@ -57,9 +57,8 @@ module Datadog
57
57
  def find_devise_scope(object)
58
58
  return if ::Devise.mappings.count == 1
59
59
 
60
- @devise_scopes[object.class.name] ||= begin
61
- ::Devise.mappings.each_value.find { |mapping| mapping.class_name == object.class.name }&.name
62
- end
60
+ @devise_scopes[object.class.name] ||= ::Devise.mappings
61
+ .each_value.find { |mapping| mapping.class_name == object.class.name }&.name
63
62
  end
64
63
 
65
64
  def transform(value)
@@ -18,6 +18,7 @@ module Datadog
18
18
  TAG_DD_LOGIN_FAILURE_MODE = '_dd.appsec.events.users.login.failure.auto.mode'
19
19
 
20
20
  TAG_USR_ID = 'usr.id'
21
+ TAG_SESSION_ID = 'usr.session_id'
21
22
  TAG_SIGNUP_TRACK = 'appsec.events.users.signup.track'
22
23
  TAG_SIGNUP_USR_ID = 'appsec.events.users.signup.usr.id'
23
24
  TAG_SIGNUP_USR_LOGIN = 'appsec.events.users.signup.usr.login'
@@ -16,7 +16,7 @@ module Datadog
16
16
  register_as :devise, auto_patch: true
17
17
 
18
18
  def self.version
19
- Gem.loaded_specs['devise'] && Gem.loaded_specs['devise'].version
19
+ Gem.loaded_specs['devise']&.version
20
20
  end
21
21
 
22
22
  def self.loaded?
@@ -30,11 +30,9 @@ module Datadog
30
30
  def patch
31
31
  ::ActiveSupport.on_load(:before_initialize) do |app|
32
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
33
+ app.middleware.insert_after(Warden::Manager, TrackingMiddleware)
34
+ rescue RuntimeError
35
+ AppSec.telemetry.error('AppSec: unable to insert Devise TrackingMiddleware')
38
36
  end
39
37
  end
40
38
 
@@ -10,6 +10,7 @@ module Datadog
10
10
  # A Rack middleware capable of tracking currently signed user
11
11
  class TrackingMiddleware
12
12
  WARDEN_KEY = 'warden'
13
+ SESSION_ID_KEY = 'session_id'
13
14
 
14
15
  def initialize(app)
15
16
  @app = app
@@ -32,16 +33,28 @@ module Datadog
32
33
  return @app.call(env)
33
34
  end
34
35
 
36
+ # NOTE: Rails session id will be set for unauthenticated users as well,
37
+ # so we need to make sure we are tracking only authenticated users.
35
38
  id = transform(extract_id(env[WARDEN_KEY]))
39
+ session_id = env[WARDEN_KEY].raw_session[SESSION_ID_KEY] if id
40
+
36
41
  if id
37
- unless context.span.has_tag?(Ext::TAG_USR_ID)
38
- context.span[Ext::TAG_USR_ID] = id
42
+ # NOTE: There is no option to set session id without setting user id via SDK.
43
+ unless context.span.has_tag?(Ext::TAG_USR_ID) && context.span.has_tag?(Ext::TAG_SESSION_ID)
44
+ user_id = context.span[Ext::TAG_USR_ID] || id
45
+ user_session_id = context.span[Ext::TAG_SESSION_ID] || session_id
46
+
47
+ # FIXME: The current implementation of event arguments is forsing us
48
+ # to bloat User class, and pass nil-value instead of skip
49
+ # passing them at first place.
50
+ # This is a temporary situation until we refactor events model.
39
51
  AppSec::Instrumentation.gateway.push(
40
- 'identity.set_user', AppSec::Instrumentation::Gateway::User.new(id, nil)
52
+ 'identity.set_user', AppSec::Instrumentation::Gateway::User.new(user_id, nil, user_session_id)
41
53
  )
42
54
  end
43
55
 
44
- context.span[Ext::TAG_DD_USR_ID] = id.to_s
56
+ context.span[Ext::TAG_USR_ID] ||= id
57
+ context.span[Ext::TAG_DD_USR_ID] = id
45
58
  context.span[Ext::TAG_DD_COLLECTION_MODE] ||= Configuration.auto_user_instrumentation_mode
46
59
  end
47
60
 
@@ -16,7 +16,7 @@ module Datadog
16
16
  register_as :excon
17
17
 
18
18
  def self.version
19
- Gem.loaded_specs['excon'] && Gem.loaded_specs['excon'].version
19
+ Gem.loaded_specs['excon']&.version
20
20
  end
21
21
 
22
22
  def self.loaded?
@@ -3,6 +3,9 @@
3
3
 
4
4
  require 'excon'
5
5
 
6
+ require_relative '../../event'
7
+ require_relative '../../security_event'
8
+
6
9
  module Datadog
7
10
  module AppSec
8
11
  module Contrib
@@ -15,22 +18,18 @@ module Datadog
15
18
  context = AppSec.active_context
16
19
 
17
20
  request_url = URI.join("#{data[:scheme]}://#{data[:host]}", data[:path]).to_s
18
- ephemeral_data = { 'server.io.net.url' => request_url }
21
+ ephemeral_data = {'server.io.net.url' => request_url}
19
22
 
20
23
  result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, Datadog.configuration.appsec.waf_timeout)
21
24
 
22
25
  if result.match?
23
- Datadog::AppSec::Event.tag_and_keep!(context, result)
26
+ AppSec::Event.tag_and_keep!(context, result)
24
27
 
25
- context.events << {
26
- waf_result: result,
27
- trace: context.trace,
28
- span: context.span,
29
- request_url: request_url,
30
- actions: result.actions
31
- }
28
+ context.events.push(
29
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
30
+ )
32
31
 
33
- ActionsHandler.handle(result.actions)
32
+ AppSec::ActionsHandler.handle(result.actions)
34
33
  end
35
34
 
36
35
  super
@@ -17,7 +17,7 @@ module Datadog
17
17
  register_as :faraday, auto_patch: true
18
18
 
19
19
  def self.version
20
- Gem.loaded_specs['faraday'] && Gem.loaded_specs['faraday'].version
20
+ Gem.loaded_specs['faraday']&.version
21
21
  end
22
22
 
23
23
  def self.loaded?
@@ -1,6 +1,9 @@
1
1
  # rubocop:disable Naming/FileName
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative '../../event'
5
+ require_relative '../../security_event'
6
+
4
7
  module Datadog
5
8
  module AppSec
6
9
  module Contrib
@@ -19,17 +22,13 @@ module Datadog
19
22
  result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, Datadog.configuration.appsec.waf_timeout)
20
23
 
21
24
  if result.match?
22
- Datadog::AppSec::Event.tag_and_keep!(context, result)
25
+ AppSec::Event.tag_and_keep!(context, result)
23
26
 
24
- context.events << {
25
- waf_result: result,
26
- trace: context.trace,
27
- span: context.span,
28
- request_url: request_env.url,
29
- actions: result.actions
30
- }
27
+ context.events.push(
28
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
29
+ )
31
30
 
32
- ActionsHandler.handle(result.actions)
31
+ AppSec::ActionsHandler.handle(result.actions)
33
32
  end
34
33
 
35
34
  @app.call(request_env)
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+
5
+ require_relative '../../../event'
6
+ require_relative '../../../security_event'
4
7
  require_relative '../../../instrumentation/gateway'
5
8
 
6
9
  module Datadog
@@ -29,17 +32,13 @@ module Datadog
29
32
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
30
33
 
31
34
  if result.match?
32
- Datadog::AppSec::Event.tag_and_keep!(context, result)
35
+ AppSec::Event.tag_and_keep!(context, result)
33
36
 
34
- context.events << {
35
- waf_result: result,
36
- trace: context.trace,
37
- span: context.span,
38
- multiplex: gateway_multiplex,
39
- actions: result.actions
40
- }
37
+ context.events.push(
38
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
39
+ )
41
40
 
42
- Datadog::AppSec::ActionsHandler.handle(result.actions)
41
+ AppSec::ActionsHandler.handle(result.actions)
43
42
  end
44
43
  end
45
44
 
@@ -23,7 +23,7 @@ module Datadog
23
23
  register_as :graphql, auto_patch: false
24
24
 
25
25
  def self.version
26
- Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version
26
+ Gem.loaded_specs['graphql']&.version
27
27
  end
28
28
 
29
29
  def self.loaded?