datadog 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/ext/datadog_profiling_loader/extconf.rb +15 -15
- 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 +113 -43
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
- 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 +49 -37
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +65 -60
- data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
- 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 +3 -3
- 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 +64 -138
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
- data/ext/datadog_profiling_native_extension/profiling.c +0 -2
- 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 +1 -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 +19 -6
- data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +57 -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/contrib/graphql/appsec_trace.rb +49 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
- data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -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 +1 -1
- data/lib/datadog/appsec/processor/actions.rb +1 -1
- data/lib/datadog/appsec/response.rb +15 -1
- data/lib/datadog/appsec.rb +1 -0
- data/lib/datadog/core/configuration/components.rb +14 -12
- data/lib/datadog/core/configuration/settings.rb +54 -7
- 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/telemetry/component.rb +49 -2
- data/lib/datadog/core/telemetry/emitter.rb +9 -11
- data/lib/datadog/core/telemetry/event.rb +32 -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/logging.rb +35 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
- data/lib/datadog/kit/appsec/events.rb +2 -4
- 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 +17 -17
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
- data/lib/datadog/profiling/collectors/info.rb +3 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
- data/lib/datadog/profiling/component.rb +69 -91
- 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 +8 -6
- 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 +31 -25
- 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/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/ext.rb +14 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +1 -1
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +4 -1
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -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/pg/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
- 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/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/metadata/errors.rb +9 -1
- data/lib/datadog/tracing/metadata/ext.rb +4 -0
- data/lib/datadog/tracing/pipeline/span_filter.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/transport/serializable_trace.rb +3 -0
- data/lib/datadog/version.rb +1 -1
- metadata +28 -10
- data/lib/datadog/profiling/crashtracker.rb +0 -91
- data/lib/datadog/profiling/ext/forking.rb +0 -98
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
# Contains a bunch of shared helpers that get used during building of extensions that link to libdatadog
|
8
|
+
module LibdatadogExtconfHelpers
|
9
|
+
# Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
|
10
|
+
# may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
|
11
|
+
LIBDATADOG_VERSION = '~> 11.0.0.1.0'
|
12
|
+
|
13
|
+
# Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
|
14
|
+
# libdatadog are moved after the extension gets compiled.
|
15
|
+
#
|
16
|
+
# Because the libdatadog native library is installed on a non-standard system path, in order for it to be
|
17
|
+
# found by the system dynamic linker (e.g. what takes care of dlopen(), which is used to load
|
18
|
+
# native extensions), we need to add a "runpath" -- a list of folders to search for libdatadog.
|
19
|
+
#
|
20
|
+
# This runpath gets hardcoded at native library linking time. You can look at it using the `readelf` tool in
|
21
|
+
# Linux: e.g. `readelf -d datadog_profiling_native_extension.2.7.3_x86_64-linux.so`.
|
22
|
+
#
|
23
|
+
# In older versions of the datadog gem, we only set as runpath an absolute path to libdatadog.
|
24
|
+
# (This gets set automatically by the call
|
25
|
+
# to `pkg_config('datadog_profiling_with_rpath')` in `extconf.rb`). This worked fine as long as libdatadog was **NOT**
|
26
|
+
# moved from the folder it was present at datadog gem installation/linking time.
|
27
|
+
#
|
28
|
+
# Unfortunately, environments such as Heroku and AWS Elastic Beanstalk move gems around in the filesystem after
|
29
|
+
# installation. Thus, the profiling native extension could not be loaded in these environments
|
30
|
+
# (see https://github.com/DataDog/dd-trace-rb/issues/2067) because libdatadog could not be found.
|
31
|
+
#
|
32
|
+
# To workaround this issue, this method computes the **relative** path between the folder where
|
33
|
+
# native extensions are going to be installed and the folder where libdatadog is installed, and returns it
|
34
|
+
# to be set as an additional runpath. (Yes, you can set multiple runpath folders to be searched).
|
35
|
+
#
|
36
|
+
# This way, if both gems are moved together (and it turns out that they are in these environments),
|
37
|
+
# the relative path can still be traversed to find libdatadog.
|
38
|
+
#
|
39
|
+
# This is incredibly awful, and it's kinda bizarre how it's not possible to just find these paths at runtime
|
40
|
+
# and set them correctly; rather than needing to set stuff at linking-time and then praying to $deity that
|
41
|
+
# weird moves don't happen.
|
42
|
+
#
|
43
|
+
# As a curiosity, `LD_LIBRARY_PATH` can be used to influence the folders that get searched but **CANNOT BE
|
44
|
+
# SET DYNAMICALLY**, e.g. it needs to be set at the start of the process (Ruby VM) and thus it's not something
|
45
|
+
# we could setup when doing a `require`.
|
46
|
+
#
|
47
|
+
def self.libdatadog_folder_relative_to_native_lib_folder(
|
48
|
+
current_folder:,
|
49
|
+
libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
|
50
|
+
)
|
51
|
+
return unless libdatadog_pkgconfig_folder
|
52
|
+
|
53
|
+
native_lib_folder = "#{current_folder}/../../lib/"
|
54
|
+
libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
|
55
|
+
|
56
|
+
Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(native_lib_folder)).to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
# In https://github.com/DataDog/dd-trace-rb/pull/3582 we got a report of a customer for which the native extension
|
60
|
+
# only got installed into the extensions folder.
|
61
|
+
#
|
62
|
+
# But then this fix was not enough to fully get them moving because then they started to see the issue from
|
63
|
+
# https://github.com/DataDog/dd-trace-rb/issues/2067 / https://github.com/DataDog/dd-trace-rb/pull/2125 :
|
64
|
+
#
|
65
|
+
# > Profiling was requested but is not supported, profiling disabled: There was an error loading the profiling
|
66
|
+
# > native extension due to 'RuntimeError Failure to load datadog_profiling_native_extension.3.2.2_x86_64-linux
|
67
|
+
# > due to libdatadog_profiling.so: cannot open shared object file: No such file or directory
|
68
|
+
#
|
69
|
+
# The problem is that when loading the native extension from the extensions directory, the relative rpath we add
|
70
|
+
# with the #libdatadog_folder_relative_to_native_lib_folder helper above is not correct, we need to add a relative
|
71
|
+
# rpath to the extensions directory.
|
72
|
+
#
|
73
|
+
# So how do we find the full path where the native extension is placed?
|
74
|
+
# * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/bundler/runtime.rb#L166
|
75
|
+
# `extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]`
|
76
|
+
# we get that's in one of two fixed subdirectories of `Gem.dir`
|
77
|
+
# * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/rubygems/basic_specification.rb#L111-L115
|
78
|
+
# we get the structure of the subdirectory (platform/extension_api_version/gem_and_version)
|
79
|
+
#
|
80
|
+
# Thus, `Gem.dir` of `/var/app/current/vendor/bundle/ruby/3.2.0` becomes (for instance)
|
81
|
+
# `/var/app/current/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/datadog-2.0.0/` or
|
82
|
+
# `/var/app/current/vendor/bundle/ruby/3.2.0/bundler/gems/extensions/x86_64-linux/3.2.0/datadog-2.0.0/`
|
83
|
+
#
|
84
|
+
# We then compute the relative path between these folders and the libdatadog folder, and use that as a relative path.
|
85
|
+
def self.libdatadog_folder_relative_to_ruby_extensions_folders(
|
86
|
+
gem_dir: Gem.dir,
|
87
|
+
libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
|
88
|
+
)
|
89
|
+
return unless libdatadog_pkgconfig_folder
|
90
|
+
|
91
|
+
# For the purposes of calculating a folder relative to the other, we don't actually NEED to fill in the
|
92
|
+
# platform, extension_api_version and gem version. We're basically just after how many folders it is deep from
|
93
|
+
# the Gem.dir.
|
94
|
+
expected_ruby_extensions_folders = [
|
95
|
+
"#{gem_dir}/extensions/platform/extension_api_version/datadog_version/",
|
96
|
+
"#{gem_dir}/bundler/gems/extensions/platform/extension_api_version/datadog_version/",
|
97
|
+
]
|
98
|
+
libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
|
99
|
+
|
100
|
+
expected_ruby_extensions_folders.map do |folder|
|
101
|
+
Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(folder)).to_s
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# mkmf sets $PKGCONFIG after the `pkg_config` gets used in extconf.rb. When `pkg_config` is unsuccessful, we use
|
106
|
+
# this helper to decide if we can show more specific error message vs a generic "something went wrong".
|
107
|
+
def self.pkg_config_missing?(command: $PKGCONFIG) # rubocop:disable Style/GlobalVars
|
108
|
+
pkg_config_available = command && xsystem("#{command} --version")
|
109
|
+
|
110
|
+
pkg_config_available != true
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.try_loading_libdatadog
|
114
|
+
gem 'libdatadog', LIBDATADOG_VERSION
|
115
|
+
require 'libdatadog'
|
116
|
+
nil
|
117
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
118
|
+
if block_given?
|
119
|
+
yield e
|
120
|
+
else
|
121
|
+
e
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.libdatadog_issue?
|
126
|
+
try_loading_libdatadog { |_exception| return true }
|
127
|
+
Libdatadog.pkgconfig_folder.nil?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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
|
+
|
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
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,73 @@
|
|
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 ||= create_arguments_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def queries
|
26
|
+
@multiplex.queries
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def create_arguments_hash
|
32
|
+
args = {}
|
33
|
+
@multiplex.queries.each_with_index do |query, index|
|
34
|
+
resolver_args = {}
|
35
|
+
resolver_dirs = {}
|
36
|
+
selections = (query.selected_operation.selections.dup if query.selected_operation) || []
|
37
|
+
# Iterative tree traversal
|
38
|
+
while selections.any?
|
39
|
+
selection = selections.shift
|
40
|
+
set_hash_with_variables(resolver_args, selection.arguments, query.provided_variables)
|
41
|
+
selection.directives.each do |dir|
|
42
|
+
resolver_dirs[dir.name] ||= {}
|
43
|
+
set_hash_with_variables(resolver_dirs[dir.name], dir.arguments, query.provided_variables)
|
44
|
+
end
|
45
|
+
selections.concat(selection.selections)
|
46
|
+
end
|
47
|
+
next if resolver_args.empty? && resolver_dirs.empty?
|
48
|
+
|
49
|
+
args_resolver = (args[query.operation_name || "query#{index + 1}"] ||= [])
|
50
|
+
# We don't want to add empty hashes so we check again if the arguments and directives are empty
|
51
|
+
args_resolver << resolver_args unless resolver_args.empty?
|
52
|
+
args_resolver << resolver_dirs unless resolver_dirs.empty?
|
53
|
+
end
|
54
|
+
args
|
55
|
+
end
|
56
|
+
|
57
|
+
# Set the resolver hash (resolver_args and resolver_dirs) with the arguments and provided variables
|
58
|
+
def set_hash_with_variables(resolver_hash, arguments, provided_variables)
|
59
|
+
arguments.each do |arg|
|
60
|
+
resolver_hash[arg.name] =
|
61
|
+
if arg.value.is_a?(::GraphQL::Language::Nodes::VariableIdentifier)
|
62
|
+
provided_variables[arg.value.name]
|
63
|
+
else
|
64
|
+
arg.value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,68 @@
|
|
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
|
+
scope = AppSec::Scope.active_scope
|
28
|
+
|
29
|
+
AppSec::Reactive::Operation.new('graphql.multiplex') do |op|
|
30
|
+
GraphQL::Reactive::Multiplex.subscribe(op, scope.processor_context) do |result|
|
31
|
+
event = {
|
32
|
+
waf_result: result,
|
33
|
+
trace: scope.trace,
|
34
|
+
span: scope.service_entry_span,
|
35
|
+
multiplex: gateway_multiplex,
|
36
|
+
actions: result.actions
|
37
|
+
}
|
38
|
+
|
39
|
+
if scope.service_entry_span
|
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')
|
42
|
+
end
|
43
|
+
|
44
|
+
scope.processor_context.events << event
|
45
|
+
end
|
46
|
+
|
47
|
+
block = GraphQL::Reactive::Multiplex.publish(op, gateway_multiplex)
|
48
|
+
end
|
49
|
+
|
50
|
+
next [nil, [[:block, event]]] if block
|
51
|
+
|
52
|
+
ret, res = stack.call(gateway_multiplex.arguments)
|
53
|
+
|
54
|
+
if event
|
55
|
+
res ||= []
|
56
|
+
res << [:monitor, event]
|
57
|
+
end
|
58
|
+
|
59
|
+
[ret, res]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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
|
+
register_as :graphql, auto_patch: false
|
17
|
+
|
18
|
+
def self.version
|
19
|
+
Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.loaded?
|
23
|
+
!defined?(::GraphQL).nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.compatible?
|
27
|
+
super && version >= MINIMUM_VERSION
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.auto_instrument?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def patcher
|
35
|
+
Patcher
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
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,7 +41,7 @@ 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
|
@@ -36,7 +36,7 @@ module Datadog
|
|
36
36
|
# I rather use break to stop the execution
|
37
37
|
next if configured_response
|
38
38
|
|
39
|
-
action_configuration = AppSec::Processor::Actions.
|
39
|
+
action_configuration = AppSec::Processor::Actions.fetch_configuration(action)
|
40
40
|
next unless action_configuration
|
41
41
|
|
42
42
|
configured_response = case action_configuration['type']
|
@@ -50,6 +50,20 @@ module Datadog
|
|
50
50
|
configured_response || default_response(env)
|
51
51
|
end
|
52
52
|
|
53
|
+
def graphql_response(gateway_multiplex)
|
54
|
+
multiplex_return = []
|
55
|
+
gateway_multiplex.queries.each do |query|
|
56
|
+
# This method is only called in places where GraphQL-Ruby is already required
|
57
|
+
query_result = ::GraphQL::Query::Result.new(
|
58
|
+
query: query,
|
59
|
+
values: JSON.parse(content('application/json'))
|
60
|
+
)
|
61
|
+
multiplex_return << query_result
|
62
|
+
end
|
63
|
+
|
64
|
+
multiplex_return
|
65
|
+
end
|
66
|
+
|
53
67
|
private
|
54
68
|
|
55
69
|
def default_response(env)
|
data/lib/datadog/appsec.rb
CHANGED
@@ -56,5 +56,6 @@ require_relative 'appsec/contrib/rack/integration'
|
|
56
56
|
require_relative 'appsec/contrib/sinatra/integration'
|
57
57
|
require_relative 'appsec/contrib/rails/integration'
|
58
58
|
require_relative 'appsec/contrib/devise/integration'
|
59
|
+
require_relative 'appsec/contrib/graphql/integration'
|
59
60
|
|
60
61
|
require_relative 'appsec/autoload'
|
@@ -13,6 +13,7 @@ require_relative '../remote/component'
|
|
13
13
|
require_relative '../../tracing/component'
|
14
14
|
require_relative '../../profiling/component'
|
15
15
|
require_relative '../../appsec/component'
|
16
|
+
require_relative '../crashtracking/component'
|
16
17
|
|
17
18
|
module Datadog
|
18
19
|
module Core
|
@@ -56,19 +57,18 @@ module Datadog
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def build_telemetry(settings, agent_settings, logger)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
Telemetry::Component.build(settings, agent_settings, logger)
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_crashtracker(settings, agent_settings, logger:)
|
64
|
+
return unless settings.crashtracking.enabled
|
65
|
+
|
66
|
+
if (libdatadog_api_failure = Datadog::Core::Crashtracking::Component::LIBDATADOG_API_FAILURE)
|
67
|
+
logger.debug("Cannot enable crashtracking: #{libdatadog_api_failure}")
|
68
|
+
return
|
63
69
|
end
|
64
70
|
|
65
|
-
|
66
|
-
enabled: enabled,
|
67
|
-
metrics_enabled: enabled && settings.telemetry.metrics_enabled,
|
68
|
-
heartbeat_interval_seconds: settings.telemetry.heartbeat_interval_seconds,
|
69
|
-
metrics_aggregation_interval_seconds: settings.telemetry.metrics_aggregation_interval_seconds,
|
70
|
-
dependency_collection: settings.telemetry.dependency_collection
|
71
|
-
)
|
71
|
+
Datadog::Core::Crashtracking::Component.build(settings, agent_settings, logger: logger)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
@@ -82,6 +82,7 @@ module Datadog
|
|
82
82
|
:runtime_metrics,
|
83
83
|
:telemetry,
|
84
84
|
:tracer,
|
85
|
+
:crashtracker,
|
85
86
|
:appsec
|
86
87
|
|
87
88
|
def initialize(settings)
|
@@ -95,11 +96,12 @@ module Datadog
|
|
95
96
|
|
96
97
|
@remote = Remote::Component.build(settings, agent_settings)
|
97
98
|
@tracer = self.class.build_tracer(settings, agent_settings, logger: @logger)
|
99
|
+
@crashtracker = self.class.build_crashtracker(settings, agent_settings, logger: @logger)
|
98
100
|
|
99
101
|
@profiler, profiler_logger_extra = Datadog::Profiling::Component.build_profiler_component(
|
100
102
|
settings: settings,
|
101
103
|
agent_settings: agent_settings,
|
102
|
-
optional_tracer: @tracer
|
104
|
+
optional_tracer: @tracer
|
103
105
|
)
|
104
106
|
@environment_logger_extra.merge!(profiler_logger_extra) if profiler_logger_extra
|
105
107
|
|
@@ -301,6 +301,16 @@ module Datadog
|
|
301
301
|
o.default true
|
302
302
|
end
|
303
303
|
|
304
|
+
# Can be used to enable/disable the Datadog::Profiling.allocation_count feature.
|
305
|
+
#
|
306
|
+
# Requires allocation profiling to be enabled.
|
307
|
+
#
|
308
|
+
# @default false
|
309
|
+
option :allocation_counting_enabled do |o|
|
310
|
+
o.type :bool
|
311
|
+
o.default false
|
312
|
+
end
|
313
|
+
|
304
314
|
# Can be used to enable/disable the collection of heap profiles.
|
305
315
|
#
|
306
316
|
# This feature is alpha and disabled by default
|
@@ -441,14 +451,16 @@ module Datadog
|
|
441
451
|
o.default 60
|
442
452
|
end
|
443
453
|
|
444
|
-
#
|
445
|
-
#
|
446
|
-
# @default `DD_PROFILING_EXPERIMENTAL_CRASH_TRACKING_ENABLED` environment variable as a boolean,
|
447
|
-
# otherwise `false`
|
454
|
+
# DEV-3.0: Remove `experimental_crash_tracking_enabled` option
|
448
455
|
option :experimental_crash_tracking_enabled do |o|
|
449
|
-
o.
|
450
|
-
|
451
|
-
|
456
|
+
o.after_set do |_, _, precedence|
|
457
|
+
unless precedence == Datadog::Core::Configuration::Option::Precedence::DEFAULT
|
458
|
+
Core.log_deprecation(key: :experimental_crash_tracking_enabled) do
|
459
|
+
'The profiling.advanced.experimental_crash_tracking_enabled setting has been deprecated for removal '\
|
460
|
+
'and no longer does anything. Please remove it from your Datadog.configure block.'
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
452
464
|
end
|
453
465
|
end
|
454
466
|
|
@@ -663,6 +675,24 @@ module Datadog
|
|
663
675
|
o.type :bool
|
664
676
|
end
|
665
677
|
|
678
|
+
# Enable agentless mode for telemetry: submit telemetry events directly to the intake without Datadog Agent.
|
679
|
+
#
|
680
|
+
# @return [Boolean]
|
681
|
+
# @!visibility private
|
682
|
+
option :agentless_enabled do |o|
|
683
|
+
o.type :bool
|
684
|
+
o.default false
|
685
|
+
end
|
686
|
+
|
687
|
+
# Overrides agentless telemetry URL. To be used internally for testing purposes only.
|
688
|
+
#
|
689
|
+
# @return [String]
|
690
|
+
# @!visibility private
|
691
|
+
option :agentless_url_override do |o|
|
692
|
+
o.type :string, nilable: true
|
693
|
+
o.env Core::Telemetry::Ext::ENV_AGENTLESS_URL_OVERRIDE
|
694
|
+
end
|
695
|
+
|
666
696
|
# Enable metrics collection for telemetry. Metrics collection only works when telemetry is enabled and
|
667
697
|
# metrics are enabled.
|
668
698
|
# @default `DD_TELEMETRY_METRICS_ENABLED` environment variable, otherwise `true`.
|
@@ -734,6 +764,14 @@ module Datadog
|
|
734
764
|
o.type :string, nilable: true
|
735
765
|
o.env Core::Telemetry::Ext::ENV_INSTALL_TIME
|
736
766
|
end
|
767
|
+
|
768
|
+
# Telemetry shutdown timeout in seconds
|
769
|
+
#
|
770
|
+
# @!visibility private
|
771
|
+
option :shutdown_timeout_seconds do |o|
|
772
|
+
o.type :float
|
773
|
+
o.default 1.0
|
774
|
+
end
|
737
775
|
end
|
738
776
|
|
739
777
|
# Remote configuration
|
@@ -794,6 +832,15 @@ module Datadog
|
|
794
832
|
option :service
|
795
833
|
end
|
796
834
|
|
835
|
+
settings :crashtracking do
|
836
|
+
# Enables reporting of information when Ruby VM crashes.
|
837
|
+
option :enabled do |o|
|
838
|
+
o.type :bool
|
839
|
+
o.default true
|
840
|
+
o.env 'DD_CRASHTRACKING_ENABLED'
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
797
844
|
# TODO: Tracing should manage its own settings.
|
798
845
|
# Keep this extension here for now to keep things working.
|
799
846
|
extend Datadog::Tracing::Configuration::Settings
|