datadog 2.8.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -1
  3. data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +66 -56
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
  7. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
  9. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +221 -127
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +2 -2
  13. data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
  16. data/ext/datadog_profiling_native_extension/profiling.c +10 -8
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
  18. data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -76
  19. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -2
  20. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
  21. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
  22. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
  23. data/ext/libdatadog_api/crashtracker.c +3 -0
  24. data/lib/datadog/appsec/actions_handler.rb +27 -0
  25. data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
  26. data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
  27. data/lib/datadog/appsec/component.rb +14 -8
  28. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  29. data/lib/datadog/appsec/context.rb +74 -0
  30. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +12 -8
  31. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
  32. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
  33. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  34. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +20 -30
  35. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +6 -6
  36. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +67 -96
  38. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +11 -11
  39. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +6 -6
  40. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +7 -7
  41. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
  42. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -60
  43. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +23 -33
  44. data/lib/datadog/appsec/contrib/rails/patcher.rb +4 -14
  45. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +7 -7
  46. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +45 -65
  47. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +5 -28
  48. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +6 -6
  49. data/lib/datadog/appsec/event.rb +6 -6
  50. data/lib/datadog/appsec/ext.rb +8 -1
  51. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  52. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  53. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  54. data/lib/datadog/appsec/metrics.rb +13 -0
  55. data/lib/datadog/appsec/monitor/gateway/watcher.rb +23 -32
  56. data/lib/datadog/appsec/monitor/reactive/set_user.rb +6 -6
  57. data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
  58. data/lib/datadog/appsec/processor.rb +4 -3
  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 +17 -8
  64. data/lib/datadog/auto_instrument.rb +3 -0
  65. data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
  66. data/lib/datadog/core/configuration/components.rb +4 -2
  67. data/lib/datadog/core/configuration.rb +1 -1
  68. data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
  69. data/lib/datadog/core/crashtracking/component.rb +1 -3
  70. data/lib/datadog/core/telemetry/event.rb +87 -3
  71. data/lib/datadog/core/telemetry/logging.rb +2 -2
  72. data/lib/datadog/core/telemetry/metric.rb +22 -0
  73. data/lib/datadog/core/telemetry/worker.rb +33 -0
  74. data/lib/datadog/di/base.rb +115 -0
  75. data/lib/datadog/di/code_tracker.rb +7 -4
  76. data/lib/datadog/di/component.rb +19 -11
  77. data/lib/datadog/di/configuration/settings.rb +11 -1
  78. data/lib/datadog/di/contrib/railtie.rb +15 -0
  79. data/lib/datadog/di/contrib.rb +26 -0
  80. data/lib/datadog/di/error.rb +5 -0
  81. data/lib/datadog/di/instrumenter.rb +39 -18
  82. data/lib/datadog/di/{init.rb → preload.rb} +2 -4
  83. data/lib/datadog/di/probe_manager.rb +4 -4
  84. data/lib/datadog/di/probe_notification_builder.rb +22 -2
  85. data/lib/datadog/di/probe_notifier_worker.rb +5 -6
  86. data/lib/datadog/di/redactor.rb +0 -1
  87. data/lib/datadog/di/remote.rb +30 -9
  88. data/lib/datadog/di/transport.rb +2 -4
  89. data/lib/datadog/di.rb +5 -108
  90. data/lib/datadog/kit/appsec/events.rb +3 -3
  91. data/lib/datadog/kit/identity.rb +4 -4
  92. data/lib/datadog/profiling/component.rb +55 -53
  93. data/lib/datadog/profiling/http_transport.rb +1 -26
  94. data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
  95. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
  96. data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
  97. data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
  98. data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
  99. data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
  100. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
  101. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
  102. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
  103. data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
  104. data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
  105. data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
  106. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
  107. data/lib/datadog/tracing/contrib/extensions.rb +15 -3
  108. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  109. data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
  110. data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
  111. data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
  112. data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
  113. data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
  114. data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
  115. data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
  116. data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
  117. data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
  118. data/lib/datadog/tracing/span.rb +12 -4
  119. data/lib/datadog/tracing/span_event.rb +123 -3
  120. data/lib/datadog/tracing/span_operation.rb +6 -0
  121. data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
  122. data/lib/datadog/version.rb +1 -1
  123. metadata +40 -17
  124. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  125. data/lib/datadog/appsec/processor/context.rb +0 -107
  126. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  127. data/lib/datadog/appsec/scope.rb +0 -58
  128. data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
@@ -15,18 +15,18 @@ module Datadog
15
15
  ].freeze
16
16
  private_constant :ADDRESSES
17
17
 
18
- def self.publish(op, gateway_request)
18
+ def self.publish(engine, gateway_request)
19
19
  catch(:block) do
20
20
  # params have been parsed from the request body
21
- op.publish('rails.request.body', gateway_request.parsed_body)
22
- op.publish('rails.request.route_params', gateway_request.route_params)
21
+ engine.publish('rails.request.body', gateway_request.parsed_body)
22
+ engine.publish('rails.request.route_params', gateway_request.route_params)
23
23
 
24
24
  nil
25
25
  end
26
26
  end
27
27
 
28
- def self.subscribe(op, waf_context)
29
- op.subscribe(*ADDRESSES) do |*values|
28
+ def self.subscribe(engine, context)
29
+ engine.subscribe(*ADDRESSES) do |*values|
30
30
  Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
31
31
  body = values[0]
32
32
  path_params = values[1]
@@ -37,9 +37,9 @@ module Datadog
37
37
  }
38
38
 
39
39
  waf_timeout = Datadog.configuration.appsec.waf_timeout
40
- result = waf_context.run(persistent_data, {}, waf_timeout)
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?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../../instrumentation/gateway'
4
- require_relative '../../../reactive/operation'
4
+ require_relative '../../../reactive/engine'
5
5
  require_relative '../../rack/reactive/request_body'
6
6
  require_relative '../reactive/routed'
7
7
  require_relative '../../../event'
@@ -23,85 +23,65 @@ module Datadog
23
23
 
24
24
  def watch_request_dispatch(gateway = Instrumentation.gateway)
25
25
  gateway.watch('sinatra.request.dispatch', :appsec) do |stack, gateway_request|
26
- block = false
27
-
28
26
  event = nil
29
- scope = gateway_request.env[Datadog::AppSec::Ext::SCOPE_KEY]
30
-
31
- AppSec::Reactive::Operation.new('sinatra.request.dispatch') do |op|
32
- Rack::Reactive::RequestBody.subscribe(op, scope.processor_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: scope.trace,
38
- span: scope.service_entry_span,
39
- request: gateway_request,
40
- actions: result.actions
41
- }
42
-
43
- # We want to keep the trace in case of security event
44
- scope.trace.keep! if scope.trace
45
- Datadog::AppSec::Event.tag_and_keep!(scope, result)
46
- scope.processor_context.events << event
47
- end
27
+ 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.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.events << event
45
+
46
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
48
47
  end
49
-
50
- block = Rack::Reactive::RequestBody.publish(op, gateway_request)
51
48
  end
52
49
 
53
- next [nil, [[:block, event]]] if block
54
-
55
- ret, res = stack.call(gateway_request.request)
56
-
57
- if event
58
- res ||= []
59
- res << [:monitor, event]
60
- end
50
+ Rack::Reactive::RequestBody.publish(engine, gateway_request)
61
51
 
62
- [ret, res]
52
+ stack.call(gateway_request.request)
63
53
  end
64
54
  end
65
55
 
66
56
  def watch_request_routed(gateway = Instrumentation.gateway)
67
57
  gateway.watch('sinatra.request.routed', :appsec) do |stack, (gateway_request, gateway_route_params)|
68
- block = false
69
-
70
58
  event = nil
71
- scope = gateway_request.env[Datadog::AppSec::Ext::SCOPE_KEY]
72
-
73
- AppSec::Reactive::Operation.new('sinatra.request.routed') do |op|
74
- Sinatra::Reactive::Routed.subscribe(op, scope.processor_context) do |result|
75
- if result.status == :match
76
- # TODO: should this hash be an Event instance instead?
77
- event = {
78
- waf_result: result,
79
- trace: scope.trace,
80
- span: scope.service_entry_span,
81
- request: gateway_request,
82
- actions: result.actions
83
- }
84
-
85
- # We want to keep the trace in case of security event
86
- scope.trace.keep! if scope.trace
87
- Datadog::AppSec::Event.tag_and_keep!(scope, result)
88
- scope.processor_context.events << event
89
- end
59
+ context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
60
+ engine = AppSec::Reactive::Engine.new
61
+
62
+ Sinatra::Reactive::Routed.subscribe(engine, context) do |result|
63
+ if result.match?
64
+ # TODO: should this hash be an Event instance instead?
65
+ event = {
66
+ waf_result: result,
67
+ trace: context.trace,
68
+ span: context.span,
69
+ request: gateway_request,
70
+ actions: result.actions
71
+ }
72
+
73
+ # We want to keep the trace in case of security event
74
+ context.trace.keep! if context.trace
75
+ Datadog::AppSec::Event.tag_and_keep!(context, result)
76
+ context.events << event
77
+
78
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
90
79
  end
91
-
92
- block = Sinatra::Reactive::Routed.publish(op, [gateway_request, gateway_route_params])
93
80
  end
94
81
 
95
- next [nil, [[:block, event]]] if block
96
-
97
- ret, res = stack.call(gateway_request.request)
98
-
99
- if event
100
- res ||= []
101
- res << [:monitor, event]
102
- end
82
+ Sinatra::Reactive::Routed.publish(engine, [gateway_request, gateway_route_params])
103
83
 
104
- [ret, res]
84
+ stack.call(gateway_request.request)
105
85
  end
106
86
  end
107
87
  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'
@@ -54,7 +53,7 @@ module Datadog
54
53
  def dispatch!
55
54
  env = @request.env
56
55
 
57
- context = env[Datadog::AppSec::Ext::SCOPE_KEY]
56
+ context = env[Datadog::AppSec::Ext::CONTEXT_KEY]
58
57
 
59
58
  return super unless context
60
59
 
@@ -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
@@ -86,7 +76,7 @@ module Datadog
86
76
  def process_route(*)
87
77
  env = @request.env
88
78
 
89
- context = env[Datadog::AppSec::Ext::SCOPE_KEY]
79
+ context = env[Datadog::AppSec::Ext::CONTEXT_KEY]
90
80
 
91
81
  return super unless context
92
82
 
@@ -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
@@ -12,18 +12,18 @@ module Datadog
12
12
  ].freeze
13
13
  private_constant :ADDRESSES
14
14
 
15
- def self.publish(op, data)
15
+ def self.publish(engine, data)
16
16
  _request, route_params = data
17
17
 
18
18
  catch(:block) do
19
- op.publish('sinatra.request.route_params', route_params.params)
19
+ engine.publish('sinatra.request.route_params', route_params.params)
20
20
 
21
21
  nil
22
22
  end
23
23
  end
24
24
 
25
- def self.subscribe(op, waf_context)
26
- op.subscribe(*ADDRESSES) do |*values|
25
+ def self.subscribe(engine, context)
26
+ engine.subscribe(*ADDRESSES) do |*values|
27
27
  Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
28
28
  path_params = values[0]
29
29
 
@@ -32,9 +32,9 @@ module Datadog
32
32
  }
33
33
 
34
34
  waf_timeout = Datadog.configuration.appsec.waf_timeout
35
- result = waf_context.run(persistent_data, {}, waf_timeout)
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?
@@ -137,16 +137,16 @@ module Datadog
137
137
  end
138
138
  # rubocop:enable Metrics/MethodLength
139
139
 
140
- def tag_and_keep!(scope, waf_result)
140
+ def tag_and_keep!(context, waf_result)
141
141
  # We want to keep the trace in case of security event
142
- scope.trace.keep! if scope.trace
142
+ context.trace.keep! if context.trace
143
143
 
144
- if scope.service_entry_span
145
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if waf_result.actions.key?('block_request')
146
- scope.service_entry_span.set_tag('appsec.event', 'true')
144
+ if context.span
145
+ context.span.set_tag('appsec.blocked', 'true') if waf_result.actions.key?('block_request')
146
+ context.span.set_tag('appsec.event', 'true')
147
147
  end
148
148
 
149
- add_distributed_tags(scope.trace)
149
+ add_distributed_tags(context.trace)
150
150
  end
151
151
 
152
152
  private
@@ -3,12 +3,19 @@
3
3
  module Datadog
4
4
  module AppSec
5
5
  module Ext
6
+ RASP_SQLI = 'sql_injection'
7
+ RASP_LFI = 'lfi'
8
+ RASP_SSRF = 'ssrf'
9
+
6
10
  INTERRUPT = :datadog_appsec_interrupt
7
- SCOPE_KEY = 'datadog.appsec.scope'
11
+ CONTEXT_KEY = 'datadog.appsec.context'
12
+ ACTIVE_CONTEXT_KEY = :datadog_appsec_active_context
8
13
 
9
14
  TAG_APPSEC_ENABLED = '_dd.appsec.enabled'
10
15
  TAG_APM_ENABLED = '_dd.apm.enabled'
11
16
  TAG_DISTRIBUTED_APPSEC_EVENT = '_dd.p.appsec'
17
+
18
+ TELEMETRY_METRICS_NAMESPACE = 'appsec'
12
19
  end
13
20
  end
14
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'
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../instrumentation/gateway'
4
- require_relative '../../reactive/operation'
4
+ require_relative '../../reactive/engine'
5
5
  require_relative '../reactive/set_user'
6
6
 
7
7
  module Datadog
@@ -19,42 +19,33 @@ module Datadog
19
19
 
20
20
  def watch_user_id(gateway = Instrumentation.gateway)
21
21
  gateway.watch('identity.set_user', :appsec) do |stack, user|
22
- block = false
23
22
  event = nil
24
- scope = Datadog::AppSec.active_scope
25
-
26
- AppSec::Reactive::Operation.new('identity.set_user') do |op|
27
- Monitor::Reactive::SetUser.subscribe(op, scope.processor_context) do |result|
28
- if result.status == :match
29
- # TODO: should this hash be an Event instance instead?
30
- event = {
31
- waf_result: result,
32
- trace: scope.trace,
33
- span: scope.service_entry_span,
34
- user: user,
35
- actions: result.actions
36
- }
37
-
38
- # We want to keep the trace in case of security event
39
- scope.trace.keep! if scope.trace
40
- Datadog::AppSec::Event.tag_and_keep!(scope, result)
41
- scope.processor_context.events << event
42
- end
23
+ context = Datadog::AppSec.active_context
24
+ engine = AppSec::Reactive::Engine.new
25
+
26
+ Monitor::Reactive::SetUser.subscribe(engine, context) do |result|
27
+ if result.match?
28
+ # TODO: should this hash be an Event instance instead?
29
+ event = {
30
+ waf_result: result,
31
+ trace: context.trace,
32
+ span: context.span,
33
+ user: user,
34
+ actions: result.actions
35
+ }
36
+
37
+ # We want to keep the trace in case of security event
38
+ context.trace.keep! if context.trace
39
+ Datadog::AppSec::Event.tag_and_keep!(context, result)
40
+ context.events << event
41
+
42
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
43
43
  end
44
-
45
- block = Monitor::Reactive::SetUser.publish(op, user)
46
44
  end
47
45
 
48
- throw(Datadog::AppSec::Ext::INTERRUPT, [nil, [[:block, event]]]) if block
49
-
50
- ret, res = stack.call(user)
51
-
52
- if event
53
- res ||= []
54
- res << [:monitor, event]
55
- end
46
+ Monitor::Reactive::SetUser.publish(engine, user)
56
47
 
57
- [ret, res]
48
+ stack.call(user)
58
49
  end
59
50
  end
60
51
  end
@@ -11,16 +11,16 @@ module Datadog
11
11
  ].freeze
12
12
  private_constant :ADDRESSES
13
13
 
14
- def self.publish(op, user)
14
+ def self.publish(engine, user)
15
15
  catch(:block) do
16
- op.publish('usr.id', user.id)
16
+ engine.publish('usr.id', user.id)
17
17
 
18
18
  nil
19
19
  end
20
20
  end
21
21
 
22
- def self.subscribe(op, waf_context)
23
- op.subscribe(*ADDRESSES) do |*values|
22
+ def self.subscribe(engine, context)
23
+ engine.subscribe(*ADDRESSES) do |*values|
24
24
  Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
25
25
 
26
26
  user_id = values[0]
@@ -30,9 +30,9 @@ module Datadog
30
30
  }
31
31
 
32
32
  waf_timeout = Datadog.configuration.appsec.waf_timeout
33
- result = waf_context.run(persistent_data, {}, waf_timeout)
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?
@@ -74,9 +74,6 @@ module Datadog
74
74
  when Hash
75
75
  pass = ip_passlist[:pass]
76
76
  monitor = ip_passlist[:monitor]
77
- else
78
- pass = []
79
- monitor = []
80
77
  end
81
78
 
82
79
  exclusions = []
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'processor/context'
3
+ require_relative 'security_engine/runner'
4
4
 
5
5
  module Datadog
6
6
  module AppSec
7
7
  # Processor integrates libddwaf into datadog/appsec
8
+ # NOTE: This class will be moved under AppSec::SecurityEngine namespace
8
9
  class Processor
9
10
  attr_reader :diagnostics, :addresses
10
11
 
@@ -29,8 +30,8 @@ module Datadog
29
30
  @handle.finalize
30
31
  end
31
32
 
32
- def new_context
33
- Context.new(@handle, telemetry: @telemetry)
33
+ def new_runner
34
+ SecurityEngine::Runner.new(@handle, telemetry: @telemetry)
34
35
  end
35
36
 
36
37
  private