datadog 2.4.0 → 2.6.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +57 -18
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +93 -106
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +8 -2
  7. data/ext/datadog_profiling_native_extension/extconf.rb +8 -8
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +174 -28
  9. data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
  10. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
  11. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +1 -1
  12. data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
  13. data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
  14. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  15. data/ext/libdatadog_api/crashtracker.c +3 -5
  16. data/ext/libdatadog_extconf_helpers.rb +1 -1
  17. data/lib/datadog/appsec/configuration/settings.rb +8 -0
  18. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -5
  19. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
  20. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
  21. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
  22. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
  23. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
  24. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
  25. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
  26. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
  27. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
  28. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
  29. data/lib/datadog/appsec/event.rb +24 -0
  30. data/lib/datadog/appsec/ext.rb +4 -0
  31. data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
  32. data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
  33. data/lib/datadog/appsec/processor/context.rb +107 -0
  34. data/lib/datadog/appsec/processor.rb +7 -71
  35. data/lib/datadog/appsec/scope.rb +1 -4
  36. data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
  37. data/lib/datadog/appsec/utils.rb +2 -0
  38. data/lib/datadog/appsec.rb +1 -0
  39. data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
  40. data/lib/datadog/core/configuration/settings.rb +12 -0
  41. data/lib/datadog/core/configuration.rb +1 -3
  42. data/lib/datadog/core/crashtracking/component.rb +8 -5
  43. data/lib/datadog/core/environment/yjit.rb +5 -0
  44. data/lib/datadog/core/remote/transport/http.rb +5 -0
  45. data/lib/datadog/core/remote/worker.rb +1 -1
  46. data/lib/datadog/core/runtime/ext.rb +1 -0
  47. data/lib/datadog/core/runtime/metrics.rb +4 -0
  48. data/lib/datadog/core/semaphore.rb +35 -0
  49. data/lib/datadog/core/telemetry/logging.rb +10 -10
  50. data/lib/datadog/core/transport/ext.rb +1 -0
  51. data/lib/datadog/core/workers/async.rb +1 -1
  52. data/lib/datadog/di/code_tracker.rb +11 -13
  53. data/lib/datadog/di/instrumenter.rb +301 -0
  54. data/lib/datadog/di/probe.rb +29 -0
  55. data/lib/datadog/di/probe_builder.rb +7 -1
  56. data/lib/datadog/di/probe_notification_builder.rb +207 -0
  57. data/lib/datadog/di/probe_notifier_worker.rb +244 -0
  58. data/lib/datadog/di/serializer.rb +23 -1
  59. data/lib/datadog/di/transport.rb +67 -0
  60. data/lib/datadog/di/utils.rb +39 -0
  61. data/lib/datadog/di.rb +43 -0
  62. data/lib/datadog/profiling/collectors/thread_context.rb +9 -11
  63. data/lib/datadog/profiling/component.rb +1 -0
  64. data/lib/datadog/profiling/stack_recorder.rb +37 -9
  65. data/lib/datadog/tracing/component.rb +13 -0
  66. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
  67. data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
  68. data/lib/datadog/tracing/contrib/faraday/middleware.rb +3 -0
  69. data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -2
  70. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
  71. data/lib/datadog/tracing/contrib/http/instrumentation.rb +4 -0
  72. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +4 -0
  73. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +4 -0
  74. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  75. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
  76. data/lib/datadog/tracing/sampling/rule_sampler.rb +6 -4
  77. data/lib/datadog/tracing/tracer.rb +15 -10
  78. data/lib/datadog/tracing/transport/http.rb +4 -0
  79. data/lib/datadog/tracing/workers.rb +1 -1
  80. data/lib/datadog/tracing/writer.rb +26 -28
  81. data/lib/datadog/version.rb +1 -1
  82. metadata +22 -14
@@ -25,30 +25,17 @@ module Datadog
25
25
  Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
26
26
  arguments = values[0]
27
27
 
28
- waf_args = {
28
+ persistent_data = {
29
29
  'graphql.server.all_resolvers' => arguments
30
30
  }
31
31
 
32
32
  waf_timeout = Datadog.configuration.appsec.waf_timeout
33
- result = waf_context.run(waf_args, waf_timeout)
34
-
35
- Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
36
-
37
- case result.status
38
- when :match
39
- Datadog.logger.debug { "WAF: #{result.inspect}" }
40
-
41
- yield result
42
- throw(:block, true) unless result.actions.empty?
43
- when :ok
44
- Datadog.logger.debug { "WAF OK: #{result.inspect}" }
45
- when :invalid_call
46
- Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
47
- when :invalid_rule, :invalid_flow, :no_rule
48
- Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
49
- else
50
- Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
51
- end
33
+ result = waf_context.run(persistent_data, {}, waf_timeout)
34
+
35
+ next if result.status != :match
36
+
37
+ yield result
38
+ throw(:block, true) unless result.actions.empty?
52
39
  end
53
40
  end
54
41
  end
@@ -41,11 +41,9 @@ module Datadog
41
41
  actions: result.actions
42
42
  }
43
43
 
44
- if scope.service_entry_span
45
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
46
- scope.service_entry_span.set_tag('appsec.event', 'true')
47
- end
48
-
44
+ # We want to keep the trace in case of security event
45
+ scope.trace.keep! if scope.trace
46
+ Datadog::AppSec::Event.tag_and_keep!(scope, result)
49
47
  scope.processor_context.events << event
50
48
  end
51
49
  end
@@ -85,11 +83,9 @@ module Datadog
85
83
  actions: result.actions
86
84
  }
87
85
 
88
- if scope.service_entry_span
89
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
90
- scope.service_entry_span.set_tag('appsec.event', 'true')
91
- end
92
-
86
+ # We want to keep the trace in case of security event
87
+ scope.trace.keep! if scope.trace
88
+ Datadog::AppSec::Event.tag_and_keep!(scope, result)
93
89
  scope.processor_context.events << event
94
90
  end
95
91
  end
@@ -129,11 +125,9 @@ module Datadog
129
125
  actions: result.actions
130
126
  }
131
127
 
132
- if scope.service_entry_span
133
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
134
- scope.service_entry_span.set_tag('appsec.event', 'true')
135
- end
136
-
128
+ # We want to keep the trace in case of security event
129
+ scope.trace.keep! if scope.trace
130
+ Datadog::AppSec::Event.tag_and_keep!(scope, result)
137
131
  scope.processor_context.events << event
138
132
  end
139
133
  end
@@ -33,6 +33,7 @@ module Datadog
33
33
  def self.subscribe(op, waf_context)
34
34
  op.subscribe(*ADDRESSES) do |*values|
35
35
  Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
36
+
36
37
  headers = values[0]
37
38
  headers_no_cookies = headers.dup.tap { |h| h.delete('cookie') }
38
39
  uri_raw = values[1]
@@ -41,7 +42,7 @@ module Datadog
41
42
  client_ip = values[4]
42
43
  request_method = values[5]
43
44
 
44
- waf_args = {
45
+ persistent_data = {
45
46
  'server.request.cookies' => cookies,
46
47
  'server.request.query' => query,
47
48
  'server.request.uri.raw' => uri_raw,
@@ -52,25 +53,12 @@ module Datadog
52
53
  }
53
54
 
54
55
  waf_timeout = Datadog.configuration.appsec.waf_timeout
55
- result = waf_context.run(waf_args, waf_timeout)
56
-
57
- Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
56
+ result = waf_context.run(persistent_data, {}, waf_timeout)
58
57
 
59
- case result.status
60
- when :match
61
- Datadog.logger.debug { "WAF: #{result.inspect}" }
58
+ next if result.status != :match
62
59
 
63
- yield result
64
- throw(:block, true) unless result.actions.empty?
65
- when :ok
66
- Datadog.logger.debug { "WAF OK: #{result.inspect}" }
67
- when :invalid_call
68
- Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
69
- when :invalid_rule, :invalid_flow, :no_rule
70
- Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
71
- else
72
- Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
73
- end
60
+ yield result
61
+ throw(:block, true) unless result.actions.empty?
74
62
  end
75
63
  end
76
64
  end
@@ -26,30 +26,17 @@ module Datadog
26
26
  Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
27
27
  body = values[0]
28
28
 
29
- waf_args = {
29
+ persistent_data = {
30
30
  'server.request.body' => body,
31
31
  }
32
32
 
33
33
  waf_timeout = Datadog.configuration.appsec.waf_timeout
34
- result = waf_context.run(waf_args, waf_timeout)
35
-
36
- Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
37
-
38
- case result.status
39
- when :match
40
- Datadog.logger.debug { "WAF: #{result.inspect}" }
41
-
42
- yield result
43
- throw(:block, true) unless result.actions.empty?
44
- when :ok
45
- Datadog.logger.debug { "WAF OK: #{result.inspect}" }
46
- when :invalid_call
47
- Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
48
- when :invalid_rule, :invalid_flow, :no_rule
49
- Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
50
- else
51
- Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
52
- end
34
+ result = waf_context.run(persistent_data, {}, waf_timeout)
35
+
36
+ next if result.status != :match
37
+
38
+ yield result
39
+ throw(:block, true) unless result.actions.empty?
53
40
  end
54
41
  end
55
42
  end
@@ -30,32 +30,19 @@ module Datadog
30
30
  response_headers = values[1]
31
31
  response_headers_no_cookies = response_headers.dup.tap { |h| h.delete('set-cookie') }
32
32
 
33
- waf_args = {
33
+ persistent_data = {
34
34
  'server.response.status' => response_status.to_s,
35
35
  'server.response.headers' => response_headers,
36
36
  'server.response.headers.no_cookies' => response_headers_no_cookies,
37
37
  }
38
38
 
39
39
  waf_timeout = Datadog.configuration.appsec.waf_timeout
40
- result = waf_context.run(waf_args, waf_timeout)
40
+ result = waf_context.run(persistent_data, {}, waf_timeout)
41
41
 
42
- Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
42
+ next if result.status != :match
43
43
 
44
- case result.status
45
- when :match
46
- Datadog.logger.debug { "WAF: #{result.inspect}" }
47
-
48
- yield result
49
- throw(:block, true) unless result.actions.empty?
50
- when :ok
51
- Datadog.logger.debug { "WAF OK: #{result.inspect}" }
52
- when :invalid_call
53
- Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
54
- when :invalid_rule, :invalid_flow, :no_rule
55
- Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
56
- else
57
- Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
58
- end
44
+ yield result
45
+ throw(:block, true) unless result.actions.empty?
59
46
  end
60
47
  end
61
48
  end
@@ -150,7 +150,9 @@ module Datadog
150
150
 
151
151
  return unless trace && span
152
152
 
153
- span.set_tag('_dd.appsec.enabled', 1)
153
+ span.set_metric(Datadog::AppSec::Ext::TAG_APPSEC_ENABLED, 1)
154
+ # We add this tag when ASM standalone is enabled to make sure we don't bill APM
155
+ span.set_metric(Datadog::AppSec::Ext::TAG_APM_ENABLED, 0) if Datadog.configuration.appsec.standalone.enabled
154
156
  span.set_tag('_dd.runtime_family', 'ruby')
155
157
  span.set_tag('_dd.appsec.waf.version', Datadog::AppSec::WAF::VERSION::BASE_STRING)
156
158
 
@@ -38,11 +38,9 @@ module Datadog
38
38
  actions: result.actions
39
39
  }
40
40
 
41
- if scope.service_entry_span
42
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
43
- scope.service_entry_span.set_tag('appsec.event', 'true')
44
- end
45
-
41
+ # We want to keep the trace in case of security event
42
+ scope.trace.keep! if scope.trace
43
+ Datadog::AppSec::Event.tag_and_keep!(scope, result)
46
44
  scope.processor_context.events << event
47
45
  end
48
46
  end
@@ -31,31 +31,18 @@ module Datadog
31
31
  body = values[0]
32
32
  path_params = values[1]
33
33
 
34
- waf_args = {
34
+ persistent_data = {
35
35
  'server.request.body' => body,
36
36
  'server.request.path_params' => path_params,
37
37
  }
38
38
 
39
39
  waf_timeout = Datadog.configuration.appsec.waf_timeout
40
- result = waf_context.run(waf_args, waf_timeout)
40
+ result = waf_context.run(persistent_data, {}, waf_timeout)
41
41
 
42
- Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
42
+ next if result.status != :match
43
43
 
44
- case result.status
45
- when :match
46
- Datadog.logger.debug { "WAF: #{result.inspect}" }
47
-
48
- yield result
49
- throw(:block, true) unless result.actions.empty?
50
- when :ok
51
- Datadog.logger.debug { "WAF OK: #{result.inspect}" }
52
- when :invalid_call
53
- Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
54
- when :invalid_rule, :invalid_flow, :no_rule
55
- Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
56
- else
57
- Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
58
- end
44
+ yield result
45
+ throw(:block, true) unless result.actions.empty?
59
46
  end
60
47
  end
61
48
  end
@@ -40,11 +40,9 @@ module Datadog
40
40
  actions: result.actions
41
41
  }
42
42
 
43
- if scope.service_entry_span
44
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
45
- scope.service_entry_span.set_tag('appsec.event', 'true')
46
- end
47
-
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)
48
46
  scope.processor_context.events << event
49
47
  end
50
48
  end
@@ -84,11 +82,9 @@ module Datadog
84
82
  actions: result.actions
85
83
  }
86
84
 
87
- if scope.service_entry_span
88
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
89
- scope.service_entry_span.set_tag('appsec.event', 'true')
90
- end
91
-
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)
92
88
  scope.processor_context.events << event
93
89
  end
94
90
  end
@@ -27,30 +27,17 @@ module Datadog
27
27
  Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
28
28
  path_params = values[0]
29
29
 
30
- waf_args = {
30
+ persistent_data = {
31
31
  'server.request.path_params' => path_params,
32
32
  }
33
33
 
34
34
  waf_timeout = Datadog.configuration.appsec.waf_timeout
35
- result = waf_context.run(waf_args, waf_timeout)
36
-
37
- Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
38
-
39
- case result.status
40
- when :match
41
- Datadog.logger.debug { "WAF: #{result.inspect}" }
42
-
43
- yield result
44
- throw(:block, true) unless result.actions.empty?
45
- when :ok
46
- Datadog.logger.debug { "WAF OK: #{result.inspect}" }
47
- when :invalid_call
48
- Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
49
- when :invalid_rule, :invalid_flow, :no_rule
50
- Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
51
- else
52
- Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
53
- end
35
+ result = waf_context.run(persistent_data, {}, waf_timeout)
36
+
37
+ next if result.status != :match
38
+
39
+ yield result
40
+ throw(:block, true) unless result.actions.empty?
54
41
  end
55
42
  end
56
43
  end
@@ -137,6 +137,18 @@ module Datadog
137
137
  end
138
138
  # rubocop:enable Metrics/MethodLength
139
139
 
140
+ def tag_and_keep!(scope, waf_result)
141
+ # We want to keep the trace in case of security event
142
+ scope.trace.keep! if scope.trace
143
+
144
+ if scope.service_entry_span
145
+ scope.service_entry_span.set_tag('appsec.blocked', 'true') if waf_result.actions.include?('block')
146
+ scope.service_entry_span.set_tag('appsec.event', 'true')
147
+ end
148
+
149
+ add_distributed_tags(scope.trace)
150
+ end
151
+
140
152
  private
141
153
 
142
154
  def compressed_and_base64_encoded(value)
@@ -165,6 +177,18 @@ module Datadog
165
177
  gz.close
166
178
  sio.string
167
179
  end
180
+
181
+ # Propagate to downstream services the information that the current distributed trace is
182
+ # containing at least one ASM security event.
183
+ def add_distributed_tags(trace)
184
+ return unless trace
185
+
186
+ trace.set_tag(
187
+ Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
188
+ Datadog::Tracing::Sampling::Ext::Decision::ASM
189
+ )
190
+ trace.set_tag(Datadog::AppSec::Ext::TAG_DISTRIBUTED_APPSEC_EVENT, '1')
191
+ end
168
192
  end
169
193
  end
170
194
  end
@@ -5,6 +5,10 @@ module Datadog
5
5
  module Ext
6
6
  INTERRUPT = :datadog_appsec_interrupt
7
7
  SCOPE_KEY = 'datadog.appsec.scope'
8
+
9
+ TAG_APPSEC_ENABLED = '_dd.appsec.enabled'
10
+ TAG_APM_ENABLED = '_dd.apm.enabled'
11
+ TAG_DISTRIBUTED_APPSEC_EVENT = '_dd.p.appsec'
8
12
  end
9
13
  end
10
14
  end
@@ -35,11 +35,9 @@ module Datadog
35
35
  actions: result.actions
36
36
  }
37
37
 
38
- if scope.service_entry_span
39
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
40
- scope.service_entry_span.set_tag('appsec.event', 'true')
41
- end
42
-
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)
43
41
  scope.processor_context.events << event
44
42
  end
45
43
  end
@@ -25,30 +25,17 @@ module Datadog
25
25
 
26
26
  user_id = values[0]
27
27
 
28
- waf_args = {
28
+ persistent_data = {
29
29
  'usr.id' => user_id,
30
30
  }
31
31
 
32
32
  waf_timeout = Datadog.configuration.appsec.waf_timeout
33
- result = waf_context.run(waf_args, waf_timeout)
34
-
35
- Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
36
-
37
- case result.status
38
- when :match
39
- Datadog.logger.debug { "WAF: #{result.inspect}" }
40
-
41
- yield result
42
- throw(:block, true) unless result.actions.empty?
43
- when :ok
44
- Datadog.logger.debug { "WAF OK: #{result.inspect}" }
45
- when :invalid_call
46
- Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
47
- when :invalid_rule, :invalid_flow, :no_rule
48
- Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
49
- else
50
- Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
51
- end
33
+ result = waf_context.run(persistent_data, {}, waf_timeout)
34
+
35
+ next if result.status != :match
36
+
37
+ yield result
38
+ throw(:block, true) unless result.actions.empty?
52
39
  end
53
40
  end
54
41
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ class Processor
6
+ # Context manages a sequence of runs
7
+ class Context
8
+ LIBDDWAF_SUCCESSFUL_EXECUTION_CODES = [:ok, :match].freeze
9
+
10
+ attr_reader :time_ns, :time_ext_ns, :timeouts, :events
11
+
12
+ def initialize(handle, telemetry:)
13
+ @context = WAF::Context.new(handle)
14
+ @telemetry = telemetry
15
+
16
+ @time_ns = 0.0
17
+ @time_ext_ns = 0.0
18
+ @timeouts = 0
19
+ @events = []
20
+ @run_mutex = Mutex.new
21
+
22
+ @libddwaf_debug_tag = "libddwaf:#{WAF::VERSION::STRING}"
23
+ end
24
+
25
+ def run(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
26
+ @run_mutex.lock
27
+
28
+ start_ns = Core::Utils::Time.get_time(:nanosecond)
29
+
30
+ persistent_data.reject! do |_, v|
31
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
32
+
33
+ v.nil? ? true : v.empty?
34
+ end
35
+
36
+ ephemeral_data.reject! do |_, v|
37
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
38
+
39
+ v.nil? ? true : v.empty?
40
+ end
41
+
42
+ _code, result = try_run(persistent_data, ephemeral_data, timeout)
43
+
44
+ stop_ns = Core::Utils::Time.get_time(:nanosecond)
45
+
46
+ # these updates are not thread safe and should be protected
47
+ @time_ns += result.total_runtime
48
+ @time_ext_ns += (stop_ns - start_ns)
49
+ @timeouts += 1 if result.timeout
50
+
51
+ report_execution(result)
52
+ result
53
+ ensure
54
+ @run_mutex.unlock
55
+ end
56
+
57
+ def extract_schema
58
+ return unless extract_schema?
59
+
60
+ input = {
61
+ 'waf.context.processor' => {
62
+ 'extract-schema' => true
63
+ }
64
+ }
65
+
66
+ _code, result = try_run(input, {}, WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
67
+
68
+ report_execution(result)
69
+ result
70
+ end
71
+
72
+ def finalize
73
+ @context.finalize
74
+ end
75
+
76
+ private
77
+
78
+ def try_run(persistent_data, ephemeral_data, timeout)
79
+ @context.run(persistent_data, ephemeral_data, timeout)
80
+ rescue WAF::LibDDWAF::Error => e
81
+ Datadog.logger.debug { "#{@libddwaf_debug_tag} execution error: #{e} backtrace: #{e.backtrace&.first(3)}" }
82
+ @telemetry.report(e, description: 'libddwaf internal low-level error')
83
+
84
+ [:err_internal, WAF::Result.new(:err_internal, [], 0.0, false, [], [])]
85
+ end
86
+
87
+ def report_execution(result)
88
+ Datadog.logger.debug { "#{@libddwaf_debug_tag} execution timed out: #{result.inspect}" } if result.timeout
89
+
90
+ if LIBDDWAF_SUCCESSFUL_EXECUTION_CODES.include?(result.status)
91
+ Datadog.logger.debug { "#{@libddwaf_debug_tag} execution result: #{result.inspect}" }
92
+ else
93
+ message = "#{@libddwaf_debug_tag} execution error: #{result.status.inspect}"
94
+
95
+ Datadog.logger.debug { message }
96
+ @telemetry.error(message)
97
+ end
98
+ end
99
+
100
+ def extract_schema?
101
+ Datadog.configuration.appsec.api_security.enabled &&
102
+ Datadog.configuration.appsec.api_security.sample_rate.sample?
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,83 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'processor/context'
4
+
3
5
  module Datadog
4
6
  module AppSec
5
7
  # Processor integrates libddwaf into datadog/appsec
6
8
  class Processor
7
- # Context manages a sequence of runs
8
- class Context
9
- attr_reader :time_ns, :time_ext_ns, :timeouts, :events
10
-
11
- def initialize(processor)
12
- @context = Datadog::AppSec::WAF::Context.new(processor.send(:handle))
13
- @time_ns = 0.0
14
- @time_ext_ns = 0.0
15
- @timeouts = 0
16
- @events = []
17
- @run_mutex = Mutex.new
18
- end
19
-
20
- def run(input, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
21
- @run_mutex.lock
22
-
23
- start_ns = Core::Utils::Time.get_time(:nanosecond)
24
-
25
- input.reject! do |_, v|
26
- case v
27
- when TrueClass, FalseClass
28
- false
29
- else
30
- v.nil? ? true : v.empty?
31
- end
32
- end
33
-
34
- _code, res = @context.run(input, timeout)
35
-
36
- stop_ns = Core::Utils::Time.get_time(:nanosecond)
37
-
38
- # these updates are not thread safe and should be protected
39
- @time_ns += res.total_runtime
40
- @time_ext_ns += (stop_ns - start_ns)
41
- @timeouts += 1 if res.timeout
42
-
43
- res
44
- ensure
45
- @run_mutex.unlock
46
- end
47
-
48
- def extract_schema
49
- return unless extract_schema?
50
-
51
- input = {
52
- 'waf.context.processor' => {
53
- 'extract-schema' => true
54
- }
55
- }
56
-
57
- _code, res = @context.run(input, WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
58
-
59
- res
60
- end
61
-
62
- def finalize
63
- @context.finalize
64
- end
65
-
66
- private
67
-
68
- def extract_schema?
69
- Datadog.configuration.appsec.api_security.enabled &&
70
- Datadog.configuration.appsec.api_security.sample_rate.sample?
71
- end
72
- end
73
-
74
9
  attr_reader :diagnostics, :addresses
75
10
 
76
11
  def initialize(ruleset:, telemetry:)
12
+ @telemetry = telemetry
77
13
  @diagnostics = nil
78
14
  @addresses = []
15
+
79
16
  settings = Datadog.configuration.appsec
80
- @telemetry = telemetry
81
17
 
82
18
  # TODO: Refactor to make it easier to test
83
19
  unless require_libddwaf && libddwaf_provides_waf? && create_waf_handle(settings, ruleset)
@@ -93,9 +29,9 @@ module Datadog
93
29
  @handle.finalize
94
30
  end
95
31
 
96
- protected
97
-
98
- attr_reader :handle
32
+ def new_context
33
+ Context.new(@handle, telemetry: @telemetry)
34
+ end
99
35
 
100
36
  private
101
37