datadog 2.3.0 → 2.4.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 +37 -1
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
- data/ext/datadog_profiling_loader/extconf.rb +10 -22
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +148 -30
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +580 -29
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +9 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
- data/ext/datadog_profiling_native_extension/extconf.rb +38 -21
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +20 -6
- data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +52 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/libdatadog_api/crashtracker.c +20 -18
- data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
- data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
- data/lib/datadog/appsec/component.rb +29 -8
- data/lib/datadog/appsec/configuration/settings.rb +2 -2
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
- data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
- data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +18 -15
- data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
- data/lib/datadog/appsec/event.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
- data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
- data/lib/datadog/appsec/processor.rb +36 -37
- data/lib/datadog/appsec/rate_limiter.rb +25 -40
- data/lib/datadog/appsec/remote.rb +7 -3
- data/lib/datadog/appsec.rb +2 -2
- data/lib/datadog/core/configuration/components.rb +4 -3
- data/lib/datadog/core/configuration/settings.rb +84 -5
- data/lib/datadog/core/crashtracking/component.rb +1 -1
- data/lib/datadog/core/environment/execution.rb +5 -5
- data/lib/datadog/core/metrics/client.rb +7 -0
- data/lib/datadog/core/rate_limiter.rb +183 -0
- data/lib/datadog/core/remote/client/capabilities.rb +4 -3
- data/lib/datadog/core/remote/component.rb +4 -2
- data/lib/datadog/core/remote/negotiation.rb +4 -4
- data/lib/datadog/core/remote/tie.rb +2 -0
- data/lib/datadog/core/runtime/metrics.rb +1 -1
- data/lib/datadog/core/telemetry/component.rb +2 -0
- data/lib/datadog/core/telemetry/event.rb +12 -7
- data/lib/datadog/core/telemetry/logger.rb +51 -0
- data/lib/datadog/core/telemetry/logging.rb +50 -14
- data/lib/datadog/core/telemetry/request.rb +13 -1
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/di/code_tracker.rb +168 -0
- data/lib/datadog/di/configuration/settings.rb +163 -0
- data/lib/datadog/di/configuration.rb +11 -0
- data/lib/datadog/di/error.rb +31 -0
- data/lib/datadog/di/extensions.rb +16 -0
- data/lib/datadog/di/probe.rb +133 -0
- data/lib/datadog/di/probe_builder.rb +41 -0
- data/lib/datadog/di/redactor.rb +188 -0
- data/lib/datadog/di/serializer.rb +193 -0
- data/lib/datadog/di.rb +14 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
- data/lib/datadog/profiling/collectors/info.rb +12 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +26 -0
- data/lib/datadog/profiling/component.rb +20 -4
- data/lib/datadog/profiling/http_transport.rb +6 -1
- data/lib/datadog/profiling/scheduler.rb +2 -0
- data/lib/datadog/profiling/stack_recorder.rb +3 -0
- data/lib/datadog/single_step_instrument.rb +12 -0
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +9 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +19 -0
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
- data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +18 -15
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -5
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
- data/lib/datadog/tracing/contrib/patcher.rb +2 -1
- data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
- data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
- data/lib/datadog/tracing/distributed/propagation.rb +7 -0
- data/lib/datadog/tracing/metadata/ext.rb +2 -0
- data/lib/datadog/tracing/remote.rb +5 -2
- data/lib/datadog/tracing/sampling/matcher.rb +6 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/rule.rb +2 -0
- data/lib/datadog/tracing/sampling/rule_sampler.rb +9 -5
- data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
- data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
- data/lib/datadog/tracing/trace_operation.rb +26 -2
- data/lib/datadog/tracing/tracer.rb +14 -12
- data/lib/datadog/tracing/transport/http/client.rb +1 -0
- data/lib/datadog/tracing/transport/io/client.rb +1 -0
- data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
- data/lib/datadog/tracing/workers.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +25 -8
- data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -10,10 +10,11 @@ module Datadog
|
|
10
10
|
# Core-pluggable component for AppSec
|
11
11
|
class Component
|
12
12
|
class << self
|
13
|
-
def build_appsec_component(settings)
|
14
|
-
return
|
13
|
+
def build_appsec_component(settings, telemetry:)
|
14
|
+
return if !settings.respond_to?(:appsec) || !settings.appsec.enabled
|
15
|
+
return if incompatible_ffi_version?
|
15
16
|
|
16
|
-
processor = create_processor(settings)
|
17
|
+
processor = create_processor(settings, telemetry)
|
17
18
|
|
18
19
|
# We want to always instrument user events when AppSec is enabled.
|
19
20
|
# There could be cases in which users use the DD_APPSEC_ENABLED Env variable to
|
@@ -28,8 +29,27 @@ module Datadog
|
|
28
29
|
|
29
30
|
private
|
30
31
|
|
31
|
-
def
|
32
|
-
|
32
|
+
def incompatible_ffi_version?
|
33
|
+
ffi_version = Gem.loaded_specs['ffi'] && Gem.loaded_specs['ffi'].version
|
34
|
+
return true unless ffi_version
|
35
|
+
|
36
|
+
return false unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3') &&
|
37
|
+
ffi_version < Gem::Version.new('1.16.0')
|
38
|
+
|
39
|
+
Datadog.logger.warn(
|
40
|
+
'AppSec is not supported in Ruby versions above 3.3.0 when using `ffi` versions older than 1.16.0, ' \
|
41
|
+
'and will be forcibly disabled due to a memory leak in `ffi`. ' \
|
42
|
+
'Please upgrade your `ffi` version to 1.16.0 or higher.'
|
43
|
+
)
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_processor(settings, telemetry)
|
49
|
+
rules = AppSec::Processor::RuleLoader.load_rules(
|
50
|
+
telemetry: telemetry,
|
51
|
+
ruleset: settings.appsec.ruleset
|
52
|
+
)
|
33
53
|
return nil unless rules
|
34
54
|
|
35
55
|
actions = rules['actions']
|
@@ -47,9 +67,10 @@ module Datadog
|
|
47
67
|
rules: [rules],
|
48
68
|
data: data,
|
49
69
|
exclusions: exclusions,
|
70
|
+
telemetry: telemetry
|
50
71
|
)
|
51
72
|
|
52
|
-
processor = Processor.new(ruleset: ruleset)
|
73
|
+
processor = Processor.new(ruleset: ruleset, telemetry: telemetry)
|
53
74
|
return nil unless processor.ready?
|
54
75
|
|
55
76
|
processor
|
@@ -63,11 +84,11 @@ module Datadog
|
|
63
84
|
@mutex = Mutex.new
|
64
85
|
end
|
65
86
|
|
66
|
-
def reconfigure(ruleset:, actions:)
|
87
|
+
def reconfigure(ruleset:, actions:, telemetry:)
|
67
88
|
@mutex.synchronize do
|
68
89
|
AppSec::Processor::Actions.merge(actions)
|
69
90
|
|
70
|
-
new = Processor.new(ruleset: ruleset)
|
91
|
+
new = Processor.new(ruleset: ruleset, telemetry: telemetry)
|
71
92
|
|
72
93
|
if new && new.ready?
|
73
94
|
old = @processor
|
@@ -9,8 +9,8 @@ module Datadog
|
|
9
9
|
# Settings
|
10
10
|
module Settings
|
11
11
|
# rubocop:disable Layout/LineLength
|
12
|
-
DEFAULT_OBFUSCATOR_KEY_REGEX = '(?i)(?:
|
13
|
-
DEFAULT_OBFUSCATOR_VALUE_REGEX = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret
|
12
|
+
DEFAULT_OBFUSCATOR_KEY_REGEX = '(?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt'
|
13
|
+
DEFAULT_OBFUSCATOR_VALUE_REGEX = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\.net(?:[_-]|-)sessionid|sid|jwt)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}'
|
14
14
|
# rubocop:enable Layout/LineLength
|
15
15
|
APPSEC_VALID_TRACK_USER_EVENTS_MODE = [
|
16
16
|
'safe',
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module Contrib
|
6
|
+
module Devise
|
7
|
+
module Patcher
|
8
|
+
# To avoid tracking new sessions that are created by
|
9
|
+
# Rememberable strategy as Login Success events.
|
10
|
+
module RememberablePatch
|
11
|
+
def validate(*args)
|
12
|
+
@_datadog_skip_track_login_event = true
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative '../patcher'
|
4
4
|
require_relative 'patcher/authenticatable_patch'
|
5
|
+
require_relative 'patcher/rememberable_patch'
|
5
6
|
require_relative 'patcher/registration_controller_patch'
|
6
7
|
|
7
8
|
module Datadog
|
@@ -23,16 +24,25 @@ module Datadog
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def patch
|
26
|
-
|
27
|
+
patch_authenticatable_strategy
|
28
|
+
patch_rememberable_strategy
|
27
29
|
patch_registration_controller
|
28
30
|
|
29
31
|
Patcher.instance_variable_set(:@patched, true)
|
30
32
|
end
|
31
33
|
|
32
|
-
def
|
34
|
+
def patch_authenticatable_strategy
|
33
35
|
::Devise::Strategies::Authenticatable.prepend(AuthenticatablePatch)
|
34
36
|
end
|
35
37
|
|
38
|
+
def patch_rememberable_strategy
|
39
|
+
return unless ::Devise::STRATEGIES.include?(:rememberable)
|
40
|
+
|
41
|
+
# Rememberable strategy is required in autoloaded Rememberable model
|
42
|
+
::Devise::Models::Rememberable # rubocop:disable Lint/Void
|
43
|
+
::Devise::Strategies::Rememberable.prepend(RememberablePatch)
|
44
|
+
end
|
45
|
+
|
36
46
|
def patch_registration_controller
|
37
47
|
::ActiveSupport.on_load(:after_initialize) do
|
38
48
|
::Devise::RegistrationsController.prepend(RegistrationControllerPatch)
|
@@ -28,20 +28,6 @@ module Datadog
|
|
28
28
|
|
29
29
|
multiplex_return
|
30
30
|
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def active_trace
|
35
|
-
return unless defined?(Datadog::Tracing)
|
36
|
-
|
37
|
-
Datadog::Tracing.active_trace
|
38
|
-
end
|
39
|
-
|
40
|
-
def active_span
|
41
|
-
return unless defined?(Datadog::Tracing)
|
42
|
-
|
43
|
-
Datadog::Tracing.active_span
|
44
|
-
end
|
45
31
|
end
|
46
32
|
end
|
47
33
|
end
|
@@ -19,7 +19,7 @@ module Datadog
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def arguments
|
22
|
-
@arguments ||=
|
22
|
+
@arguments ||= build_arguments_hash
|
23
23
|
end
|
24
24
|
|
25
25
|
def queries
|
@@ -28,41 +28,77 @@ module Datadog
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
31
|
+
# This method builds an array of argument hashes for each field with arguments in the query.
|
32
|
+
#
|
33
|
+
# For example, given the following query:
|
34
|
+
# query ($postSlug: ID = "my-first-post", $withComments: Boolean!) {
|
35
|
+
# firstPost: post(slug: $postSlug) {
|
36
|
+
# title
|
37
|
+
# comments @include(if: $withComments) {
|
38
|
+
# author { name }
|
39
|
+
# content
|
40
|
+
# }
|
41
|
+
# }
|
42
|
+
# }
|
43
|
+
#
|
44
|
+
# The result would be:
|
45
|
+
# {"post"=>[{"slug"=>"my-first-post"}], "comments"=>[{"include"=>{"if"=>true}}]}
|
46
|
+
#
|
47
|
+
# Note that the `comments` "include" directive is included in the arguments list
|
48
|
+
def build_arguments_hash
|
49
|
+
queries.each_with_object({}) do |query, args_hash|
|
50
|
+
next unless query.selected_operation
|
51
|
+
|
52
|
+
arguments_from_selections(query.selected_operation.selections, query.variables, args_hash)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def arguments_from_selections(selections, query_variables, args_hash)
|
57
|
+
selections.each do |selection|
|
58
|
+
# rubocop:disable Style/ClassEqualityComparison
|
59
|
+
next unless selection.class.name == Integration::AST_NODE_CLASS_NAMES[:field]
|
60
|
+
# rubocop:enable Style/ClassEqualityComparison
|
61
|
+
|
62
|
+
selection_name = selection.alias || selection.name
|
63
|
+
|
64
|
+
if !selection.arguments.empty? || !selection.directives.empty?
|
65
|
+
args_hash[selection_name] ||= []
|
66
|
+
args_hash[selection_name] <<
|
67
|
+
arguments_hash(selection.arguments, query_variables).merge!(
|
68
|
+
arguments_from_directives(selection.directives, query_variables)
|
69
|
+
)
|
46
70
|
end
|
47
|
-
next if resolver_args.empty? && resolver_dirs.empty?
|
48
71
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
72
|
+
arguments_from_selections(selection.selections, query_variables, args_hash)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def arguments_from_directives(directives, query_variables)
|
77
|
+
directives.each_with_object({}) do |directive, args_hash|
|
78
|
+
# rubocop:disable Style/ClassEqualityComparison
|
79
|
+
next unless directive.class.name == Integration::AST_NODE_CLASS_NAMES[:directive]
|
80
|
+
# rubocop:enable Style/ClassEqualityComparison
|
81
|
+
|
82
|
+
args_hash[directive.name] = arguments_hash(directive.arguments, query_variables)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def arguments_hash(arguments, query_variables)
|
87
|
+
arguments.each_with_object({}) do |argument, args_hash|
|
88
|
+
args_hash[argument.name] = argument_value(argument, query_variables)
|
53
89
|
end
|
54
|
-
args
|
55
90
|
end
|
56
91
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
92
|
+
def argument_value(argument, query_variables)
|
93
|
+
case argument.value.class.name
|
94
|
+
when Integration::AST_NODE_CLASS_NAMES[:variable_identifier]
|
95
|
+
# we need to pass query.variables here instead of query.provided_variables,
|
96
|
+
# since #provided_variables don't know anything about variable default value
|
97
|
+
query_variables[argument.value.name]
|
98
|
+
when Integration::AST_NODE_CLASS_NAMES[:input_object]
|
99
|
+
arguments_hash(argument.value.arguments, query_variables)
|
100
|
+
else
|
101
|
+
argument.value
|
66
102
|
end
|
67
103
|
end
|
68
104
|
end
|
@@ -24,27 +24,30 @@ module Datadog
|
|
24
24
|
gateway.watch('graphql.multiplex', :appsec) do |stack, gateway_multiplex|
|
25
25
|
block = false
|
26
26
|
event = nil
|
27
|
+
|
27
28
|
scope = AppSec::Scope.active_scope
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
if scope
|
31
|
+
AppSec::Reactive::Operation.new('graphql.multiplex') do |op|
|
32
|
+
GraphQL::Reactive::Multiplex.subscribe(op, scope.processor_context) do |result|
|
33
|
+
event = {
|
34
|
+
waf_result: result,
|
35
|
+
trace: scope.trace,
|
36
|
+
span: scope.service_entry_span,
|
37
|
+
multiplex: gateway_multiplex,
|
38
|
+
actions: result.actions
|
39
|
+
}
|
40
|
+
|
41
|
+
if scope.service_entry_span
|
42
|
+
scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
|
43
|
+
scope.service_entry_span.set_tag('appsec.event', 'true')
|
44
|
+
end
|
38
45
|
|
39
|
-
|
40
|
-
scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
|
41
|
-
scope.service_entry_span.set_tag('appsec.event', 'true')
|
46
|
+
scope.processor_context.events << event
|
42
47
|
end
|
43
48
|
|
44
|
-
|
49
|
+
block = GraphQL::Reactive::Multiplex.publish(op, gateway_multiplex)
|
45
50
|
end
|
46
|
-
|
47
|
-
block = GraphQL::Reactive::Multiplex.publish(op, gateway_multiplex)
|
48
51
|
end
|
49
52
|
|
50
53
|
next [nil, [[:block, event]]] if block
|
@@ -13,6 +13,13 @@ module Datadog
|
|
13
13
|
|
14
14
|
MINIMUM_VERSION = Gem::Version.new('2.0.19')
|
15
15
|
|
16
|
+
AST_NODE_CLASS_NAMES = {
|
17
|
+
field: 'GraphQL::Language::Nodes::Field',
|
18
|
+
directive: 'GraphQL::Language::Nodes::Directive',
|
19
|
+
variable_identifier: 'GraphQL::Language::Nodes::VariableIdentifier',
|
20
|
+
input_object: 'GraphQL::Language::Nodes::InputObject',
|
21
|
+
}.freeze
|
22
|
+
|
16
23
|
register_as :graphql, auto_patch: false
|
17
24
|
|
18
25
|
def self.version
|
@@ -24,13 +31,19 @@ module Datadog
|
|
24
31
|
end
|
25
32
|
|
26
33
|
def self.compatible?
|
27
|
-
super && version >= MINIMUM_VERSION
|
34
|
+
super && version >= MINIMUM_VERSION && ast_node_classes_defined?
|
28
35
|
end
|
29
36
|
|
30
37
|
def self.auto_instrument?
|
31
38
|
true
|
32
39
|
end
|
33
40
|
|
41
|
+
def self.ast_node_classes_defined?
|
42
|
+
AST_NODE_CLASS_NAMES.all? do |_, class_name|
|
43
|
+
Object.const_defined?(class_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
34
47
|
def patcher
|
35
48
|
Patcher
|
36
49
|
end
|
@@ -45,14 +45,11 @@ module Datadog
|
|
45
45
|
end
|
46
46
|
|
47
47
|
result['content-type'] = request.content_type if request.content_type
|
48
|
-
|
48
|
+
# Since Rack 3.1, content-length is nil if the body is empty, but we still want to send it to the WAF.
|
49
|
+
result['content-length'] = request.content_length || '0'
|
49
50
|
result
|
50
51
|
end
|
51
52
|
|
52
|
-
def body
|
53
|
-
request.body.read.tap { request.body.rewind }
|
54
|
-
end
|
55
|
-
|
56
53
|
def url
|
57
54
|
request.url
|
58
55
|
end
|
data/lib/datadog/appsec/event.rb
CHANGED
@@ -52,7 +52,7 @@ module Datadog
|
|
52
52
|
# ensure rate limiter is called only when there are events to record
|
53
53
|
return if events.empty? || span.nil?
|
54
54
|
|
55
|
-
Datadog::AppSec::RateLimiter.limit
|
55
|
+
Datadog::AppSec::RateLimiter.thread_local.limit do
|
56
56
|
record_via_span(span, *events)
|
57
57
|
end
|
58
58
|
end
|
@@ -9,7 +9,7 @@ module Datadog
|
|
9
9
|
# that load appsec rules and data from settings
|
10
10
|
module RuleLoader
|
11
11
|
class << self
|
12
|
-
def load_rules(ruleset:)
|
12
|
+
def load_rules(ruleset:, telemetry:)
|
13
13
|
begin
|
14
14
|
case ruleset
|
15
15
|
when :recommended, :strict
|
@@ -35,6 +35,8 @@ module Datadog
|
|
35
35
|
"libddwaf ruleset failed to load, ruleset: #{ruleset.inspect} error: #{e.inspect}"
|
36
36
|
end
|
37
37
|
|
38
|
+
telemetry.report(e, description: 'libddwaf ruleset failed to load')
|
39
|
+
|
38
40
|
nil
|
39
41
|
end
|
40
42
|
end
|
@@ -18,25 +18,35 @@ module Datadog
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
DEFAULT_WAF_PROCESSORS = begin
|
22
|
-
JSON.parse(Datadog::AppSec::Assets.waf_processors)
|
23
|
-
rescue StandardError => e
|
24
|
-
Datadog.logger.error { "libddwaf rulemerger failed to parse default waf processors. Error: #{e.inspect}" }
|
25
|
-
[]
|
26
|
-
end
|
27
|
-
|
28
|
-
DEFAULT_WAF_SCANNERS = begin
|
29
|
-
JSON.parse(Datadog::AppSec::Assets.waf_scanners)
|
30
|
-
rescue StandardError => e
|
31
|
-
Datadog.logger.error { "libddwaf rulemerger failed to parse default waf scanners. Error: #{e.inspect}" }
|
32
|
-
[]
|
33
|
-
end
|
34
|
-
|
35
21
|
class << self
|
22
|
+
# TODO: `processors` and `scanners` are not provided by the caller, consider removing them
|
36
23
|
def merge(
|
24
|
+
telemetry:,
|
37
25
|
rules:, data: [], overrides: [], exclusions: [], custom_rules: [],
|
38
|
-
processors:
|
26
|
+
processors: nil, scanners: nil
|
39
27
|
)
|
28
|
+
processors ||= begin
|
29
|
+
default_waf_processors
|
30
|
+
rescue StandardError => e
|
31
|
+
Datadog.logger.error("libddwaf rulemerger failed to parse default waf processors. Error: #{e.inspect}")
|
32
|
+
telemetry.report(
|
33
|
+
e,
|
34
|
+
description: 'libddwaf rulemerger failed to parse default waf processors'
|
35
|
+
)
|
36
|
+
[]
|
37
|
+
end
|
38
|
+
|
39
|
+
scanners ||= begin
|
40
|
+
default_waf_scanners
|
41
|
+
rescue StandardError => e
|
42
|
+
Datadog.logger.error("libddwaf rulemerger failed to parse default waf scanners. Error: #{e.inspect}")
|
43
|
+
telemetry.report(
|
44
|
+
e,
|
45
|
+
description: 'libddwaf rulemerger failed to parse default waf scanners'
|
46
|
+
)
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
40
50
|
combined_rules = combine_rules(rules)
|
41
51
|
|
42
52
|
combined_data = combine_data(data) if data.any?
|
@@ -53,6 +63,14 @@ module Datadog
|
|
53
63
|
combined_rules
|
54
64
|
end
|
55
65
|
|
66
|
+
def default_waf_processors
|
67
|
+
@default_waf_processors ||= JSON.parse(Datadog::AppSec::Assets.waf_processors)
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_waf_scanners
|
71
|
+
@default_waf_scanners ||= JSON.parse(Datadog::AppSec::Assets.waf_scanners)
|
72
|
+
end
|
73
|
+
|
56
74
|
private
|
57
75
|
|
58
76
|
def combine_rules(rules)
|
@@ -73,13 +73,15 @@ module Datadog
|
|
73
73
|
|
74
74
|
attr_reader :diagnostics, :addresses
|
75
75
|
|
76
|
-
def initialize(ruleset:)
|
76
|
+
def initialize(ruleset:, telemetry:)
|
77
77
|
@diagnostics = nil
|
78
78
|
@addresses = []
|
79
79
|
settings = Datadog.configuration.appsec
|
80
|
+
@telemetry = telemetry
|
80
81
|
|
81
|
-
|
82
|
-
|
82
|
+
# TODO: Refactor to make it easier to test
|
83
|
+
unless require_libddwaf && libddwaf_provides_waf? && create_waf_handle(settings, ruleset)
|
84
|
+
Datadog.logger.warn('AppSec is disabled, see logged errors above')
|
83
85
|
end
|
84
86
|
end
|
85
87
|
|
@@ -97,8 +99,27 @@ module Datadog
|
|
97
99
|
|
98
100
|
private
|
99
101
|
|
100
|
-
|
101
|
-
|
102
|
+
# libddwaf raises a LoadError on unsupported platforms; it may at some
|
103
|
+
# point succeed in being required yet not provide a specific needed feature.
|
104
|
+
def require_libddwaf
|
105
|
+
Datadog.logger.debug { "libddwaf platform: #{libddwaf_platform}" }
|
106
|
+
|
107
|
+
require 'libddwaf'
|
108
|
+
|
109
|
+
true
|
110
|
+
rescue LoadError => e
|
111
|
+
Datadog.logger.error do
|
112
|
+
'libddwaf failed to load,' \
|
113
|
+
"installed platform: #{libddwaf_platform} ruby platforms: #{ruby_platforms} error: #{e.inspect}"
|
114
|
+
end
|
115
|
+
@telemetry.report(e, description: 'libddwaf failed to load')
|
116
|
+
|
117
|
+
false
|
118
|
+
end
|
119
|
+
|
120
|
+
# check whether libddwaf is required *and* able to provide the needed feature
|
121
|
+
def libddwaf_provides_waf?
|
122
|
+
defined?(Datadog::AppSec::WAF) ? true : false
|
102
123
|
end
|
103
124
|
|
104
125
|
def create_waf_handle(settings, ruleset)
|
@@ -119,6 +140,7 @@ module Datadog
|
|
119
140
|
Datadog.logger.error do
|
120
141
|
"libddwaf failed to initialize, error: #{e.inspect}"
|
121
142
|
end
|
143
|
+
@telemetry.report(e, description: 'libddwaf failed to initialize')
|
122
144
|
|
123
145
|
@diagnostics = e.diagnostics if e.diagnostics
|
124
146
|
|
@@ -127,44 +149,21 @@ module Datadog
|
|
127
149
|
Datadog.logger.error do
|
128
150
|
"libddwaf failed to initialize, error: #{e.inspect}"
|
129
151
|
end
|
152
|
+
@telemetry.report(e, description: 'libddwaf failed to initialize')
|
130
153
|
|
131
154
|
false
|
132
155
|
end
|
133
156
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
# libddwaf raises a LoadError on unsupported platforms; it may at some
|
141
|
-
# point succeed in being required yet not provide a specific needed feature.
|
142
|
-
def require_libddwaf
|
143
|
-
Datadog.logger.debug { "libddwaf platform: #{libddwaf_platform}" }
|
144
|
-
|
145
|
-
require 'libddwaf'
|
146
|
-
|
147
|
-
true
|
148
|
-
rescue LoadError => e
|
149
|
-
Datadog.logger.error do
|
150
|
-
'libddwaf failed to load,' \
|
151
|
-
"installed platform: #{libddwaf_platform} ruby platforms: #{ruby_platforms} error: #{e.inspect}"
|
152
|
-
end
|
153
|
-
|
154
|
-
false
|
155
|
-
end
|
156
|
-
|
157
|
-
def libddwaf_spec
|
158
|
-
Gem.loaded_specs['libddwaf']
|
159
|
-
end
|
160
|
-
|
161
|
-
def libddwaf_platform
|
162
|
-
libddwaf_spec ? libddwaf_spec.platform.to_s : 'unknown'
|
157
|
+
def libddwaf_platform
|
158
|
+
if Gem.loaded_specs['libddwaf']
|
159
|
+
Gem.loaded_specs['libddwaf'].platform.to_s
|
160
|
+
else
|
161
|
+
'unknown'
|
163
162
|
end
|
163
|
+
end
|
164
164
|
|
165
|
-
|
166
|
-
|
167
|
-
end
|
165
|
+
def ruby_platforms
|
166
|
+
Gem.platforms.map(&:to_s)
|
168
167
|
end
|
169
168
|
end
|
170
169
|
end
|