datadog 2.3.0 → 2.5.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -2
  3. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
  4. data/ext/datadog_profiling_loader/extconf.rb +10 -22
  5. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
  6. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
  7. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +645 -107
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
  12. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
  13. data/ext/datadog_profiling_native_extension/extconf.rb +42 -25
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  15. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  16. data/ext/datadog_profiling_native_extension/heap_recorder.c +194 -34
  17. data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
  18. data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
  19. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
  20. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
  21. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
  22. data/ext/datadog_profiling_native_extension/profiling.c +1 -1
  23. data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
  24. data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
  25. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  26. data/ext/libdatadog_api/crashtracker.c +20 -18
  27. data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
  28. data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
  29. data/ext/libdatadog_extconf_helpers.rb +1 -1
  30. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  31. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  32. data/lib/datadog/appsec/component.rb +29 -8
  33. data/lib/datadog/appsec/configuration/settings.rb +10 -2
  34. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  35. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  36. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  37. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
  38. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
  39. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +14 -15
  40. data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
  41. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
  42. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
  43. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
  44. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
  45. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
  46. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
  47. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
  48. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
  49. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
  50. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
  51. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
  52. data/lib/datadog/appsec/event.rb +25 -1
  53. data/lib/datadog/appsec/ext.rb +4 -0
  54. data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
  55. data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
  56. data/lib/datadog/appsec/processor/context.rb +109 -0
  57. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  58. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  59. data/lib/datadog/appsec/processor.rb +42 -107
  60. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  61. data/lib/datadog/appsec/remote.rb +7 -3
  62. data/lib/datadog/appsec/scope.rb +1 -4
  63. data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
  64. data/lib/datadog/appsec/utils.rb +2 -0
  65. data/lib/datadog/appsec.rb +3 -2
  66. data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
  67. data/lib/datadog/core/configuration/components.rb +4 -3
  68. data/lib/datadog/core/configuration/settings.rb +96 -5
  69. data/lib/datadog/core/configuration.rb +1 -3
  70. data/lib/datadog/core/crashtracking/component.rb +9 -6
  71. data/lib/datadog/core/environment/execution.rb +5 -5
  72. data/lib/datadog/core/environment/yjit.rb +5 -0
  73. data/lib/datadog/core/metrics/client.rb +7 -0
  74. data/lib/datadog/core/rate_limiter.rb +183 -0
  75. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  76. data/lib/datadog/core/remote/component.rb +4 -2
  77. data/lib/datadog/core/remote/negotiation.rb +4 -4
  78. data/lib/datadog/core/remote/tie.rb +2 -0
  79. data/lib/datadog/core/remote/transport/http.rb +5 -0
  80. data/lib/datadog/core/remote/worker.rb +1 -1
  81. data/lib/datadog/core/runtime/ext.rb +1 -0
  82. data/lib/datadog/core/runtime/metrics.rb +5 -1
  83. data/lib/datadog/core/semaphore.rb +35 -0
  84. data/lib/datadog/core/telemetry/component.rb +2 -0
  85. data/lib/datadog/core/telemetry/event.rb +12 -7
  86. data/lib/datadog/core/telemetry/logger.rb +51 -0
  87. data/lib/datadog/core/telemetry/logging.rb +50 -14
  88. data/lib/datadog/core/telemetry/request.rb +13 -1
  89. data/lib/datadog/core/transport/ext.rb +1 -0
  90. data/lib/datadog/core/utils/time.rb +12 -0
  91. data/lib/datadog/core/workers/async.rb +1 -1
  92. data/lib/datadog/di/code_tracker.rb +166 -0
  93. data/lib/datadog/di/configuration/settings.rb +163 -0
  94. data/lib/datadog/di/configuration.rb +11 -0
  95. data/lib/datadog/di/error.rb +31 -0
  96. data/lib/datadog/di/extensions.rb +16 -0
  97. data/lib/datadog/di/instrumenter.rb +301 -0
  98. data/lib/datadog/di/probe.rb +162 -0
  99. data/lib/datadog/di/probe_builder.rb +47 -0
  100. data/lib/datadog/di/probe_notification_builder.rb +207 -0
  101. data/lib/datadog/di/probe_notifier_worker.rb +244 -0
  102. data/lib/datadog/di/redactor.rb +188 -0
  103. data/lib/datadog/di/serializer.rb +215 -0
  104. data/lib/datadog/di/transport.rb +67 -0
  105. data/lib/datadog/di/utils.rb +39 -0
  106. data/lib/datadog/di.rb +57 -0
  107. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
  109. data/lib/datadog/profiling/collectors/info.rb +12 -3
  110. data/lib/datadog/profiling/collectors/thread_context.rb +32 -8
  111. data/lib/datadog/profiling/component.rb +21 -4
  112. data/lib/datadog/profiling/http_transport.rb +6 -1
  113. data/lib/datadog/profiling/scheduler.rb +2 -0
  114. data/lib/datadog/profiling/stack_recorder.rb +40 -9
  115. data/lib/datadog/single_step_instrument.rb +12 -0
  116. data/lib/datadog/tracing/component.rb +13 -0
  117. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  118. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  119. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  120. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  121. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  122. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  123. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  124. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
  125. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  126. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  127. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  128. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
  129. data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
  130. data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
  131. data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
  132. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  133. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  134. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  135. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
  136. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
  137. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
  138. data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
  139. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
  140. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  141. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -0
  142. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  143. data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
  144. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  145. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  146. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  147. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  148. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  149. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  150. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  151. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
  152. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  153. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  154. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  155. data/lib/datadog/tracing/metadata/ext.rb +2 -0
  156. data/lib/datadog/tracing/remote.rb +5 -2
  157. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  158. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  159. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  160. data/lib/datadog/tracing/sampling/rule_sampler.rb +15 -9
  161. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  162. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  163. data/lib/datadog/tracing/trace_operation.rb +26 -2
  164. data/lib/datadog/tracing/tracer.rb +29 -22
  165. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  166. data/lib/datadog/tracing/transport/http.rb +4 -0
  167. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  168. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  169. data/lib/datadog/tracing/workers.rb +2 -2
  170. data/lib/datadog/tracing/writer.rb +26 -28
  171. data/lib/datadog/version.rb +1 -1
  172. metadata +40 -15
  173. data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -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
@@ -52,7 +52,7 @@ module Datadog
52
52
  # ensure rate limiter is called only when there are events to record
53
53
  return if events.empty? || span.nil?
54
54
 
55
- Datadog::AppSec::RateLimiter.limit(:traces) do
55
+ Datadog::AppSec::RateLimiter.thread_local.limit do
56
56
  record_via_span(span, *events)
57
57
  end
58
58
  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,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'libddwaf'
4
+
5
+ module Datadog
6
+ module AppSec
7
+ class Processor
8
+ # Context manages a sequence of runs
9
+ class Context
10
+ LIBDDWAF_SUCCESSFUL_EXECUTION_CODES = [:ok, :match].freeze
11
+
12
+ attr_reader :time_ns, :time_ext_ns, :timeouts, :events
13
+
14
+ def initialize(handle, telemetry:)
15
+ @context = WAF::Context.new(handle)
16
+ @telemetry = telemetry
17
+
18
+ @time_ns = 0.0
19
+ @time_ext_ns = 0.0
20
+ @timeouts = 0
21
+ @events = []
22
+ @run_mutex = Mutex.new
23
+
24
+ @libddwaf_debug_tag = "libddwaf:#{WAF::VERSION::STRING}"
25
+ end
26
+
27
+ def run(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
28
+ @run_mutex.lock
29
+
30
+ start_ns = Core::Utils::Time.get_time(:nanosecond)
31
+
32
+ persistent_data.reject! do |_, v|
33
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
34
+
35
+ v.nil? ? true : v.empty?
36
+ end
37
+
38
+ ephemeral_data.reject! do |_, v|
39
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
40
+
41
+ v.nil? ? true : v.empty?
42
+ end
43
+
44
+ _code, result = try_run(persistent_data, ephemeral_data, timeout)
45
+
46
+ stop_ns = Core::Utils::Time.get_time(:nanosecond)
47
+
48
+ # these updates are not thread safe and should be protected
49
+ @time_ns += result.total_runtime
50
+ @time_ext_ns += (stop_ns - start_ns)
51
+ @timeouts += 1 if result.timeout
52
+
53
+ report_execution(result)
54
+ result
55
+ ensure
56
+ @run_mutex.unlock
57
+ end
58
+
59
+ def extract_schema
60
+ return unless extract_schema?
61
+
62
+ input = {
63
+ 'waf.context.processor' => {
64
+ 'extract-schema' => true
65
+ }
66
+ }
67
+
68
+ _code, result = try_run(input, {}, WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
69
+
70
+ report_execution(result)
71
+ result
72
+ end
73
+
74
+ def finalize
75
+ @context.finalize
76
+ end
77
+
78
+ private
79
+
80
+ def try_run(persistent_data, ephemeral_data, timeout)
81
+ @context.run(persistent_data, ephemeral_data, timeout)
82
+ rescue WAF::LibDDWAF::Error => e
83
+ Datadog.logger.debug { "#{@libddwaf_debug_tag} execution error: #{e} backtrace: #{e.backtrace&.first(3)}" }
84
+ @telemetry.report(e, description: 'libddwaf internal low-level error')
85
+
86
+ [:err_internal, WAF::Result.new(:err_internal, [], 0.0, false, [], [])]
87
+ end
88
+
89
+ def report_execution(result)
90
+ Datadog.logger.debug { "#{@libddwaf_debug_tag} execution timed out: #{result.inspect}" } if result.timeout
91
+
92
+ if LIBDDWAF_SUCCESSFUL_EXECUTION_CODES.include?(result.status)
93
+ Datadog.logger.debug { "#{@libddwaf_debug_tag} execution result: #{result.inspect}" }
94
+ else
95
+ message = "#{@libddwaf_debug_tag} execution error: #{result.status.inspect}"
96
+
97
+ Datadog.logger.debug { message }
98
+ @telemetry.error(message)
99
+ end
100
+ end
101
+
102
+ def extract_schema?
103
+ Datadog.configuration.appsec.api_security.enabled &&
104
+ Datadog.configuration.appsec.api_security.sample_rate.sample?
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -9,7 +9,7 @@ module Datadog
9
9
  # that load appsec rules and data from settings
10
10
  module RuleLoader
11
11
  class << self
12
- def load_rules(ruleset:)
12
+ def load_rules(ruleset:, telemetry:)
13
13
  begin
14
14
  case ruleset
15
15
  when :recommended, :strict
@@ -35,6 +35,8 @@ module Datadog
35
35
  "libddwaf ruleset failed to load, ruleset: #{ruleset.inspect} error: #{e.inspect}"
36
36
  end
37
37
 
38
+ telemetry.report(e, description: 'libddwaf ruleset failed to load')
39
+
38
40
  nil
39
41
  end
40
42
  end
@@ -18,25 +18,35 @@ module Datadog
18
18
  end
19
19
  end
20
20
 
21
- DEFAULT_WAF_PROCESSORS = begin
22
- JSON.parse(Datadog::AppSec::Assets.waf_processors)
23
- rescue StandardError => e
24
- Datadog.logger.error { "libddwaf rulemerger failed to parse default waf processors. Error: #{e.inspect}" }
25
- []
26
- end
27
-
28
- DEFAULT_WAF_SCANNERS = begin
29
- JSON.parse(Datadog::AppSec::Assets.waf_scanners)
30
- rescue StandardError => e
31
- Datadog.logger.error { "libddwaf rulemerger failed to parse default waf scanners. Error: #{e.inspect}" }
32
- []
33
- end
34
-
35
21
  class << self
22
+ # TODO: `processors` and `scanners` are not provided by the caller, consider removing them
36
23
  def merge(
24
+ telemetry:,
37
25
  rules:, data: [], overrides: [], exclusions: [], custom_rules: [],
38
- processors: DEFAULT_WAF_PROCESSORS, scanners: DEFAULT_WAF_SCANNERS
26
+ processors: nil, scanners: nil
39
27
  )
28
+ processors ||= begin
29
+ default_waf_processors
30
+ rescue StandardError => e
31
+ Datadog.logger.error("libddwaf rulemerger failed to parse default waf processors. Error: #{e.inspect}")
32
+ telemetry.report(
33
+ e,
34
+ description: 'libddwaf rulemerger failed to parse default waf processors'
35
+ )
36
+ []
37
+ end
38
+
39
+ scanners ||= begin
40
+ default_waf_scanners
41
+ rescue StandardError => e
42
+ Datadog.logger.error("libddwaf rulemerger failed to parse default waf scanners. Error: #{e.inspect}")
43
+ telemetry.report(
44
+ e,
45
+ description: 'libddwaf rulemerger failed to parse default waf scanners'
46
+ )
47
+ []
48
+ end
49
+
40
50
  combined_rules = combine_rules(rules)
41
51
 
42
52
  combined_data = combine_data(data) if data.any?
@@ -53,6 +63,14 @@ module Datadog
53
63
  combined_rules
54
64
  end
55
65
 
66
+ def default_waf_processors
67
+ @default_waf_processors ||= JSON.parse(Datadog::AppSec::Assets.waf_processors)
68
+ end
69
+
70
+ def default_waf_scanners
71
+ @default_waf_scanners ||= JSON.parse(Datadog::AppSec::Assets.waf_scanners)
72
+ end
73
+
56
74
  private
57
75
 
58
76
  def combine_rules(rules)
@@ -1,85 +1,23 @@
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
- def initialize(ruleset:)
11
+ def initialize(ruleset:, telemetry:)
12
+ @telemetry = telemetry
77
13
  @diagnostics = nil
78
14
  @addresses = []
15
+
79
16
  settings = Datadog.configuration.appsec
80
17
 
81
- unless load_libddwaf && create_waf_handle(settings, ruleset)
82
- Datadog.logger.warn { 'AppSec is disabled, see logged errors above' }
18
+ # TODO: Refactor to make it easier to test
19
+ unless require_libddwaf && libddwaf_provides_waf? && create_waf_handle(settings, ruleset)
20
+ Datadog.logger.warn('AppSec is disabled, see logged errors above')
83
21
  end
84
22
  end
85
23
 
@@ -91,14 +29,33 @@ module Datadog
91
29
  @handle.finalize
92
30
  end
93
31
 
94
- protected
95
-
96
- attr_reader :handle
32
+ def new_context
33
+ Context.new(@handle, telemetry: @telemetry)
34
+ end
97
35
 
98
36
  private
99
37
 
100
- def load_libddwaf
101
- Processor.require_libddwaf && Processor.libddwaf_provides_waf?
38
+ # libddwaf raises a LoadError on unsupported platforms; it may at some
39
+ # point succeed in being required yet not provide a specific needed feature.
40
+ def require_libddwaf
41
+ Datadog.logger.debug { "libddwaf platform: #{libddwaf_platform}" }
42
+
43
+ require 'libddwaf'
44
+
45
+ true
46
+ rescue LoadError => e
47
+ Datadog.logger.error do
48
+ 'libddwaf failed to load,' \
49
+ "installed platform: #{libddwaf_platform} ruby platforms: #{ruby_platforms} error: #{e.inspect}"
50
+ end
51
+ @telemetry.report(e, description: 'libddwaf failed to load')
52
+
53
+ false
54
+ end
55
+
56
+ # check whether libddwaf is required *and* able to provide the needed feature
57
+ def libddwaf_provides_waf?
58
+ defined?(Datadog::AppSec::WAF) ? true : false
102
59
  end
103
60
 
104
61
  def create_waf_handle(settings, ruleset)
@@ -119,6 +76,7 @@ module Datadog
119
76
  Datadog.logger.error do
120
77
  "libddwaf failed to initialize, error: #{e.inspect}"
121
78
  end
79
+ @telemetry.report(e, description: 'libddwaf failed to initialize')
122
80
 
123
81
  @diagnostics = e.diagnostics if e.diagnostics
124
82
 
@@ -127,44 +85,21 @@ module Datadog
127
85
  Datadog.logger.error do
128
86
  "libddwaf failed to initialize, error: #{e.inspect}"
129
87
  end
88
+ @telemetry.report(e, description: 'libddwaf failed to initialize')
130
89
 
131
90
  false
132
91
  end
133
92
 
134
- class << self
135
- # check whether libddwaf is required *and* able to provide the needed feature
136
- def libddwaf_provides_waf?
137
- defined?(Datadog::AppSec::WAF) ? true : false
138
- end
139
-
140
- # libddwaf raises a LoadError on unsupported platforms; it may at some
141
- # point succeed in being required yet not provide a specific needed feature.
142
- def require_libddwaf
143
- Datadog.logger.debug { "libddwaf platform: #{libddwaf_platform}" }
144
-
145
- require 'libddwaf'
146
-
147
- true
148
- rescue LoadError => e
149
- Datadog.logger.error do
150
- 'libddwaf failed to load,' \
151
- "installed platform: #{libddwaf_platform} ruby platforms: #{ruby_platforms} error: #{e.inspect}"
152
- end
153
-
154
- false
155
- end
156
-
157
- def libddwaf_spec
158
- Gem.loaded_specs['libddwaf']
159
- end
160
-
161
- def libddwaf_platform
162
- libddwaf_spec ? libddwaf_spec.platform.to_s : 'unknown'
93
+ def libddwaf_platform
94
+ if Gem.loaded_specs['libddwaf']
95
+ Gem.loaded_specs['libddwaf'].platform.to_s
96
+ else
97
+ 'unknown'
163
98
  end
99
+ end
164
100
 
165
- def ruby_platforms
166
- Gem.platforms.map(&:to_s)
167
- end
101
+ def ruby_platforms
102
+ Gem.platforms.map(&:to_s)
168
103
  end
169
104
  end
170
105
  end