datadog 2.9.0 → 2.11.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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +3 -3
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +46 -6
  7. data/ext/datadog_profiling_native_extension/extconf.rb +4 -0
  8. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +2 -0
  9. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +0 -8
  10. data/ext/datadog_profiling_native_extension/heap_recorder.c +51 -93
  11. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  12. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +56 -0
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +7 -0
  14. data/ext/datadog_profiling_native_extension/profiling.c +7 -0
  15. data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
  16. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  17. data/ext/libdatadog_api/crashtracker.c +4 -4
  18. data/ext/libdatadog_extconf_helpers.rb +1 -1
  19. data/lib/datadog/appsec/actions_handler.rb +27 -0
  20. data/lib/datadog/appsec/component.rb +14 -8
  21. data/lib/datadog/appsec/configuration/settings.rb +73 -11
  22. data/lib/datadog/appsec/context.rb +28 -8
  23. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
  24. data/lib/datadog/appsec/contrib/active_record/patcher.rb +0 -3
  25. data/lib/datadog/appsec/contrib/devise/configuration.rb +76 -0
  26. data/lib/datadog/appsec/contrib/devise/event.rb +4 -7
  27. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +16 -21
  28. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +8 -15
  29. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +1 -1
  30. data/lib/datadog/appsec/contrib/devise/patcher.rb +0 -3
  31. data/lib/datadog/appsec/contrib/devise/tracking.rb +1 -1
  32. data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
  33. data/lib/datadog/appsec/contrib/excon/patcher.rb +28 -0
  34. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +43 -0
  35. data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
  36. data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
  37. data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
  38. data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
  39. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +42 -0
  40. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  41. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +11 -14
  42. data/lib/datadog/appsec/contrib/graphql/patcher.rb +0 -3
  43. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +65 -70
  44. data/lib/datadog/appsec/contrib/rack/patcher.rb +0 -3
  45. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
  46. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
  47. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +20 -24
  48. data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -16
  49. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +38 -47
  50. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -29
  51. data/lib/datadog/appsec/ext.rb +6 -1
  52. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  53. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  54. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  55. data/lib/datadog/appsec/metrics.rb +13 -0
  56. data/lib/datadog/appsec/monitor/gateway/watcher.rb +19 -24
  57. data/lib/datadog/appsec/processor.rb +4 -3
  58. data/lib/datadog/appsec/remote.rb +4 -0
  59. data/lib/datadog/appsec/response.rb +18 -80
  60. data/lib/datadog/appsec/security_engine/result.rb +67 -0
  61. data/lib/datadog/appsec/security_engine/runner.rb +88 -0
  62. data/lib/datadog/appsec/security_engine.rb +9 -0
  63. data/lib/datadog/appsec.rb +16 -5
  64. data/lib/datadog/core/configuration/components.rb +7 -1
  65. data/lib/datadog/core/configuration/ext.rb +1 -1
  66. data/lib/datadog/core/configuration/option_definition.rb +2 -0
  67. data/lib/datadog/core/configuration/settings.rb +22 -6
  68. data/lib/datadog/core/encoding.rb +16 -0
  69. data/lib/datadog/core/environment/agent_info.rb +77 -0
  70. data/lib/datadog/core/remote/transport/http/api.rb +13 -18
  71. data/lib/datadog/core/remote/transport/http/config.rb +0 -18
  72. data/lib/datadog/core/remote/transport/http/negotiation.rb +1 -18
  73. data/lib/datadog/core/remote/transport/http.rb +7 -12
  74. data/lib/datadog/core/remote/transport/negotiation.rb +13 -1
  75. data/lib/datadog/core/telemetry/event.rb +5 -0
  76. data/lib/datadog/core/transport/http/adapters/unix_socket.rb +1 -1
  77. data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +1 -1
  78. data/lib/datadog/{tracing → core}/transport/http/api/spec.rb +1 -1
  79. data/lib/datadog/{tracing → core}/transport/http/builder.rb +37 -17
  80. data/lib/datadog/core/transport/response.rb +4 -0
  81. data/lib/datadog/di/code_tracker.rb +15 -8
  82. data/lib/datadog/di/component.rb +3 -0
  83. data/lib/datadog/di/configuration/settings.rb +14 -0
  84. data/lib/datadog/di/contrib.rb +2 -0
  85. data/lib/datadog/di/logger.rb +30 -0
  86. data/lib/datadog/di/probe.rb +3 -6
  87. data/lib/datadog/di/probe_manager.rb +5 -2
  88. data/lib/datadog/di/probe_notification_builder.rb +6 -0
  89. data/lib/datadog/di/probe_notifier_worker.rb +15 -4
  90. data/lib/datadog/di/redactor.rb +0 -1
  91. data/lib/datadog/di/remote.rb +29 -8
  92. data/lib/datadog/di/utils.rb +91 -0
  93. data/lib/datadog/di.rb +3 -0
  94. data/lib/datadog/profiling/component.rb +2 -8
  95. data/lib/datadog/profiling/load_native_extension.rb +1 -33
  96. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  97. data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
  98. data/lib/datadog/tracing/contrib/extensions.rb +29 -3
  99. data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
  100. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +11 -0
  101. data/lib/datadog/tracing/contrib/graphql/ext.rb +5 -0
  102. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +102 -11
  103. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  104. data/lib/datadog/tracing/contrib/rack/header_collection.rb +11 -1
  105. data/lib/datadog/tracing/contrib/rack/middlewares.rb +1 -1
  106. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +6 -1
  107. data/lib/datadog/tracing/transport/http/api.rb +11 -2
  108. data/lib/datadog/tracing/transport/http/traces.rb +0 -3
  109. data/lib/datadog/tracing/transport/http.rb +12 -7
  110. data/lib/datadog/tracing/transport/serializable_trace.rb +8 -4
  111. data/lib/datadog/tracing/transport/traces.rb +25 -8
  112. data/lib/datadog/version.rb +1 -1
  113. metadata +51 -42
  114. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
  115. data/ext/datadog_profiling_loader/extconf.rb +0 -60
  116. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -46
  117. data/lib/datadog/appsec/contrib/patcher.rb +0 -12
  118. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -69
  119. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -47
  120. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -53
  121. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -53
  122. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  123. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -48
  124. data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -45
  125. data/lib/datadog/appsec/processor/context.rb +0 -107
  126. data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
  127. data/lib/datadog/appsec/reactive/engine.rb +0 -47
  128. data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
  129. data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
  130. data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
  131. data/lib/datadog/core/remote/transport/http/builder.rb +0 -219
@@ -1,10 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../../instrumentation/gateway'
4
- require_relative '../../../reactive/engine'
5
- require_relative '../reactive/request'
6
- require_relative '../reactive/request_body'
7
- require_relative '../reactive/response'
8
4
  require_relative '../../../event'
9
5
 
10
6
  module Datadog
@@ -25,30 +21,33 @@ module Datadog
25
21
 
26
22
  def watch_request(gateway = Instrumentation.gateway)
27
23
  gateway.watch('rack.request', :appsec) do |stack, gateway_request|
28
- event = nil
29
24
  context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
30
- engine = AppSec::Reactive::Engine.new
31
-
32
- Rack::Reactive::Request.subscribe(engine, context) do |result|
33
- if result.status == :match
34
- # TODO: should this hash be an Event instance instead?
35
- event = {
36
- waf_result: result,
37
- trace: context.trace,
38
- span: context.span,
39
- request: gateway_request,
40
- actions: result.actions
41
- }
42
-
43
- # We want to keep the trace in case of security event
44
- context.trace.keep! if context.trace
45
- Datadog::AppSec::Event.tag_and_keep!(context, result)
46
- context.waf_runner.events << event
47
- end
48
- end
49
25
 
50
- block = Rack::Reactive::Request.publish(engine, gateway_request)
51
- throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
26
+ persistent_data = {
27
+ 'server.request.cookies' => gateway_request.cookies,
28
+ 'server.request.query' => gateway_request.query,
29
+ 'server.request.uri.raw' => gateway_request.fullpath,
30
+ 'server.request.headers' => gateway_request.headers,
31
+ 'server.request.headers.no_cookies' => gateway_request.headers.dup.tap { |h| h.delete('cookie') },
32
+ 'http.client_ip' => gateway_request.client_ip,
33
+ 'server.request.method' => gateway_request.method
34
+ }
35
+
36
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
37
+
38
+ if result.match?
39
+ Datadog::AppSec::Event.tag_and_keep!(context, result)
40
+
41
+ context.events << {
42
+ waf_result: result,
43
+ trace: context.trace,
44
+ span: context.span,
45
+ request: gateway_request,
46
+ actions: result.actions
47
+ }
48
+
49
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
50
+ end
52
51
 
53
52
  stack.call(gateway_request.request)
54
53
  end
@@ -56,30 +55,29 @@ module Datadog
56
55
 
57
56
  def watch_response(gateway = Instrumentation.gateway)
58
57
  gateway.watch('rack.response', :appsec) do |stack, gateway_response|
59
- event = nil
60
58
  context = gateway_response.context
61
- engine = AppSec::Reactive::Engine.new
62
-
63
- Rack::Reactive::Response.subscribe(engine, context) do |result|
64
- if result.status == :match
65
- # TODO: should this hash be an Event instance instead?
66
- event = {
67
- waf_result: result,
68
- trace: context.trace,
69
- span: context.span,
70
- response: gateway_response,
71
- actions: result.actions
72
- }
73
-
74
- # We want to keep the trace in case of security event
75
- context.trace.keep! if context.trace
76
- Datadog::AppSec::Event.tag_and_keep!(context, result)
77
- context.waf_runner.events << event
78
- end
79
- end
80
59
 
81
- block = Rack::Reactive::Response.publish(engine, gateway_response)
82
- throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
60
+ persistent_data = {
61
+ 'server.response.status' => gateway_response.status.to_s,
62
+ 'server.response.headers' => gateway_response.headers,
63
+ 'server.response.headers.no_cookies' => gateway_response.headers.dup.tap { |h| h.delete('set-cookie') }
64
+ }
65
+
66
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
67
+
68
+ if result.match?
69
+ Datadog::AppSec::Event.tag_and_keep!(context, result)
70
+
71
+ context.events << {
72
+ waf_result: result,
73
+ trace: context.trace,
74
+ span: context.span,
75
+ response: gateway_response,
76
+ actions: result.actions
77
+ }
78
+
79
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
80
+ end
83
81
 
84
82
  stack.call(gateway_response.response)
85
83
  end
@@ -87,30 +85,27 @@ module Datadog
87
85
 
88
86
  def watch_request_body(gateway = Instrumentation.gateway)
89
87
  gateway.watch('rack.request.body', :appsec) do |stack, gateway_request|
90
- event = nil
91
88
  context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
92
- engine = AppSec::Reactive::Engine.new
93
-
94
- Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
95
- if result.status == :match
96
- # TODO: should this hash be an Event instance instead?
97
- event = {
98
- waf_result: result,
99
- trace: context.trace,
100
- span: context.span,
101
- request: gateway_request,
102
- actions: result.actions
103
- }
104
-
105
- # We want to keep the trace in case of security event
106
- context.trace.keep! if context.trace
107
- Datadog::AppSec::Event.tag_and_keep!(context, result)
108
- context.waf_runner.events << event
109
- end
110
- end
111
89
 
112
- block = Rack::Reactive::RequestBody.publish(engine, gateway_request)
113
- throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
90
+ persistent_data = {
91
+ 'server.request.body' => gateway_request.form_hash
92
+ }
93
+
94
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
95
+
96
+ if result.match?
97
+ Datadog::AppSec::Event.tag_and_keep!(context, result)
98
+
99
+ context.events << {
100
+ waf_result: result,
101
+ trace: context.trace,
102
+ span: context.span,
103
+ request: gateway_request,
104
+ actions: result.actions
105
+ }
106
+
107
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
108
+ end
114
109
 
115
110
  stack.call(gateway_request.request)
116
111
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../patcher'
4
3
  require_relative '../../monitor'
5
4
  require_relative 'gateway/watcher'
6
5
 
@@ -10,8 +9,6 @@ module Datadog
10
9
  module Rack
11
10
  # Patcher for Rack integration
12
11
  module Patcher
13
- include Datadog::AppSec::Contrib::Patcher
14
-
15
12
  module_function
16
13
 
17
14
  def patched?
@@ -24,15 +24,15 @@ module Datadog
24
24
  # TODO: handle exceptions, except for @app.call
25
25
 
26
26
  http_response = nil
27
- block_actions = catch(::Datadog::AppSec::Ext::INTERRUPT) do
28
- http_response, = Instrumentation.gateway.push('rack.request.body', Gateway::Request.new(env)) do
27
+ interrupt_params = catch(::Datadog::AppSec::Ext::INTERRUPT) do
28
+ http_response, _request = Instrumentation.gateway.push('rack.request.body', Gateway::Request.new(env)) do
29
29
  @app.call(env)
30
30
  end
31
31
 
32
32
  nil
33
33
  end
34
34
 
35
- return AppSec::Response.negotiate(env, block_actions).to_rack if block_actions
35
+ return AppSec::Response.from_interrupt_params(interrupt_params, env['HTTP_ACCEPT']).to_rack if interrupt_params
36
36
 
37
37
  http_response
38
38
  end
@@ -76,8 +76,8 @@ module Datadog
76
76
  gateway_request = Gateway::Request.new(env)
77
77
  gateway_response = nil
78
78
 
79
- block_actions = catch(::Datadog::AppSec::Ext::INTERRUPT) do
80
- http_response, = Instrumentation.gateway.push('rack.request', gateway_request) do
79
+ interrupt_params = catch(::Datadog::AppSec::Ext::INTERRUPT) do
80
+ http_response, _gateway_request = Instrumentation.gateway.push('rack.request', gateway_request) do
81
81
  @app.call(env)
82
82
  end
83
83
 
@@ -90,27 +90,29 @@ module Datadog
90
90
  nil
91
91
  end
92
92
 
93
- http_response = AppSec::Response.negotiate(env, block_actions).to_rack if block_actions
93
+ if interrupt_params
94
+ http_response = AppSec::Response.from_interrupt_params(interrupt_params, env['HTTP_ACCEPT']).to_rack
95
+ end
94
96
 
95
- if (result = ctx.waf_runner.extract_schema)
96
- ctx.waf_runner.events << {
97
+ if AppSec.api_security_enabled?
98
+ ctx.events << {
97
99
  trace: ctx.trace,
98
100
  span: ctx.span,
99
- waf_result: result,
101
+ waf_result: ctx.extract_schema,
100
102
  }
101
103
  end
102
104
 
103
- ctx.waf_runner.events.each do |e|
105
+ ctx.events.each do |e|
104
106
  e[:response] ||= gateway_response
105
107
  e[:request] ||= gateway_request
106
108
  end
107
109
 
108
- AppSec::Event.record(ctx.span, *ctx.waf_runner.events)
110
+ AppSec::Event.record(ctx.span, *ctx.events)
109
111
 
110
112
  http_response
111
113
  ensure
112
114
  if ctx
113
- add_waf_runtime_tags(ctx)
115
+ ctx.export_metrics
114
116
  Datadog::AppSec::Context.deactivate
115
117
  end
116
118
  end
@@ -198,19 +200,6 @@ module Datadog
198
200
  end
199
201
  end
200
202
 
201
- def add_waf_runtime_tags(context)
202
- span = context.span
203
- context = context.waf_runner
204
-
205
- return unless span && context
206
-
207
- span.set_tag('_dd.appsec.waf.timeouts', context.timeouts)
208
-
209
- # these tags expect time in us
210
- span.set_tag('_dd.appsec.waf.duration', context.time_ns / 1000.0)
211
- span.set_tag('_dd.appsec.waf.duration_ext', context.time_ext_ns / 1000.0)
212
- end
213
-
214
203
  def to_rack_header(header)
215
204
  @rack_headers[header] ||= Datadog::Tracing::Contrib::Rack::Header.to_rack_header(header)
216
205
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../../instrumentation/gateway'
4
- require_relative '../../../reactive/engine'
5
- require_relative '../reactive/action'
6
4
  require_relative '../../../event'
7
5
 
8
6
  module Datadog
@@ -21,30 +19,28 @@ module Datadog
21
19
 
22
20
  def watch_request_action(gateway = Instrumentation.gateway)
23
21
  gateway.watch('rails.request.action', :appsec) do |stack, gateway_request|
24
- event = nil
25
22
  context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
26
- engine = AppSec::Reactive::Engine.new
27
-
28
- Rails::Reactive::Action.subscribe(engine, context) do |result|
29
- if result.status == :match
30
- # TODO: should this hash be an Event instance instead?
31
- event = {
32
- waf_result: result,
33
- trace: context.trace,
34
- span: context.span,
35
- request: gateway_request,
36
- actions: result.actions
37
- }
38
-
39
- # We want to keep the trace in case of security event
40
- context.trace.keep! if context.trace
41
- Datadog::AppSec::Event.tag_and_keep!(context, result)
42
- context.waf_runner.events << event
43
- end
44
- end
45
23
 
46
- block = Rails::Reactive::Action.publish(engine, gateway_request)
47
- next [nil, [[:block, event]]] if block
24
+ persistent_data = {
25
+ 'server.request.body' => gateway_request.parsed_body,
26
+ 'server.request.path_params' => gateway_request.route_params
27
+ }
28
+
29
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
30
+
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
+ }
41
+
42
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
43
+ end
48
44
 
49
45
  stack.call(gateway_request.request)
50
46
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative '../../../core/utils/only_once'
4
4
 
5
- require_relative '../patcher'
6
5
  require_relative 'framework'
7
6
  require_relative '../../response'
8
7
  require_relative '../rack/request_middleware'
@@ -18,8 +17,6 @@ module Datadog
18
17
  module Rails
19
18
  # Patcher for AppSec on Rails
20
19
  module Patcher
21
- include Datadog::AppSec::Contrib::Patcher
22
-
23
20
  BEFORE_INITIALIZE_ONLY_ONCE_PER_APP = Hash.new { |h, key| h[key] = Datadog::Core::Utils::OnlyOnce.new }
24
21
  AFTER_INITIALIZE_ONLY_ONCE_PER_APP = Hash.new { |h, key| h[key] = Datadog::Core::Utils::OnlyOnce.new }
25
22
 
@@ -80,22 +77,12 @@ module Datadog
80
77
  # TODO: handle exceptions, except for super
81
78
 
82
79
  gateway_request = Gateway::Request.new(request)
83
- request_return, request_response = Instrumentation.gateway.push('rails.request.action', gateway_request) do
84
- super
85
- end
86
80
 
87
- if request_response
88
- blocked_event = request_response.find { |action, _options| action == :block }
89
- if blocked_event
90
- @_response = AppSec::Response.negotiate(
91
- env,
92
- blocked_event.last[:actions]
93
- ).to_action_dispatch_response
94
- request_return = @_response.body
95
- end
81
+ http_response, _gateway_request = Instrumentation.gateway.push('rails.request.action', gateway_request) do
82
+ super
96
83
  end
97
84
 
98
- request_return
85
+ http_response
99
86
  end
100
87
  end
101
88
 
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../../instrumentation/gateway'
4
- require_relative '../../../reactive/engine'
5
- require_relative '../../rack/reactive/request_body'
6
- require_relative '../reactive/routed'
7
4
  require_relative '../../../event'
8
5
 
9
6
  module Datadog
@@ -23,30 +20,27 @@ module Datadog
23
20
 
24
21
  def watch_request_dispatch(gateway = Instrumentation.gateway)
25
22
  gateway.watch('sinatra.request.dispatch', :appsec) do |stack, gateway_request|
26
- event = nil
27
23
  context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
28
- engine = AppSec::Reactive::Engine.new
29
-
30
- Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
31
- if result.status == :match
32
- # TODO: should this hash be an Event instance instead?
33
- event = {
34
- waf_result: result,
35
- trace: context.trace,
36
- span: context.span,
37
- request: gateway_request,
38
- actions: result.actions
39
- }
40
-
41
- # We want to keep the trace in case of security event
42
- context.trace.keep! if context.trace
43
- Datadog::AppSec::Event.tag_and_keep!(context, result)
44
- context.waf_runner.events << event
45
- end
46
- end
47
24
 
48
- block = Rack::Reactive::RequestBody.publish(engine, gateway_request)
49
- next [nil, [[:block, event]]] if block
25
+ persistent_data = {
26
+ 'server.request.body' => gateway_request.form_hash
27
+ }
28
+
29
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
30
+
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
+ }
41
+
42
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
43
+ end
50
44
 
51
45
  stack.call(gateway_request.request)
52
46
  end
@@ -54,30 +48,27 @@ module Datadog
54
48
 
55
49
  def watch_request_routed(gateway = Instrumentation.gateway)
56
50
  gateway.watch('sinatra.request.routed', :appsec) do |stack, (gateway_request, gateway_route_params)|
57
- event = nil
58
51
  context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
59
- engine = AppSec::Reactive::Engine.new
60
-
61
- Sinatra::Reactive::Routed.subscribe(engine, context) do |result|
62
- if result.status == :match
63
- # TODO: should this hash be an Event instance instead?
64
- event = {
65
- waf_result: result,
66
- trace: context.trace,
67
- span: context.span,
68
- request: gateway_request,
69
- actions: result.actions
70
- }
71
-
72
- # We want to keep the trace in case of security event
73
- context.trace.keep! if context.trace
74
- Datadog::AppSec::Event.tag_and_keep!(context, result)
75
- context.waf_runner.events << event
76
- end
77
- end
78
52
 
79
- block = Sinatra::Reactive::Routed.publish(engine, [gateway_request, gateway_route_params])
80
- next [nil, [[:block, event]]] if block
53
+ persistent_data = {
54
+ 'server.request.path_params' => gateway_route_params.params
55
+ }
56
+
57
+ result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
58
+
59
+ if result.match?
60
+ Datadog::AppSec::Event.tag_and_keep!(context, result)
61
+
62
+ context.events << {
63
+ waf_result: result,
64
+ trace: context.trace,
65
+ span: context.span,
66
+ request: gateway_request,
67
+ actions: result.actions
68
+ }
69
+
70
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
71
+ end
81
72
 
82
73
  stack.call(gateway_request.request)
83
74
  end
@@ -2,11 +2,9 @@
2
2
 
3
3
  require_relative '../../../tracing/contrib'
4
4
 
5
- require_relative '../patcher'
6
5
  require_relative '../../response'
7
6
  require_relative '../rack/request_middleware'
8
7
  require_relative 'framework'
9
- require_relative 'ext'
10
8
  require_relative 'gateway/watcher'
11
9
  require_relative 'gateway/route_params'
12
10
  require_relative 'gateway/request'
@@ -62,17 +60,8 @@ module Datadog
62
60
 
63
61
  gateway_request = Gateway::Request.new(env)
64
62
 
65
- request_return, request_response = Instrumentation.gateway.push('sinatra.request.dispatch', gateway_request) do
66
- # handle process_route interruption
67
- catch(Datadog::AppSec::Contrib::Sinatra::Ext::ROUTE_INTERRUPT) { super }
68
- end
69
-
70
- if request_response
71
- blocked_event = request_response.find { |action, _options| action == :block }
72
- if blocked_event
73
- self.response = AppSec::Response.negotiate(env, blocked_event.last[:actions]).to_sinatra_response
74
- request_return = nil
75
- end
63
+ request_return, _gateway_request = Instrumentation.gateway.push('sinatra.request.dispatch', gateway_request) do
64
+ super
76
65
  end
77
66
 
78
67
  request_return
@@ -103,20 +92,7 @@ module Datadog
103
92
  gateway_request = Gateway::Request.new(env)
104
93
  gateway_route_params = Gateway::RouteParams.new(route_params)
105
94
 
106
- _, request_response = Instrumentation.gateway.push(
107
- 'sinatra.request.routed',
108
- [gateway_request, gateway_route_params]
109
- )
110
-
111
- if request_response
112
- blocked_event = request_response.find { |action, _options| action == :block }
113
- if blocked_event
114
- self.response = AppSec::Response.negotiate(env, blocked_event.last[:actions]).to_sinatra_response
115
-
116
- # interrupt request and return response to dispatch! for consistency
117
- throw(Datadog::AppSec::Contrib::Sinatra::Ext::ROUTE_INTERRUPT, response)
118
- end
119
- end
95
+ Instrumentation.gateway.push('sinatra.request.routed', [gateway_request, gateway_route_params])
120
96
 
121
97
  yield(*args)
122
98
  end
@@ -125,8 +101,6 @@ module Datadog
125
101
 
126
102
  # Patcher for AppSec on Sinatra
127
103
  module Patcher
128
- include Datadog::AppSec::Contrib::Patcher
129
-
130
104
  module_function
131
105
 
132
106
  def patched?
@@ -3,7 +3,10 @@
3
3
  module Datadog
4
4
  module AppSec
5
5
  module Ext
6
- RASP_SQLI = :sql_injection
6
+ RASP_SQLI = 'sql_injection'
7
+ RASP_LFI = 'lfi'
8
+ RASP_SSRF = 'ssrf'
9
+
7
10
  INTERRUPT = :datadog_appsec_interrupt
8
11
  CONTEXT_KEY = 'datadog.appsec.context'
9
12
  ACTIVE_CONTEXT_KEY = :datadog_appsec_active_context
@@ -11,6 +14,8 @@ module Datadog
11
14
  TAG_APPSEC_ENABLED = '_dd.appsec.enabled'
12
15
  TAG_APM_ENABLED = '_dd.apm.enabled'
13
16
  TAG_DISTRIBUTED_APPSEC_EVENT = '_dd.p.appsec'
17
+
18
+ TELEMETRY_METRICS_NAMESPACE = 'appsec'
14
19
  end
15
20
  end
16
21
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Metrics
6
+ # A class responsible for collecting WAF and RASP call metrics.
7
+ class Collector
8
+ Store = Struct.new(:evals, :timeouts, :duration_ns, :duration_ext_ns, keyword_init: true)
9
+
10
+ attr_reader :waf, :rasp
11
+
12
+ def initialize
13
+ @mutex = Mutex.new
14
+ @waf = Store.new(evals: 0, timeouts: 0, duration_ns: 0, duration_ext_ns: 0)
15
+ @rasp = Store.new(evals: 0, timeouts: 0, duration_ns: 0, duration_ext_ns: 0)
16
+ end
17
+
18
+ def record_waf(result)
19
+ @mutex.synchronize do
20
+ @waf.evals += 1
21
+ @waf.timeouts += 1 if result.timeout?
22
+ @waf.duration_ns += result.duration_ns
23
+ @waf.duration_ext_ns += result.duration_ext_ns
24
+ end
25
+ end
26
+
27
+ def record_rasp(result)
28
+ @mutex.synchronize do
29
+ @rasp.evals += 1
30
+ @rasp.timeouts += 1 if result.timeout?
31
+ @rasp.duration_ns += result.duration_ns
32
+ @rasp.duration_ext_ns += result.duration_ext_ns
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Metrics
6
+ # A class responsible for exporting WAF and RASP call metrics.
7
+ module Exporter
8
+ module_function
9
+
10
+ def export_waf_metrics(metrics, span)
11
+ return if metrics.evals.zero?
12
+
13
+ span.set_tag('_dd.appsec.waf.timeouts', metrics.timeouts)
14
+ span.set_tag('_dd.appsec.waf.duration', convert_ns_to_us(metrics.duration_ns))
15
+ span.set_tag('_dd.appsec.waf.duration_ext', convert_ns_to_us(metrics.duration_ext_ns))
16
+ end
17
+
18
+ def export_rasp_metrics(metrics, span)
19
+ return if metrics.evals.zero?
20
+
21
+ span.set_tag('_dd.appsec.rasp.rule.eval', metrics.evals)
22
+ span.set_tag('_dd.appsec.rasp.timeout', 1) unless metrics.timeouts.zero?
23
+ span.set_tag('_dd.appsec.rasp.duration', convert_ns_to_us(metrics.duration_ns))
24
+ span.set_tag('_dd.appsec.rasp.duration_ext', convert_ns_to_us(metrics.duration_ext_ns))
25
+ end
26
+
27
+ # private
28
+
29
+ def convert_ns_to_us(value)
30
+ value / 1000.0
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Metrics
6
+ # A class responsible for reporting WAF and RASP telemetry metrics.
7
+ module Telemetry
8
+ module_function
9
+
10
+ def report_rasp(type, result)
11
+ return if result.is_a?(SecurityEngine::Result::Error)
12
+
13
+ tags = { rule_type: type, waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING }
14
+ namespace = Ext::TELEMETRY_METRICS_NAMESPACE
15
+
16
+ AppSec.telemetry.inc(namespace, 'rasp.rule.eval', 1, tags: tags)
17
+ AppSec.telemetry.inc(namespace, 'rasp.rule.match', 1, tags: tags) if result.match?
18
+ AppSec.telemetry.inc(namespace, 'rasp.timeout', 1, tags: tags) if result.timeout?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end