datadog 2.15.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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -2
  3. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +1 -4
  4. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +3 -0
  6. data/ext/datadog_profiling_native_extension/heap_recorder.c +8 -1
  7. data/ext/libdatadog_api/crashtracker.c +1 -9
  8. data/ext/libdatadog_api/crashtracker.h +5 -0
  9. data/ext/libdatadog_api/datadog_ruby_common.c +1 -4
  10. data/ext/libdatadog_api/datadog_ruby_common.h +7 -0
  11. data/ext/libdatadog_api/init.c +15 -0
  12. data/ext/libdatadog_api/library_config.c +122 -0
  13. data/ext/libdatadog_api/library_config.h +19 -0
  14. data/ext/libdatadog_api/process_discovery.c +117 -0
  15. data/ext/libdatadog_api/process_discovery.h +5 -0
  16. data/lib/datadog/appsec/actions_handler.rb +3 -2
  17. data/lib/datadog/appsec/assets/waf_rules/recommended.json +1344 -0
  18. data/lib/datadog/appsec/assets/waf_rules/strict.json +1344 -0
  19. data/lib/datadog/appsec/autoload.rb +1 -1
  20. data/lib/datadog/appsec/component.rb +11 -4
  21. data/lib/datadog/appsec/configuration/settings.rb +31 -18
  22. data/lib/datadog/appsec/context.rb +1 -1
  23. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +10 -12
  24. data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
  25. data/lib/datadog/appsec/contrib/active_record/patcher.rb +22 -22
  26. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +2 -3
  27. data/lib/datadog/appsec/contrib/devise/ext.rb +1 -0
  28. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  29. data/lib/datadog/appsec/contrib/devise/patcher.rb +3 -5
  30. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +17 -4
  31. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  32. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +9 -10
  33. data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
  34. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +8 -9
  35. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +8 -9
  36. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +22 -32
  38. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  39. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +16 -16
  40. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +11 -13
  41. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  42. data/lib/datadog/appsec/contrib/rails/patcher.rb +21 -21
  43. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  44. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +10 -11
  45. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +17 -23
  46. data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
  47. data/lib/datadog/appsec/event.rb +85 -95
  48. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +5 -2
  49. data/lib/datadog/appsec/metrics/telemetry.rb +1 -1
  50. data/lib/datadog/appsec/monitor/gateway/watcher.rb +42 -12
  51. data/lib/datadog/appsec/processor/rule_loader.rb +26 -28
  52. data/lib/datadog/appsec/processor/rule_merger.rb +5 -5
  53. data/lib/datadog/appsec/processor.rb +1 -1
  54. data/lib/datadog/appsec/remote.rb +14 -13
  55. data/lib/datadog/appsec/response.rb +6 -6
  56. data/lib/datadog/appsec/security_engine/runner.rb +1 -1
  57. data/lib/datadog/appsec/security_event.rb +39 -0
  58. data/lib/datadog/appsec.rb +1 -1
  59. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
  60. data/lib/datadog/core/configuration/components.rb +19 -10
  61. data/lib/datadog/core/configuration/option.rb +61 -25
  62. data/lib/datadog/core/configuration/settings.rb +10 -0
  63. data/lib/datadog/core/configuration/stable_config.rb +23 -0
  64. data/lib/datadog/core/configuration.rb +24 -0
  65. data/lib/datadog/core/crashtracking/component.rb +1 -9
  66. data/lib/datadog/core/environment/git.rb +1 -0
  67. data/lib/datadog/core/environment/variable_helpers.rb +1 -1
  68. data/lib/datadog/core/metrics/client.rb +8 -7
  69. data/lib/datadog/core/process_discovery.rb +32 -0
  70. data/lib/datadog/core/remote/client.rb +7 -0
  71. data/lib/datadog/core/runtime/metrics.rb +1 -1
  72. data/lib/datadog/core/telemetry/component.rb +60 -50
  73. data/lib/datadog/core/telemetry/emitter.rb +17 -11
  74. data/lib/datadog/core/telemetry/event.rb +7 -4
  75. data/lib/datadog/core/telemetry/http/adapters/net.rb +12 -97
  76. data/lib/datadog/core/telemetry/request.rb +3 -3
  77. data/lib/datadog/core/telemetry/transport/http/api.rb +43 -0
  78. data/lib/datadog/core/telemetry/transport/http/client.rb +49 -0
  79. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +92 -0
  80. data/lib/datadog/core/telemetry/transport/http.rb +63 -0
  81. data/lib/datadog/core/telemetry/transport/telemetry.rb +52 -0
  82. data/lib/datadog/core/telemetry/worker.rb +45 -0
  83. data/lib/datadog/core/utils/time.rb +12 -0
  84. data/lib/datadog/core/workers/async.rb +20 -2
  85. data/lib/datadog/core/workers/interval_loop.rb +12 -1
  86. data/lib/datadog/core/workers/runtime_metrics.rb +2 -2
  87. data/lib/datadog/core.rb +8 -0
  88. data/lib/datadog/di/boot.rb +34 -0
  89. data/lib/datadog/di/remote.rb +2 -0
  90. data/lib/datadog/di.rb +5 -32
  91. data/lib/datadog/error_tracking/collector.rb +87 -0
  92. data/lib/datadog/error_tracking/component.rb +167 -0
  93. data/lib/datadog/error_tracking/configuration/settings.rb +63 -0
  94. data/lib/datadog/error_tracking/configuration.rb +11 -0
  95. data/lib/datadog/error_tracking/ext.rb +18 -0
  96. data/lib/datadog/error_tracking/extensions.rb +16 -0
  97. data/lib/datadog/error_tracking/filters.rb +77 -0
  98. data/lib/datadog/error_tracking.rb +18 -0
  99. data/lib/datadog/kit/identity.rb +1 -1
  100. data/lib/datadog/profiling/exporter.rb +1 -1
  101. data/lib/datadog/tracing/analytics.rb +1 -1
  102. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
  103. data/lib/datadog/tracing/contrib/karafka/monitor.rb +1 -1
  104. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +8 -0
  105. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -0
  106. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +18 -1
  107. data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
  108. data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
  109. data/lib/datadog/tracing/distributed/datadog.rb +2 -2
  110. data/lib/datadog/tracing/sampling/rate_sampler.rb +2 -1
  111. data/lib/datadog/tracing/span_operation.rb +38 -14
  112. data/lib/datadog/tracing/trace_operation.rb +15 -7
  113. data/lib/datadog/tracing/tracer.rb +7 -3
  114. data/lib/datadog/tracing/utils.rb +1 -1
  115. data/lib/datadog/version.rb +1 -1
  116. data/lib/datadog.rb +2 -3
  117. metadata +34 -8
  118. data/lib/datadog/core/telemetry/http/env.rb +0 -20
  119. data/lib/datadog/core/telemetry/http/ext.rb +0 -28
  120. data/lib/datadog/core/telemetry/http/response.rb +0 -70
  121. data/lib/datadog/core/telemetry/http/transport.rb +0 -90
@@ -4,9 +4,12 @@ require 'json'
4
4
 
5
5
  require_relative 'gateway/request'
6
6
  require_relative 'gateway/response'
7
- require_relative '../../instrumentation/gateway'
8
- require_relative '../../processor'
7
+
8
+ require_relative '../../event'
9
9
  require_relative '../../response'
10
+ require_relative '../../processor'
11
+ require_relative '../../security_event'
12
+ require_relative '../../instrumentation/gateway'
10
13
 
11
14
  require_relative '../../../tracing/client_ip'
12
15
  require_relative '../../../tracing/contrib/rack/header_collection'
@@ -36,7 +39,7 @@ module Datadog
36
39
  @rack_headers = {}
37
40
  end
38
41
 
39
- # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
42
+ # rubocop:disable Metrics/MethodLength
40
43
  def call(env)
41
44
  return @app.call(env) unless Datadog::AppSec.enabled?
42
45
 
@@ -97,20 +100,13 @@ module Datadog
97
100
  http_response = AppSec::Response.from_interrupt_params(interrupt_params, env['HTTP_ACCEPT']).to_rack
98
101
  end
99
102
 
100
- if AppSec.api_security_enabled?
101
- ctx.events << {
102
- trace: ctx.trace,
103
- span: ctx.span,
104
- waf_result: ctx.extract_schema,
105
- }
106
- end
107
-
108
- ctx.events.each do |e|
109
- e[:response] ||= gateway_response
110
- e[:request] ||= gateway_request
103
+ if AppSec.perform_api_security_check?
104
+ ctx.events.push(
105
+ AppSec::SecurityEvent.new(ctx.extract_schema, trace: ctx.trace, span: ctx.span)
106
+ )
111
107
  end
112
108
 
113
- AppSec::Event.record(ctx.span, *ctx.events)
109
+ AppSec::Event.record(ctx, request: gateway_request, response: gateway_response)
114
110
 
115
111
  http_response
116
112
  ensure
@@ -119,7 +115,7 @@ module Datadog
119
115
  Datadog::AppSec::Context.deactivate
120
116
  end
121
117
  end
122
- # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
118
+ # rubocop:enable Metrics/MethodLength
123
119
 
124
120
  private
125
121
 
@@ -143,6 +139,7 @@ module Datadog
143
139
  Datadog::Tracing.active_span
144
140
  end
145
141
 
142
+ # standard:disable Metrics/MethodLength
146
143
  def add_appsec_tags(processor, context)
147
144
  span = context.span
148
145
  trace = context.trace
@@ -177,7 +174,9 @@ module Datadog
177
174
  end
178
175
  end
179
176
  end
177
+ # standard:enable Metrics/MethodLength
180
178
 
179
+ # standard:disable Metrics/MethodLength
181
180
  def add_request_tags(context, env)
182
181
  span = context.span
183
182
 
@@ -200,6 +199,7 @@ module Datadog
200
199
  )
201
200
  end
202
201
  end
202
+ # standard:enable Metrics/MethodLength
203
203
 
204
204
  def to_rack_header(header)
205
205
  @rack_headers[header] ||= Datadog::Tracing::Contrib::Rack::Header.to_rack_header(header)
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../../instrumentation/gateway'
4
3
  require_relative '../../../event'
4
+ require_relative '../../../security_event'
5
+ require_relative '../../../instrumentation/gateway'
5
6
 
6
7
  module Datadog
7
8
  module AppSec
@@ -19,7 +20,7 @@ module Datadog
19
20
 
20
21
  def watch_request_action(gateway = Instrumentation.gateway)
21
22
  gateway.watch('rails.request.action', :appsec) do |stack, gateway_request|
22
- context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
23
+ context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
23
24
 
24
25
  persistent_data = {
25
26
  'server.request.body' => gateway_request.parsed_body,
@@ -28,18 +29,15 @@ module Datadog
28
29
 
29
30
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
30
31
 
31
- if result.match?
32
- Datadog::AppSec::Event.tag_and_keep!(context, result)
33
-
34
- context.events << {
35
- waf_result: result,
36
- trace: context.trace,
37
- span: context.span,
38
- request: gateway_request,
39
- actions: result.actions
40
- }
32
+ if result.match? || !result.derivatives.empty?
33
+ context.events.push(
34
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
35
+ )
36
+ end
41
37
 
42
- Datadog::AppSec::ActionsHandler.handle(result.actions)
38
+ if result.match?
39
+ AppSec::Event.tag_and_keep!(context, result)
40
+ AppSec::ActionsHandler.handle(result.actions)
43
41
  end
44
42
 
45
43
  stack.call(gateway_request.request)
@@ -18,7 +18,7 @@ module Datadog
18
18
  register_as :rails, auto_patch: false
19
19
 
20
20
  def self.version
21
- Gem.loaded_specs['railties'] && Gem.loaded_specs['railties'].version
21
+ Gem.loaded_specs['railties']&.version
22
22
  end
23
23
 
24
24
  def self.loaded?
@@ -96,27 +96,27 @@ module Datadog
96
96
  # find tracer middleware reference in Rails::Configuration::MiddlewareStackProxy
97
97
  app.middleware.instance_variable_get(:@operations).each do |operation|
98
98
  args = case operation
99
- when Array
100
- # rails 5.2
101
- _op, args = operation
102
- args
103
- when Proc
104
- if operation.binding.local_variables.include?(:args)
105
- # rails 6.0, 6.1
106
- operation.binding.local_variable_get(:args)
107
- else
108
- # rails 7.0 uses ... to pass args
109
- args_getter = Class.new do
110
- def method_missing(_op, *args) # rubocop:disable Style/MissingRespondToMissing
111
- args
112
- end
113
- end.new
114
- operation.call(args_getter)
115
- end
116
- else
117
- # unknown, pass through
118
- []
119
- end
99
+ when Array
100
+ # rails 5.2
101
+ _op, args = operation
102
+ args
103
+ when Proc
104
+ if operation.binding.local_variables.include?(:args)
105
+ # rails 6.0, 6.1
106
+ operation.binding.local_variable_get(:args)
107
+ else
108
+ # rails 7.0 uses ... to pass args
109
+ args_getter = Class.new do
110
+ def method_missing(_op, *args) # standard:disable Style/MissingRespondToMissing
111
+ args
112
+ end
113
+ end.new
114
+ operation.call(args_getter)
115
+ end
116
+ else
117
+ # unknown, pass through
118
+ []
119
+ end
120
120
 
121
121
  found = true if args.include?(middleware)
122
122
  end
@@ -20,7 +20,7 @@ module Datadog
20
20
  end
21
21
 
22
22
  def self.version
23
- Gem.loaded_specs['rest-client'] && Gem.loaded_specs['rest-client'].version
23
+ Gem.loaded_specs['rest-client']&.version
24
24
  end
25
25
 
26
26
  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
@@ -12,24 +15,20 @@ module Datadog
12
15
 
13
16
  context = AppSec.active_context
14
17
 
15
- ephemeral_data = { 'server.io.net.url' => url }
18
+ ephemeral_data = {'server.io.net.url' => url}
16
19
  result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, Datadog.configuration.appsec.waf_timeout)
17
20
 
18
21
  if result.match?
19
- Datadog::AppSec::Event.tag_and_keep!(context, result)
22
+ AppSec::Event.tag_and_keep!(context, result)
20
23
 
21
- context.events << {
22
- waf_result: result,
23
- trace: context.trace,
24
- span: context.span,
25
- request_url: url,
26
- actions: result.actions
27
- }
24
+ context.events.push(
25
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
26
+ )
28
27
 
29
- ActionsHandler.handle(result.actions)
28
+ AppSec::ActionsHandler.handle(result.actions)
30
29
  end
31
30
 
32
- super(&block)
31
+ super
33
32
  end
34
33
  end
35
34
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../../instrumentation/gateway'
4
3
  require_relative '../../../event'
4
+ require_relative '../../../security_event'
5
+ require_relative '../../../instrumentation/gateway'
5
6
 
6
7
  module Datadog
7
8
  module AppSec
@@ -20,7 +21,7 @@ module Datadog
20
21
 
21
22
  def watch_request_dispatch(gateway = Instrumentation.gateway)
22
23
  gateway.watch('sinatra.request.dispatch', :appsec) do |stack, gateway_request|
23
- context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
24
+ context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
24
25
 
25
26
  persistent_data = {
26
27
  'server.request.body' => gateway_request.form_hash
@@ -28,18 +29,15 @@ module Datadog
28
29
 
29
30
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
30
31
 
31
- if result.match?
32
- Datadog::AppSec::Event.tag_and_keep!(context, result)
33
-
34
- context.events << {
35
- waf_result: result,
36
- trace: context.trace,
37
- span: context.span,
38
- request: gateway_request,
39
- actions: result.actions
40
- }
32
+ if result.match? || !result.derivatives.empty?
33
+ context.events.push(
34
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
35
+ )
36
+ end
41
37
 
42
- Datadog::AppSec::ActionsHandler.handle(result.actions)
38
+ if result.match?
39
+ AppSec::Event.tag_and_keep!(context, result)
40
+ AppSec::ActionsHandler.handle(result.actions)
43
41
  end
44
42
 
45
43
  stack.call(gateway_request.request)
@@ -48,7 +46,7 @@ module Datadog
48
46
 
49
47
  def watch_request_routed(gateway = Instrumentation.gateway)
50
48
  gateway.watch('sinatra.request.routed', :appsec) do |stack, (gateway_request, gateway_route_params)|
51
- context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
49
+ context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
52
50
 
53
51
  persistent_data = {
54
52
  'server.request.path_params' => gateway_route_params.params
@@ -57,17 +55,13 @@ module Datadog
57
55
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
58
56
 
59
57
  if result.match?
60
- Datadog::AppSec::Event.tag_and_keep!(context, result)
58
+ AppSec::Event.tag_and_keep!(context, result)
61
59
 
62
- context.events << {
63
- waf_result: result,
64
- trace: context.trace,
65
- span: context.span,
66
- request: gateway_request,
67
- actions: result.actions
68
- }
60
+ context.events.push(
61
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
62
+ )
69
63
 
70
- Datadog::AppSec::ActionsHandler.handle(result.actions)
64
+ AppSec::ActionsHandler.handle(result.actions)
71
65
  end
72
66
 
73
67
  stack.call(gateway_request.request)
@@ -18,7 +18,7 @@ module Datadog
18
18
  register_as :sinatra
19
19
 
20
20
  def self.version
21
- Gem.loaded_specs['sinatra'] && Gem.loaded_specs['sinatra'].version
21
+ Gem.loaded_specs['sinatra']&.version
22
22
  end
23
23
 
24
24
  def self.loaded?
@@ -11,96 +11,106 @@ module Datadog
11
11
  DERIVATIVE_SCHEMA_KEY_PREFIX = '_dd.appsec.s.'
12
12
  DERIVATIVE_SCHEMA_MAX_COMPRESSED_SIZE = 25000
13
13
  ALLOWED_REQUEST_HEADERS = %w[
14
- X-Forwarded-For
15
- X-Client-IP
16
- X-Real-IP
17
- X-Forwarded
18
- X-Cluster-Client-IP
19
- Forwarded-For
20
- Forwarded
21
- Via
22
- True-Client-IP
23
- Content-Length
24
- Content-Type
25
- Content-Encoding
26
- Content-Language
27
- Host
28
- User-Agent
29
- Accept
30
- Accept-Encoding
31
- Accept-Language
32
- ].map!(&:downcase).freeze
14
+ x-forwarded-for
15
+ x-client-ip
16
+ x-real-ip
17
+ x-forwarded
18
+ x-cluster-client-ip
19
+ forwarded-for
20
+ forwarded
21
+ via
22
+ true-client-ip
23
+ content-length
24
+ content-type
25
+ content-encoding
26
+ content-language
27
+ host
28
+ user-agent
29
+ accept
30
+ accept-encoding
31
+ accept-language
32
+ ].freeze
33
33
 
34
34
  ALLOWED_RESPONSE_HEADERS = %w[
35
- Content-Length
36
- Content-Type
37
- Content-Encoding
38
- Content-Language
39
- ].map!(&:downcase).freeze
40
-
41
- # Record events for a trace
42
- #
43
- # This is expected to be called only once per trace for the rate limiter
44
- # to properly apply
35
+ content-length
36
+ content-type
37
+ content-encoding
38
+ content-language
39
+ ].freeze
40
+
45
41
  class << self
46
- def record(span, *events)
47
- # ensure rate limiter is called only when there are events to record
48
- return if events.empty? || span.nil?
42
+ def tag_and_keep!(context, waf_result)
43
+ # We want to keep the trace in case of security event
44
+ context.trace&.keep!
49
45
 
50
- Datadog::AppSec::RateLimiter.thread_local.limit do
51
- record_via_span(span, *events)
46
+ if context.span
47
+ if waf_result.actions.key?('block_request') || waf_result.actions.key?('redirect_request')
48
+ context.span.set_tag('appsec.blocked', 'true')
49
+ end
50
+
51
+ context.span.set_tag('appsec.event', 'true')
52
52
  end
53
+
54
+ add_distributed_tags(context.trace)
53
55
  end
54
56
 
55
- def record_via_span(span, *events)
56
- events.group_by { |e| e[:trace] }.each do |trace, event_group|
57
- unless trace
58
- Datadog.logger.debug { "{ error: 'no trace: cannot record', event_group: #{event_group.inspect}}" }
59
- next
60
- end
57
+ def record(context, request: nil, response: nil)
58
+ return if context.events.empty? || context.span.nil?
59
+
60
+ Datadog::AppSec::RateLimiter.thread_local.limit do
61
+ context.events.group_by(&:trace).each do |trace, event_group|
62
+ unless trace
63
+ next Datadog.logger.debug do
64
+ "AppSec: Cannot record event group with #{event_group.count} events because it has no trace"
65
+ end
66
+ end
61
67
 
62
- trace.keep!
63
- trace.set_tag(
64
- Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
65
- Datadog::Tracing::Sampling::Ext::Decision::ASM
66
- )
68
+ if event_group.any? { |event| event.attack? || event.schema? }
69
+ trace.keep!
70
+ trace[Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER] = Tracing::Sampling::Ext::Decision::ASM
67
71
 
68
- # prepare and gather tags to apply
69
- service_entry_tags = build_service_entry_tags(event_group)
72
+ context.span['_dd.origin'] = 'appsec'
73
+ context.span.set_tags(request_tags(request)) if request
74
+ context.span.set_tags(response_tags(response)) if response
75
+ end
70
76
 
71
- # apply tags to service entry span
72
- service_entry_tags.each do |key, value|
73
- span.set_tag(key, value)
77
+ context.span.set_tags(waf_tags(event_group))
74
78
  end
75
79
  end
76
80
  end
77
81
 
78
- def build_service_entry_tags(event_group)
79
- waf_events = []
80
- entry_tags = event_group.each_with_object({ '_dd.origin' => 'appsec' }) do |event, tags|
81
- # TODO: assume HTTP request context for now
82
- if (request = event[:request])
83
- request.headers.each do |header, value|
84
- tags["http.request.headers.#{header}"] = value if ALLOWED_REQUEST_HEADERS.include?(header.downcase)
85
- end
82
+ private
86
83
 
87
- tags['http.host'] = request.host
88
- tags['http.useragent'] = request.user_agent
89
- tags['network.client.ip'] = request.remote_addr
90
- end
84
+ def request_tags(request)
85
+ tags = {}
91
86
 
92
- if (response = event[:response])
93
- response.headers.each do |header, value|
94
- tags["http.response.headers.#{header}"] = value if ALLOWED_RESPONSE_HEADERS.include?(header.downcase)
95
- end
96
- end
87
+ tags['http.host'] = request.host if request.host
88
+ tags['http.useragent'] = request.user_agent if request.user_agent
89
+ tags['network.client.ip'] = request.remote_addr if request.remote_addr
90
+
91
+ request.headers.each_with_object(tags) do |(name, value), memo|
92
+ next unless ALLOWED_REQUEST_HEADERS.include?(name)
93
+
94
+ memo["http.request.headers.#{name}"] = value
95
+ end
96
+ end
97
+
98
+ def response_tags(response)
99
+ response.headers.each_with_object({}) do |(name, value), memo|
100
+ next unless ALLOWED_RESPONSE_HEADERS.include?(name)
101
+
102
+ memo["http.response.headers.#{name}"] = value
103
+ end
104
+ end
97
105
 
98
- waf_result = event[:waf_result]
99
- # accumulate triggers
100
- waf_events += waf_result.events
106
+ def waf_tags(security_events)
107
+ triggers = []
101
108
 
102
- waf_result.derivatives.each do |key, value|
103
- next tags[key] = value unless key.start_with?(DERIVATIVE_SCHEMA_KEY_PREFIX)
109
+ tags = security_events.each_with_object({}) do |security_event, memo|
110
+ triggers.concat(security_event.waf_result.events)
111
+
112
+ security_event.waf_result.derivatives.each do |key, value|
113
+ next memo[key] = value unless key.start_with?(DERIVATIVE_SCHEMA_KEY_PREFIX)
104
114
 
105
115
  value = CompressedJson.dump(value)
106
116
  next if value.nil?
@@ -110,34 +120,14 @@ module Datadog
110
120
  next
111
121
  end
112
122
 
113
- tags[key] = value
114
- end
115
-
116
- tags
117
- end
118
-
119
- appsec_events = json_parse({ triggers: waf_events })
120
- entry_tags['_dd.appsec.json'] = appsec_events if appsec_events
121
- entry_tags
122
- end
123
-
124
- def tag_and_keep!(context, waf_result)
125
- # We want to keep the trace in case of security event
126
- context.trace.keep! if context.trace
127
-
128
- if context.span
129
- if waf_result.actions.key?('block_request') || waf_result.actions.key?('redirect_request')
130
- context.span.set_tag('appsec.blocked', 'true')
123
+ memo[key] = value
131
124
  end
132
-
133
- context.span.set_tag('appsec.event', 'true')
134
125
  end
135
126
 
136
- add_distributed_tags(context.trace)
127
+ tags['_dd.appsec.json'] = json_parse({triggers: triggers}) unless triggers.empty?
128
+ tags
137
129
  end
138
130
 
139
- private
140
-
141
131
  # NOTE: Handling of Encoding::UndefinedConversionError is added as a quick fix to
142
132
  # the issue between Ruby encoded strings and libddwaf produced events and now
143
133
  # is under investigation.
@@ -8,14 +8,17 @@ module Datadog
8
8
  class Argument; end # rubocop:disable Lint/EmptyClass
9
9
 
10
10
  # Gateway User argument
11
+ # NOTE: This class is a subject of elimination and will be removed when
12
+ # the event system is refactored.
11
13
  class User < Argument
12
- attr_reader :id, :login
14
+ attr_reader :id, :login, :session_id
13
15
 
14
- def initialize(id, login)
16
+ def initialize(id, login = nil, session_id = nil)
15
17
  super()
16
18
 
17
19
  @id = id
18
20
  @login = login
21
+ @session_id = session_id
19
22
  end
20
23
  end
21
24
  end
@@ -10,7 +10,7 @@ module Datadog
10
10
  def report_rasp(type, result)
11
11
  return if result.is_a?(SecurityEngine::Result::Error)
12
12
 
13
- tags = { rule_type: type, waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING }
13
+ tags = {rule_type: type, waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING}
14
14
  namespace = Ext::TELEMETRY_METRICS_NAMESPACE
15
15
 
16
16
  AppSec.telemetry.inc(namespace, 'rasp.rule.eval', 1, tags: tags)