datadog 2.28.0 → 2.30.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 +87 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +82 -12
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +32 -11
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +9 -24
- data/ext/datadog_profiling_native_extension/heap_recorder.c +186 -55
- data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +51 -64
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
- data/ext/datadog_profiling_native_extension/profiling.c +3 -1
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +24 -8
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -3
- data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -48
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -1
- data/ext/libdatadog_api/crashtracker.c +5 -0
- data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
- data/ext/libdatadog_api/extconf.rb +4 -21
- data/ext/libdatadog_extconf_helpers.rb +49 -11
- data/lib/datadog/ai_guard/configuration/settings.rb +3 -0
- data/lib/datadog/appsec/assets/blocked.html +2 -1
- data/lib/datadog/appsec/configuration/settings.rb +14 -0
- data/lib/datadog/appsec/context.rb +44 -9
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +3 -0
- data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/patcher.rb +2 -0
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +55 -6
- data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +60 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +11 -6
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +6 -10
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -4
- data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +26 -5
- data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
- data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -0
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +2 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +72 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +5 -3
- data/lib/datadog/appsec/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/event.rb +1 -17
- data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
- data/lib/datadog/appsec/instrumentation/gateway.rb +2 -2
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +18 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -2
- data/lib/datadog/appsec/security_engine/engine.rb +23 -2
- data/lib/datadog/appsec/utils/http/body.rb +38 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
- data/lib/datadog/appsec/utils/http/media_type.rb +28 -35
- data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
- data/lib/datadog/core/configuration/components.rb +29 -4
- data/lib/datadog/core/configuration/option.rb +2 -1
- data/lib/datadog/core/configuration/options.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +27 -3
- data/lib/datadog/core/configuration/supported_configurations.rb +19 -0
- data/lib/datadog/core/configuration.rb +2 -2
- data/lib/datadog/core/crashtracking/component.rb +71 -19
- data/lib/datadog/core/environment/agent_info.rb +65 -1
- data/lib/datadog/core/logger.rb +1 -1
- data/lib/datadog/core/metrics/logging.rb +1 -1
- data/lib/datadog/core/process_discovery.rb +20 -19
- data/lib/datadog/core/rate_limiter.rb +2 -0
- data/lib/datadog/core/remote/component.rb +16 -5
- data/lib/datadog/core/remote/transport/config.rb +5 -11
- data/lib/datadog/core/runtime/metrics.rb +1 -2
- data/lib/datadog/core/telemetry/component.rb +0 -13
- data/lib/datadog/core/telemetry/transport/telemetry.rb +5 -6
- data/lib/datadog/core/transport/ext.rb +1 -0
- data/lib/datadog/core/transport/http/response.rb +4 -0
- data/lib/datadog/core/transport/parcel.rb +61 -9
- data/lib/datadog/core/utils/base64.rb +1 -1
- data/lib/datadog/core/utils/fnv.rb +26 -0
- data/lib/datadog/core/workers/interval_loop.rb +13 -6
- data/lib/datadog/core/workers/queue.rb +0 -4
- data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
- data/lib/datadog/core.rb +6 -1
- data/lib/datadog/data_streams/processor.rb +35 -33
- data/lib/datadog/data_streams/transport/http/stats.rb +6 -0
- data/lib/datadog/data_streams/transport/http.rb +0 -4
- data/lib/datadog/data_streams/transport/stats.rb +5 -12
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/component.rb +17 -5
- data/lib/datadog/di/configuration/settings.rb +9 -0
- data/lib/datadog/di/context.rb +6 -0
- data/lib/datadog/di/instrumenter.rb +183 -134
- data/lib/datadog/di/probe.rb +10 -1
- data/lib/datadog/di/probe_file_loader.rb +2 -2
- data/lib/datadog/di/probe_manager.rb +86 -64
- data/lib/datadog/di/probe_notification_builder.rb +46 -18
- data/lib/datadog/di/probe_notifier_worker.rb +65 -9
- data/lib/datadog/di/probe_repository.rb +198 -0
- data/lib/datadog/di/proc_responder.rb +4 -0
- data/lib/datadog/di/remote.rb +6 -7
- data/lib/datadog/di/serializer.rb +127 -9
- data/lib/datadog/di/transport/diagnostics.rb +5 -7
- data/lib/datadog/di/transport/http/diagnostics.rb +3 -1
- data/lib/datadog/di/transport/http/input.rb +1 -1
- data/lib/datadog/di/transport/http.rb +12 -3
- data/lib/datadog/di/transport/input.rb +51 -14
- data/lib/datadog/kit/tracing/method_tracer.rb +132 -0
- data/lib/datadog/open_feature/configuration.rb +2 -0
- data/lib/datadog/open_feature/transport.rb +8 -11
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +13 -0
- data/lib/datadog/profiling/component.rb +20 -6
- data/lib/datadog/profiling/http_transport.rb +5 -6
- data/lib/datadog/profiling/profiler.rb +15 -8
- data/lib/datadog/tracing/contrib/dalli/integration.rb +4 -1
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +2 -2
- data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
- data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +10 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +24 -0
- data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/rack/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +18 -6
- data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +1 -0
- data/lib/datadog/tracing/metadata/ext.rb +4 -0
- data/lib/datadog/tracing/sync_writer.rb +0 -1
- data/lib/datadog/tracing/transport/io/client.rb +5 -8
- data/lib/datadog/tracing/transport/io/traces.rb +28 -34
- data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
- data/lib/datadog/tracing/transport/traces.rb +4 -10
- data/lib/datadog/tracing/writer.rb +0 -1
- data/lib/datadog/version.rb +1 -1
- metadata +14 -8
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
- data/lib/datadog/tracing/workers/trace_writer.rb +0 -204
|
@@ -11,6 +11,8 @@ module Datadog
|
|
|
11
11
|
context = request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
|
|
12
12
|
return super unless context
|
|
13
13
|
|
|
14
|
+
context.state[:web_framework] = 'rails'
|
|
15
|
+
|
|
14
16
|
# TODO: handle exceptions, except for super
|
|
15
17
|
gateway_request = Gateway::Request.new(request)
|
|
16
18
|
http_response, _gateway_request = Instrumentation.gateway.push('rails.request.action', gateway_request) do
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require_relative '../../event'
|
|
4
4
|
require_relative '../../trace_keeper'
|
|
5
5
|
require_relative '../../security_event'
|
|
6
|
+
require_relative '../../utils/http/media_type'
|
|
7
|
+
require_relative '../../utils/http/body'
|
|
6
8
|
|
|
7
9
|
module Datadog
|
|
8
10
|
module AppSec
|
|
@@ -10,35 +12,97 @@ module Datadog
|
|
|
10
12
|
module RestClient
|
|
11
13
|
# Module that adds SSRF detection to RestClient::Request#execute
|
|
12
14
|
module RequestSSRFDetectionPatch
|
|
15
|
+
REDIRECT_STATUS_CODES = (300..399).freeze
|
|
16
|
+
|
|
13
17
|
def execute(&block)
|
|
14
18
|
context = AppSec.active_context
|
|
15
19
|
return super unless context && AppSec.rasp_enabled?
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
headers = normalize_request_headers
|
|
22
|
+
# @type var ephemeral_data: ::Datadog::AppSec::Context::input_data
|
|
18
23
|
ephemeral_data = {
|
|
19
24
|
'server.io.net.url' => url,
|
|
20
25
|
'server.io.net.request.method' => method.to_s.upcase,
|
|
21
|
-
'server.io.net.request.headers' =>
|
|
26
|
+
'server.io.net.request.headers' => headers
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
is_redirect = context.state[:downstream_redirect_url] == url
|
|
30
|
+
if is_redirect
|
|
31
|
+
context.state.delete(:downstream_redirect_url)
|
|
32
|
+
sample_body = true
|
|
33
|
+
else
|
|
34
|
+
sample_body = mark_body_sampling!(context)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if !is_redirect && sample_body
|
|
38
|
+
body = parse_body(payload.to_s, content_type: headers['content-type'])
|
|
39
|
+
ephemeral_data['server.io.net.request.body'] = body if body
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
24
43
|
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_REQUEST_PHASE)
|
|
25
44
|
handle(result, context: context) if result.match?
|
|
26
45
|
|
|
27
|
-
|
|
46
|
+
# NOTE: RestClient raises exceptions for non-2xx responses. For POST/PUT/PATCH
|
|
47
|
+
# requests with 3xx redirects, RestClient raises instead of auto-following.
|
|
48
|
+
# We rescue to process the response before re-raising.
|
|
49
|
+
begin
|
|
50
|
+
response = super
|
|
51
|
+
rescue ::RestClient::Exception => e
|
|
52
|
+
response = e.response
|
|
53
|
+
process_response(response, sample_body: sample_body) if response.is_a?(::RestClient::AbstractResponse)
|
|
54
|
+
|
|
55
|
+
raise
|
|
56
|
+
end
|
|
28
57
|
|
|
58
|
+
process_response(response, sample_body: sample_body) if response.is_a?(::RestClient::AbstractResponse)
|
|
59
|
+
response
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def process_response(response, sample_body:)
|
|
63
|
+
context = AppSec.active_context
|
|
64
|
+
return unless context
|
|
65
|
+
|
|
66
|
+
headers = normalize_response_headers(response)
|
|
67
|
+
# @type var ephemeral_data: ::Datadog::AppSec::Context::input_data
|
|
29
68
|
ephemeral_data = {
|
|
30
69
|
'server.io.net.response.status' => response.code.to_s,
|
|
31
|
-
'server.io.net.response.headers' =>
|
|
70
|
+
'server.io.net.response.headers' => headers
|
|
32
71
|
}
|
|
33
72
|
|
|
73
|
+
is_redirect = REDIRECT_STATUS_CODES.cover?(response.code.to_i) && headers.key?('location')
|
|
74
|
+
context.state[:downstream_redirect_url] = URI.join(url, headers['location']).to_s if is_redirect && sample_body
|
|
75
|
+
|
|
76
|
+
if sample_body && !is_redirect
|
|
77
|
+
body = parse_body(response.body, content_type: headers['content-type'])
|
|
78
|
+
ephemeral_data['server.io.net.response.body'] = body if body
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
34
82
|
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_RESPONSE_PHASE)
|
|
35
83
|
handle(result, context: context) if result.match?
|
|
36
|
-
|
|
37
|
-
response
|
|
38
84
|
end
|
|
39
85
|
|
|
40
86
|
private
|
|
41
87
|
|
|
88
|
+
def mark_body_sampling!(context)
|
|
89
|
+
max = Datadog.configuration.appsec.api_security.downstream_body_analysis.max_requests
|
|
90
|
+
return false if context.state[:downstream_body_analyzed_count] >= max
|
|
91
|
+
return false unless context.downstream_body_sampler.sample?
|
|
92
|
+
|
|
93
|
+
context.state[:downstream_body_analyzed_count] += 1
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def parse_body(body, content_type:)
|
|
98
|
+
return if body.empty?
|
|
99
|
+
|
|
100
|
+
media_type = Utils::HTTP::MediaType.parse(content_type)
|
|
101
|
+
return unless media_type
|
|
102
|
+
|
|
103
|
+
Utils::HTTP::Body.parse(body, media_type: media_type)
|
|
104
|
+
end
|
|
105
|
+
|
|
42
106
|
# NOTE: Starting version 2.1.0 headers are already normalized via internal
|
|
43
107
|
# variable `@processed_headers_lowercase`. In case it's available,
|
|
44
108
|
# we use it to avoid unnecessary transformation.
|
|
@@ -55,7 +119,8 @@ module Datadog
|
|
|
55
119
|
# FIXME: Steep has issues with `transform_values!` modifying the original
|
|
56
120
|
# type and it failed with "Cannot allow block body" error
|
|
57
121
|
def normalize_response_headers(response) # steep:ignore MethodBodyTypeMismatch
|
|
58
|
-
response.net_http_res.to_hash
|
|
122
|
+
response.net_http_res.to_hash
|
|
123
|
+
.transform_values! { |value| Array(value).join(', ') } # steep:ignore BlockBodyTypeMismatch
|
|
59
124
|
end
|
|
60
125
|
|
|
61
126
|
def handle(result, context:)
|
|
@@ -22,9 +22,11 @@ module Datadog
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def watch_request_dispatch(gateway = Instrumentation.gateway)
|
|
25
|
-
gateway.watch('sinatra.request.dispatch'
|
|
25
|
+
gateway.watch('sinatra.request.dispatch') do |stack, gateway_request|
|
|
26
26
|
context = gateway_request.env[AppSec::Ext::CONTEXT_KEY] # : Context
|
|
27
27
|
|
|
28
|
+
context.state[:web_framework] = 'sinatra'
|
|
29
|
+
|
|
28
30
|
persistent_data = {
|
|
29
31
|
'server.request.body' => gateway_request.form_hash
|
|
30
32
|
}
|
|
@@ -49,7 +51,7 @@ module Datadog
|
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
def watch_request_routed(gateway = Instrumentation.gateway)
|
|
52
|
-
gateway.watch('sinatra.request.routed'
|
|
54
|
+
gateway.watch('sinatra.request.routed') do |stack, args|
|
|
53
55
|
gateway_request, gateway_route_params = args # : [Gateway::Request, Gateway::RouteParams]
|
|
54
56
|
context = gateway_request.env[AppSec::Ext::CONTEXT_KEY] # : Context
|
|
55
57
|
|
|
@@ -75,7 +77,7 @@ module Datadog
|
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
def watch_response_body_json(gateway = Instrumentation.gateway)
|
|
78
|
-
gateway.watch('sinatra.response.body.json'
|
|
80
|
+
gateway.watch('sinatra.response.body.json') do |stack, container|
|
|
79
81
|
context = container.context # : Context
|
|
80
82
|
|
|
81
83
|
persistent_data = {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../core/knuth_sampler'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module AppSec
|
|
7
|
+
# Sampler that uses an internal counter to make deterministic sampling decisions.
|
|
8
|
+
#
|
|
9
|
+
# Each call to {#sample?} increments the counter and uses it as input to
|
|
10
|
+
# the underlying Knuth multiplicative hash algorithm.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
class CounterSampler
|
|
14
|
+
def initialize(rate = 1.0)
|
|
15
|
+
@sampler = Core::KnuthSampler.new(rate)
|
|
16
|
+
@counter = 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sample?
|
|
20
|
+
@counter += 1
|
|
21
|
+
@sampler.sample?(@counter)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/datadog/appsec/event.rb
CHANGED
|
@@ -32,13 +32,6 @@ module Datadog
|
|
|
32
32
|
accept-language
|
|
33
33
|
].freeze
|
|
34
34
|
|
|
35
|
-
ALLOWED_RESPONSE_HEADERS = %w[
|
|
36
|
-
content-length
|
|
37
|
-
content-type
|
|
38
|
-
content-encoding
|
|
39
|
-
content-language
|
|
40
|
-
].freeze
|
|
41
|
-
|
|
42
35
|
class << self
|
|
43
36
|
def tag(context, waf_result)
|
|
44
37
|
return if context.span.nil?
|
|
@@ -50,7 +43,7 @@ module Datadog
|
|
|
50
43
|
context.span.set_tag('appsec.event', 'true')
|
|
51
44
|
end
|
|
52
45
|
|
|
53
|
-
def record(context, request: nil
|
|
46
|
+
def record(context, request: nil)
|
|
54
47
|
return if context.events.empty? || context.span.nil?
|
|
55
48
|
|
|
56
49
|
Datadog::AppSec::RateLimiter.thread_local.limit do
|
|
@@ -66,7 +59,6 @@ module Datadog
|
|
|
66
59
|
|
|
67
60
|
context.span['_dd.origin'] = 'appsec'
|
|
68
61
|
context.span.set_tags(request_tags(request)) if request
|
|
69
|
-
context.span.set_tags(response_tags(response)) if response
|
|
70
62
|
end
|
|
71
63
|
|
|
72
64
|
context.span.set_tags(waf_tags(event_group))
|
|
@@ -90,14 +82,6 @@ module Datadog
|
|
|
90
82
|
end
|
|
91
83
|
end
|
|
92
84
|
|
|
93
|
-
def response_tags(response)
|
|
94
|
-
response.headers.each_with_object({}) do |(name, value), memo|
|
|
95
|
-
next unless ALLOWED_RESPONSE_HEADERS.include?(name)
|
|
96
|
-
|
|
97
|
-
memo["http.response.headers.#{name}"] = value
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
85
|
def waf_tags(security_events)
|
|
102
86
|
triggers = []
|
|
103
87
|
|
|
@@ -7,10 +7,9 @@ module Datadog
|
|
|
7
7
|
# NOTE: This class extracted as-is and will be deprecated
|
|
8
8
|
# Instrumentation gateway middleware
|
|
9
9
|
class Middleware
|
|
10
|
-
attr_reader :
|
|
10
|
+
attr_reader :block
|
|
11
11
|
|
|
12
|
-
def initialize(
|
|
13
|
-
@key = key
|
|
12
|
+
def initialize(&block)
|
|
14
13
|
@block = block
|
|
15
14
|
end
|
|
16
15
|
|
|
@@ -41,8 +41,8 @@ module Datadog
|
|
|
41
41
|
stack.call(env)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def watch(name,
|
|
45
|
-
@middlewares[name] << Middleware.new(
|
|
44
|
+
def watch(name, &block)
|
|
45
|
+
@middlewares[name] << Middleware.new(&block)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def pushed?(name)
|
|
@@ -23,6 +23,24 @@ module Datadog
|
|
|
23
23
|
}
|
|
24
24
|
)
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
def export_api_security_metrics(context)
|
|
28
|
+
web_framework = context.state[:web_framework]
|
|
29
|
+
return unless web_framework
|
|
30
|
+
|
|
31
|
+
if context.span&.get_tag(Tracing::Metadata::Ext::HTTP::TAG_ROUTE).nil?
|
|
32
|
+
AppSec.telemetry.inc(
|
|
33
|
+
AppSec::Ext::TELEMETRY_METRICS_NAMESPACE, 'api_security.missing_route', 1,
|
|
34
|
+
tags: {framework: web_framework}
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
metric_name = context.state[:schema_extracted] ? 'schema' : 'no_schema'
|
|
39
|
+
AppSec.telemetry.inc(
|
|
40
|
+
AppSec::Ext::TELEMETRY_METRICS_NAMESPACE, "api_security.request.#{metric_name}", 1,
|
|
41
|
+
tags: {framework: web_framework}
|
|
42
|
+
)
|
|
43
|
+
end
|
|
26
44
|
end
|
|
27
45
|
end
|
|
28
46
|
end
|
|
@@ -24,7 +24,7 @@ module Datadog
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def watch_user_id(gateway = Instrumentation.gateway)
|
|
27
|
-
gateway.watch('identity.set_user'
|
|
27
|
+
gateway.watch('identity.set_user') do |stack, user|
|
|
28
28
|
context = AppSec.active_context
|
|
29
29
|
|
|
30
30
|
if user.id.nil? && user.login.nil? && user.session_id.nil?
|
|
@@ -55,7 +55,7 @@ module Datadog
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def watch_user_login(gateway = Instrumentation.gateway)
|
|
58
|
-
gateway.watch('appsec.events.user_lifecycle'
|
|
58
|
+
gateway.watch('appsec.events.user_lifecycle') do |stack, kind|
|
|
59
59
|
context = AppSec.active_context
|
|
60
60
|
|
|
61
61
|
next stack.call(kind) unless WATCHED_LOGIN_EVENTS.include?(kind)
|
|
@@ -40,12 +40,16 @@ module Datadog
|
|
|
40
40
|
@ruleset_version = diagnostics['ruleset_version']
|
|
41
41
|
|
|
42
42
|
@handle_ref = ThreadSafeRef.new(@waf_builder.build_handle)
|
|
43
|
+
|
|
44
|
+
metric('init', success: true, ruleset_version: @ruleset_version, telemetry: telemetry)
|
|
43
45
|
rescue WAF::Error => e
|
|
44
|
-
error_message =
|
|
46
|
+
error_message = 'AppSec security engine failed to initialize'
|
|
45
47
|
|
|
46
48
|
Datadog.logger.error("#{error_message}, error #{e.inspect}")
|
|
47
49
|
telemetry.report(e, description: error_message)
|
|
48
50
|
|
|
51
|
+
metric('init', success: false, ruleset_version: @ruleset_version, telemetry: telemetry)
|
|
52
|
+
|
|
49
53
|
raise e
|
|
50
54
|
end
|
|
51
55
|
|
|
@@ -103,17 +107,34 @@ module Datadog
|
|
|
103
107
|
@ruleset_version = @reconfigured_ruleset_version
|
|
104
108
|
|
|
105
109
|
@handle_ref.current = new_waf_handle
|
|
110
|
+
|
|
111
|
+
metric('updates', success: true, ruleset_version: @ruleset_version, telemetry: AppSec.telemetry)
|
|
106
112
|
rescue WAF::Error => e
|
|
107
113
|
# WAF::Error can only be raised during new WAF handle creation or when reading known addresses.
|
|
108
114
|
# This means that the current WAF handle was not yet substituted.
|
|
109
|
-
error_message =
|
|
115
|
+
error_message = 'AppSec security engine failed to reconfigure, reverting to the previous configuration'
|
|
110
116
|
|
|
111
117
|
Datadog.logger.error("#{error_message}, error #{e.inspect}")
|
|
112
118
|
AppSec.telemetry.report(e, description: error_message)
|
|
119
|
+
|
|
120
|
+
metric('updates', success: false, ruleset_version: @reconfigured_ruleset_version, telemetry: AppSec.telemetry)
|
|
113
121
|
end
|
|
114
122
|
|
|
115
123
|
private
|
|
116
124
|
|
|
125
|
+
def metric(metric_name, success:, ruleset_version:, telemetry:)
|
|
126
|
+
telemetry.inc(
|
|
127
|
+
Ext::TELEMETRY_METRICS_NAMESPACE,
|
|
128
|
+
"waf.#{metric_name}",
|
|
129
|
+
1,
|
|
130
|
+
tags: {
|
|
131
|
+
waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING,
|
|
132
|
+
event_rules_version: ruleset_version.to_s,
|
|
133
|
+
success: success.to_s
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
117
138
|
def load_default_config(telemetry:)
|
|
118
139
|
config = AppSec::Processor::RuleLoader.load_rules(telemetry: telemetry, ruleset: @default_ruleset)
|
|
119
140
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'cgi'
|
|
5
|
+
|
|
6
|
+
require_relative 'url_encoded'
|
|
7
|
+
|
|
8
|
+
module Datadog
|
|
9
|
+
module AppSec
|
|
10
|
+
module Utils
|
|
11
|
+
module HTTP
|
|
12
|
+
# Module for handling HTTP body parsing
|
|
13
|
+
module Body
|
|
14
|
+
def self.parse(body, media_type:)
|
|
15
|
+
return if body.nil?
|
|
16
|
+
|
|
17
|
+
body.rewind if body.respond_to?(:rewind) # steep:ignore NoMethod
|
|
18
|
+
# @type var content: ::String?
|
|
19
|
+
content = body.respond_to?(:read) ? body.read : body # steep:ignore NoMethod, IncompatibleAssignment
|
|
20
|
+
body.rewind if body.respond_to?(:rewind) # steep:ignore NoMethod
|
|
21
|
+
|
|
22
|
+
return if content.nil? || content.empty?
|
|
23
|
+
|
|
24
|
+
if media_type.subtype == 'json' || media_type.subtype.end_with?('+json')
|
|
25
|
+
JSON.parse(content)
|
|
26
|
+
elsif media_type.subtype == 'x-www-form-urlencoded'
|
|
27
|
+
URLEncoded.parse(content)
|
|
28
|
+
end
|
|
29
|
+
rescue => e
|
|
30
|
+
AppSec.telemetry.report(e, description: 'AppSec: Failed to parse body')
|
|
31
|
+
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -168,7 +168,8 @@ module Datadog
|
|
|
168
168
|
#
|
|
169
169
|
# returns true if the MediaType is accepted by this MediaRange
|
|
170
170
|
def ===(other)
|
|
171
|
-
return
|
|
171
|
+
return false if other.nil?
|
|
172
|
+
return self === MediaType.parse(other) if other.is_a?(::String)
|
|
172
173
|
|
|
173
174
|
type == other.type && subtype == other.subtype && other.parameters.all? { |k, v| parameters[k] == v } ||
|
|
174
175
|
type == other.type && wildcard?(:subtype) ||
|
|
@@ -10,8 +10,6 @@ module Datadog
|
|
|
10
10
|
# - https://www.rfc-editor.org/rfc/rfc7231#section-5.3.1
|
|
11
11
|
# - https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
|
|
12
12
|
class MediaType
|
|
13
|
-
ParseError = Class.new(StandardError) # steep:ignore IncompatibleAssignment
|
|
14
|
-
|
|
15
13
|
WILDCARD = '*'
|
|
16
14
|
|
|
17
15
|
# See: https://www.rfc-editor.org/rfc/rfc7230#section-3.2.6
|
|
@@ -45,48 +43,43 @@ module Datadog
|
|
|
45
43
|
|
|
46
44
|
attr_reader :type, :subtype, :parameters
|
|
47
45
|
|
|
48
|
-
def self.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
match = MEDIA_TYPE_RE.match(media_type)
|
|
52
|
-
return false if match.nil?
|
|
46
|
+
def self.parse(media)
|
|
47
|
+
match = MEDIA_TYPE_RE.match(media)
|
|
48
|
+
return if match.nil?
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
type = match['type'] || WILDCARD
|
|
51
|
+
type.downcase!
|
|
56
52
|
|
|
53
|
+
subtype = match['subtype'] || WILDCARD
|
|
57
54
|
subtype.downcase!
|
|
58
|
-
subtype == 'json' || subtype.end_with?('+json')
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def initialize(media_type)
|
|
62
|
-
match = MEDIA_TYPE_RE.match(media_type)
|
|
63
|
-
raise ParseError, media_type.inspect if match.nil?
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
parameters = {}
|
|
57
|
+
params = match['parameters']
|
|
67
58
|
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
unless params.nil? || params.empty?
|
|
60
|
+
params.scan(PARAMETER_RE) do |name, unquoted_value, quoted_value|
|
|
61
|
+
# NOTE: Order of unquoted_value and quoted_value does not matter,
|
|
62
|
+
# as they are mutually exclusive by the regex.
|
|
63
|
+
# @type var value: ::String?
|
|
64
|
+
value = unquoted_value || quoted_value
|
|
65
|
+
next if name.nil? || value.nil?
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
# See https://github.com/soutaro/steep/issues/2051
|
|
68
|
+
name.downcase! # steep:ignore NoMethod
|
|
69
|
+
value.downcase!
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# NOTE: Order of unquoted_value and quoted_value does not matter,
|
|
78
|
-
# as they are mutually exclusive by the regex.
|
|
79
|
-
# @type var value: ::String?
|
|
80
|
-
value = unquoted_value || quoted_value
|
|
81
|
-
next if name.nil? || value.nil?
|
|
71
|
+
# See https://github.com/soutaro/steep/issues/2051
|
|
72
|
+
parameters[name] = value # steep:ignore ArgumentTypeMismatch
|
|
73
|
+
end
|
|
74
|
+
end
|
|
82
75
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
value.downcase!
|
|
76
|
+
new(type: type, subtype: subtype, parameters: parameters)
|
|
77
|
+
end
|
|
86
78
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
def initialize(type:, subtype:, parameters: {})
|
|
80
|
+
@type = type
|
|
81
|
+
@subtype = subtype
|
|
82
|
+
@parameters = parameters
|
|
90
83
|
end
|
|
91
84
|
|
|
92
85
|
def to_s
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module AppSec
|
|
7
|
+
module Utils
|
|
8
|
+
module HTTP
|
|
9
|
+
# Module for parsing URL encoded payloads
|
|
10
|
+
module URLEncoded
|
|
11
|
+
# Parses a URL encoded payload (query string or form data) into a hash
|
|
12
|
+
# of keys and values, merging duplicate keys.
|
|
13
|
+
#
|
|
14
|
+
# Example:
|
|
15
|
+
#
|
|
16
|
+
# URLEncoded.parse("foo=bar&foo=baz&qux=quux") # => {"foo" => ["bar", "baz"], "qux" => "quux"}
|
|
17
|
+
#
|
|
18
|
+
# NOTE: Use it in the absence of `Rack::Utils.parse_query`
|
|
19
|
+
#
|
|
20
|
+
# WARNING: This method doesn't limit params byte size.
|
|
21
|
+
# See: https://github.com/rack/rack/blob/603b799de38b5eb9b2ff1657c8036a20f4c4db7b/lib/rack/query_parser.rb#L231-L233
|
|
22
|
+
def self.parse(payload)
|
|
23
|
+
return {} if payload.nil? || payload.empty?
|
|
24
|
+
|
|
25
|
+
payload.split('&').each_with_object({}) do |pair, memo|
|
|
26
|
+
next if pair.empty?
|
|
27
|
+
|
|
28
|
+
# NOTE: Steep has issues with mutation methods
|
|
29
|
+
# See https://github.com/ruby/rbs/issues/2819
|
|
30
|
+
#
|
|
31
|
+
# @type var key: ::String
|
|
32
|
+
# @type var value: ::String
|
|
33
|
+
key, value = pair.split('=', 2).map! do |value| #: ::String
|
|
34
|
+
CGI.unescape(value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if (stored = memo[key])
|
|
38
|
+
if stored.is_a?(Array)
|
|
39
|
+
stored.push(value)
|
|
40
|
+
else
|
|
41
|
+
memo[key] = [stored, value]
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
memo[key] = value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -11,6 +11,8 @@ require_relative '../runtime/metrics'
|
|
|
11
11
|
require_relative '../telemetry/component'
|
|
12
12
|
require_relative '../workers/runtime_metrics'
|
|
13
13
|
require_relative '../remote/component'
|
|
14
|
+
require_relative '../utils/at_fork_monkey_patch'
|
|
15
|
+
require_relative '../utils/only_once'
|
|
14
16
|
require_relative '../../tracing/component'
|
|
15
17
|
require_relative '../../profiling/component'
|
|
16
18
|
require_relative '../../appsec/component'
|
|
@@ -28,6 +30,9 @@ module Datadog
|
|
|
28
30
|
module Configuration
|
|
29
31
|
# Global components for the trace library.
|
|
30
32
|
class Components
|
|
33
|
+
# Class-level constant to ensure fork patch is applied only once
|
|
34
|
+
AT_FORK_ONLY_ONCE = Utils::OnlyOnce.new
|
|
35
|
+
|
|
31
36
|
class << self
|
|
32
37
|
def build_health_metrics(settings, logger, telemetry)
|
|
33
38
|
settings = settings.health_metrics
|
|
@@ -38,7 +43,7 @@ module Datadog
|
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
def build_logger(settings)
|
|
41
|
-
logger = settings.logger.instance || Core::Logger.new($
|
|
46
|
+
logger = settings.logger.instance || Core::Logger.new($stderr)
|
|
42
47
|
logger.level = settings.diagnostics.debug ? ::Logger::DEBUG : settings.logger.level
|
|
43
48
|
|
|
44
49
|
logger
|
|
@@ -80,14 +85,15 @@ module Datadog
|
|
|
80
85
|
Datadog::Core::Crashtracking::Component.build(settings, agent_settings, logger: logger)
|
|
81
86
|
end
|
|
82
87
|
|
|
83
|
-
def build_data_streams(settings, agent_settings, logger)
|
|
88
|
+
def build_data_streams(settings, agent_settings, logger, agent_info)
|
|
84
89
|
return unless settings.data_streams.enabled
|
|
85
90
|
|
|
86
91
|
Datadog::DataStreams::Processor.new(
|
|
87
92
|
interval: settings.data_streams.interval,
|
|
88
93
|
logger: logger,
|
|
89
94
|
settings: settings,
|
|
90
|
-
agent_settings: agent_settings
|
|
95
|
+
agent_settings: agent_settings,
|
|
96
|
+
agent_info: agent_info
|
|
91
97
|
)
|
|
92
98
|
rescue => e
|
|
93
99
|
logger.warn("Failed to initialize Data Streams Monitoring: #{e.class}: #{e}")
|
|
@@ -121,6 +127,17 @@ module Datadog
|
|
|
121
127
|
StableConfig.log_result(@logger)
|
|
122
128
|
Deprecations.log_deprecations_from_all_sources(@logger)
|
|
123
129
|
|
|
130
|
+
# Register fork handling once globally
|
|
131
|
+
self.class::AT_FORK_ONLY_ONCE.run do
|
|
132
|
+
Utils::AtForkMonkeyPatch.apply!
|
|
133
|
+
|
|
134
|
+
# Register callback that calls Components.after_fork
|
|
135
|
+
Utils::AtForkMonkeyPatch.at_fork(:child) do
|
|
136
|
+
# Access via global to avoid capturing 'self'
|
|
137
|
+
Datadog.send(:components, allow_initialization: false)&.after_fork
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
124
141
|
# This agent_settings is intended for use within Core. If you require
|
|
125
142
|
# agent_settings within a product outside of core you should extend
|
|
126
143
|
# the Core resolver from within your product/component's namespace.
|
|
@@ -150,13 +167,21 @@ module Datadog
|
|
|
150
167
|
@open_feature = OpenFeature::Component.build(settings, agent_settings, logger: @logger, telemetry: telemetry)
|
|
151
168
|
@dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
|
|
152
169
|
@error_tracking = Datadog::ErrorTracking::Component.build(settings, @tracer, @logger)
|
|
153
|
-
@data_streams = self.class.build_data_streams(settings, agent_settings, @logger)
|
|
170
|
+
@data_streams = self.class.build_data_streams(settings, agent_settings, @logger, @agent_info)
|
|
154
171
|
@environment_logger_extra[:dynamic_instrumentation_enabled] = !!@dynamic_instrumentation
|
|
155
172
|
|
|
156
173
|
# Configure non-privileged components.
|
|
157
174
|
Datadog::Tracing::Contrib::Component.configure(settings)
|
|
158
175
|
end
|
|
159
176
|
|
|
177
|
+
# Called when a fork is detected
|
|
178
|
+
def after_fork
|
|
179
|
+
telemetry.after_fork
|
|
180
|
+
remote&.after_fork
|
|
181
|
+
crashtracker&.update_on_fork
|
|
182
|
+
ProcessDiscovery.after_fork
|
|
183
|
+
end
|
|
184
|
+
|
|
160
185
|
# Hot-swaps with a new sampler.
|
|
161
186
|
# This operation acquires the Components lock to ensure
|
|
162
187
|
# there is no concurrent modification of the sampler.
|
|
@@ -167,7 +167,8 @@ module Datadog
|
|
|
167
167
|
|
|
168
168
|
def default_value
|
|
169
169
|
if definition.default.instance_of?(Proc)
|
|
170
|
-
|
|
170
|
+
# Steep: https://github.com/soutaro/steep/issues/335
|
|
171
|
+
context_eval(&definition.default) # steep:ignore BlockTypeMismatch
|
|
171
172
|
else
|
|
172
173
|
definition.default_proc || Core::Utils::SafeDup.frozen_or_dup(definition.default)
|
|
173
174
|
end
|
|
@@ -120,7 +120,7 @@ module Datadog
|
|
|
120
120
|
|
|
121
121
|
assert_valid_option!(name)
|
|
122
122
|
definition = self.class.options[name]
|
|
123
|
-
# @type self: Configuration::Options::
|
|
123
|
+
# @type self: Configuration::Options::_Settings
|
|
124
124
|
options[name] = definition.build(self)
|
|
125
125
|
end
|
|
126
126
|
|