datadog 2.9.0 → 2.10.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -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.h +2 -2
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +2 -5
  6. data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
  7. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  8. data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
  9. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  10. data/lib/datadog/appsec/actions_handler.rb +27 -0
  11. data/lib/datadog/appsec/component.rb +14 -8
  12. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  13. data/lib/datadog/appsec/context.rb +28 -8
  14. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
  15. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  16. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -5
  17. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +1 -1
  18. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +15 -12
  19. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +1 -1
  20. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +1 -1
  21. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +1 -1
  22. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
  23. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
  24. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +5 -4
  25. data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -13
  26. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +1 -1
  27. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -8
  28. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -26
  29. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +1 -1
  30. data/lib/datadog/appsec/ext.rb +6 -1
  31. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  32. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  33. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  34. data/lib/datadog/appsec/metrics.rb +13 -0
  35. data/lib/datadog/appsec/monitor/gateway/watcher.rb +5 -4
  36. data/lib/datadog/appsec/monitor/reactive/set_user.rb +1 -1
  37. data/lib/datadog/appsec/processor.rb +4 -3
  38. data/lib/datadog/appsec/response.rb +18 -80
  39. data/lib/datadog/appsec/security_engine/result.rb +67 -0
  40. data/lib/datadog/appsec/security_engine/runner.rb +88 -0
  41. data/lib/datadog/appsec/security_engine.rb +9 -0
  42. data/lib/datadog/appsec.rb +14 -5
  43. data/lib/datadog/di/component.rb +2 -0
  44. data/lib/datadog/di/probe_notification_builder.rb +6 -0
  45. data/lib/datadog/di/redactor.rb +0 -1
  46. data/lib/datadog/di/remote.rb +26 -5
  47. data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
  48. data/lib/datadog/tracing/contrib/extensions.rb +15 -3
  49. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  50. data/lib/datadog/version.rb +1 -1
  51. metadata +32 -18
  52. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  53. data/lib/datadog/appsec/processor/context.rb +0 -107
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'metrics'
4
+
3
5
  module Datadog
4
6
  module AppSec
5
7
  # This class accumulates the context over the request life-cycle and exposes
@@ -7,10 +9,7 @@ module Datadog
7
9
  class Context
8
10
  ActiveContextError = Class.new(StandardError)
9
11
 
10
- attr_reader :trace, :span
11
-
12
- # NOTE: This is an intermediate state and will be changed
13
- attr_reader :waf_runner
12
+ attr_reader :trace, :span, :events
14
13
 
15
14
  class << self
16
15
  def activate(context)
@@ -34,16 +33,37 @@ module Datadog
34
33
  def initialize(trace, span, security_engine)
35
34
  @trace = trace
36
35
  @span = span
36
+ @events = []
37
37
  @security_engine = security_engine
38
- @waf_runner = security_engine.new_context
38
+ @waf_runner = security_engine.new_runner
39
+ @metrics = Metrics::Collector.new
39
40
  end
40
41
 
41
42
  def run_waf(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
42
- @waf_runner.run(persistent_data, ephemeral_data, timeout)
43
+ result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
44
+
45
+ @metrics.record_waf(result)
46
+ result
47
+ end
48
+
49
+ def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
50
+ result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
51
+
52
+ Metrics::Telemetry.report_rasp(type, result)
53
+ @metrics.record_rasp(result)
54
+
55
+ result
43
56
  end
44
57
 
45
- def run_rasp(_type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
46
- @waf_runner.run(persistent_data, ephemeral_data, timeout)
58
+ def extract_schema
59
+ @waf_runner.run({ 'waf.context.processor' => { 'extract-schema' => true } }, {})
60
+ end
61
+
62
+ def export_metrics
63
+ return if @span.nil?
64
+
65
+ Metrics::Exporter.export_waf_metrics(@metrics.waf, @span)
66
+ Metrics::Exporter.export_rasp_metrics(@metrics.rasp, @span)
47
67
  end
48
68
 
49
69
  def finalize
@@ -9,6 +9,8 @@ module Datadog
9
9
  module_function
10
10
 
11
11
  def detect_sql_injection(sql, adapter_name)
12
+ return unless AppSec.rasp_enabled?
13
+
12
14
  context = AppSec.active_context
13
15
  return unless context
14
16
 
@@ -25,7 +27,7 @@ module Datadog
25
27
  waf_timeout = Datadog.configuration.appsec.waf_timeout
26
28
  result = context.run_rasp(Ext::RASP_SQLI, {}, ephemeral_data, waf_timeout)
27
29
 
28
- if result.status == :match
30
+ if result.match?
29
31
  Datadog::AppSec::Event.tag_and_keep!(context, result)
30
32
 
31
33
  event = {
@@ -35,7 +37,9 @@ module Datadog
35
37
  sql: sql,
36
38
  actions: result.actions
37
39
  }
38
- context.waf_runner.events << event
40
+ context.events << event
41
+
42
+ ActionsHandler.handle(result.actions)
39
43
  end
40
44
  end
41
45
 
@@ -16,16 +16,10 @@ module Datadog
16
16
 
17
17
  gateway_multiplex = Gateway::Multiplex.new(multiplex)
18
18
 
19
- multiplex_return, multiplex_response = Instrumentation.gateway.push('graphql.multiplex', gateway_multiplex) do
19
+ multiplex_return, _gateway_multiplex = Instrumentation.gateway.push('graphql.multiplex', gateway_multiplex) do
20
20
  super
21
21
  end
22
22
 
23
- # Returns an error * the number of queries so that the entire multiplex is blocked
24
- if multiplex_response
25
- blocked_event = multiplex_response.find { |action, _options| action == :block }
26
- multiplex_return = AppSec::Response.graphql_response(gateway_multiplex) if blocked_event
27
- end
28
-
29
23
  multiplex_return
30
24
  end
31
25
  end
@@ -22,7 +22,6 @@ module Datadog
22
22
  # This time we don't throw but use next
23
23
  def watch_multiplex(gateway = Instrumentation.gateway)
24
24
  gateway.watch('graphql.multiplex', :appsec) do |stack, gateway_multiplex|
25
- block = false
26
25
  event = nil
27
26
  context = AppSec::Context.active
28
27
  engine = AppSec::Reactive::Engine.new
@@ -38,14 +37,14 @@ module Datadog
38
37
  }
39
38
 
40
39
  Datadog::AppSec::Event.tag_and_keep!(context, result)
41
- context.waf_runner.events << event
40
+ context.events << event
41
+
42
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
42
43
  end
43
44
 
44
- block = GraphQL::Reactive::Multiplex.publish(engine, gateway_multiplex)
45
+ GraphQL::Reactive::Multiplex.publish(engine, gateway_multiplex)
45
46
  end
46
47
 
47
- next [nil, [[:block, event]]] if block
48
-
49
48
  stack.call(gateway_multiplex.arguments)
50
49
  end
51
50
  end
@@ -32,7 +32,7 @@ module Datadog
32
32
  waf_timeout = Datadog.configuration.appsec.waf_timeout
33
33
  result = context.run_waf(persistent_data, {}, waf_timeout)
34
34
 
35
- next if result.status != :match
35
+ next unless result.match?
36
36
 
37
37
  yield result
38
38
  throw(:block, true) unless result.actions.empty?
@@ -30,7 +30,7 @@ module Datadog
30
30
  engine = AppSec::Reactive::Engine.new
31
31
 
32
32
  Rack::Reactive::Request.subscribe(engine, context) do |result|
33
- if result.status == :match
33
+ if result.match?
34
34
  # TODO: should this hash be an Event instance instead?
35
35
  event = {
36
36
  waf_result: result,
@@ -43,12 +43,13 @@ module Datadog
43
43
  # We want to keep the trace in case of security event
44
44
  context.trace.keep! if context.trace
45
45
  Datadog::AppSec::Event.tag_and_keep!(context, result)
46
- context.waf_runner.events << event
46
+ context.events << event
47
+
48
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
47
49
  end
48
50
  end
49
51
 
50
- block = Rack::Reactive::Request.publish(engine, gateway_request)
51
- throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
52
+ Rack::Reactive::Request.publish(engine, gateway_request)
52
53
 
53
54
  stack.call(gateway_request.request)
54
55
  end
@@ -61,7 +62,7 @@ module Datadog
61
62
  engine = AppSec::Reactive::Engine.new
62
63
 
63
64
  Rack::Reactive::Response.subscribe(engine, context) do |result|
64
- if result.status == :match
65
+ if result.match?
65
66
  # TODO: should this hash be an Event instance instead?
66
67
  event = {
67
68
  waf_result: result,
@@ -74,12 +75,13 @@ module Datadog
74
75
  # We want to keep the trace in case of security event
75
76
  context.trace.keep! if context.trace
76
77
  Datadog::AppSec::Event.tag_and_keep!(context, result)
77
- context.waf_runner.events << event
78
+ context.events << event
79
+
80
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
78
81
  end
79
82
  end
80
83
 
81
- block = Rack::Reactive::Response.publish(engine, gateway_response)
82
- throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
84
+ Rack::Reactive::Response.publish(engine, gateway_response)
83
85
 
84
86
  stack.call(gateway_response.response)
85
87
  end
@@ -92,7 +94,7 @@ module Datadog
92
94
  engine = AppSec::Reactive::Engine.new
93
95
 
94
96
  Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
95
- if result.status == :match
97
+ if result.match?
96
98
  # TODO: should this hash be an Event instance instead?
97
99
  event = {
98
100
  waf_result: result,
@@ -105,12 +107,13 @@ module Datadog
105
107
  # We want to keep the trace in case of security event
106
108
  context.trace.keep! if context.trace
107
109
  Datadog::AppSec::Event.tag_and_keep!(context, result)
108
- context.waf_runner.events << event
110
+ context.events << event
111
+
112
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
109
113
  end
110
114
  end
111
115
 
112
- block = Rack::Reactive::RequestBody.publish(engine, gateway_request)
113
- throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
116
+ Rack::Reactive::RequestBody.publish(engine, gateway_request)
114
117
 
115
118
  stack.call(gateway_request.request)
116
119
  end
@@ -55,7 +55,7 @@ module Datadog
55
55
  waf_timeout = Datadog.configuration.appsec.waf_timeout
56
56
  result = context.run_waf(persistent_data, {}, waf_timeout)
57
57
 
58
- next if result.status != :match
58
+ next unless result.match?
59
59
 
60
60
  yield result
61
61
  throw(:block, true) unless result.actions.empty?
@@ -33,7 +33,7 @@ module Datadog
33
33
  waf_timeout = Datadog.configuration.appsec.waf_timeout
34
34
  result = context.run_waf(persistent_data, {}, waf_timeout)
35
35
 
36
- next if result.status != :match
36
+ next unless result.match?
37
37
 
38
38
  yield result
39
39
  throw(:block, true) unless result.actions.empty?
@@ -39,7 +39,7 @@ module Datadog
39
39
  waf_timeout = Datadog.configuration.appsec.waf_timeout
40
40
  result = context.run_waf(persistent_data, {}, waf_timeout)
41
41
 
42
- next if result.status != :match
42
+ next unless result.match?
43
43
 
44
44
  yield result
45
45
  throw(:block, true) unless result.actions.empty?
@@ -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
@@ -26,7 +26,7 @@ module Datadog
26
26
  engine = AppSec::Reactive::Engine.new
27
27
 
28
28
  Rails::Reactive::Action.subscribe(engine, context) do |result|
29
- if result.status == :match
29
+ if result.match?
30
30
  # TODO: should this hash be an Event instance instead?
31
31
  event = {
32
32
  waf_result: result,
@@ -39,12 +39,13 @@ module Datadog
39
39
  # We want to keep the trace in case of security event
40
40
  context.trace.keep! if context.trace
41
41
  Datadog::AppSec::Event.tag_and_keep!(context, result)
42
- context.waf_runner.events << event
42
+ context.events << event
43
+
44
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
43
45
  end
44
46
  end
45
47
 
46
- block = Rails::Reactive::Action.publish(engine, gateway_request)
47
- next [nil, [[:block, event]]] if block
48
+ Rails::Reactive::Action.publish(engine, gateway_request)
48
49
 
49
50
  stack.call(gateway_request.request)
50
51
  end
@@ -80,22 +80,12 @@ module Datadog
80
80
  # TODO: handle exceptions, except for super
81
81
 
82
82
  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
83
 
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
84
+ http_response, _gateway_request = Instrumentation.gateway.push('rails.request.action', gateway_request) do
85
+ super
96
86
  end
97
87
 
98
- request_return
88
+ http_response
99
89
  end
100
90
  end
101
91
 
@@ -39,7 +39,7 @@ module Datadog
39
39
  waf_timeout = Datadog.configuration.appsec.waf_timeout
40
40
  result = context.run_waf(persistent_data, {}, waf_timeout)
41
41
 
42
- next if result.status != :match
42
+ next unless result.match?
43
43
 
44
44
  yield result
45
45
  throw(:block, true) unless result.actions.empty?
@@ -28,7 +28,7 @@ module Datadog
28
28
  engine = AppSec::Reactive::Engine.new
29
29
 
30
30
  Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
31
- if result.status == :match
31
+ if result.match?
32
32
  # TODO: should this hash be an Event instance instead?
33
33
  event = {
34
34
  waf_result: result,
@@ -41,12 +41,13 @@ module Datadog
41
41
  # We want to keep the trace in case of security event
42
42
  context.trace.keep! if context.trace
43
43
  Datadog::AppSec::Event.tag_and_keep!(context, result)
44
- context.waf_runner.events << event
44
+ context.events << event
45
+
46
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
45
47
  end
46
48
  end
47
49
 
48
- block = Rack::Reactive::RequestBody.publish(engine, gateway_request)
49
- next [nil, [[:block, event]]] if block
50
+ Rack::Reactive::RequestBody.publish(engine, gateway_request)
50
51
 
51
52
  stack.call(gateway_request.request)
52
53
  end
@@ -59,7 +60,7 @@ module Datadog
59
60
  engine = AppSec::Reactive::Engine.new
60
61
 
61
62
  Sinatra::Reactive::Routed.subscribe(engine, context) do |result|
62
- if result.status == :match
63
+ if result.match?
63
64
  # TODO: should this hash be an Event instance instead?
64
65
  event = {
65
66
  waf_result: result,
@@ -72,12 +73,13 @@ module Datadog
72
73
  # We want to keep the trace in case of security event
73
74
  context.trace.keep! if context.trace
74
75
  Datadog::AppSec::Event.tag_and_keep!(context, result)
75
- context.waf_runner.events << event
76
+ context.events << event
77
+
78
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
76
79
  end
77
80
  end
78
81
 
79
- block = Sinatra::Reactive::Routed.publish(engine, [gateway_request, gateway_route_params])
80
- next [nil, [[:block, event]]] if block
82
+ Sinatra::Reactive::Routed.publish(engine, [gateway_request, gateway_route_params])
81
83
 
82
84
  stack.call(gateway_request.request)
83
85
  end
@@ -6,7 +6,6 @@ require_relative '../patcher'
6
6
  require_relative '../../response'
7
7
  require_relative '../rack/request_middleware'
8
8
  require_relative 'framework'
9
- require_relative 'ext'
10
9
  require_relative 'gateway/watcher'
11
10
  require_relative 'gateway/route_params'
12
11
  require_relative 'gateway/request'
@@ -62,17 +61,8 @@ module Datadog
62
61
 
63
62
  gateway_request = Gateway::Request.new(env)
64
63
 
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
64
+ request_return, _gateway_request = Instrumentation.gateway.push('sinatra.request.dispatch', gateway_request) do
65
+ super
76
66
  end
77
67
 
78
68
  request_return
@@ -103,20 +93,7 @@ module Datadog
103
93
  gateway_request = Gateway::Request.new(env)
104
94
  gateway_route_params = Gateway::RouteParams.new(route_params)
105
95
 
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
96
+ Instrumentation.gateway.push('sinatra.request.routed', [gateway_request, gateway_route_params])
120
97
 
121
98
  yield(*args)
122
99
  end
@@ -34,7 +34,7 @@ module Datadog
34
34
  waf_timeout = Datadog.configuration.appsec.waf_timeout
35
35
  result = context.run_waf(persistent_data, {}, waf_timeout)
36
36
 
37
- next if result.status != :match
37
+ next unless result.match?
38
38
 
39
39
  yield result
40
40
  throw(:block, true) unless result.actions.empty?
@@ -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ # This namespace contains classes related to metrics collection and exportation.
6
+ module Metrics
7
+ end
8
+ end
9
+ end
10
+
11
+ require_relative 'metrics/collector'
12
+ require_relative 'metrics/exporter'
13
+ require_relative 'metrics/telemetry'