datadog 2.9.0 → 2.11.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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +3 -3
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +46 -6
  7. data/ext/datadog_profiling_native_extension/extconf.rb +4 -0
  8. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +2 -0
  9. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +0 -8
  10. data/ext/datadog_profiling_native_extension/heap_recorder.c +51 -93
  11. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  12. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +56 -0
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +7 -0
  14. data/ext/datadog_profiling_native_extension/profiling.c +7 -0
  15. data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
  16. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  17. data/ext/libdatadog_api/crashtracker.c +4 -4
  18. data/ext/libdatadog_extconf_helpers.rb +1 -1
  19. data/lib/datadog/appsec/actions_handler.rb +27 -0
  20. data/lib/datadog/appsec/component.rb +14 -8
  21. data/lib/datadog/appsec/configuration/settings.rb +73 -11
  22. data/lib/datadog/appsec/context.rb +28 -8
  23. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
  24. data/lib/datadog/appsec/contrib/active_record/patcher.rb +0 -3
  25. data/lib/datadog/appsec/contrib/devise/configuration.rb +76 -0
  26. data/lib/datadog/appsec/contrib/devise/event.rb +4 -7
  27. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +16 -21
  28. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +8 -15
  29. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +1 -1
  30. data/lib/datadog/appsec/contrib/devise/patcher.rb +0 -3
  31. data/lib/datadog/appsec/contrib/devise/tracking.rb +1 -1
  32. data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
  33. data/lib/datadog/appsec/contrib/excon/patcher.rb +28 -0
  34. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +43 -0
  35. data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
  36. data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
  37. data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
  38. data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
  39. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +42 -0
  40. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  41. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +11 -14
  42. data/lib/datadog/appsec/contrib/graphql/patcher.rb +0 -3
  43. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +65 -70
  44. data/lib/datadog/appsec/contrib/rack/patcher.rb +0 -3
  45. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
  46. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
  47. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +20 -24
  48. data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -16
  49. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +38 -47
  50. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -29
  51. data/lib/datadog/appsec/ext.rb +6 -1
  52. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  53. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  54. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  55. data/lib/datadog/appsec/metrics.rb +13 -0
  56. data/lib/datadog/appsec/monitor/gateway/watcher.rb +19 -24
  57. data/lib/datadog/appsec/processor.rb +4 -3
  58. data/lib/datadog/appsec/remote.rb +4 -0
  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 +16 -5
  64. data/lib/datadog/core/configuration/components.rb +7 -1
  65. data/lib/datadog/core/configuration/ext.rb +1 -1
  66. data/lib/datadog/core/configuration/option_definition.rb +2 -0
  67. data/lib/datadog/core/configuration/settings.rb +22 -6
  68. data/lib/datadog/core/encoding.rb +16 -0
  69. data/lib/datadog/core/environment/agent_info.rb +77 -0
  70. data/lib/datadog/core/remote/transport/http/api.rb +13 -18
  71. data/lib/datadog/core/remote/transport/http/config.rb +0 -18
  72. data/lib/datadog/core/remote/transport/http/negotiation.rb +1 -18
  73. data/lib/datadog/core/remote/transport/http.rb +7 -12
  74. data/lib/datadog/core/remote/transport/negotiation.rb +13 -1
  75. data/lib/datadog/core/telemetry/event.rb +5 -0
  76. data/lib/datadog/core/transport/http/adapters/unix_socket.rb +1 -1
  77. data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +1 -1
  78. data/lib/datadog/{tracing → core}/transport/http/api/spec.rb +1 -1
  79. data/lib/datadog/{tracing → core}/transport/http/builder.rb +37 -17
  80. data/lib/datadog/core/transport/response.rb +4 -0
  81. data/lib/datadog/di/code_tracker.rb +15 -8
  82. data/lib/datadog/di/component.rb +3 -0
  83. data/lib/datadog/di/configuration/settings.rb +14 -0
  84. data/lib/datadog/di/contrib.rb +2 -0
  85. data/lib/datadog/di/logger.rb +30 -0
  86. data/lib/datadog/di/probe.rb +3 -6
  87. data/lib/datadog/di/probe_manager.rb +5 -2
  88. data/lib/datadog/di/probe_notification_builder.rb +6 -0
  89. data/lib/datadog/di/probe_notifier_worker.rb +15 -4
  90. data/lib/datadog/di/redactor.rb +0 -1
  91. data/lib/datadog/di/remote.rb +29 -8
  92. data/lib/datadog/di/utils.rb +91 -0
  93. data/lib/datadog/di.rb +3 -0
  94. data/lib/datadog/profiling/component.rb +2 -8
  95. data/lib/datadog/profiling/load_native_extension.rb +1 -33
  96. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  97. data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
  98. data/lib/datadog/tracing/contrib/extensions.rb +29 -3
  99. data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
  100. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +11 -0
  101. data/lib/datadog/tracing/contrib/graphql/ext.rb +5 -0
  102. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +102 -11
  103. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  104. data/lib/datadog/tracing/contrib/rack/header_collection.rb +11 -1
  105. data/lib/datadog/tracing/contrib/rack/middlewares.rb +1 -1
  106. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +6 -1
  107. data/lib/datadog/tracing/transport/http/api.rb +11 -2
  108. data/lib/datadog/tracing/transport/http/traces.rb +0 -3
  109. data/lib/datadog/tracing/transport/http.rb +12 -7
  110. data/lib/datadog/tracing/transport/serializable_trace.rb +8 -4
  111. data/lib/datadog/tracing/transport/traces.rb +25 -8
  112. data/lib/datadog/version.rb +1 -1
  113. metadata +51 -42
  114. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
  115. data/ext/datadog_profiling_loader/extconf.rb +0 -60
  116. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -46
  117. data/lib/datadog/appsec/contrib/patcher.rb +0 -12
  118. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -69
  119. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -47
  120. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -53
  121. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -53
  122. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  123. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -48
  124. data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -45
  125. data/lib/datadog/appsec/processor/context.rb +0 -107
  126. data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
  127. data/lib/datadog/appsec/reactive/engine.rb +0 -47
  128. data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
  129. data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
  130. data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
  131. data/lib/datadog/core/remote/transport/http/builder.rb +0 -219
@@ -40,6 +40,13 @@
40
40
  #endif
41
41
  #endif
42
42
 
43
+ // This file can't include datadog_ruby_common.h so we replicate this here
44
+ #ifdef __GNUC__
45
+ #define DDTRACE_UNUSED __attribute__((unused))
46
+ #else
47
+ #define DDTRACE_UNUSED
48
+ #endif
49
+
43
50
  #define PRIVATE_VM_API_ACCESS_SKIP_RUBY_INCLUDES
44
51
  #include "private_vm_api_access.h"
45
52
 
@@ -803,3 +810,52 @@ static inline int ddtrace_imemo_type(VALUE imemo) {
803
810
 
804
811
  // Is the VM smack in the middle of raising an exception?
805
812
  bool is_raised_flag_set(VALUE thread) { return thread_struct_from_object(thread)->ec->raised_flag > 0; }
813
+
814
+ #ifndef NO_CURRENT_FIBER_FOR
815
+ // The following three declarations are all
816
+ // taken from upstream cont.c at commit d97884a58be32e829fd03a80cd521f4733d65c79 (February 2025, master branch)
817
+ // (See the Ruby project copyright and license above)
818
+ // to enable building `current_fiber_for`.
819
+ //
820
+ // We needed to copy them because they aren't otherwise exposed in any VM APIs or headers.
821
+ // @ivoanjo: I manually checked the Ruby 3.1, 3.2, 3.3 and 3.4 branches + master, and the parts we care about in these
822
+ // structures have not changed in many years (in fact, last change I spotted was for 2.7).
823
+ enum context_type {
824
+ CONTINUATION_CONTEXT = 0,
825
+ FIBER_CONTEXT = 1
826
+ };
827
+
828
+ typedef struct rb_context_struct { // This declaration is incomplete -- only contains up to `self` which is the part we care about
829
+ enum context_type type;
830
+ int argc;
831
+ int kw_splat;
832
+ VALUE self;
833
+ } rb_context_t;
834
+
835
+ struct rb_fiber_struct { // This declaration is incomplete -- only contains the first entry which is the part we care about
836
+ rb_context_t cont;
837
+ };
838
+
839
+ VALUE current_fiber_for(VALUE thread) {
840
+ VALUE self = thread_struct_from_object(thread)->ec->fiber_ptr->cont.self;
841
+ return self == 0 ? Qnil : self;
842
+ }
843
+
844
+ void self_test_current_fiber_for(void) {
845
+ VALUE expected_current_fiber = current_fiber_for(rb_thread_current());
846
+ VALUE actual_current_fiber = rb_fiber_current();
847
+
848
+ if (expected_current_fiber == Qnil) {
849
+ // On purpose above we tried reading before calling `rb_fiber_current()` so the fiber may have not existed yet.
850
+ // But now it should be there.
851
+ expected_current_fiber = current_fiber_for(rb_thread_current());
852
+ }
853
+
854
+ if (expected_current_fiber != actual_current_fiber) rb_raise(rb_eRuntimeError, "current_fiber_for() self-test failed");
855
+ }
856
+ #else
857
+ NORETURN(VALUE current_fiber_for(DDTRACE_UNUSED VALUE thread));
858
+
859
+ VALUE current_fiber_for(DDTRACE_UNUSED VALUE thread) { rb_raise(rb_eRuntimeError, "Not implemented for Ruby < 3.1"); }
860
+ void self_test_current_fiber_for(void) { } // Nothing to do
861
+ #endif
@@ -44,6 +44,7 @@ bool is_thread_alive(VALUE thread);
44
44
  VALUE thread_name_for(VALUE thread);
45
45
 
46
46
  int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
47
+
47
48
  // Returns true if the current thread belongs to the main Ractor or if Ruby has no Ractor support
48
49
  bool ddtrace_rb_ractor_main_p(void);
49
50
 
@@ -70,3 +71,9 @@ const char *imemo_kind(VALUE imemo);
70
71
  { if (RB_UNLIKELY(!rb_typeddata_is_kind_of(value, RTYPEDDATA_TYPE(rb_thread_current())))) raise_unexpected_type(value, ADD_QUOTES(value), "Thread", __FILE__, __LINE__, __func__); }
71
72
 
72
73
  bool is_raised_flag_set(VALUE thread);
74
+
75
+ // Can be nil if `rb_fiber_current()` or similar has not been called (gets allocated lazily)
76
+ // Only implemented for Ruby 3.1+
77
+ VALUE current_fiber_for(VALUE thread);
78
+
79
+ void self_test_current_fiber_for(void);
@@ -41,6 +41,12 @@ static VALUE _native_malloc_stats(DDTRACE_UNUSED VALUE _self);
41
41
  static VALUE _native_safe_object_info(DDTRACE_UNUSED VALUE _self, VALUE obj);
42
42
 
43
43
  void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
44
+ // The profiler still has a lot of limitations around being used in Ractors BUT for now we're choosing to take care of those
45
+ // on our side, rather than asking Ruby to block calling our APIs from Ractors.
46
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
47
+ rb_ext_ractor_safe(true);
48
+ #endif
49
+
44
50
  VALUE datadog_module = rb_define_module("Datadog");
45
51
  VALUE profiling_module = rb_define_module_under(datadog_module, "Profiling");
46
52
  VALUE native_extension_module = rb_define_module_under(profiling_module, "NativeExtension");
@@ -80,6 +86,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
80
86
 
81
87
  static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
82
88
  self_test_clock_id();
89
+ self_test_current_fiber_for();
83
90
  self_test_mn_enabled();
84
91
 
85
92
  return Qtrue;
@@ -332,23 +332,16 @@ static VALUE _native_new(VALUE klass) {
332
332
  .serialization_time_ns_min = INT64_MAX,
333
333
  };
334
334
 
335
- // Note: At this point, slot_one_profile and slot_two_profile contain null pointers. Libdatadog validates pointers
335
+ // Note: At this point, slot_one_profile/slot_two_profile contain null pointers. Libdatadog validates pointers
336
336
  // before using them so it's ok for us to go ahead and create the StackRecorder object.
337
337
 
338
- // Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
339
- // to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
340
- // since the instance representing the state does not yet exist, such objects will not get marked.
341
-
342
338
  VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
343
339
 
344
- // NOTE: We initialize this because we want a new recorder to be operational even without initialization and our
340
+ // NOTE: We initialize this because we want a new recorder to be operational even before #initialize runs and our
345
341
  // default is everything enabled. However, if during recording initialization it turns out we don't want
346
- // heap samples, we will free and reset heap_recorder to NULL, effectively disabling all behaviour specific
347
- // to heap profiling (all calls to heap_recorder_* with a NULL heap recorder are noops).
342
+ // heap samples, we will free and reset heap_recorder back to NULL.
348
343
  state->heap_recorder = heap_recorder_new();
349
344
 
350
- // Note: Don't raise exceptions after this point, since it'll lead to libdatadog memory leaking!
351
-
352
345
  initialize_profiles(state, sample_types);
353
346
 
354
347
  return stack_recorder;
@@ -372,22 +365,17 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Val
372
365
  rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_error_details_and_drop(&slot_one_profile_result.err));
373
366
  }
374
367
 
368
+ state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_result.ok };
369
+
375
370
  ddog_prof_Profile_NewResult slot_two_profile_result =
376
371
  ddog_prof_Profile_new(sample_types, NULL /* period is optional */, NULL /* start_time is optional */);
377
372
 
378
373
  if (slot_two_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
379
- // Uff! Though spot. We need to make sure to properly clean up the other profile as well first
380
- ddog_prof_Profile_drop(&slot_one_profile_result.ok);
381
- // And now we can raise...
374
+ // Note: No need to take any special care of slot one, it'll get cleaned up by stack_recorder_typed_data_free
382
375
  rb_raise(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_error_details_and_drop(&slot_two_profile_result.err));
383
376
  }
384
377
 
385
- state->profile_slot_one = (profile_slot) {
386
- .profile = slot_one_profile_result.ok,
387
- };
388
- state->profile_slot_two = (profile_slot) {
389
- .profile = slot_two_profile_result.ok,
390
- };
378
+ state->profile_slot_two = (profile_slot) { .profile = slot_two_profile_result.ok };
391
379
  }
392
380
 
393
381
  static void stack_recorder_typed_data_free(void *state_ptr) {
@@ -651,7 +639,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
651
639
  }
652
640
  }
653
641
 
654
- void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice *alloc_class) {
642
+ void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice alloc_class) {
655
643
  stack_recorder_state *state;
656
644
  TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
657
645
  // FIXME: Heap sampling currently has to be done in 2 parts because the construction of locations is happening
@@ -926,8 +914,7 @@ static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_
926
914
 
927
915
  static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class) {
928
916
  ENFORCE_TYPE(weight, T_FIXNUM);
929
- ddog_CharSlice alloc_class_slice = char_slice_from_ruby_string(alloc_class);
930
- track_object(recorder_instance, new_obj, NUM2UINT(weight), &alloc_class_slice);
917
+ track_object(recorder_instance, new_obj, NUM2UINT(weight), char_slice_from_ruby_string(alloc_class));
931
918
  return Qtrue;
932
919
  }
933
920
 
@@ -26,6 +26,6 @@ typedef struct {
26
26
 
27
27
  void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations, sample_values values, sample_labels labels);
28
28
  void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint);
29
- void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice *alloc_class);
29
+ void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice alloc_class);
30
30
  void recorder_after_gc_step(VALUE recorder_instance);
31
31
  VALUE enforce_recorder_instance(VALUE object);
@@ -98,7 +98,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
98
98
  .optional_stdout_filename = {},
99
99
  };
100
100
 
101
- ddog_crasht_Result result =
101
+ ddog_VoidResult result =
102
102
  action == start_action ?
103
103
  ddog_crasht_init(config, receiver_config, metadata) :
104
104
  ddog_crasht_update_on_fork(config, receiver_config, metadata);
@@ -108,7 +108,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
108
108
  ddog_endpoint_drop(endpoint);
109
109
  // }} End of exception-free zone to prevent leaks
110
110
 
111
- if (result.tag == DDOG_CRASHT_RESULT_ERR) {
111
+ if (result.tag == DDOG_VOID_RESULT_ERR) {
112
112
  rb_raise(rb_eRuntimeError, "Failed to start/update the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
113
113
  }
114
114
 
@@ -116,9 +116,9 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
116
116
  }
117
117
 
118
118
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self) {
119
- ddog_crasht_Result result = ddog_crasht_shutdown();
119
+ ddog_VoidResult result = ddog_crasht_shutdown();
120
120
 
121
- if (result.tag == DDOG_CRASHT_RESULT_ERR) {
121
+ if (result.tag == DDOG_VOID_RESULT_ERR) {
122
122
  rb_raise(rb_eRuntimeError, "Failed to stop the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
123
123
  }
124
124
 
@@ -8,7 +8,7 @@ module Datadog
8
8
  module LibdatadogExtconfHelpers
9
9
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
10
10
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
11
- LIBDATADOG_VERSION = '~> 14.3.1.1.0'
11
+ LIBDATADOG_VERSION = '~> 16.0.1.1.0'
12
12
 
13
13
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
14
14
  # libdatadog are moved after the extension gets compiled.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ # this module encapsulates functions for handling actions that libddawf returns
6
+ module ActionsHandler
7
+ module_function
8
+
9
+ def handle(actions_hash)
10
+ # handle actions according their precedence
11
+ # stack and schema generation should be done before we throw an interrupt signal
12
+ generate_stack(actions_hash['generate_stack']) if actions_hash.key?('generate_stack')
13
+ generate_schema(actions_hash['generate_schema']) if actions_hash.key?('generate_schema')
14
+ interrupt_execution(actions_hash['redirect_request']) if actions_hash.key?('redirect_request')
15
+ interrupt_execution(actions_hash['block_request']) if actions_hash.key?('block_request')
16
+ end
17
+
18
+ def interrupt_execution(action_params)
19
+ throw(Datadog::AppSec::Ext::INTERRUPT, action_params)
20
+ end
21
+
22
+ def generate_stack(_action_params); end
23
+
24
+ def generate_schema(_action_params); end
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,7 @@
3
3
  require_relative 'processor'
4
4
  require_relative 'processor/rule_merger'
5
5
  require_relative 'processor/rule_loader'
6
+ require_relative 'actions_handler'
6
7
 
7
8
  module Datadog
8
9
  module AppSec
@@ -23,7 +24,7 @@ module Datadog
23
24
  devise_integration = Datadog::AppSec::Contrib::Devise::Integration.new
24
25
  settings.appsec.instrument(:devise) unless devise_integration.patcher.patched?
25
26
 
26
- new(processor: processor)
27
+ new(processor, telemetry)
27
28
  end
28
29
 
29
30
  private
@@ -72,21 +73,26 @@ module Datadog
72
73
  end
73
74
  end
74
75
 
75
- attr_reader :processor
76
+ attr_reader :processor, :telemetry
76
77
 
77
- def initialize(processor:)
78
+ def initialize(processor, telemetry)
78
79
  @processor = processor
80
+ @telemetry = telemetry
81
+
79
82
  @mutex = Mutex.new
80
83
  end
81
84
 
82
85
  def reconfigure(ruleset:, telemetry:)
83
86
  @mutex.synchronize do
84
- new = Processor.new(ruleset: ruleset, telemetry: telemetry)
87
+ new_processor = Processor.new(ruleset: ruleset, telemetry: telemetry)
88
+
89
+ if new_processor && new_processor.ready?
90
+ old_processor = @processor
91
+
92
+ @telemetry = telemetry
93
+ @processor = new_processor
85
94
 
86
- if new && new.ready?
87
- old = @processor
88
- @processor = new
89
- old.finalize if old
95
+ old_processor.finalize if old_processor
90
96
  end
91
97
  end
92
98
  end
@@ -12,14 +12,29 @@ module Datadog
12
12
  DEFAULT_OBFUSCATOR_KEY_REGEX = '(?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt'
13
13
  DEFAULT_OBFUSCATOR_VALUE_REGEX = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\.net(?:[_-]|-)sessionid|sid|jwt)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}'
14
14
  # rubocop:enable Layout/LineLength
15
+
16
+ DISABLED_AUTO_USER_INSTRUMENTATION_MODE = 'disabled'
17
+ ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE = 'anonymization'
18
+ IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE = 'identification'
19
+ AUTO_USER_INSTRUMENTATION_MODES = [
20
+ DISABLED_AUTO_USER_INSTRUMENTATION_MODE,
21
+ ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE,
22
+ IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
23
+ ].freeze
24
+ AUTO_USER_INSTRUMENTATION_MODES_ALIASES = {
25
+ 'ident' => IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE,
26
+ 'anon' => ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE,
27
+ }.freeze
28
+
29
+ # NOTE: These two constants are deprecated
30
+ SAFE_TRACK_USER_EVENTS_MODE = 'safe'
31
+ EXTENDED_TRACK_USER_EVENTS_MODE = 'extended'
15
32
  APPSEC_VALID_TRACK_USER_EVENTS_MODE = [
16
- 'safe',
17
- 'extended'
33
+ SAFE_TRACK_USER_EVENTS_MODE, EXTENDED_TRACK_USER_EVENTS_MODE
18
34
  ].freeze
19
- APPSEC_VALID_TRACK_USER_EVENTS_ENABLED_VALUES = [
20
- '1',
21
- 'true'
22
- ].concat(APPSEC_VALID_TRACK_USER_EVENTS_MODE).freeze
35
+ APPSEC_VALID_TRACK_USER_EVENTS_ENABLED_VALUES = ['1', 'true'].concat(
36
+ APPSEC_VALID_TRACK_USER_EVENTS_MODE
37
+ ).freeze
23
38
 
24
39
  def self.extended(base)
25
40
  base = base.singleton_class unless base.is_a?(Class)
@@ -49,6 +64,15 @@ module Datadog
49
64
  end
50
65
  end
51
66
 
67
+ # RASP or Runtime Application Self-Protection
68
+ # is a collection of techniques and heuristics aimed at detecting malicious inputs and preventing
69
+ # any potential side-effects on the application resulting from the use of said malicious inputs.
70
+ option :rasp_enabled do |o|
71
+ o.type :bool, nilable: true
72
+ o.env 'DD_APPSEC_RASP_ENABLED'
73
+ o.default true
74
+ end
75
+
52
76
  option :ruleset do |o|
53
77
  o.env 'DD_APPSEC_RULES'
54
78
  o.default :recommended
@@ -140,6 +164,29 @@ module Datadog
140
164
  end
141
165
  end
142
166
 
167
+ settings :auto_user_instrumentation do
168
+ define_method(:enabled?) { get_option(:mode) != DISABLED_AUTO_USER_INSTRUMENTATION_MODE }
169
+
170
+ option :mode do |o|
171
+ o.type :string
172
+ o.env 'DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE'
173
+ o.default IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
174
+ o.setter do |value|
175
+ mode = AUTO_USER_INSTRUMENTATION_MODES_ALIASES.fetch(value, value)
176
+ next mode if AUTO_USER_INSTRUMENTATION_MODES.include?(mode)
177
+
178
+ Datadog.logger.warn(
179
+ 'The appsec.auto_user_instrumentation.mode value provided is not supported. ' \
180
+ "Supported values are: #{AUTO_USER_INSTRUMENTATION_MODES.join(' | ')}. " \
181
+ "Using default value: #{IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE}."
182
+ )
183
+
184
+ IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
185
+ end
186
+ end
187
+ end
188
+
189
+ # DEV-3.0: Remove `track_user_events.enabled` and `track_user_events.mode` options
143
190
  settings :track_user_events do
144
191
  option :enabled do |o|
145
192
  o.default true
@@ -152,24 +199,39 @@ module Datadog
152
199
  APPSEC_VALID_TRACK_USER_EVENTS_ENABLED_VALUES.include?(env_value.strip.downcase)
153
200
  end
154
201
  end
202
+ o.after_set do
203
+ Core.log_deprecation(key: :appsec_track_user_events_enabled) do
204
+ 'The appsec.track_user_events.enabled setting has been deprecated for removal. ' \
205
+ 'Please remove it from your Datadog.configure block and use ' \
206
+ 'appsec.auto_user_instrumentation.mode instead.'
207
+ end
208
+ end
155
209
  end
156
210
 
157
211
  option :mode do |o|
158
212
  o.type :string
159
213
  o.env 'DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING'
160
- o.default 'safe'
214
+ o.default SAFE_TRACK_USER_EVENTS_MODE
161
215
  o.setter do |v|
162
216
  if APPSEC_VALID_TRACK_USER_EVENTS_MODE.include?(v)
163
217
  v
164
218
  elsif v == 'disabled'
165
- 'safe'
219
+ SAFE_TRACK_USER_EVENTS_MODE
166
220
  else
167
221
  Datadog.logger.warn(
168
222
  'The appsec.track_user_events.mode value provided is not supported.' \
169
- 'Supported values are: safe | extended.' \
170
- 'Using default value `safe`'
223
+ "Supported values are: #{APPSEC_VALID_TRACK_USER_EVENTS_MODE.join(' | ')}." \
224
+ "Using default value: #{SAFE_TRACK_USER_EVENTS_MODE}."
171
225
  )
172
- 'safe'
226
+
227
+ SAFE_TRACK_USER_EVENTS_MODE
228
+ end
229
+ end
230
+ o.after_set do
231
+ Core.log_deprecation(key: :appsec_track_user_events_mode) do
232
+ 'The appsec.track_user_events.mode setting has been deprecated for removal. ' \
233
+ 'Please remove it from your Datadog.configure block and use ' \
234
+ 'appsec.auto_user_instrumentation.mode instead.'
173
235
  end
174
236
  end
175
237
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'metrics'
4
+
3
5
  module Datadog
4
6
  module AppSec
5
7
  # This class accumulates the context over the request life-cycle and exposes
@@ -7,10 +9,7 @@ module Datadog
7
9
  class Context
8
10
  ActiveContextError = Class.new(StandardError)
9
11
 
10
- attr_reader :trace, :span
11
-
12
- # NOTE: This is an intermediate state and will be changed
13
- attr_reader :waf_runner
12
+ attr_reader :trace, :span, :events
14
13
 
15
14
  class << self
16
15
  def activate(context)
@@ -34,16 +33,37 @@ module Datadog
34
33
  def initialize(trace, span, security_engine)
35
34
  @trace = trace
36
35
  @span = span
36
+ @events = []
37
37
  @security_engine = security_engine
38
- @waf_runner = security_engine.new_context
38
+ @waf_runner = security_engine.new_runner
39
+ @metrics = Metrics::Collector.new
39
40
  end
40
41
 
41
42
  def run_waf(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
42
- @waf_runner.run(persistent_data, ephemeral_data, timeout)
43
+ result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
44
+
45
+ @metrics.record_waf(result)
46
+ result
47
+ end
48
+
49
+ def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
50
+ result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
51
+
52
+ Metrics::Telemetry.report_rasp(type, result)
53
+ @metrics.record_rasp(result)
54
+
55
+ result
43
56
  end
44
57
 
45
- def run_rasp(_type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
46
- @waf_runner.run(persistent_data, ephemeral_data, timeout)
58
+ def extract_schema
59
+ @waf_runner.run({ 'waf.context.processor' => { 'extract-schema' => true } }, {})
60
+ end
61
+
62
+ def export_metrics
63
+ return if @span.nil?
64
+
65
+ Metrics::Exporter.export_waf_metrics(@metrics.waf, @span)
66
+ Metrics::Exporter.export_rasp_metrics(@metrics.rasp, @span)
47
67
  end
48
68
 
49
69
  def finalize
@@ -9,6 +9,8 @@ module Datadog
9
9
  module_function
10
10
 
11
11
  def detect_sql_injection(sql, adapter_name)
12
+ return unless AppSec.rasp_enabled?
13
+
12
14
  context = AppSec.active_context
13
15
  return unless context
14
16
 
@@ -25,7 +27,7 @@ module Datadog
25
27
  waf_timeout = Datadog.configuration.appsec.waf_timeout
26
28
  result = context.run_rasp(Ext::RASP_SQLI, {}, ephemeral_data, waf_timeout)
27
29
 
28
- if result.status == :match
30
+ if result.match?
29
31
  Datadog::AppSec::Event.tag_and_keep!(context, result)
30
32
 
31
33
  event = {
@@ -35,7 +37,9 @@ module Datadog
35
37
  sql: sql,
36
38
  actions: result.actions
37
39
  }
38
- context.waf_runner.events << event
40
+ context.events << event
41
+
42
+ ActionsHandler.handle(result.actions)
39
43
  end
40
44
  end
41
45
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../patcher'
4
3
  require_relative 'instrumentation'
5
4
 
6
5
  module Datadog
@@ -9,8 +8,6 @@ module Datadog
9
8
  module ActiveRecord
10
9
  # AppSec patcher module for ActiveRecord
11
10
  module Patcher
12
- include Datadog::AppSec::Contrib::Patcher
13
-
14
11
  module_function
15
12
 
16
13
  def patched?
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Contrib
6
+ module Devise
7
+ # A temporary configuration module to accomodate new RFC changes.
8
+ # NOTE: DEV-3 Remove module
9
+ module Configuration
10
+ MODES_CONVERSION_RULES = {
11
+ track_user_to_auto_instrumentation: {
12
+ AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE =>
13
+ AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE,
14
+ AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE =>
15
+ AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
16
+ }.freeze,
17
+ auto_instrumentation_to_track_user: {
18
+ AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE =>
19
+ AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE,
20
+ AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE =>
21
+ AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE
22
+ }.freeze
23
+ }.freeze
24
+
25
+ module_function
26
+
27
+ # NOTE: DEV-3 Replace method use with `auto_user_instrumentation.enabled?`
28
+ def auto_user_instrumentation_enabled?
29
+ appsec = Datadog.configuration.appsec
30
+ appsec.auto_user_instrumentation.mode
31
+
32
+ unless appsec.auto_user_instrumentation.options[:mode].default_precedence?
33
+ return appsec.auto_user_instrumentation.enabled?
34
+ end
35
+
36
+ appsec.track_user_events.enabled
37
+ end
38
+
39
+ # NOTE: DEV-3 Replace method use with `auto_user_instrumentation.mode`
40
+ def auto_user_instrumentation_mode
41
+ appsec = Datadog.configuration.appsec
42
+
43
+ # NOTE: Reading both to trigger precedence set
44
+ appsec.auto_user_instrumentation.mode
45
+ appsec.track_user_events.mode
46
+
47
+ if !appsec.auto_user_instrumentation.options[:mode].default_precedence? &&
48
+ appsec.track_user_events.options[:mode].default_precedence?
49
+ return appsec.auto_user_instrumentation.mode
50
+ end
51
+
52
+ if appsec.auto_user_instrumentation.options[:mode].default_precedence?
53
+ return MODES_CONVERSION_RULES[:track_user_to_auto_instrumentation].fetch(
54
+ appsec.track_user_events.mode, appsec.auto_user_instrumentation.mode
55
+ )
56
+ end
57
+
58
+ identification_mode = AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
59
+ if appsec.auto_user_instrumentation.mode == identification_mode ||
60
+ appsec.track_user_events.mode == AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE
61
+ return identification_mode
62
+ end
63
+
64
+ AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE
65
+ end
66
+
67
+ # NOTE: Remove in next version of tracking
68
+ def track_user_events_mode
69
+ MODES_CONVERSION_RULES[:auto_instrumentation_to_track_user]
70
+ .fetch(auto_user_instrumentation_mode, Datadog.configuration.appsec.track_user_events.mode)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -8,9 +8,6 @@ module Datadog
8
8
  class Event
9
9
  UUID_REGEX = /^\h{8}-\h{4}-\h{4}-\h{4}-\h{12}$/.freeze
10
10
 
11
- SAFE_MODE = 'safe'
12
- EXTENDED_MODE = 'extended'
13
-
14
11
  attr_reader :user_id
15
12
 
16
13
  def initialize(resource, mode)
@@ -38,15 +35,15 @@ module Datadog
38
35
  @user_id = @resource.id
39
36
 
40
37
  case @mode
41
- when EXTENDED_MODE
38
+ when AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
42
39
  @email = @resource.email
43
40
  @username = @resource.username
44
- when SAFE_MODE
41
+ when AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE
45
42
  @user_id = nil unless @user_id && @user_id.to_s =~ UUID_REGEX
46
43
  else
47
44
  Datadog.logger.warn(
48
- "Invalid automated user evenst mode: `#{@mode}`. "\
49
- 'Supported modes are: `safe` and `extended`.'
45
+ "Invalid auto_user_instrumentation.mode: `#{@mode}`. " \
46
+ "Supported modes are: #{AppSec::Configuration::Settings::AUTO_USER_INSTRUMENTATION_MODES.join(' | ')}."
50
47
  )
51
48
  end
52
49
  end