datadog 2.14.0 → 2.16.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 +67 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +7 -6
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +1 -4
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.c +69 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.h +7 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +3 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +8 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +25 -32
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +22 -21
- data/ext/libdatadog_api/crashtracker.c +1 -9
- data/ext/libdatadog_api/crashtracker.h +5 -0
- data/ext/libdatadog_api/datadog_ruby_common.c +1 -4
- data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
- data/ext/libdatadog_api/init.c +15 -0
- data/ext/libdatadog_api/library_config.c +122 -0
- data/ext/libdatadog_api/library_config.h +19 -0
- data/ext/libdatadog_api/process_discovery.c +117 -0
- data/ext/libdatadog_api/process_discovery.h +5 -0
- data/lib/datadog/appsec/actions_handler.rb +3 -2
- data/lib/datadog/appsec/assets/waf_rules/README.md +50 -5
- data/lib/datadog/appsec/assets/waf_rules/processors.json +239 -10
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +926 -17
- data/lib/datadog/appsec/autoload.rb +1 -1
- data/lib/datadog/appsec/component.rb +29 -20
- data/lib/datadog/appsec/compressed_json.rb +40 -0
- data/lib/datadog/appsec/configuration/settings.rb +31 -18
- data/lib/datadog/appsec/context.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +10 -12
- data/lib/datadog/appsec/contrib/active_record/integration.rb +2 -2
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +22 -22
- data/lib/datadog/appsec/contrib/devise/data_extractor.rb +2 -3
- data/lib/datadog/appsec/contrib/devise/ext.rb +1 -0
- data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/patcher.rb +3 -5
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +17 -4
- data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +9 -10
- data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +8 -9
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +8 -9
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +22 -32
- data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +16 -16
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +11 -13
- data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/patcher.rb +21 -21
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +10 -11
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +17 -23
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/appsec/event.rb +95 -134
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +5 -2
- data/lib/datadog/appsec/metrics/telemetry.rb +1 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +42 -12
- data/lib/datadog/appsec/processor/rule_loader.rb +26 -28
- data/lib/datadog/appsec/processor/rule_merger.rb +5 -5
- data/lib/datadog/appsec/processor.rb +1 -1
- data/lib/datadog/appsec/remote.rb +16 -11
- data/lib/datadog/appsec/response.rb +6 -6
- data/lib/datadog/appsec/security_engine/runner.rb +1 -1
- data/lib/datadog/appsec/security_event.rb +39 -0
- data/lib/datadog/appsec.rb +1 -1
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
- data/lib/datadog/core/configuration/components.rb +19 -10
- data/lib/datadog/core/configuration/option.rb +61 -25
- data/lib/datadog/core/configuration/settings.rb +10 -0
- data/lib/datadog/core/configuration/stable_config.rb +23 -0
- data/lib/datadog/core/configuration.rb +24 -0
- data/lib/datadog/core/crashtracking/component.rb +1 -9
- data/lib/datadog/core/diagnostics/environment_logger.rb +1 -1
- data/lib/datadog/core/environment/git.rb +1 -0
- data/lib/datadog/core/environment/variable_helpers.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +8 -7
- data/lib/datadog/core/process_discovery.rb +32 -0
- data/lib/datadog/core/remote/client.rb +7 -0
- data/lib/datadog/core/runtime/metrics.rb +1 -1
- data/lib/datadog/core/telemetry/component.rb +60 -50
- data/lib/datadog/core/telemetry/emitter.rb +17 -11
- data/lib/datadog/core/telemetry/event.rb +7 -4
- data/lib/datadog/core/telemetry/http/adapters/net.rb +12 -97
- data/lib/datadog/core/telemetry/metric.rb +5 -5
- data/lib/datadog/core/telemetry/request.rb +4 -4
- data/lib/datadog/core/telemetry/transport/http/api.rb +43 -0
- data/lib/datadog/core/telemetry/transport/http/client.rb +49 -0
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +92 -0
- data/lib/datadog/core/telemetry/transport/http.rb +63 -0
- data/lib/datadog/core/telemetry/transport/telemetry.rb +52 -0
- data/lib/datadog/core/telemetry/worker.rb +45 -0
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/core/workers/async.rb +20 -2
- data/lib/datadog/core/workers/interval_loop.rb +12 -1
- data/lib/datadog/core/workers/runtime_metrics.rb +2 -2
- data/lib/datadog/core.rb +8 -0
- data/lib/datadog/di/boot.rb +34 -0
- data/lib/datadog/di/probe_notification_builder.rb +1 -1
- data/lib/datadog/di/remote.rb +2 -0
- data/lib/datadog/di/transport/http/diagnostics.rb +0 -1
- data/lib/datadog/di/transport/http/input.rb +0 -1
- data/lib/datadog/di/transport/http.rb +0 -6
- data/lib/datadog/di.rb +5 -32
- data/lib/datadog/error_tracking/collector.rb +87 -0
- data/lib/datadog/error_tracking/component.rb +167 -0
- data/lib/datadog/error_tracking/configuration/settings.rb +63 -0
- data/lib/datadog/error_tracking/configuration.rb +11 -0
- data/lib/datadog/error_tracking/ext.rb +18 -0
- data/lib/datadog/error_tracking/extensions.rb +16 -0
- data/lib/datadog/error_tracking/filters.rb +77 -0
- data/lib/datadog/error_tracking.rb +18 -0
- data/lib/datadog/kit/identity.rb +1 -1
- data/lib/datadog/profiling/collectors/info.rb +3 -0
- data/lib/datadog/profiling/encoded_profile.rb +11 -0
- data/lib/datadog/profiling/exporter.rb +3 -4
- data/lib/datadog/profiling/ext.rb +0 -1
- data/lib/datadog/profiling/flush.rb +4 -7
- data/lib/datadog/profiling/http_transport.rb +10 -59
- data/lib/datadog/profiling/stack_recorder.rb +4 -4
- data/lib/datadog/profiling.rb +1 -0
- data/lib/datadog/tracing/analytics.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +8 -0
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +18 -1
- data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +17 -0
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +9 -0
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
- data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
- data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
- data/lib/datadog/tracing/distributed/datadog.rb +2 -2
- data/lib/datadog/tracing/sampling/rate_sampler.rb +2 -1
- data/lib/datadog/tracing/span_event.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +38 -14
- data/lib/datadog/tracing/trace_operation.rb +15 -7
- data/lib/datadog/tracing/tracer.rb +7 -3
- data/lib/datadog/tracing/utils.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +2 -3
- metadata +40 -10
- data/lib/datadog/core/telemetry/http/env.rb +0 -20
- data/lib/datadog/core/telemetry/http/ext.rb +0 -28
- data/lib/datadog/core/telemetry/http/response.rb +0 -70
- data/lib/datadog/core/telemetry/http/transport.rb +0 -90
data/lib/datadog/appsec/event.rb
CHANGED
@@ -1,181 +1,142 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
-
require 'zlib'
|
5
|
-
|
6
4
|
require_relative 'rate_limiter'
|
7
|
-
require_relative '
|
5
|
+
require_relative 'compressed_json'
|
8
6
|
|
9
7
|
module Datadog
|
10
8
|
module AppSec
|
11
9
|
# AppSec event
|
12
10
|
module Event
|
11
|
+
DERIVATIVE_SCHEMA_KEY_PREFIX = '_dd.appsec.s.'
|
12
|
+
DERIVATIVE_SCHEMA_MAX_COMPRESSED_SIZE = 25000
|
13
13
|
ALLOWED_REQUEST_HEADERS = %w[
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
].
|
14
|
+
x-forwarded-for
|
15
|
+
x-client-ip
|
16
|
+
x-real-ip
|
17
|
+
x-forwarded
|
18
|
+
x-cluster-client-ip
|
19
|
+
forwarded-for
|
20
|
+
forwarded
|
21
|
+
via
|
22
|
+
true-client-ip
|
23
|
+
content-length
|
24
|
+
content-type
|
25
|
+
content-encoding
|
26
|
+
content-language
|
27
|
+
host
|
28
|
+
user-agent
|
29
|
+
accept
|
30
|
+
accept-encoding
|
31
|
+
accept-language
|
32
|
+
].freeze
|
33
33
|
|
34
34
|
ALLOWED_RESPONSE_HEADERS = %w[
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
].
|
40
|
-
|
41
|
-
MAX_ENCODED_SCHEMA_SIZE = 25000
|
42
|
-
# For more information about this number
|
43
|
-
# please check https://github.com/DataDog/dd-trace-rb/pull/3177#issuecomment-1747221082
|
44
|
-
MIN_SCHEMA_SIZE_FOR_COMPRESSION = 260
|
45
|
-
|
46
|
-
# Record events for a trace
|
47
|
-
#
|
48
|
-
# This is expected to be called only once per trace for the rate limiter
|
49
|
-
# to properly apply
|
50
|
-
class << self
|
51
|
-
def record(span, *events)
|
52
|
-
# ensure rate limiter is called only when there are events to record
|
53
|
-
return if events.empty? || span.nil?
|
35
|
+
content-length
|
36
|
+
content-type
|
37
|
+
content-encoding
|
38
|
+
content-language
|
39
|
+
].freeze
|
54
40
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
41
|
+
class << self
|
42
|
+
def tag_and_keep!(context, waf_result)
|
43
|
+
# We want to keep the trace in case of security event
|
44
|
+
context.trace&.keep!
|
59
45
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
Datadog.logger.debug { "{ error: 'no trace: cannot record', event_group: #{event_group.inspect}}" }
|
64
|
-
next
|
46
|
+
if context.span
|
47
|
+
if waf_result.actions.key?('block_request') || waf_result.actions.key?('redirect_request')
|
48
|
+
context.span.set_tag('appsec.blocked', 'true')
|
65
49
|
end
|
66
50
|
|
67
|
-
|
68
|
-
trace.set_tag(
|
69
|
-
Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
|
70
|
-
Datadog::Tracing::Sampling::Ext::Decision::ASM
|
71
|
-
)
|
72
|
-
|
73
|
-
# prepare and gather tags to apply
|
74
|
-
service_entry_tags = build_service_entry_tags(event_group)
|
75
|
-
|
76
|
-
# apply tags to service entry span
|
77
|
-
service_entry_tags.each do |key, value|
|
78
|
-
span.set_tag(key, value)
|
79
|
-
end
|
51
|
+
context.span.set_tag('appsec.event', 'true')
|
80
52
|
end
|
53
|
+
|
54
|
+
add_distributed_tags(context.trace)
|
81
55
|
end
|
82
56
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
57
|
+
def record(context, request: nil, response: nil)
|
58
|
+
return if context.events.empty? || context.span.nil?
|
59
|
+
|
60
|
+
Datadog::AppSec::RateLimiter.thread_local.limit do
|
61
|
+
context.events.group_by(&:trace).each do |trace, event_group|
|
62
|
+
unless trace
|
63
|
+
next Datadog.logger.debug do
|
64
|
+
"AppSec: Cannot record event group with #{event_group.count} events because it has no trace"
|
65
|
+
end
|
91
66
|
end
|
92
67
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
68
|
+
if event_group.any? { |event| event.attack? || event.schema? }
|
69
|
+
trace.keep!
|
70
|
+
trace[Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER] = Tracing::Sampling::Ext::Decision::ASM
|
97
71
|
|
98
|
-
|
99
|
-
|
100
|
-
|
72
|
+
context.span['_dd.origin'] = 'appsec'
|
73
|
+
context.span.set_tags(request_tags(request)) if request
|
74
|
+
context.span.set_tags(response_tags(response)) if response
|
101
75
|
end
|
76
|
+
|
77
|
+
context.span.set_tags(waf_tags(event_group))
|
102
78
|
end
|
79
|
+
end
|
80
|
+
end
|
103
81
|
|
104
|
-
|
105
|
-
# accumulate triggers
|
106
|
-
waf_events += waf_result.events
|
82
|
+
private
|
107
83
|
|
108
|
-
|
109
|
-
|
110
|
-
next unless parsed_value
|
84
|
+
def request_tags(request)
|
85
|
+
tags = {}
|
111
86
|
|
112
|
-
|
87
|
+
tags['http.host'] = request.host if request.host
|
88
|
+
tags['http.useragent'] = request.user_agent if request.user_agent
|
89
|
+
tags['network.client.ip'] = request.remote_addr if request.remote_addr
|
113
90
|
|
114
|
-
|
115
|
-
|
116
|
-
else
|
117
|
-
parsed_value
|
118
|
-
end
|
119
|
-
next unless schema_value
|
91
|
+
request.headers.each_with_object(tags) do |(name, value), memo|
|
92
|
+
next unless ALLOWED_REQUEST_HEADERS.include?(name)
|
120
93
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
next
|
126
|
-
end
|
94
|
+
memo["http.request.headers.#{name}"] = value
|
95
|
+
end
|
96
|
+
end
|
127
97
|
|
128
|
-
|
129
|
-
|
98
|
+
def response_tags(response)
|
99
|
+
response.headers.each_with_object({}) do |(name, value), memo|
|
100
|
+
next unless ALLOWED_RESPONSE_HEADERS.include?(name)
|
130
101
|
|
131
|
-
|
102
|
+
memo["http.response.headers.#{name}"] = value
|
132
103
|
end
|
133
|
-
|
134
|
-
appsec_events = json_parse({ triggers: waf_events })
|
135
|
-
entry_tags['_dd.appsec.json'] = appsec_events if appsec_events
|
136
|
-
entry_tags
|
137
104
|
end
|
138
|
-
# rubocop:enable Metrics/MethodLength
|
139
105
|
|
140
|
-
def
|
141
|
-
|
142
|
-
context.trace.keep! if context.trace
|
106
|
+
def waf_tags(security_events)
|
107
|
+
triggers = []
|
143
108
|
|
144
|
-
|
145
|
-
|
146
|
-
context.span.set_tag('appsec.event', 'true')
|
147
|
-
end
|
109
|
+
tags = security_events.each_with_object({}) do |security_event, memo|
|
110
|
+
triggers.concat(security_event.waf_result.events)
|
148
111
|
|
149
|
-
|
150
|
-
|
112
|
+
security_event.waf_result.derivatives.each do |key, value|
|
113
|
+
next memo[key] = value unless key.start_with?(DERIVATIVE_SCHEMA_KEY_PREFIX)
|
151
114
|
|
152
|
-
|
115
|
+
value = CompressedJson.dump(value)
|
116
|
+
next if value.nil?
|
117
|
+
|
118
|
+
if value.size >= DERIVATIVE_SCHEMA_MAX_COMPRESSED_SIZE
|
119
|
+
Datadog.logger.debug { "AppSec: Schema key '#{key}' will not be included into span tags due to it's size" }
|
120
|
+
next
|
121
|
+
end
|
153
122
|
|
154
|
-
|
155
|
-
|
156
|
-
rescue TypeError => e
|
157
|
-
Datadog.logger.debug do
|
158
|
-
"Failed to compress and encode value when populating AppSec::Event. Error: #{e.message}"
|
123
|
+
memo[key] = value
|
124
|
+
end
|
159
125
|
end
|
160
|
-
|
126
|
+
|
127
|
+
tags['_dd.appsec.json'] = json_parse({triggers: triggers}) unless triggers.empty?
|
128
|
+
tags
|
161
129
|
end
|
162
130
|
|
131
|
+
# NOTE: Handling of Encoding::UndefinedConversionError is added as a quick fix to
|
132
|
+
# the issue between Ruby encoded strings and libddwaf produced events and now
|
133
|
+
# is under investigation.
|
163
134
|
def json_parse(value)
|
164
135
|
JSON.dump(value)
|
165
|
-
rescue ArgumentError => e
|
166
|
-
|
167
|
-
"Failed to parse value to JSON when populating AppSec::Event. Error: #{e.message}"
|
168
|
-
end
|
169
|
-
nil
|
170
|
-
end
|
136
|
+
rescue ArgumentError, Encoding::UndefinedConversionError, JSON::JSONError => e
|
137
|
+
AppSec.telemetry.report(e, description: 'AppSec: Failed to convert value into JSON')
|
171
138
|
|
172
|
-
|
173
|
-
sio = StringIO.new
|
174
|
-
# For an in depth comparison of Zlib options check https://github.com/DataDog/dd-trace-rb/pull/3177#issuecomment-1747215473
|
175
|
-
gz = Zlib::GzipWriter.new(sio, Zlib::BEST_SPEED, Zlib::DEFAULT_STRATEGY)
|
176
|
-
gz.write(value)
|
177
|
-
gz.close
|
178
|
-
sio.string
|
139
|
+
nil
|
179
140
|
end
|
180
141
|
|
181
142
|
# Propagate to downstream services the information that the current distributed trace is
|
@@ -8,14 +8,17 @@ module Datadog
|
|
8
8
|
class Argument; end # rubocop:disable Lint/EmptyClass
|
9
9
|
|
10
10
|
# Gateway User argument
|
11
|
+
# NOTE: This class is a subject of elimination and will be removed when
|
12
|
+
# the event system is refactored.
|
11
13
|
class User < Argument
|
12
|
-
attr_reader :id, :login
|
14
|
+
attr_reader :id, :login, :session_id
|
13
15
|
|
14
|
-
def initialize(id, login)
|
16
|
+
def initialize(id, login = nil, session_id = nil)
|
15
17
|
super()
|
16
18
|
|
17
19
|
@id = id
|
18
20
|
@login = login
|
21
|
+
@session_id = session_id
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
@@ -10,7 +10,7 @@ module Datadog
|
|
10
10
|
def report_rasp(type, result)
|
11
11
|
return if result.is_a?(SecurityEngine::Result::Error)
|
12
12
|
|
13
|
-
tags = {
|
13
|
+
tags = {rule_type: type, waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING}
|
14
14
|
namespace = Ext::TELEMETRY_METRICS_NAMESPACE
|
15
15
|
|
16
16
|
AppSec.telemetry.inc(namespace, 'rasp.rule.eval', 1, tags: tags)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../../event'
|
4
|
+
require_relative '../../security_event'
|
3
5
|
require_relative '../../instrumentation/gateway'
|
4
6
|
|
5
7
|
module Datadog
|
@@ -8,18 +10,24 @@ module Datadog
|
|
8
10
|
module Gateway
|
9
11
|
# Watcher for Apssec internal events
|
10
12
|
module Watcher
|
13
|
+
ARBITRARY_VALUE = 'invalid'
|
14
|
+
EVENT_LOGIN_SUCCESS = 'users.login.success'
|
15
|
+
EVENT_LOGIN_FAILURE = 'users.login.failure'
|
16
|
+
WATCHED_LOGIN_EVENTS = [EVENT_LOGIN_SUCCESS, EVENT_LOGIN_FAILURE].freeze
|
17
|
+
|
11
18
|
class << self
|
12
19
|
def watch
|
13
20
|
gateway = Instrumentation.gateway
|
14
21
|
|
15
22
|
watch_user_id(gateway)
|
23
|
+
watch_user_login(gateway)
|
16
24
|
end
|
17
25
|
|
18
26
|
def watch_user_id(gateway = Instrumentation.gateway)
|
19
27
|
gateway.watch('identity.set_user', :appsec) do |stack, user|
|
20
|
-
context =
|
28
|
+
context = AppSec.active_context
|
21
29
|
|
22
|
-
if user.id.nil? && user.login.nil?
|
30
|
+
if user.id.nil? && user.login.nil? && user.session_id.nil?
|
23
31
|
Datadog.logger.debug { 'AppSec: skipping WAF check because no user information was provided' }
|
24
32
|
next stack.call(user)
|
25
33
|
end
|
@@ -27,24 +35,46 @@ module Datadog
|
|
27
35
|
persistent_data = {}
|
28
36
|
persistent_data['usr.id'] = user.id if user.id
|
29
37
|
persistent_data['usr.login'] = user.login if user.login
|
38
|
+
persistent_data['usr.session_id'] = user.session_id if user.session_id
|
30
39
|
|
31
40
|
result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
|
32
41
|
|
42
|
+
if result.match? || result.derivatives.any?
|
43
|
+
context.events.push(
|
44
|
+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
33
48
|
if result.match?
|
34
|
-
|
49
|
+
AppSec::Event.tag_and_keep!(context, result)
|
50
|
+
AppSec::ActionsHandler.handle(result.actions)
|
51
|
+
end
|
52
|
+
|
53
|
+
stack.call(user)
|
54
|
+
end
|
55
|
+
end
|
35
56
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
span: context.span,
|
40
|
-
user: user,
|
41
|
-
actions: result.actions
|
42
|
-
}
|
57
|
+
def watch_user_login(gateway = Instrumentation.gateway)
|
58
|
+
gateway.watch('appsec.events.user_lifecycle', :appsec) do |stack, kind|
|
59
|
+
context = AppSec.active_context
|
43
60
|
|
44
|
-
|
61
|
+
next stack.call(kind) unless WATCHED_LOGIN_EVENTS.include?(kind)
|
62
|
+
|
63
|
+
persistent_data = {"server.business_logic.#{kind}" => ARBITRARY_VALUE}
|
64
|
+
result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
|
65
|
+
|
66
|
+
if result.match? || result.derivatives.any?
|
67
|
+
context.events.push(
|
68
|
+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
69
|
+
)
|
45
70
|
end
|
46
71
|
|
47
|
-
|
72
|
+
if result.match?
|
73
|
+
AppSec::Event.tag_and_keep!(context, result)
|
74
|
+
AppSec::ActionsHandler.handle(result.actions)
|
75
|
+
end
|
76
|
+
|
77
|
+
stack.call(kind)
|
48
78
|
end
|
49
79
|
end
|
50
80
|
end
|
@@ -10,35 +10,33 @@ module Datadog
|
|
10
10
|
module RuleLoader
|
11
11
|
class << self
|
12
12
|
def load_rules(ruleset:, telemetry:)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
13
|
+
case ruleset
|
14
|
+
when :recommended, :strict
|
15
|
+
JSON.parse(Datadog::AppSec::Assets.waf_rules(ruleset))
|
16
|
+
when :risky
|
17
|
+
Datadog.logger.warn(
|
18
|
+
'The :risky Application Security Management ruleset has been deprecated and no longer available.' \
|
19
|
+
'The `:recommended` ruleset will be used instead.' \
|
20
|
+
'Please remove the `appsec.ruleset = :risky` setting from your Datadog.configure block.'
|
21
|
+
)
|
22
|
+
JSON.parse(Datadog::AppSec::Assets.waf_rules(:recommended))
|
23
|
+
when String
|
24
|
+
JSON.parse(File.read(File.expand_path(ruleset)))
|
25
|
+
when File, StringIO
|
26
|
+
JSON.parse(ruleset.read || '').tap { ruleset.rewind }
|
27
|
+
when Hash
|
28
|
+
ruleset
|
29
|
+
else
|
30
|
+
raise ArgumentError, "unsupported value for ruleset setting: #{ruleset.inspect}"
|
31
|
+
end
|
32
|
+
rescue => e
|
33
|
+
Datadog.logger.error do
|
34
|
+
"libddwaf ruleset failed to load, ruleset: #{ruleset.inspect} error: #{e.inspect}"
|
35
|
+
end
|
37
36
|
|
38
|
-
|
37
|
+
telemetry.report(e, description: 'libddwaf ruleset failed to load')
|
39
38
|
|
40
|
-
|
41
|
-
end
|
39
|
+
nil
|
42
40
|
end
|
43
41
|
|
44
42
|
def load_data(ip_denylist: [], user_id_denylist: [])
|
@@ -62,7 +60,7 @@ module Datadog
|
|
62
60
|
{
|
63
61
|
'id' => id,
|
64
62
|
'type' => 'data_with_expiration',
|
65
|
-
'data' => denylist.map { |v| {
|
63
|
+
'data' => denylist.map { |v| {'value' => v.to_s, 'expiration' => 2**63} }
|
66
64
|
}
|
67
65
|
end
|
68
66
|
|
@@ -11,8 +11,8 @@ module Datadog
|
|
11
11
|
# RuleVersionMismatchError
|
12
12
|
class RuleVersionMismatchError < StandardError
|
13
13
|
def initialize(version1, version2)
|
14
|
-
msg = 'Merging rule files with different version could lead to unkown behaviour. '\
|
15
|
-
"We have receieve two rule files with versions: #{version1}, #{version2}. "\
|
14
|
+
msg = 'Merging rule files with different version could lead to unkown behaviour. ' \
|
15
|
+
"We have receieve two rule files with versions: #{version1}, #{version2}. " \
|
16
16
|
'Please validate the configuration is correct and try again.'
|
17
17
|
super(msg)
|
18
18
|
end
|
@@ -27,7 +27,7 @@ module Datadog
|
|
27
27
|
)
|
28
28
|
processors ||= begin
|
29
29
|
default_waf_processors
|
30
|
-
rescue
|
30
|
+
rescue => e
|
31
31
|
Datadog.logger.error("libddwaf rulemerger failed to parse default waf processors. Error: #{e.inspect}")
|
32
32
|
telemetry.report(
|
33
33
|
e,
|
@@ -38,7 +38,7 @@ module Datadog
|
|
38
38
|
|
39
39
|
scanners ||= begin
|
40
40
|
default_waf_scanners
|
41
|
-
rescue
|
41
|
+
rescue => e
|
42
42
|
Datadog.logger.error("libddwaf rulemerger failed to parse default waf scanners. Error: #{e.inspect}")
|
43
43
|
telemetry.report(
|
44
44
|
e,
|
@@ -146,7 +146,7 @@ module Datadog
|
|
146
146
|
end
|
147
147
|
|
148
148
|
result.each_with_object([]) do |entry, acc|
|
149
|
-
value = {
|
149
|
+
value = {'value' => entry[0]}
|
150
150
|
value['expiration'] = entry[1] if entry[1]
|
151
151
|
|
152
152
|
acc << value
|
@@ -9,20 +9,23 @@ module Datadog
|
|
9
9
|
# Remote
|
10
10
|
module Remote
|
11
11
|
class ReadError < StandardError; end
|
12
|
+
|
12
13
|
class NoRulesError < StandardError; end
|
13
14
|
|
14
15
|
class << self
|
15
|
-
CAP_ASM_RESERVED_1
|
16
|
-
CAP_ASM_ACTIVATION
|
17
|
-
CAP_ASM_IP_BLOCKING
|
18
|
-
CAP_ASM_DD_RULES
|
19
|
-
CAP_ASM_EXCLUSIONS
|
20
|
-
CAP_ASM_REQUEST_BLOCKING
|
21
|
-
CAP_ASM_RESPONSE_BLOCKING
|
22
|
-
CAP_ASM_USER_BLOCKING
|
23
|
-
CAP_ASM_CUSTOM_RULES
|
24
|
-
CAP_ASM_CUSTOM_BLOCKING_RESPONSE
|
25
|
-
CAP_ASM_TRUSTED_IPS
|
16
|
+
CAP_ASM_RESERVED_1 = 1 << 0 # RESERVED
|
17
|
+
CAP_ASM_ACTIVATION = 1 << 1 # Remote activation via ASM_FEATURES product
|
18
|
+
CAP_ASM_IP_BLOCKING = 1 << 2 # accept IP blocking data from ASM_DATA product
|
19
|
+
CAP_ASM_DD_RULES = 1 << 3 # read ASM rules from ASM_DD product
|
20
|
+
CAP_ASM_EXCLUSIONS = 1 << 4 # exclusion filters (passlist) via ASM product
|
21
|
+
CAP_ASM_REQUEST_BLOCKING = 1 << 5 # can block on request info
|
22
|
+
CAP_ASM_RESPONSE_BLOCKING = 1 << 6 # can block on response info
|
23
|
+
CAP_ASM_USER_BLOCKING = 1 << 7 # accept user blocking data from ASM_DATA product
|
24
|
+
CAP_ASM_CUSTOM_RULES = 1 << 8 # accept custom rules
|
25
|
+
CAP_ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9 # supports custom http code or redirect sa blocking response
|
26
|
+
CAP_ASM_TRUSTED_IPS = 1 << 10 # supports trusted ip
|
27
|
+
CAP_ASM_RASP_SSRF = 1 << 23 # support for server-side request forgery exploit prevention rules
|
28
|
+
CAP_ASM_RASP_SQLI = 1 << 21 # support for SQL injection exploit prevention rules
|
26
29
|
|
27
30
|
# TODO: we need to dynamically add CAP_ASM_ACTIVATION once we support it
|
28
31
|
ASM_CAPABILITIES = [
|
@@ -35,6 +38,8 @@ module Datadog
|
|
35
38
|
CAP_ASM_CUSTOM_RULES,
|
36
39
|
CAP_ASM_CUSTOM_BLOCKING_RESPONSE,
|
37
40
|
CAP_ASM_TRUSTED_IPS,
|
41
|
+
CAP_ASM_RASP_SSRF,
|
42
|
+
CAP_ASM_RASP_SQLI,
|
38
43
|
].freeze
|
39
44
|
|
40
45
|
ASM_PRODUCTS = [
|
@@ -30,13 +30,13 @@ module Datadog
|
|
30
30
|
|
31
31
|
def block_response(interrupt_params, http_accept_header)
|
32
32
|
content_type = case interrupt_params['type']
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
when nil, 'auto' then content_type(http_accept_header)
|
34
|
+
else FORMAT_TO_CONTENT_TYPE.fetch(interrupt_params['type'], DEFAULT_CONTENT_TYPE)
|
35
|
+
end
|
36
36
|
|
37
37
|
Response.new(
|
38
38
|
status: interrupt_params['status_code']&.to_i || 403,
|
39
|
-
headers: {
|
39
|
+
headers: {'Content-Type' => content_type},
|
40
40
|
body: [content(content_type)],
|
41
41
|
)
|
42
42
|
end
|
@@ -45,8 +45,8 @@ module Datadog
|
|
45
45
|
status_code = interrupt_params['status_code'].to_i
|
46
46
|
|
47
47
|
Response.new(
|
48
|
-
status: (status_code >= 300 && status_code < 400 ? status_code : 303),
|
49
|
-
headers: {
|
48
|
+
status: ((status_code >= 300 && status_code < 400) ? status_code : 303),
|
49
|
+
headers: {'Location' => interrupt_params.fetch('location')},
|
50
50
|
body: [],
|
51
51
|
)
|
52
52
|
end
|
@@ -42,7 +42,7 @@ module Datadog
|
|
42
42
|
return Result::Error.new(duration_ext_ns: stop_ns - start_ns)
|
43
43
|
end
|
44
44
|
|
45
|
-
klass = result.status == :match ? Result::Match : Result::Ok
|
45
|
+
klass = (result.status == :match) ? Result::Match : Result::Ok
|
46
46
|
klass.new(
|
47
47
|
events: result.events,
|
48
48
|
actions: result.actions,
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
# A class that represents a security event of any kind. It could be an event
|
6
|
+
# representing an attack or fingerprinting results as derivatives or an API
|
7
|
+
# security check with extracted schema.
|
8
|
+
class SecurityEvent
|
9
|
+
SCHEMA_KEY_PREFIX = '_dd.appsec.s.'
|
10
|
+
FINGERPRINT_KEY_PREFIX = '_dd.appsec.fp.'
|
11
|
+
|
12
|
+
attr_reader :waf_result, :trace, :span
|
13
|
+
|
14
|
+
def initialize(waf_result, trace:, span:)
|
15
|
+
@waf_result = waf_result
|
16
|
+
@trace = trace
|
17
|
+
@span = span
|
18
|
+
end
|
19
|
+
|
20
|
+
def attack?
|
21
|
+
return @is_attack if defined?(@is_attack)
|
22
|
+
|
23
|
+
@is_attack = @waf_result.is_a?(SecurityEngine::Result::Match)
|
24
|
+
end
|
25
|
+
|
26
|
+
def schema?
|
27
|
+
return @has_schema if defined?(@has_schema)
|
28
|
+
|
29
|
+
@has_schema = @waf_result.derivatives.any? { |name, _| name.start_with?(SCHEMA_KEY_PREFIX) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def fingerprint?
|
33
|
+
return @has_fingerprint if defined?(@has_fingerprint)
|
34
|
+
|
35
|
+
@has_fingerprint = @waf_result.derivatives.any? { |name, _| name.start_with?(FINGERPRINT_KEY_PREFIX) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/datadog/appsec.rb
CHANGED
@@ -44,7 +44,7 @@ module Datadog
|
|
44
44
|
appsec_component.reconfigure_lock(&block)
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
47
|
+
def perform_api_security_check?
|
48
48
|
Datadog.configuration.appsec.api_security.enabled &&
|
49
49
|
Datadog.configuration.appsec.api_security.sample_rate.sample?
|
50
50
|
end
|