datadog 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
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'