datadog 2.12.1 → 2.13.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -2
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +14 -13
  4. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +8 -0
  5. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +89 -0
  6. data/lib/datadog/appsec/actions_handler.rb +22 -1
  7. data/lib/datadog/appsec/anonymizer.rb +16 -0
  8. data/lib/datadog/appsec/configuration/settings.rb +62 -10
  9. data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
  10. data/lib/datadog/appsec/contrib/devise/configuration.rb +7 -31
  11. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +79 -0
  12. data/lib/datadog/appsec/contrib/devise/ext.rb +21 -0
  13. data/lib/datadog/appsec/contrib/devise/integration.rb +0 -1
  14. data/lib/datadog/appsec/contrib/devise/patcher.rb +36 -23
  15. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +102 -0
  16. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +69 -0
  17. data/lib/datadog/appsec/contrib/devise/{patcher/rememberable_patch.rb → patches/skip_signin_tracking_patch.rb} +2 -2
  18. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +93 -0
  19. data/lib/datadog/appsec/contrib/rack/ext.rb +34 -0
  20. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +27 -0
  21. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -2
  22. data/lib/datadog/appsec/event.rb +1 -1
  23. data/lib/datadog/appsec/ext.rb +4 -2
  24. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +4 -2
  25. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +24 -0
  26. data/lib/datadog/appsec/instrumentation/gateway.rb +17 -22
  27. data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -3
  28. data/lib/datadog/appsec/processor/rule_merger.rb +2 -1
  29. data/lib/datadog/appsec/remote.rb +7 -0
  30. data/lib/datadog/appsec/security_engine/runner.rb +2 -2
  31. data/lib/datadog/appsec/utils.rb +0 -2
  32. data/lib/datadog/core/configuration/components.rb +2 -1
  33. data/lib/datadog/core/configuration/ext.rb +4 -0
  34. data/lib/datadog/core/configuration/options.rb +2 -2
  35. data/lib/datadog/core/configuration/settings.rb +53 -30
  36. data/lib/datadog/core/environment/agent_info.rb +4 -3
  37. data/lib/datadog/core/remote/component.rb +3 -6
  38. data/lib/datadog/core/remote/configuration/repository.rb +2 -1
  39. data/lib/datadog/core/remote/negotiation.rb +9 -9
  40. data/lib/datadog/core/remote/transport/config.rb +4 -3
  41. data/lib/datadog/core/remote/transport/http/client.rb +4 -3
  42. data/lib/datadog/core/remote/transport/http/config.rb +6 -32
  43. data/lib/datadog/core/remote/transport/http/negotiation.rb +6 -32
  44. data/lib/datadog/core/remote/transport/http.rb +22 -57
  45. data/lib/datadog/core/remote/transport/negotiation.rb +4 -3
  46. data/lib/datadog/core/runtime/metrics.rb +8 -1
  47. data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
  48. data/lib/datadog/core/transport/http/api/instance.rb +17 -0
  49. data/lib/datadog/core/transport/http/api/spec.rb +17 -0
  50. data/lib/datadog/core/transport/http/builder.rb +5 -3
  51. data/lib/datadog/core/transport/http.rb +39 -2
  52. data/lib/datadog/di/component.rb +0 -2
  53. data/lib/datadog/di/probe_notifier_worker.rb +16 -16
  54. data/lib/datadog/di/transport/diagnostics.rb +4 -3
  55. data/lib/datadog/di/transport/http/api.rb +2 -12
  56. data/lib/datadog/di/transport/http/client.rb +4 -3
  57. data/lib/datadog/di/transport/http/diagnostics.rb +7 -33
  58. data/lib/datadog/di/transport/http/input.rb +7 -33
  59. data/lib/datadog/di/transport/http.rb +14 -56
  60. data/lib/datadog/di/transport/input.rb +4 -3
  61. data/lib/datadog/di/utils.rb +5 -0
  62. data/lib/datadog/kit/appsec/events.rb +9 -0
  63. data/lib/datadog/kit/identity.rb +5 -1
  64. data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
  65. data/lib/datadog/opentelemetry/api/baggage.rbs +26 -0
  66. data/lib/datadog/opentelemetry/api/context.rb +16 -2
  67. data/lib/datadog/opentelemetry/sdk/trace/span.rb +1 -1
  68. data/lib/datadog/opentelemetry.rb +2 -1
  69. data/lib/datadog/profiling/collectors/thread_context.rb +1 -1
  70. data/lib/datadog/profiling.rb +5 -2
  71. data/lib/datadog/tracing/component.rb +15 -12
  72. data/lib/datadog/tracing/configuration/ext.rb +7 -1
  73. data/lib/datadog/tracing/configuration/settings.rb +18 -2
  74. data/lib/datadog/tracing/context_provider.rb +1 -1
  75. data/lib/datadog/tracing/contrib/configuration/settings.rb +1 -1
  76. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -5
  77. data/lib/datadog/tracing/contrib/excon/middleware.rb +5 -3
  78. data/lib/datadog/tracing/contrib/faraday/middleware.rb +5 -3
  79. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +7 -1
  80. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +3 -0
  81. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +0 -15
  82. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +4 -1
  83. data/lib/datadog/tracing/contrib/http/instrumentation.rb +5 -5
  84. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +5 -11
  85. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +6 -10
  86. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +5 -3
  87. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +6 -1
  88. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +3 -0
  89. data/lib/datadog/tracing/correlation.rb +9 -2
  90. data/lib/datadog/tracing/distributed/baggage.rb +131 -0
  91. data/lib/datadog/tracing/distributed/datadog.rb +2 -0
  92. data/lib/datadog/tracing/distributed/propagation.rb +25 -4
  93. data/lib/datadog/tracing/distributed/propagation_policy.rb +42 -0
  94. data/lib/datadog/tracing/metadata/ext.rb +5 -0
  95. data/lib/datadog/tracing/metadata/metastruct.rb +36 -0
  96. data/lib/datadog/tracing/metadata/metastruct_tagging.rb +42 -0
  97. data/lib/datadog/tracing/metadata.rb +2 -0
  98. data/lib/datadog/tracing/sampling/span/rule.rb +0 -1
  99. data/lib/datadog/tracing/span.rb +10 -1
  100. data/lib/datadog/tracing/span_operation.rb +8 -2
  101. data/lib/datadog/tracing/sync_writer.rb +1 -2
  102. data/lib/datadog/tracing/trace_digest.rb +9 -2
  103. data/lib/datadog/tracing/trace_operation.rb +29 -17
  104. data/lib/datadog/tracing/trace_segment.rb +6 -4
  105. data/lib/datadog/tracing/tracer.rb +38 -2
  106. data/lib/datadog/tracing/transport/http/api.rb +2 -10
  107. data/lib/datadog/tracing/transport/http/client.rb +5 -4
  108. data/lib/datadog/tracing/transport/http/traces.rb +13 -41
  109. data/lib/datadog/tracing/transport/http.rb +11 -44
  110. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -1
  111. data/lib/datadog/tracing/transport/trace_formatter.rb +7 -0
  112. data/lib/datadog/tracing/transport/traces.rb +21 -9
  113. data/lib/datadog/tracing/workers/trace_writer.rb +2 -6
  114. data/lib/datadog/tracing/writer.rb +2 -6
  115. data/lib/datadog/tracing.rb +16 -3
  116. data/lib/datadog/version.rb +2 -2
  117. metadata +20 -13
  118. data/lib/datadog/appsec/contrib/devise/event.rb +0 -54
  119. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +0 -72
  120. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +0 -47
  121. data/lib/datadog/appsec/contrib/devise/resource.rb +0 -35
  122. data/lib/datadog/appsec/contrib/devise/tracking.rb +0 -57
  123. data/lib/datadog/appsec/utils/trace_operation.rb +0 -15
@@ -1,15 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'patcher/authenticatable_patch'
4
- require_relative 'patcher/rememberable_patch'
5
- require_relative 'patcher/registration_controller_patch'
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
- # Patcher for AppSec on Devise
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
- patch_authenticatable_strategy
25
- patch_rememberable_strategy
26
- patch_registration_controller
27
-
28
- Patcher.instance_variable_set(:@patched, true)
29
- end
30
-
31
- def patch_authenticatable_strategy
32
- ::Devise::Strategies::Authenticatable.prepend(AuthenticatablePatch)
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
- def patch_rememberable_strategy
36
- return unless ::Devise::STRATEGIES.include?(:rememberable)
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
- # Rememberable strategy is required in autoloaded Rememberable model
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
- def patch_registration_controller
44
- ::ActiveSupport.on_load(:after_initialize) do
45
- ::Devise::RegistrationsController.prepend(RegistrationControllerPatch)
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 Patcher
7
+ module Patches
8
8
  # To avoid tracking new sessions that are created by
9
9
  # Rememberable strategy as Login Success events.
10
- module RememberablePatch
10
+ module SkipSigninTrackingPatch
11
11
  def validate(*args)
12
12
  @_datadog_appsec_skip_track_login_event = true
13
13
 
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require_relative '../../anonymizer'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module Devise
10
+ # A Rack middleware capable of tracking currently signed user
11
+ class TrackingMiddleware
12
+ WARDEN_KEY = 'warden'
13
+
14
+ def initialize(app)
15
+ @app = app
16
+ @devise_session_scope_keys = {}
17
+ end
18
+
19
+ def call(env)
20
+ return @app.call(env) unless AppSec.enabled?
21
+ return @app.call(env) unless Configuration.auto_user_instrumentation_enabled?
22
+ return @app.call(env) unless AppSec.active_context
23
+
24
+ unless env.key?(WARDEN_KEY)
25
+ Datadog.logger.debug { 'AppSec: unable to track requests, due to missing warden manager' }
26
+ return @app.call(env)
27
+ end
28
+
29
+ context = AppSec.active_context
30
+ if context.trace.nil? || context.span.nil?
31
+ Datadog.logger.debug { 'AppSec: unable to track requests, due to missing trace or span' }
32
+ return @app.call(env)
33
+ end
34
+
35
+ id = transform(extract_id(env[WARDEN_KEY]))
36
+ if id
37
+ unless context.span.has_tag?(Ext::TAG_USR_ID)
38
+ context.span[Ext::TAG_USR_ID] = id
39
+ AppSec::Instrumentation.gateway.push(
40
+ 'identity.set_user', AppSec::Instrumentation::Gateway::User.new(id, nil)
41
+ )
42
+ end
43
+
44
+ context.span[Ext::TAG_DD_USR_ID] = id.to_s
45
+ context.span[Ext::TAG_DD_COLLECTION_MODE] ||= Configuration.auto_user_instrumentation_mode
46
+ end
47
+
48
+ @app.call(env)
49
+ end
50
+
51
+ private
52
+
53
+ def extract_id(warden)
54
+ session_serializer = warden.session_serializer
55
+
56
+ key = session_key_for(session_serializer, ::Devise.default_scope)
57
+ id = session_serializer.session[key]&.dig(0, 0)
58
+
59
+ return id if ::Devise.mappings.size == 1
60
+ return "#{::Devise.default_scope}:#{id}" if id
61
+
62
+ ::Devise.mappings.each_key do |scope|
63
+ next if scope == ::Devise.default_scope
64
+
65
+ key = session_key_for(session_serializer, scope)
66
+ id = session_serializer.session[key]&.dig(0, 0)
67
+
68
+ return "#{scope}:#{id}" if id
69
+ end
70
+
71
+ nil
72
+ end
73
+
74
+ def session_key_for(session_serializer, scope)
75
+ @devise_session_scope_keys[scope] ||= session_serializer.key_for(scope)
76
+ end
77
+
78
+ def transform(value)
79
+ return if value.nil?
80
+ return value.to_s unless anonymize?
81
+
82
+ Anonymizer.anonimyze(value.to_s)
83
+ end
84
+
85
+ def anonymize?
86
+ Configuration.auto_user_instrumentation_mode ==
87
+ AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -6,6 +6,40 @@ module Datadog
6
6
  module Rack
7
7
  # Rack integration constants
8
8
  module Ext
9
+ COLLECTABLE_REQUEST_HEADERS = [
10
+ 'accept',
11
+ 'akamai-user-risk',
12
+ 'cf-ray',
13
+ 'cloudfront-viewer-ja3-fingerprint',
14
+ 'content-type',
15
+ 'user-agent',
16
+ 'x-amzn-trace-Id',
17
+ 'x-appgw-trace-id',
18
+ 'x-cloud-trace-context',
19
+ 'x-sigsci-requestid',
20
+ 'x-sigsci-tags'
21
+ ].freeze
22
+
23
+ IDENTITY_COLLECTABLE_REQUEST_HEADERS = [
24
+ 'accept-encoding',
25
+ 'accept-language',
26
+ 'cf-connecting-ip',
27
+ 'cf-connecting-ipv6',
28
+ 'content-encoding',
29
+ 'content-language',
30
+ 'content-length',
31
+ 'fastly-client-ip',
32
+ 'forwarded',
33
+ 'forwarded-for',
34
+ 'host',
35
+ 'true-client-ip',
36
+ 'via',
37
+ 'x-client-ip',
38
+ 'x-cluster-client-ip',
39
+ 'x-forwarded',
40
+ 'x-forwarded-for',
41
+ 'x-real-ip'
42
+ ].freeze
9
43
  end
10
44
  end
11
45
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../ext'
3
4
  require_relative '../../../instrumentation/gateway'
4
5
  require_relative '../../../event'
5
6
 
@@ -17,6 +18,7 @@ module Datadog
17
18
  watch_request(gateway)
18
19
  watch_response(gateway)
19
20
  watch_request_body(gateway)
21
+ watch_request_finish(gateway)
20
22
  end
21
23
 
22
24
  def watch_request(gateway = Instrumentation.gateway)
@@ -110,6 +112,31 @@ module Datadog
110
112
  stack.call(gateway_request.request)
111
113
  end
112
114
  end
115
+
116
+ # NOTE: In the current state we unable to substibe twice to the same
117
+ # event within the same group. Ideally this code should live
118
+ # somewhere closer to identity related monitor.
119
+ # WARNING: The Gateway is a subject of refactoring
120
+ def watch_request_finish(gateway = Instrumentation.gateway)
121
+ gateway.watch('rack.request.finish', :appsec) do |stack, gateway_request|
122
+ context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
123
+
124
+ if context.span.nil? || !gateway.pushed?('appsec.events.user_lifecycle')
125
+ next stack.call(gateway_request.request)
126
+ end
127
+
128
+ gateway_request.headers.each do |name, value|
129
+ if !Ext::COLLECTABLE_REQUEST_HEADERS.include?(name) &&
130
+ !Ext::IDENTITY_COLLECTABLE_REQUEST_HEADERS.include?(name)
131
+ next
132
+ end
133
+
134
+ context.span["http.request.headers.#{name}"] ||= value
135
+ end
136
+
137
+ stack.call(gateway_request.request)
138
+ end
139
+ end
113
140
  end
114
141
  end
115
142
  end
@@ -77,6 +77,8 @@ module Datadog
77
77
  gateway_response = nil
78
78
 
79
79
  interrupt_params = catch(::Datadog::AppSec::Ext::INTERRUPT) do
80
+ # TODO: This event should be renamed into `rack.request.start` to
81
+ # reflect that it's the beginning of the request-cycle
80
82
  http_response, _gateway_request = Instrumentation.gateway.push('rack.request', gateway_request) do
81
83
  @app.call(env)
82
84
  end
@@ -85,6 +87,7 @@ module Datadog
85
87
  http_response[2], http_response[0], http_response[1], context: ctx
86
88
  )
87
89
 
90
+ Instrumentation.gateway.push('rack.request.finish', gateway_request)
88
91
  Instrumentation.gateway.push('rack.response', gateway_response)
89
92
 
90
93
  nil
@@ -147,8 +150,6 @@ module Datadog
147
150
  return unless trace && span
148
151
 
149
152
  span.set_metric(Datadog::AppSec::Ext::TAG_APPSEC_ENABLED, 1)
150
- # We add this tag when ASM standalone is enabled to make sure we don't bill APM
151
- span.set_metric(Datadog::AppSec::Ext::TAG_APM_ENABLED, 0) if Datadog.configuration.appsec.standalone.enabled
152
153
  span.set_tag('_dd.runtime_family', 'ruby')
153
154
  span.set_tag('_dd.appsec.waf.version', Datadog::AppSec::WAF::VERSION::BASE_STRING)
154
155
 
@@ -187,7 +187,7 @@ module Datadog
187
187
  Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
188
188
  Datadog::Tracing::Sampling::Ext::Decision::ASM
189
189
  )
190
- trace.set_tag(Datadog::AppSec::Ext::TAG_DISTRIBUTED_APPSEC_EVENT, '1')
190
+ trace.set_distributed_source(Datadog::AppSec::Ext::PRODUCT_BIT)
191
191
  end
192
192
  end
193
193
  end
@@ -7,13 +7,15 @@ module Datadog
7
7
  RASP_LFI = 'lfi'
8
8
  RASP_SSRF = 'ssrf'
9
9
 
10
+ PRODUCT_BIT = 0b00000010
11
+
10
12
  INTERRUPT = :datadog_appsec_interrupt
11
13
  CONTEXT_KEY = 'datadog.appsec.context'
12
14
  ACTIVE_CONTEXT_KEY = :datadog_appsec_active_context
15
+ EXPLOIT_PREVENTION_EVENT_CATEGORY = 'exploit'
13
16
 
14
17
  TAG_APPSEC_ENABLED = '_dd.appsec.enabled'
15
- TAG_APM_ENABLED = '_dd.apm.enabled'
16
- TAG_DISTRIBUTED_APPSEC_EVENT = '_dd.p.appsec'
18
+ TAG_METASTRUCT_STACK_TRACE = '_dd.stack'
17
19
 
18
20
  TELEMETRY_METRICS_NAMESPACE = 'appsec'
19
21
  end
@@ -9,11 +9,13 @@ module Datadog
9
9
 
10
10
  # Gateway User argument
11
11
  class User < Argument
12
- attr_reader :id
12
+ attr_reader :id, :login
13
13
 
14
- def initialize(id)
14
+ def initialize(id, login)
15
15
  super()
16
+
16
17
  @id = id
18
+ @login = login
17
19
  end
18
20
  end
19
21
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Instrumentation
6
+ class Gateway
7
+ # NOTE: This class extracted as-is and will be deprecated
8
+ # Instrumentation gateway middleware
9
+ class Middleware
10
+ attr_reader :key, :block
11
+
12
+ def initialize(key, &block)
13
+ @key = key
14
+ @block = block
15
+ end
16
+
17
+ def call(stack, env)
18
+ @block.call(stack, env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,35 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'gateway/middleware'
4
+
3
5
  module Datadog
4
6
  module AppSec
5
7
  # Instrumentation for AppSec
6
8
  module Instrumentation
7
9
  # Instrumentation gateway implementation
8
10
  class Gateway
9
- # Instrumentation gateway middleware
10
- class Middleware
11
- attr_reader :key, :block
12
-
13
- def initialize(key, &block)
14
- @key = key
15
- @block = block
16
- end
17
-
18
- def call(stack, env)
19
- @block.call(stack, env)
20
- end
21
- end
22
-
23
- private_constant :Middleware
24
-
25
11
  def initialize
26
12
  @middlewares = Hash.new { |h, k| h[k] = [] }
13
+ @pushed_events = {}
27
14
  end
28
15
 
16
+ # NOTE: Be careful with pushed names because every pushed event name
17
+ # is recorded in order to provide an ability to any subscriber
18
+ # to check wether an arbitrary event had happened.
19
+ #
20
+ # WARNING: If we start pushing generated names we should consider
21
+ # limiting the storage of pushed names.
29
22
  def push(name, env, &block)
30
- block ||= -> {}
23
+ @pushed_events[name] = true
31
24
 
32
- middlewares_for_name = middlewares[name]
25
+ block ||= -> {}
26
+ middlewares_for_name = @middlewares[name]
33
27
 
34
28
  return [block.call, nil] if middlewares_for_name.empty?
35
29
 
@@ -48,14 +42,15 @@ module Datadog
48
42
  end
49
43
 
50
44
  def watch(name, key, &block)
51
- @middlewares[name] << Middleware.new(key, &block) unless middlewares[name].any? { |m| m.key == key }
45
+ @middlewares[name] << Middleware.new(key, &block) unless @middlewares[name].any? { |m| m.key == key }
52
46
  end
53
47
 
54
- private
55
-
56
- attr_reader :middlewares
48
+ def pushed?(name)
49
+ @pushed_events.key?(name)
50
+ end
57
51
  end
58
52
 
53
+ # NOTE: This left as-is and will be depricated soon.
59
54
  def self.gateway
60
55
  @gateway ||= Gateway.new # TODO: not thread safe
61
56
  end
@@ -19,9 +19,14 @@ module Datadog
19
19
  gateway.watch('identity.set_user', :appsec) do |stack, user|
20
20
  context = Datadog::AppSec.active_context
21
21
 
22
- persistent_data = {
23
- 'usr.id' => user.id
24
- }
22
+ if user.id.nil? && user.login.nil?
23
+ Datadog.logger.debug { 'AppSec: skipping WAF check because no user information was provided' }
24
+ next stack.call(user)
25
+ end
26
+
27
+ persistent_data = {}
28
+ persistent_data['usr.id'] = user.id if user.id
29
+ persistent_data['usr.login'] = user.login if user.login
25
30
 
26
31
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
27
32
 
@@ -22,7 +22,7 @@ module Datadog
22
22
  # TODO: `processors` and `scanners` are not provided by the caller, consider removing them
23
23
  def merge(
24
24
  telemetry:,
25
- rules:, data: [], overrides: [], exclusions: [], custom_rules: [],
25
+ rules:, actions: [], data: [], overrides: [], exclusions: [], custom_rules: [],
26
26
  processors: nil, scanners: nil
27
27
  )
28
28
  processors ||= begin
@@ -54,6 +54,7 @@ module Datadog
54
54
  combined_exclusions = combine_exclusions(exclusions) if exclusions.any?
55
55
  combined_custom_rules = combine_custom_rules(custom_rules) if custom_rules.any?
56
56
 
57
+ combined_rules['actions'] = actions if actions.any?
57
58
  combined_rules['rules_data'] = combined_data if combined_data
58
59
  combined_rules['rules_override'] = combined_overrides if combined_overrides
59
60
  combined_rules['exclusions'] = combined_exclusions if combined_exclusions