datadog 2.2.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 +87 -2
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
- data/ext/datadog_profiling_loader/extconf.rb +14 -26
- data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +257 -69
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +53 -28
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
- data/ext/datadog_profiling_native_extension/collectors_stack.c +136 -81
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +661 -48
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +83 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +53 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +91 -69
- 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 +54 -12
- data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/helpers.h +6 -17
- data/ext/datadog_profiling_native_extension/http_transport.c +41 -9
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +116 -139
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +20 -11
- data/ext/datadog_profiling_native_extension/profiling.c +1 -3
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
- data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
- data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
- data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +37 -22
- data/ext/libdatadog_api/datadog_ruby_common.c +83 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +53 -0
- data/ext/libdatadog_api/extconf.rb +108 -0
- data/ext/libdatadog_api/macos_development.md +26 -0
- data/ext/libdatadog_extconf_helpers.rb +130 -0
- 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 +35 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +109 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +71 -0
- data/lib/datadog/appsec/contrib/graphql/integration.rb +54 -0
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +3 -6
- data/lib/datadog/appsec/event.rb +1 -1
- data/lib/datadog/appsec/processor/actions.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/response.rb +15 -1
- data/lib/datadog/appsec.rb +3 -2
- data/lib/datadog/core/configuration/components.rb +18 -15
- data/lib/datadog/core/configuration/settings.rb +135 -9
- data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
- data/lib/datadog/core/crashtracking/component.rb +111 -0
- data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
- data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
- 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 +51 -2
- data/lib/datadog/core/telemetry/emitter.rb +9 -11
- data/lib/datadog/core/telemetry/event.rb +37 -1
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
- data/lib/datadog/core/telemetry/http/ext.rb +3 -0
- data/lib/datadog/core/telemetry/http/transport.rb +38 -9
- data/lib/datadog/core/telemetry/logger.rb +51 -0
- data/lib/datadog/core/telemetry/logging.rb +71 -0
- data/lib/datadog/core/telemetry/request.rb +13 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
- 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/kit/appsec/events.rb +2 -4
- data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +28 -26
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
- data/lib/datadog/profiling/collectors/info.rb +15 -6
- data/lib/datadog/profiling/collectors/thread_context.rb +30 -2
- data/lib/datadog/profiling/component.rb +89 -95
- data/lib/datadog/profiling/exporter.rb +3 -3
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/profiling/ext.rb +21 -21
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/http_transport.rb +14 -7
- data/lib/datadog/profiling/load_native_extension.rb +5 -5
- data/lib/datadog/profiling/preload.rb +1 -1
- data/lib/datadog/profiling/profiler.rb +5 -8
- data/lib/datadog/profiling/scheduler.rb +33 -25
- data/lib/datadog/profiling/stack_recorder.rb +3 -0
- data/lib/datadog/profiling/tag_builder.rb +2 -2
- data/lib/datadog/profiling/tasks/exec.rb +5 -5
- data/lib/datadog/profiling/tasks/setup.rb +16 -35
- data/lib/datadog/profiling.rb +4 -5
- 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 +4 -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/ext.rb +14 -0
- 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 +14 -10
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +10 -4
- 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 +15 -0
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
- data/lib/datadog/tracing/contrib/patcher.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
- data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
- 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/contrib/trilogy/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
- data/lib/datadog/tracing/distributed/propagation.rb +7 -0
- data/lib/datadog/tracing/metadata/errors.rb +9 -1
- data/lib/datadog/tracing/metadata/ext.rb +6 -0
- data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
- 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/span.rb +9 -2
- data/lib/datadog/tracing/span_event.rb +41 -0
- data/lib/datadog/tracing/span_operation.rb +6 -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/transport/serializable_trace.rb +3 -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 +46 -11
- data/lib/datadog/profiling/crashtracker.rb +0 -91
- data/lib/datadog/profiling/ext/forking.rb +0 -98
- 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)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative 'gateway/multiplex'
|
5
|
+
require_relative '../../instrumentation/gateway'
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module AppSec
|
9
|
+
module Contrib
|
10
|
+
module GraphQL
|
11
|
+
# These methods will be called by the GraphQL runtime to send the variables to the WAF.
|
12
|
+
# We actually don't need to create any span/trace.
|
13
|
+
module AppSecTrace
|
14
|
+
def execute_multiplex(multiplex:)
|
15
|
+
return super unless Datadog::AppSec.enabled?
|
16
|
+
|
17
|
+
gateway_multiplex = Gateway::Multiplex.new(multiplex)
|
18
|
+
|
19
|
+
multiplex_return, multiplex_response = Instrumentation.gateway.push('graphql.multiplex', gateway_multiplex) do
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an error * the number of queries so that the entire multiplex is blocked
|
24
|
+
if multiplex_response
|
25
|
+
blocked_event = multiplex_response.find { |action, _options| action == :block }
|
26
|
+
multiplex_return = AppSec::Response.graphql_response(gateway_multiplex) if blocked_event
|
27
|
+
end
|
28
|
+
|
29
|
+
multiplex_return
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql'
|
4
|
+
|
5
|
+
require_relative '../../../instrumentation/gateway/argument'
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module AppSec
|
9
|
+
module Contrib
|
10
|
+
module GraphQL
|
11
|
+
module Gateway
|
12
|
+
# Gateway Request argument. Normalized extration of data from Rack::Request
|
13
|
+
class Multiplex < Instrumentation::Gateway::Argument
|
14
|
+
attr_reader :multiplex
|
15
|
+
|
16
|
+
def initialize(multiplex)
|
17
|
+
super()
|
18
|
+
@multiplex = multiplex
|
19
|
+
end
|
20
|
+
|
21
|
+
def arguments
|
22
|
+
@arguments ||= build_arguments_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def queries
|
26
|
+
@multiplex.queries
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
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
|
+
)
|
70
|
+
end
|
71
|
+
|
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)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
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
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative '../../../instrumentation/gateway'
|
5
|
+
require_relative '../reactive/multiplex'
|
6
|
+
require_relative '../../../reactive/operation'
|
7
|
+
|
8
|
+
module Datadog
|
9
|
+
module AppSec
|
10
|
+
module Contrib
|
11
|
+
module GraphQL
|
12
|
+
module Gateway
|
13
|
+
# Watcher for Rack gateway events
|
14
|
+
module Watcher
|
15
|
+
class << self
|
16
|
+
def watch
|
17
|
+
gateway = Instrumentation.gateway
|
18
|
+
|
19
|
+
watch_multiplex(gateway)
|
20
|
+
end
|
21
|
+
|
22
|
+
# This time we don't throw but use next
|
23
|
+
def watch_multiplex(gateway = Instrumentation.gateway)
|
24
|
+
gateway.watch('graphql.multiplex', :appsec) do |stack, gateway_multiplex|
|
25
|
+
block = false
|
26
|
+
event = nil
|
27
|
+
|
28
|
+
scope = AppSec::Scope.active_scope
|
29
|
+
|
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
|
45
|
+
|
46
|
+
scope.processor_context.events << event
|
47
|
+
end
|
48
|
+
|
49
|
+
block = GraphQL::Reactive::Multiplex.publish(op, gateway_multiplex)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
next [nil, [[:block, event]]] if block
|
54
|
+
|
55
|
+
ret, res = stack.call(gateway_multiplex.arguments)
|
56
|
+
|
57
|
+
if event
|
58
|
+
res ||= []
|
59
|
+
res << [:monitor, event]
|
60
|
+
end
|
61
|
+
|
62
|
+
[ret, res]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../integration'
|
4
|
+
require_relative 'patcher'
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
module AppSec
|
8
|
+
module Contrib
|
9
|
+
module GraphQL
|
10
|
+
# Description of GraphQL integration
|
11
|
+
class Integration
|
12
|
+
include Datadog::AppSec::Contrib::Integration
|
13
|
+
|
14
|
+
MINIMUM_VERSION = Gem::Version.new('2.0.19')
|
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
|
+
|
23
|
+
register_as :graphql, auto_patch: false
|
24
|
+
|
25
|
+
def self.version
|
26
|
+
Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.loaded?
|
30
|
+
!defined?(::GraphQL).nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.compatible?
|
34
|
+
super && version >= MINIMUM_VERSION && ast_node_classes_defined?
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.auto_instrument?
|
38
|
+
true
|
39
|
+
end
|
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
|
+
|
47
|
+
def patcher
|
48
|
+
Patcher
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../patcher'
|
4
|
+
require_relative 'gateway/watcher'
|
5
|
+
|
6
|
+
if Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version >= Gem::Version.new('2.0.19')
|
7
|
+
require_relative 'appsec_trace'
|
8
|
+
end
|
9
|
+
|
10
|
+
module Datadog
|
11
|
+
module AppSec
|
12
|
+
module Contrib
|
13
|
+
module GraphQL
|
14
|
+
# Patcher for AppSec on GraphQL
|
15
|
+
module Patcher
|
16
|
+
include Datadog::AppSec::Contrib::Patcher
|
17
|
+
|
18
|
+
module_function
|
19
|
+
|
20
|
+
def patched?
|
21
|
+
Patcher.instance_variable_get(:@patched)
|
22
|
+
end
|
23
|
+
|
24
|
+
def target_version
|
25
|
+
Integration.version
|
26
|
+
end
|
27
|
+
|
28
|
+
def patch
|
29
|
+
Gateway::Watcher.watch
|
30
|
+
::GraphQL::Schema.trace_with(AppSecTrace)
|
31
|
+
Patcher.instance_variable_set(:@patched, true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module Contrib
|
6
|
+
module GraphQL
|
7
|
+
module Reactive
|
8
|
+
# Dispatch data from a GraphQL resolve query to the WAF context
|
9
|
+
module Multiplex
|
10
|
+
ADDRESSES = [
|
11
|
+
'graphql.server.all_resolvers'
|
12
|
+
].freeze
|
13
|
+
private_constant :ADDRESSES
|
14
|
+
|
15
|
+
def self.publish(op, gateway_multiplex)
|
16
|
+
catch(:block) do
|
17
|
+
op.publish('graphql.server.all_resolvers', gateway_multiplex.arguments)
|
18
|
+
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.subscribe(op, waf_context)
|
24
|
+
op.subscribe(*ADDRESSES) do |*values|
|
25
|
+
Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
|
26
|
+
arguments = values[0]
|
27
|
+
|
28
|
+
waf_args = {
|
29
|
+
'graphql.server.all_resolvers' => arguments
|
30
|
+
}
|
31
|
+
|
32
|
+
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
33
|
+
result = waf_context.run(waf_args, waf_timeout)
|
34
|
+
|
35
|
+
Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
|
36
|
+
|
37
|
+
case result.status
|
38
|
+
when :match
|
39
|
+
Datadog.logger.debug { "WAF: #{result.inspect}" }
|
40
|
+
|
41
|
+
yield result
|
42
|
+
throw(:block, true) unless result.actions.empty?
|
43
|
+
when :ok
|
44
|
+
Datadog.logger.debug { "WAF OK: #{result.inspect}" }
|
45
|
+
when :invalid_call
|
46
|
+
Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
|
47
|
+
when :invalid_rule, :invalid_flow, :no_rule
|
48
|
+
Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
|
49
|
+
else
|
50
|
+
Datadog.logger.debug { "WAF UNKNOWN: #{result.status.inspect} #{result.inspect}" }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -41,18 +41,15 @@ module Datadog
|
|
41
41
|
|
42
42
|
def headers
|
43
43
|
result = request.env.each_with_object({}) do |(k, v), h|
|
44
|
-
h[k.
|
44
|
+
h[k.delete_prefix('HTTP_').tap(&:downcase!).tap { |s| s.tr!('_', '-') }] = v if k.start_with?('HTTP_')
|
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)
|