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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -2
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +100 -29
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +2 -2
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
- data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
- data/ext/datadog_profiling_native_extension/http_transport.c +10 -4
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +9 -8
- data/ext/datadog_profiling_native_extension/profiling.c +20 -15
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
- data/ext/libdatadog_api/crashtracker.c +5 -8
- data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
- data/ext/libdatadog_api/ddsketch.c +4 -8
- data/ext/libdatadog_api/feature_flags.c +5 -5
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +4 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
- data/lib/datadog/appsec/component.rb +1 -1
- data/lib/datadog/appsec/context.rb +3 -3
- data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
- data/lib/datadog/appsec/ext.rb +2 -0
- data/lib/datadog/appsec/metrics/collector.rb +8 -3
- data/lib/datadog/appsec/metrics/exporter.rb +7 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
- data/lib/datadog/appsec/metrics.rb +5 -5
- data/lib/datadog/appsec/remote.rb +4 -4
- data/lib/datadog/appsec.rb +7 -1
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/configuration/settings.rb +17 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
- data/lib/datadog/core/runtime/metrics.rb +11 -1
- data/lib/datadog/core/telemetry/logger.rb +2 -0
- data/lib/datadog/core/telemetry/logging.rb +20 -2
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -2
- data/lib/datadog/profiling/component.rb +13 -0
- data/lib/datadog/profiling/exporter.rb +4 -0
- data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
- data/lib/datadog/profiling/flush.rb +3 -0
- data/lib/datadog/profiling/profiler.rb +3 -5
- data/lib/datadog/profiling/scheduler.rb +8 -7
- data/lib/datadog/profiling/tag_builder.rb +1 -0
- data/lib/datadog/version.rb +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
data/ext/libdatadog_api/init.c
CHANGED
|
@@ -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 = '~>
|
|
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
|
-
|
|
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 =
|
|
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
|
{
|
|
@@ -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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
21
|
-
ephemeral_data = {
|
|
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,
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
TraceKeeper.keep!(context.trace) if result.keep?
|
|
45
|
+
super
|
|
46
|
+
end
|
|
28
47
|
|
|
29
|
-
|
|
30
|
-
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
|
31
|
-
)
|
|
48
|
+
private
|
|
32
49
|
|
|
33
|
-
|
|
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
|
-
|
|
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(
|
|
13
|
+
def call(env)
|
|
14
14
|
context = AppSec.active_context
|
|
15
|
+
return @app.call(env) unless context && AppSec.rasp_enabled?
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
18
18
|
ephemeral_data = {
|
|
19
|
-
'server.io.net.url' =>
|
|
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,
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
def handle(result, context:)
|
|
46
|
+
AppSec::Event.tag(context, result)
|
|
47
|
+
TraceKeeper.keep!(context.trace) if result.keep?
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
context.events.push(
|
|
50
|
+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
|
51
|
+
)
|
|
34
52
|
|
|
35
|
-
|
|
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
|
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
61
|
+
def handle(result, context:)
|
|
62
|
+
AppSec::Event.tag(context, result)
|
|
63
|
+
TraceKeeper.keep!(context.trace) if result.keep?
|
|
28
64
|
|
|
29
|
-
|
|
30
|
-
|
|
65
|
+
context.events.push(
|
|
66
|
+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
|
67
|
+
)
|
|
31
68
|
|
|
32
|
-
|
|
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
|
data/lib/datadog/appsec/ext.rb
CHANGED
|
@@ -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,
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
88
|
+
engine.add_or_update_config(parse_content(content), path: change.path.to_s)
|
|
89
89
|
content.applied
|
|
90
90
|
when :delete
|
|
91
|
-
|
|
91
|
+
engine.remove_config_at_path(change.path.to_s)
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
|
data/lib/datadog/appsec.rb
CHANGED
|
@@ -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|
|