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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +72 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +3 -3
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +46 -6
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +2 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +0 -8
- data/ext/datadog_profiling_native_extension/heap_recorder.c +51 -93
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +56 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +7 -0
- data/ext/datadog_profiling_native_extension/profiling.c +7 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/ext/libdatadog_api/crashtracker.c +4 -4
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/actions_handler.rb +27 -0
- data/lib/datadog/appsec/component.rb +14 -8
- data/lib/datadog/appsec/configuration/settings.rb +73 -11
- data/lib/datadog/appsec/context.rb +28 -8
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/devise/configuration.rb +76 -0
- data/lib/datadog/appsec/contrib/devise/event.rb +4 -7
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +16 -21
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +8 -15
- data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/devise/tracking.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/excon/patcher.rb +28 -0
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +43 -0
- data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
- data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +42 -0
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +11 -14
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +65 -70
- data/lib/datadog/appsec/contrib/rack/patcher.rb +0 -3
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +20 -24
- data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -16
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +38 -47
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -29
- data/lib/datadog/appsec/ext.rb +6 -1
- data/lib/datadog/appsec/metrics/collector.rb +38 -0
- data/lib/datadog/appsec/metrics/exporter.rb +35 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
- data/lib/datadog/appsec/metrics.rb +13 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +19 -24
- data/lib/datadog/appsec/processor.rb +4 -3
- data/lib/datadog/appsec/remote.rb +4 -0
- data/lib/datadog/appsec/response.rb +18 -80
- data/lib/datadog/appsec/security_engine/result.rb +67 -0
- data/lib/datadog/appsec/security_engine/runner.rb +88 -0
- data/lib/datadog/appsec/security_engine.rb +9 -0
- data/lib/datadog/appsec.rb +16 -5
- data/lib/datadog/core/configuration/components.rb +7 -1
- data/lib/datadog/core/configuration/ext.rb +1 -1
- data/lib/datadog/core/configuration/option_definition.rb +2 -0
- data/lib/datadog/core/configuration/settings.rb +22 -6
- data/lib/datadog/core/encoding.rb +16 -0
- data/lib/datadog/core/environment/agent_info.rb +77 -0
- data/lib/datadog/core/remote/transport/http/api.rb +13 -18
- data/lib/datadog/core/remote/transport/http/config.rb +0 -18
- data/lib/datadog/core/remote/transport/http/negotiation.rb +1 -18
- data/lib/datadog/core/remote/transport/http.rb +7 -12
- data/lib/datadog/core/remote/transport/negotiation.rb +13 -1
- data/lib/datadog/core/telemetry/event.rb +5 -0
- data/lib/datadog/core/transport/http/adapters/unix_socket.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/api/spec.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/builder.rb +37 -17
- data/lib/datadog/core/transport/response.rb +4 -0
- data/lib/datadog/di/code_tracker.rb +15 -8
- data/lib/datadog/di/component.rb +3 -0
- data/lib/datadog/di/configuration/settings.rb +14 -0
- data/lib/datadog/di/contrib.rb +2 -0
- data/lib/datadog/di/logger.rb +30 -0
- data/lib/datadog/di/probe.rb +3 -6
- data/lib/datadog/di/probe_manager.rb +5 -2
- data/lib/datadog/di/probe_notification_builder.rb +6 -0
- data/lib/datadog/di/probe_notifier_worker.rb +15 -4
- data/lib/datadog/di/redactor.rb +0 -1
- data/lib/datadog/di/remote.rb +29 -8
- data/lib/datadog/di/utils.rb +91 -0
- data/lib/datadog/di.rb +3 -0
- data/lib/datadog/profiling/component.rb +2 -8
- data/lib/datadog/profiling/load_native_extension.rb +1 -33
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/extensions.rb +29 -3
- data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +11 -0
- data/lib/datadog/tracing/contrib/graphql/ext.rb +5 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +102 -11
- data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/header_collection.rb +11 -1
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +1 -1
- data/lib/datadog/tracing/contrib/span_attribute_schema.rb +6 -1
- data/lib/datadog/tracing/transport/http/api.rb +11 -2
- data/lib/datadog/tracing/transport/http/traces.rb +0 -3
- data/lib/datadog/tracing/transport/http.rb +12 -7
- data/lib/datadog/tracing/transport/serializable_trace.rb +8 -4
- data/lib/datadog/tracing/transport/traces.rb +25 -8
- data/lib/datadog/version.rb +1 -1
- metadata +51 -42
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
- data/ext/datadog_profiling_loader/extconf.rb +0 -60
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -46
- data/lib/datadog/appsec/contrib/patcher.rb +0 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -69
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -47
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -53
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -53
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -48
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -45
- data/lib/datadog/appsec/processor/context.rb +0 -107
- data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
- data/lib/datadog/appsec/reactive/engine.rb +0 -47
- data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
- data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
- data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
- 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
|
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
|
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
|
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
|
-
//
|
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->
|
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
|
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
|
-
|
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
|
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
|
-
|
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 ==
|
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
|
-
|
119
|
+
ddog_VoidResult result = ddog_crasht_shutdown();
|
120
120
|
|
121
|
-
if (result.tag ==
|
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 = '~>
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
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
|
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
|
-
|
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
|
-
|
170
|
-
|
223
|
+
"Supported values are: #{APPSEC_VALID_TRACK_USER_EVENTS_MODE.join(' | ')}." \
|
224
|
+
"Using default value: #{SAFE_TRACK_USER_EVENTS_MODE}."
|
171
225
|
)
|
172
|
-
|
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.
|
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
|
46
|
-
@waf_runner.run(
|
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.
|
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.
|
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
|
38
|
+
when AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
|
42
39
|
@email = @resource.email
|
43
40
|
@username = @resource.username
|
44
|
-
when
|
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
|
49
|
-
|
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
|