datadog 2.25.0 → 2.27.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -2
  3. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +100 -29
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
  6. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -5
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
  9. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +2 -2
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
  12. data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
  13. data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
  14. data/ext/datadog_profiling_native_extension/http_transport.c +10 -4
  15. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
  16. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
  17. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +9 -8
  18. data/ext/datadog_profiling_native_extension/profiling.c +20 -15
  19. data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
  20. data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
  21. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
  22. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
  23. data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
  24. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
  25. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
  26. data/ext/libdatadog_api/crashtracker.c +5 -8
  27. data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
  28. data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
  29. data/ext/libdatadog_api/ddsketch.c +4 -8
  30. data/ext/libdatadog_api/feature_flags.c +5 -5
  31. data/ext/libdatadog_api/helpers.h +27 -0
  32. data/ext/libdatadog_api/init.c +4 -0
  33. data/ext/libdatadog_extconf_helpers.rb +1 -1
  34. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
  35. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
  36. data/lib/datadog/appsec/component.rb +1 -1
  37. data/lib/datadog/appsec/context.rb +3 -3
  38. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  39. data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
  40. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
  41. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
  42. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  43. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
  44. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
  45. data/lib/datadog/appsec/ext.rb +2 -0
  46. data/lib/datadog/appsec/metrics/collector.rb +8 -3
  47. data/lib/datadog/appsec/metrics/exporter.rb +7 -0
  48. data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
  49. data/lib/datadog/appsec/metrics.rb +5 -5
  50. data/lib/datadog/appsec/remote.rb +4 -4
  51. data/lib/datadog/appsec.rb +7 -1
  52. data/lib/datadog/core/configuration/components.rb +1 -0
  53. data/lib/datadog/core/configuration/settings.rb +17 -0
  54. data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
  55. data/lib/datadog/core/runtime/metrics.rb +11 -1
  56. data/lib/datadog/core/telemetry/logger.rb +2 -0
  57. data/lib/datadog/core/telemetry/logging.rb +20 -2
  58. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -2
  59. data/lib/datadog/profiling/component.rb +13 -0
  60. data/lib/datadog/profiling/exporter.rb +4 -0
  61. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  62. data/lib/datadog/profiling/flush.rb +3 -0
  63. data/lib/datadog/profiling/profiler.rb +3 -5
  64. data/lib/datadog/profiling/scheduler.rb +8 -7
  65. data/lib/datadog/profiling/tag_builder.rb +1 -0
  66. data/lib/datadog/version.rb +1 -1
  67. metadata +10 -8
@@ -130,7 +130,7 @@ void feature_flags_init(VALUE core_module) {
130
130
  static VALUE configuration_new(VALUE klass, VALUE json_str) {
131
131
  struct ddog_ffe_Result_HandleConfiguration result = ddog_ffe_configuration_new(borrow_str(json_str));
132
132
  if (result.tag == DDOG_FFE_RESULT_HANDLE_CONFIGURATION_ERR_HANDLE_CONFIGURATION) {
133
- rb_raise(feature_flags_error_class, "Failed to create configuration from JSON: %"PRIsVALUE, get_error_details_and_drop(&result.err));
133
+ raise_error(feature_flags_error_class, "Failed to create configuration from JSON: %"PRIsVALUE, get_error_details_and_drop(&result.err));
134
134
  }
135
135
  return TypedData_Wrap_Struct(klass, &configuration_data_type, result.ok);
136
136
  }
@@ -159,7 +159,7 @@ static ddog_ffe_ExpectedFlagType expected_type_from_value(VALUE expected_type) {
159
159
  } else if (id == id_float) {
160
160
  return DDOG_FFE_EXPECTED_FLAG_TYPE_FLOAT;
161
161
  } else {
162
- rb_raise(feature_flags_error_class, "Internal: Unexpected flag type: %"PRIsVALUE, expected_type);
162
+ raise_error(feature_flags_error_class, "Internal: Unexpected flag type: %"PRIsVALUE, expected_type);
163
163
  }
164
164
  }
165
165
 
@@ -199,7 +199,7 @@ static int evaluation_context_foreach_callback(VALUE key, VALUE value, VALUE arg
199
199
  if (builder->attr_count >= builder->attr_capacity) {
200
200
  // This should never happen because evaluation_context_from_hash()
201
201
  // pre-allocates attr_capacity equal to iterated Hash size.
202
- rb_raise(feature_flags_error_class, "Internal: Attribute count exceeded capacity");
202
+ raise_error(feature_flags_error_class, "Internal: Attribute count exceeded capacity");
203
203
  }
204
204
 
205
205
  ddog_ffe_AttributePair *attr = &builder->attrs[builder->attr_count];
@@ -354,7 +354,7 @@ static VALUE resolution_details_get_raw_value(VALUE self) {
354
354
  return Qnil;
355
355
  default:
356
356
  // This should never happen as we checked for all possible tag values.
357
- rb_raise(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
357
+ raise_error(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
358
358
  }
359
359
  }
360
360
 
@@ -387,7 +387,7 @@ static VALUE resolution_details_get_flag_type(VALUE self) {
387
387
  return Qnil;
388
388
  default:
389
389
  // This should never happen as we checked for all possible tag values.
390
- rb_raise(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
390
+ raise_error(feature_flags_error_class, "Internal: Unexpected ResolutionDetails value tag");
391
391
  }
392
392
  }
393
393
 
@@ -0,0 +1,27 @@
1
+ #pragma once
2
+
3
+ #include "datadog_ruby_common.h"
4
+
5
+ // Raises a Ruby error if the `ddog_VoidResult` indicates an error.
6
+ // The error message in `result.err` is appended to the provided `message`.
7
+ //
8
+ // @param[in] message (const char *) The error message
9
+ // @param[in] result (ddog_VoidResult) A result to check
10
+ #define CHECK_VOID_RESULT(message, result) \
11
+ do { \
12
+ if (result.tag == DDOG_VOID_RESULT_ERR) { \
13
+ raise_lib_error(message, result); \
14
+ } \
15
+ } while (0)
16
+
17
+ // Raises a Ruby error for the error result.
18
+ // The error message in `result.err` is appended to the provided `message`.
19
+ //
20
+ // @param[in] message (const char *) The error message
21
+ // @param[in] result (struct { ddog_Error res; ... }) Any type of result
22
+ #define raise_lib_error(message, result) \
23
+ do { \
24
+ char error_msg[MAX_RAISE_MESSAGE_SIZE]; \
25
+ read_ddogerr_string_and_drop(&result.err, error_msg, MAX_RAISE_MESSAGE_SIZE); \
26
+ raise_error(rb_eRuntimeError, message ": %s", error_msg); \
27
+ } while (0)
@@ -10,6 +10,10 @@ void ddsketch_init(VALUE core_module);
10
10
 
11
11
  void DDTRACE_EXPORT Init_libdatadog_api(void) {
12
12
  VALUE datadog_module = rb_define_module("Datadog");
13
+
14
+ // MUST be called before all other initialization
15
+ datadog_ruby_common_init();
16
+
13
17
  VALUE core_module = rb_define_module_under(datadog_module, "Core");
14
18
 
15
19
  crashtracker_init(core_module);
@@ -10,7 +10,7 @@ module Datadog
10
10
  module LibdatadogExtconfHelpers
11
11
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
12
12
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
13
- LIBDATADOG_VERSION = '~> 24.0.1.1.0'
13
+ LIBDATADOG_VERSION = '~> 25.0.0.1.0'
14
14
 
15
15
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
16
16
  # libdatadog are moved after the extension gets compiled.
@@ -19,7 +19,14 @@ module Datadog
19
19
  Enumerator.new do |yielder|
20
20
  @routes.each do |route|
21
21
  if route.dispatcher?
22
- yielder.yield RailsRouteSerializer.serialize(route)
22
+ if route.verb.include?('|')
23
+ # report separate route for each method for multi-method routes
24
+ route.verb.split('|').each do |method|
25
+ yielder.yield RailsRouteSerializer.serialize(route, method_override: method)
26
+ end
27
+ else
28
+ yielder.yield RailsRouteSerializer.serialize(route)
29
+ end
23
30
  elsif mounted_grape_app?(route.app.rack_app)
24
31
  route.app.rack_app.routes.each do |grape_route|
25
32
  yielder.yield GrapeRouteSerializer.serialize(grape_route, path_prefix: route.path.spec.to_s)
@@ -10,8 +10,15 @@ module Datadog
10
10
 
11
11
  module_function
12
12
 
13
- def serialize(route)
14
- method = route.verb.empty? ? "*" : route.verb
13
+ def serialize(route, method_override: nil)
14
+ method = if method_override
15
+ method_override
16
+ elsif route.verb.empty?
17
+ "*"
18
+ else
19
+ route.verb
20
+ end
21
+
15
22
  path = route.path.spec.to_s.delete_suffix(FORMAT_SUFFIX)
16
23
 
17
24
  {
@@ -78,7 +78,7 @@ module Datadog
78
78
  end
79
79
 
80
80
  def reconfigure!
81
- security_engine.reconfigure!
81
+ security_engine&.reconfigure!
82
82
  end
83
83
 
84
84
  def shutdown!
@@ -48,11 +48,11 @@ module Datadog
48
48
  result
49
49
  end
50
50
 
51
- def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
51
+ def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT, phase: nil)
52
52
  result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
53
53
 
54
- Metrics::Telemetry.report_rasp(type, result)
55
- @metrics.record_rasp(result)
54
+ Metrics::Telemetry.report_rasp(type, result, phase: phase)
55
+ @metrics.record_rasp(result, type: type, phase: phase)
56
56
 
57
57
  result
58
58
  end
@@ -24,7 +24,7 @@ module Datadog
24
24
  end
25
25
 
26
26
  def self.compatible?
27
- super && version >= MINIMUM_VERSION
27
+ super && !!(version&.>= MINIMUM_VERSION)
28
28
  end
29
29
 
30
30
  def self.auto_instrument?
@@ -9,7 +9,7 @@ module Datadog
9
9
  module_function
10
10
 
11
11
  def patched?
12
- Patcher.instance_variable_get(:@patched)
12
+ !!Patcher.instance_variable_get(:@patched)
13
13
  end
14
14
 
15
15
  def target_version
@@ -13,27 +13,62 @@ module Datadog
13
13
  # AppSec Middleware for Excon
14
14
  class SSRFDetectionMiddleware < ::Excon::Middleware::Base
15
15
  def request_call(data)
16
- return super unless AppSec.rasp_enabled? && AppSec.active_context
16
+ context = AppSec.active_context
17
+ return super unless context && AppSec.rasp_enabled?
18
+
19
+ timeout = Datadog.configuration.appsec.waf_timeout
20
+ ephemeral_data = {
21
+ 'server.io.net.url' => request_url(data),
22
+ 'server.io.net.request.method' => data[:method].to_s.upcase,
23
+ 'server.io.net.request.headers' => normalize_headers(data[:headers])
24
+ }
25
+
26
+ result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_REQUEST_PHASE)
27
+ handle(result, context: context) if result.match?
28
+
29
+ super
30
+ end
17
31
 
32
+ def response_call(data)
18
33
  context = AppSec.active_context
34
+ return super unless context && AppSec.rasp_enabled?
19
35
 
20
- request_url = URI.join("#{data[:scheme]}://#{data[:host]}", data[:path]).to_s
21
- ephemeral_data = {'server.io.net.url' => request_url}
36
+ timeout = Datadog.configuration.appsec.waf_timeout
37
+ ephemeral_data = {
38
+ 'server.io.net.response.status' => data.dig(:response, :status).to_s,
39
+ 'server.io.net.response.headers' => normalize_headers(data.dig(:response, :headers))
40
+ }
22
41
 
23
- result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, Datadog.configuration.appsec.waf_timeout)
42
+ result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_RESPONSE_PHASE)
43
+ handle(result, context: context) if result.match?
24
44
 
25
- if result.match?
26
- AppSec::Event.tag(context, result)
27
- TraceKeeper.keep!(context.trace) if result.keep?
45
+ super
46
+ end
28
47
 
29
- context.events.push(
30
- AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
31
- )
48
+ private
32
49
 
33
- AppSec::ActionsHandler.handle(result.actions)
50
+ def request_url(data)
51
+ klass = (data[:scheme] == 'https') ? URI::HTTPS : URI::HTTP
52
+ klass.build(host: data[:host], path: data[:path], query: data[:query]).to_s
53
+ end
54
+
55
+ def normalize_headers(headers)
56
+ return {} if headers.nil? || headers.empty?
57
+
58
+ headers.each_with_object({}) do |(key, value), memo|
59
+ memo[key.downcase] = value.is_a?(Array) ? value.join(', ') : value
34
60
  end
61
+ end
35
62
 
36
- super
63
+ def handle(result, context:)
64
+ AppSec::Event.tag(context, result)
65
+ TraceKeeper.keep!(context.trace) if result.keep?
66
+
67
+ context.events.push(
68
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
69
+ )
70
+
71
+ AppSec::ActionsHandler.handle(result.actions)
37
72
  end
38
73
  end
39
74
  end
@@ -10,33 +10,50 @@ module Datadog
10
10
  module Faraday
11
11
  # AppSec SSRF detection Middleware for Faraday
12
12
  class SSRFDetectionMiddleware < ::Faraday::Middleware
13
- def call(request_env)
13
+ def call(env)
14
14
  context = AppSec.active_context
15
+ return @app.call(env) unless context && AppSec.rasp_enabled?
15
16
 
16
- return @app.call(request_env) unless context && AppSec.rasp_enabled?
17
-
17
+ timeout = Datadog.configuration.appsec.waf_timeout
18
18
  ephemeral_data = {
19
- 'server.io.net.url' => request_env.url.to_s
19
+ 'server.io.net.url' => env.url.to_s,
20
+ 'server.io.net.request.method' => env.method.to_s.upcase,
21
+ 'server.io.net.request.headers' => env.request_headers.transform_keys(&:downcase)
20
22
  }
21
23
 
22
- result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, Datadog.configuration.appsec.waf_timeout)
24
+ result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_REQUEST_PHASE)
25
+ handle(result, context: context) if result.match?
26
+
27
+ @app.call(env).on_complete { |response_env| on_complete(response_env, context: context) }
28
+ end
29
+
30
+ private
31
+
32
+ def on_complete(env, context:)
33
+ timeout = Datadog.configuration.appsec.waf_timeout
23
34
 
24
- if result.match?
25
- AppSec::Event.tag(context, result)
26
- TraceKeeper.keep!(context.trace) if result.keep?
35
+ response_headers = env.response_headers || {}
36
+ ephemeral_data = {
37
+ 'server.io.net.response.status' => env.status.to_s,
38
+ 'server.io.net.response.headers' => response_headers.transform_keys(&:downcase)
39
+ }
40
+
41
+ result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_RESPONSE_PHASE)
42
+ handle(result, context: context) if result.match?
43
+ end
27
44
 
28
- context.events.push(
29
- AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
30
- )
45
+ def handle(result, context:)
46
+ AppSec::Event.tag(context, result)
47
+ TraceKeeper.keep!(context.trace) if result.keep?
31
48
 
32
- AppSec::ActionsHandler.handle(result.actions)
33
- end
49
+ context.events.push(
50
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
51
+ )
34
52
 
35
- @app.call(request_env)
53
+ AppSec::ActionsHandler.handle(result.actions)
36
54
  end
37
55
  end
38
56
  end
39
57
  end
40
58
  end
41
59
  end
42
- # rubocop:enable Naming/FileName
@@ -28,7 +28,7 @@ module Datadog
28
28
  end
29
29
 
30
30
  def self.compatible?
31
- super && version >= MINIMUM_VERSION
31
+ super && !!(version&.>= MINIMUM_VERSION)
32
32
  end
33
33
 
34
34
  def self.auto_instrument?
@@ -9,7 +9,7 @@ module Datadog
9
9
  module_function
10
10
 
11
11
  def patched?
12
- Patcher.instance_variable_get(:@patched)
12
+ !!Patcher.instance_variable_get(:@patched)
13
13
  end
14
14
 
15
15
  def target_version
@@ -11,29 +11,65 @@ module Datadog
11
11
  # Module that adds SSRF detection to RestClient::Request#execute
12
12
  module RequestSSRFDetectionPatch
13
13
  def execute(&block)
14
- return super unless AppSec.rasp_enabled? && AppSec.active_context
15
-
16
14
  context = AppSec.active_context
15
+ return super unless context && AppSec.rasp_enabled?
16
+
17
+ timeout = Datadog.configuration.appsec.waf_timeout
18
+ ephemeral_data = {
19
+ 'server.io.net.url' => url,
20
+ 'server.io.net.request.method' => method.to_s.upcase,
21
+ 'server.io.net.request.headers' => normalize_request_headers
22
+ }
23
+
24
+ result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_REQUEST_PHASE)
25
+ handle(result, context: context) if result.match?
26
+
27
+ response = super
28
+
29
+ ephemeral_data = {
30
+ 'server.io.net.response.status' => response.code.to_s,
31
+ 'server.io.net.response.headers' => normalize_response_headers(response)
32
+ }
33
+
34
+ result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_RESPONSE_PHASE)
35
+ handle(result, context: context) if result.match?
17
36
 
18
- ephemeral_data = {'server.io.net.url' => url}
19
- result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, Datadog.configuration.appsec.waf_timeout)
37
+ response
38
+ end
39
+
40
+ private
41
+
42
+ # NOTE: Starting version 2.1.0 headers are already normalized via internal
43
+ # variable `@processed_headers_lowercase`. In case it's available,
44
+ # we use it to avoid unnecessary transformation.
45
+ def normalize_request_headers
46
+ return @processed_headers_lowercase if defined?(@processed_headers_lowercase)
20
47
 
21
- if result.match?
22
- AppSec::Event.tag(context, result)
23
- TraceKeeper.keep!(context.trace) if result.keep?
48
+ processed_headers.transform_keys(&:downcase)
49
+ end
50
+
51
+ # NOTE: Headers values are always an `Array` in `Net::HTTPResponse`,
52
+ # but we want to avoid accidents and will wrap them in no-op
53
+ # `Array` call just in case of a breaking change in the future
54
+ #
55
+ # FIXME: Steep has issues with `transform_values!` modifying the original
56
+ # type and it failed with "Cannot allow block body" error
57
+ def normalize_response_headers(response) # steep:ignore MethodBodyTypeMismatch
58
+ response.net_http_res.to_hash.transform_values! { |value| Array(value).join(', ') } # steep:ignore BlockBodyTypeMismatch
59
+ end
24
60
 
25
- context.events.push(
26
- AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
27
- )
61
+ def handle(result, context:)
62
+ AppSec::Event.tag(context, result)
63
+ TraceKeeper.keep!(context.trace) if result.keep?
28
64
 
29
- AppSec::ActionsHandler.handle(result.actions)
30
- end
65
+ context.events.push(
66
+ AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
67
+ )
31
68
 
32
- super
69
+ AppSec::ActionsHandler.handle(result.actions)
33
70
  end
34
71
  end
35
72
  end
36
73
  end
37
74
  end
38
75
  end
39
- # rubocop:enable Naming/FileName
@@ -6,6 +6,8 @@ module Datadog
6
6
  RASP_SQLI = 'sql_injection'
7
7
  RASP_LFI = 'lfi'
8
8
  RASP_SSRF = 'ssrf'
9
+ RASP_REQUEST_PHASE = 'request'
10
+ RASP_RESPONSE_PHASE = 'response'
9
11
 
10
12
  PRODUCT_BIT = 0b00000010
11
13
 
@@ -13,6 +13,7 @@ module Datadog
13
13
  :duration_ns,
14
14
  :duration_ext_ns,
15
15
  :inputs_truncated,
16
+ :downstream_requests,
16
17
  keyword_init: true
17
18
  )
18
19
 
@@ -22,10 +23,13 @@ module Datadog
22
23
  @mutex = Mutex.new
23
24
 
24
25
  @waf = Store.new(
25
- evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0, duration_ext_ns: 0, inputs_truncated: 0
26
+ evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0,
27
+ duration_ext_ns: 0, inputs_truncated: 0, downstream_requests: 0
26
28
  )
29
+
27
30
  @rasp = Store.new(
28
- evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0, duration_ext_ns: 0, inputs_truncated: 0
31
+ evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0,
32
+ duration_ext_ns: 0, inputs_truncated: 0, downstream_requests: 0
29
33
  )
30
34
  end
31
35
 
@@ -41,7 +45,7 @@ module Datadog
41
45
  end
42
46
  end
43
47
 
44
- def record_rasp(result)
48
+ def record_rasp(result, type:, phase: nil)
45
49
  @mutex.synchronize do
46
50
  @rasp.evals += 1
47
51
  @waf.matches += 1 if result.match?
@@ -50,6 +54,7 @@ module Datadog
50
54
  @rasp.duration_ns += result.duration_ns
51
55
  @rasp.duration_ext_ns += result.duration_ext_ns
52
56
  @rasp.inputs_truncated += 1 if result.input_truncated?
57
+ @rasp.downstream_requests += 1 if type == Ext::RASP_SSRF && phase == Ext::RASP_REQUEST_PHASE
53
58
  end
54
59
  end
55
60
  end
@@ -22,6 +22,13 @@ module Datadog
22
22
  span.set_tag('_dd.appsec.rasp.timeout', 1) unless metrics.timeouts.zero?
23
23
  span.set_tag('_dd.appsec.rasp.duration', convert_ns_to_us(metrics.duration_ns))
24
24
  span.set_tag('_dd.appsec.rasp.duration_ext', convert_ns_to_us(metrics.duration_ext_ns))
25
+
26
+ # NOTE: In case of downstream requests being analyzed additionally
27
+ # with `Context.run_waf` method, we would need to share it
28
+ # between two exporting methods
29
+ unless metrics.downstream_requests.zero?
30
+ span.set_tag('_dd.appsec.downstream_request', metrics.downstream_requests)
31
+ end
25
32
  end
26
33
 
27
34
  # private
@@ -7,10 +7,15 @@ module Datadog
7
7
  module Telemetry
8
8
  module_function
9
9
 
10
- def report_rasp(type, result)
10
+ def report_rasp(type, result, phase: nil)
11
11
  return if result.error?
12
12
 
13
- tags = {rule_type: type, waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING}
13
+ tags = {rule_type: type, waf_version: WAF::VERSION::BASE_STRING}
14
+ tags[:rule_variant] = phase if phase
15
+
16
+ context = AppSec.active_context
17
+ tags[:event_rules_version] = context.waf_runner_ruleset_version if context
18
+
14
19
  namespace = Ext::TELEMETRY_METRICS_NAMESPACE
15
20
 
16
21
  AppSec.telemetry.inc(namespace, 'rasp.rule.eval', 1, tags: tags)
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'metrics/collector'
4
+ require_relative 'metrics/exporter'
5
+ require_relative 'metrics/telemetry'
6
+ require_relative 'metrics/telemetry_exporter'
7
+
3
8
  module Datadog
4
9
  module AppSec
5
10
  # This namespace contains classes related to metrics collection and exportation.
@@ -7,8 +12,3 @@ module Datadog
7
12
  end
8
13
  end
9
14
  end
10
-
11
- require_relative 'metrics/collector'
12
- require_relative 'metrics/exporter'
13
- require_relative 'metrics/telemetry'
14
- require_relative 'metrics/telemetry_exporter'
@@ -75,7 +75,8 @@ module Datadog
75
75
 
76
76
  matcher = Core::Remote::Dispatcher::Matcher::Product.new(ASM_PRODUCTS)
77
77
  receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
78
- next unless AppSec.security_engine
78
+ engine = AppSec.security_engine
79
+ next unless engine
79
80
 
80
81
  changes.each do |change|
81
82
  content = repository[change.path]
@@ -84,11 +85,10 @@ module Datadog
84
85
  case change.type
85
86
  when :insert, :update
86
87
  # @type var content: Core::Remote::Configuration::Content
87
- AppSec.security_engine.add_or_update_config(parse_content(content), path: change.path.to_s)
88
-
88
+ engine.add_or_update_config(parse_content(content), path: change.path.to_s)
89
89
  content.applied
90
90
  when :delete
91
- AppSec.security_engine.remove_config_at_path(change.path.to_s)
91
+ engine.remove_config_at_path(change.path.to_s)
92
92
  end
93
93
  end
94
94
 
@@ -22,8 +22,14 @@ module Datadog
22
22
  Datadog::AppSec::Context.active
23
23
  end
24
24
 
25
+ # NOTE: This is a temporary workaround for type checking.
26
+ #
27
+ # We want to move from possible nil-component to the disabled-component
28
+ # on an initialization error. Technically, telemetry will be never
29
+ # used if AppSec was not able to initialize, so it's safe to assume
30
+ # that telemetry will never be used and will be nil at the same time.
25
31
  def telemetry
26
- components.appsec&.telemetry
32
+ components.appsec&.telemetry || components.telemetry
27
33
  end
28
34
 
29
35
  def security_engine
@@ -49,6 +49,7 @@ module Datadog
49
49
  options[:statsd] = settings.runtime_metrics.statsd unless settings.runtime_metrics.statsd.nil?
50
50
  options[:services] = [settings.service] unless settings.service.nil?
51
51
  options[:experimental_runtime_id_enabled] = settings.runtime_metrics.experimental_runtime_id_enabled
52
+ options[:experimental_propagate_process_tags_enabled] = settings.experimental_propagate_process_tags_enabled
52
53
 
53
54
  Core::Runtime::Metrics.new(logger: logger, telemetry: telemetry, **options)
54
55
  end
@@ -436,6 +436,23 @@ module Datadog
436
436
  o.default true
437
437
  end
438
438
 
439
+ # The profiler gathers data by sending `SIGPROF` unix signals to Ruby application threads.
440
+ #
441
+ # When using `Kernel#exec` on Linux, it can happen that a signal sent before calling `exec` arrives after
442
+ # the new process is running, causing it to fail with the `Profiling timer expired` error message.
443
+ # To avoid this, the profiler installs a monkey patch on `Kernel#exec` to stop profiling before actually
444
+ # calling `exec`.
445
+ # This monkey patch is available for Ruby 2.7+; let us know if you need it on earlier Rubies.
446
+ # For more details see https://github.com/DataDog/dd-trace-rb/issues/5101 .
447
+ #
448
+ # @default `DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED` environment variable as a boolean,
449
+ # otherwise `true`
450
+ option :shutdown_on_exec_enabled do |o|
451
+ o.env 'DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED'
452
+ o.type :bool
453
+ o.default true
454
+ end
455
+
439
456
  # Configures how much wall-time overhead the profiler targets. The profiler will dynamically adjust the
440
457
  # interval between samples it takes so as to try and maintain the property that it spends no longer than
441
458
  # this amount of wall-clock time profiling. For example, with the default value of 2%, the profiler will
@@ -81,6 +81,7 @@ module Datadog
81
81
  "DD_PROFILING_NO_SIGNALS_WORKAROUND_ENABLED",
82
82
  "DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE",
83
83
  "DD_PROFILING_PREVIEW_OTEL_CONTEXT_ENABLED",
84
+ "DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED",
84
85
  "DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED",
85
86
  "DD_PROFILING_SKIP_MYSQL2_CHECK",
86
87
  "DD_PROFILING_TIMELINE_ENABLED",
@@ -8,6 +8,7 @@ require_relative '../environment/gc'
8
8
  require_relative '../environment/thread_count'
9
9
  require_relative '../environment/vm_cache'
10
10
  require_relative '../environment/yjit'
11
+ require_relative '../environment/process'
11
12
 
12
13
  module Datadog
13
14
  module Core
@@ -24,6 +25,9 @@ module Datadog
24
25
 
25
26
  # Initialize the collection of runtime-id
26
27
  @runtime_id_enabled = options.fetch(:experimental_runtime_id_enabled, false)
28
+
29
+ # Initialized process tags support
30
+ @process_tags_enabled = options.fetch(:experimental_propagate_process_tags_enabled, false)
27
31
  end
28
32
 
29
33
  # Associate service with runtime metrics
@@ -111,6 +115,11 @@ module Datadog
111
115
 
112
116
  # Add runtime-id dynamically because it might change during runtime.
113
117
  options[:tags].concat(["runtime-id:#{Core::Environment::Identity.id}"]) if @runtime_id_enabled
118
+
119
+ # Add process tags when enabled
120
+ if @process_tags_enabled
121
+ options[:tags].concat(Core::Environment::Process.tags)
122
+ end
114
123
  end
115
124
  end
116
125
 
@@ -119,7 +128,8 @@ module Datadog
119
128
  attr_reader \
120
129
  :service_tags,
121
130
  :services,
122
- :runtime_id_enabled
131
+ :runtime_id_enabled,
132
+ :process_tags_enabled
123
133
 
124
134
  def compile_service_tags!
125
135
  @service_tags = services.to_a.collect do |service|