ddtrace 1.0.0 → 1.1.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/.gitignore +4 -16
- data/CHANGELOG.md +31 -2
- data/LICENSE-3rdparty.csv +3 -2
- data/README.md +2 -2
- data/ddtrace.gemspec +12 -3
- data/docs/GettingStarted.md +19 -2
- data/docs/ProfilingDevelopment.md +8 -8
- data/docs/UpgradeGuide.md +3 -3
- data/ext/ddtrace_profiling_loader/ddtrace_profiling_loader.c +118 -0
- data/ext/ddtrace_profiling_loader/extconf.rb +53 -0
- data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +31 -5
- data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +0 -8
- data/ext/ddtrace_profiling_native_extension/collectors_stack.c +278 -0
- data/ext/ddtrace_profiling_native_extension/extconf.rb +70 -100
- data/ext/ddtrace_profiling_native_extension/libddprof_helpers.h +13 -0
- data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +186 -0
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +579 -7
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +30 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +7 -0
- data/ext/ddtrace_profiling_native_extension/stack_recorder.c +139 -0
- data/ext/ddtrace_profiling_native_extension/stack_recorder.h +28 -0
- data/lib/datadog/appsec/autoload.rb +2 -2
- data/lib/datadog/appsec/configuration/settings.rb +19 -0
- data/lib/datadog/appsec/configuration.rb +8 -0
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +76 -33
- data/lib/datadog/appsec/contrib/rack/integration.rb +1 -0
- data/lib/datadog/appsec/contrib/rack/patcher.rb +0 -1
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +64 -0
- data/lib/datadog/appsec/contrib/rack/request.rb +6 -0
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +41 -0
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +60 -5
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +81 -0
- data/lib/datadog/appsec/contrib/rails/patcher.rb +34 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +68 -0
- data/lib/datadog/appsec/contrib/rails/request.rb +33 -0
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +124 -0
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +69 -2
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +63 -0
- data/lib/datadog/appsec/event.rb +33 -18
- data/lib/datadog/appsec/extensions.rb +0 -3
- data/lib/datadog/appsec/processor.rb +45 -2
- data/lib/datadog/appsec/rate_limiter.rb +5 -0
- data/lib/datadog/appsec/reactive/operation.rb +0 -1
- data/lib/datadog/ci/ext/environment.rb +21 -7
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +1 -1
- data/lib/datadog/core/configuration/components.rb +22 -4
- data/lib/datadog/core/configuration/settings.rb +3 -3
- data/lib/datadog/core/configuration.rb +7 -5
- data/lib/datadog/core/environment/cgroup.rb +3 -1
- data/lib/datadog/core/environment/container.rb +2 -1
- data/lib/datadog/core/environment/variable_helpers.rb +26 -2
- data/lib/datadog/core/logging/ext.rb +11 -0
- data/lib/datadog/core/metrics/client.rb +15 -5
- data/lib/datadog/core/runtime/metrics.rb +1 -1
- data/lib/datadog/core/workers/async.rb +3 -1
- data/lib/datadog/core/workers/runtime_metrics.rb +0 -3
- data/lib/datadog/core.rb +6 -0
- data/lib/datadog/kit/enable_core_dumps.rb +50 -0
- data/lib/datadog/kit/identity.rb +63 -0
- data/lib/datadog/kit.rb +11 -0
- data/lib/datadog/opentracer/tracer.rb +0 -2
- data/lib/datadog/profiling/collectors/old_stack.rb +298 -0
- data/lib/datadog/profiling/collectors/stack.rb +6 -287
- data/lib/datadog/profiling/encoding/profile.rb +0 -1
- data/lib/datadog/profiling/ext.rb +1 -1
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/load_native_extension.rb +22 -0
- data/lib/datadog/profiling/recorder.rb +1 -1
- data/lib/datadog/profiling/scheduler.rb +1 -1
- data/lib/datadog/profiling/stack_recorder.rb +33 -0
- data/lib/datadog/profiling/tag_builder.rb +48 -0
- data/lib/datadog/profiling/tasks/exec.rb +2 -2
- data/lib/datadog/profiling/tasks/setup.rb +6 -4
- data/lib/datadog/profiling.rb +29 -27
- data/lib/datadog/tracing/buffer.rb +9 -3
- data/lib/datadog/tracing/contrib/action_view/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/vendor/connection_specification.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +4 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +10 -3
- data/lib/datadog/tracing/contrib/dalli/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +9 -3
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +38 -2
- data/lib/datadog/tracing/contrib/ethon/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/extensions.rb +0 -2
- data/lib/datadog/tracing/contrib/faraday/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/grape/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/grpc/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/qless/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/que/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/racecar/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +3 -16
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
- data/lib/datadog/tracing/contrib/rake/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/redis/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/resque/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/rest_client/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +20 -1
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +11 -0
- data/lib/datadog/tracing/contrib/sinatra/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/sneakers/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +0 -1
- data/lib/datadog/tracing/event.rb +2 -1
- data/lib/datadog/tracing/sampling/priority_sampler.rb +4 -5
- data/lib/datadog/tracing/sampling/rule.rb +12 -6
- data/lib/datadog/tracing/sampling/rule_sampler.rb +3 -5
- data/lib/datadog/tracing/span_operation.rb +2 -3
- data/lib/datadog/tracing/trace_operation.rb +0 -1
- data/lib/ddtrace/transport/http/client.rb +2 -1
- data/lib/ddtrace/transport/http/response.rb +34 -4
- data/lib/ddtrace/transport/io/client.rb +3 -1
- data/lib/ddtrace/version.rb +1 -1
- data/lib/ddtrace.rb +1 -0
- metadata +43 -6
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
|
|
3
|
+
require 'datadog/appsec/instrumentation/gateway'
|
|
4
|
+
require 'datadog/appsec/reactive/operation'
|
|
5
|
+
require 'datadog/appsec/contrib/rails/reactive/action'
|
|
6
|
+
require 'datadog/appsec/event'
|
|
7
|
+
|
|
8
|
+
module Datadog
|
|
9
|
+
module AppSec
|
|
10
|
+
module Contrib
|
|
11
|
+
module Rails
|
|
12
|
+
module Gateway
|
|
13
|
+
# Watcher for Rails gateway events
|
|
14
|
+
module Watcher
|
|
15
|
+
def self.watch
|
|
16
|
+
Instrumentation.gateway.watch('rails.request.action') do |stack, request|
|
|
17
|
+
block = false
|
|
18
|
+
event = nil
|
|
19
|
+
waf_context = request.env['datadog.waf.context']
|
|
20
|
+
|
|
21
|
+
AppSec::Reactive::Operation.new('rails.request.action') do |op|
|
|
22
|
+
trace = active_trace
|
|
23
|
+
span = active_span
|
|
24
|
+
|
|
25
|
+
Rails::Reactive::Action.subscribe(op, waf_context) do |action, result, _block|
|
|
26
|
+
record = [:block, :monitor].include?(action)
|
|
27
|
+
if record
|
|
28
|
+
# TODO: should this hash be an Event instance instead?
|
|
29
|
+
event = {
|
|
30
|
+
waf_result: result,
|
|
31
|
+
trace: trace,
|
|
32
|
+
span: span,
|
|
33
|
+
request: request,
|
|
34
|
+
action: action
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
waf_context.events << event
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
_action, _result, block = Rails::Reactive::Action.publish(op, request)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
next [nil, [[:block, event]]] if block
|
|
45
|
+
|
|
46
|
+
ret, res = stack.call(request)
|
|
47
|
+
|
|
48
|
+
if event
|
|
49
|
+
res ||= []
|
|
50
|
+
res << [:monitor, event]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
[ret, res]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def active_trace
|
|
61
|
+
# TODO: factor out tracing availability detection
|
|
62
|
+
|
|
63
|
+
return unless defined?(Datadog::Tracing)
|
|
64
|
+
|
|
65
|
+
Datadog::Tracing.active_trace
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def active_span
|
|
69
|
+
# TODO: factor out tracing availability detection
|
|
70
|
+
|
|
71
|
+
return unless defined?(Datadog::Tracing)
|
|
72
|
+
|
|
73
|
+
Datadog::Tracing.active_span
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
require 'datadog/core/utils/only_once'
|
|
4
4
|
|
|
5
5
|
require 'datadog/appsec/contrib/patcher'
|
|
6
|
-
require 'datadog/appsec/contrib/rails/integration'
|
|
7
6
|
require 'datadog/appsec/contrib/rails/framework'
|
|
8
7
|
require 'datadog/appsec/contrib/rack/request_middleware'
|
|
8
|
+
require 'datadog/appsec/contrib/rack/request_body_middleware'
|
|
9
|
+
require 'datadog/appsec/contrib/rails/gateway/watcher'
|
|
9
10
|
|
|
10
11
|
require 'datadog/tracing/contrib/rack/middlewares'
|
|
11
12
|
|
|
@@ -31,6 +32,7 @@ module Datadog
|
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def patch
|
|
35
|
+
Gateway::Watcher.watch
|
|
34
36
|
patch_before_intialize
|
|
35
37
|
patch_after_intialize
|
|
36
38
|
|
|
@@ -49,6 +51,7 @@ module Datadog
|
|
|
49
51
|
# Otherwise the middleware stack will be frozen.
|
|
50
52
|
# Sometimes we don't want to activate middleware e.g. OpenTracing, etc.
|
|
51
53
|
add_middleware(app) if Datadog.configuration.tracing[:rails][:middleware]
|
|
54
|
+
patch_process_action
|
|
52
55
|
end
|
|
53
56
|
end
|
|
54
57
|
|
|
@@ -62,6 +65,36 @@ module Datadog
|
|
|
62
65
|
end
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
# Hook into ActionController::Instrumentation#process_action, which encompasses action filters
|
|
69
|
+
module ProcessActionPatch
|
|
70
|
+
def process_action(*args)
|
|
71
|
+
env = request.env
|
|
72
|
+
|
|
73
|
+
context = env['datadog.waf.context']
|
|
74
|
+
|
|
75
|
+
return super unless context
|
|
76
|
+
|
|
77
|
+
# TODO: handle exceptions, except for super
|
|
78
|
+
|
|
79
|
+
request_return, request_response = Instrumentation.gateway.push('rails.request.action', request) do
|
|
80
|
+
super
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if request_response && request_response.any? { |action, _event| action == :block }
|
|
84
|
+
@_response = ::ActionDispatch::Response.new(403,
|
|
85
|
+
{ 'Content-Type' => 'text/html' },
|
|
86
|
+
[Datadog::AppSec::Assets.blocked])
|
|
87
|
+
request_return = @_response.body
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
request_return
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def patch_process_action
|
|
95
|
+
ActionController::Instrumentation.prepend(ProcessActionPatch)
|
|
96
|
+
end
|
|
97
|
+
|
|
65
98
|
def include_middleware?(middleware, app)
|
|
66
99
|
found = false
|
|
67
100
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
|
|
3
|
+
require 'datadog/appsec/contrib/rails/request'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module AppSec
|
|
7
|
+
module Contrib
|
|
8
|
+
module Rails
|
|
9
|
+
module Reactive
|
|
10
|
+
# Dispatch data from a Rails request to the WAF context
|
|
11
|
+
module Action
|
|
12
|
+
def self.publish(op, request)
|
|
13
|
+
catch(:block) do
|
|
14
|
+
# params have been parsed from the request body
|
|
15
|
+
op.publish('rails.request.body', Rails::Request.parsed_body(request))
|
|
16
|
+
op.publish('rails.request.route_params', Rails::Request.route_params(request))
|
|
17
|
+
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.subscribe(op, waf_context)
|
|
23
|
+
addresses = [
|
|
24
|
+
'rails.request.body',
|
|
25
|
+
'rails.request.route_params',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
op.subscribe(*addresses) do |*values|
|
|
29
|
+
Datadog.logger.debug { "reacted to #{addresses.inspect}: #{values.inspect}" }
|
|
30
|
+
body = values[0]
|
|
31
|
+
path_params = values[1]
|
|
32
|
+
|
|
33
|
+
waf_args = {
|
|
34
|
+
'server.request.body' => body,
|
|
35
|
+
'server.request.path_params' => path_params,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
waf_timeout = Datadog::AppSec.settings.waf_timeout
|
|
39
|
+
action, result = waf_context.run(waf_args, waf_timeout)
|
|
40
|
+
|
|
41
|
+
Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
|
|
42
|
+
|
|
43
|
+
# TODO: encapsulate return array in a type
|
|
44
|
+
case action
|
|
45
|
+
when :monitor
|
|
46
|
+
Datadog.logger.debug { "WAF: #{result.inspect}" }
|
|
47
|
+
yield [action, result, false]
|
|
48
|
+
when :block
|
|
49
|
+
Datadog.logger.debug { "WAF: #{result.inspect}" }
|
|
50
|
+
yield [action, result, true]
|
|
51
|
+
throw(:block, [action, result, true])
|
|
52
|
+
when :good
|
|
53
|
+
Datadog.logger.debug { "WAF OK: #{result.inspect}" }
|
|
54
|
+
when :invalid_call
|
|
55
|
+
Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
|
|
56
|
+
when :invalid_rule, :invalid_flow, :no_rule
|
|
57
|
+
Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
|
|
58
|
+
else
|
|
59
|
+
Datadog.logger.debug { "WAF UNKNOWN: #{action.inspect} #{result.inspect}" }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AppSec
|
|
5
|
+
module Contrib
|
|
6
|
+
module Rails
|
|
7
|
+
# Normalized extration of data from ActionDispatch::Request
|
|
8
|
+
module Request
|
|
9
|
+
def self.parsed_body(request)
|
|
10
|
+
# usually Hash<String,String> but can be a more complex
|
|
11
|
+
# Hash<String,String||Array||Hash> when e.g coming from JSON or
|
|
12
|
+
# with Rails advanced param square bracket parsing
|
|
13
|
+
body = request.env['action_dispatch.request.request_parameters']
|
|
14
|
+
|
|
15
|
+
return if body.nil?
|
|
16
|
+
|
|
17
|
+
body.reject do |k, _v|
|
|
18
|
+
request.env['action_dispatch.request.path_parameters'].key?(k)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.route_params(request)
|
|
23
|
+
excluded = [:controller, :action]
|
|
24
|
+
|
|
25
|
+
request.env['action_dispatch.request.path_parameters'].reject do |k, _v|
|
|
26
|
+
excluded.include?(k)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
|
|
3
|
+
require 'datadog/appsec/instrumentation/gateway'
|
|
4
|
+
require 'datadog/appsec/reactive/operation'
|
|
5
|
+
require 'datadog/appsec/contrib/rack/reactive/request_body'
|
|
6
|
+
require 'datadog/appsec/contrib/sinatra/reactive/routed'
|
|
7
|
+
require 'datadog/appsec/event'
|
|
8
|
+
|
|
9
|
+
module Datadog
|
|
10
|
+
module AppSec
|
|
11
|
+
module Contrib
|
|
12
|
+
module Sinatra
|
|
13
|
+
module Gateway
|
|
14
|
+
# Watcher for Rails gateway events
|
|
15
|
+
module Watcher
|
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
|
17
|
+
def self.watch
|
|
18
|
+
Instrumentation.gateway.watch('sinatra.request.dispatch') do |stack, request|
|
|
19
|
+
block = false
|
|
20
|
+
event = nil
|
|
21
|
+
waf_context = request.env['datadog.waf.context']
|
|
22
|
+
|
|
23
|
+
AppSec::Reactive::Operation.new('sinatra.request.dispatch') do |op|
|
|
24
|
+
trace = active_trace
|
|
25
|
+
span = active_span
|
|
26
|
+
|
|
27
|
+
Rack::Reactive::RequestBody.subscribe(op, waf_context) do |action, result, _block|
|
|
28
|
+
record = [:block, :monitor].include?(action)
|
|
29
|
+
if record
|
|
30
|
+
# TODO: should this hash be an Event instance instead?
|
|
31
|
+
event = {
|
|
32
|
+
waf_result: result,
|
|
33
|
+
trace: trace,
|
|
34
|
+
span: span,
|
|
35
|
+
request: request,
|
|
36
|
+
action: action
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
waf_context.events << event
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
_action, _result, block = Rack::Reactive::RequestBody.publish(op, request)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
next [nil, [[:block, event]]] if block
|
|
47
|
+
|
|
48
|
+
ret, res = stack.call(request)
|
|
49
|
+
|
|
50
|
+
if event
|
|
51
|
+
res ||= []
|
|
52
|
+
res << [:monitor, event]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
[ret, res]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Instrumentation.gateway.watch('sinatra.request.routed') do |stack, (request, route_params)|
|
|
59
|
+
block = false
|
|
60
|
+
event = nil
|
|
61
|
+
waf_context = request.env['datadog.waf.context']
|
|
62
|
+
|
|
63
|
+
AppSec::Reactive::Operation.new('sinatra.request.routed') do |op|
|
|
64
|
+
trace = active_trace
|
|
65
|
+
span = active_span
|
|
66
|
+
|
|
67
|
+
Sinatra::Reactive::Routed.subscribe(op, waf_context) do |action, result, _block|
|
|
68
|
+
record = [:block, :monitor].include?(action)
|
|
69
|
+
if record
|
|
70
|
+
# TODO: should this hash be an Event instance instead?
|
|
71
|
+
event = {
|
|
72
|
+
waf_result: result,
|
|
73
|
+
trace: trace,
|
|
74
|
+
span: span,
|
|
75
|
+
request: request,
|
|
76
|
+
action: action
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
waf_context.events << event
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
_action, _result, block = Sinatra::Reactive::Routed.publish(op, [request, route_params])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
next [nil, [[:block, event]]] if block
|
|
87
|
+
|
|
88
|
+
ret, res = stack.call(request)
|
|
89
|
+
|
|
90
|
+
if event
|
|
91
|
+
res ||= []
|
|
92
|
+
res << [:monitor, event]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
[ret, res]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
# rubocop:enable Metrics/MethodLength
|
|
99
|
+
|
|
100
|
+
class << self
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def active_trace
|
|
104
|
+
# TODO: factor out tracing availability detection
|
|
105
|
+
|
|
106
|
+
return unless defined?(Datadog::Tracing)
|
|
107
|
+
|
|
108
|
+
Datadog::Tracing.active_trace
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def active_span
|
|
112
|
+
# TODO: factor out tracing availability detection
|
|
113
|
+
|
|
114
|
+
return unless defined?(Datadog::Tracing)
|
|
115
|
+
|
|
116
|
+
Datadog::Tracing.active_span
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
require 'datadog/tracing/contrib/rack/middlewares'
|
|
4
4
|
|
|
5
5
|
require 'datadog/appsec/contrib/patcher'
|
|
6
|
-
require 'datadog/appsec/contrib/sinatra/integration'
|
|
7
6
|
require 'datadog/appsec/contrib/rack/request_middleware'
|
|
8
7
|
require 'datadog/appsec/contrib/sinatra/framework'
|
|
8
|
+
require 'datadog/appsec/contrib/sinatra/gateway/watcher'
|
|
9
9
|
require 'datadog/tracing/contrib/sinatra/framework'
|
|
10
10
|
|
|
11
11
|
module Datadog
|
|
@@ -37,11 +37,68 @@ module Datadog
|
|
|
37
37
|
else
|
|
38
38
|
tracing_sinatra_framework.add_middleware(Datadog::AppSec::Contrib::Rack::RequestMiddleware, builder)
|
|
39
39
|
end
|
|
40
|
+
|
|
40
41
|
tracing_sinatra_framework.inspect_middlewares(builder)
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
# Hook into Base#dispatch!, which encompasses route filters
|
|
47
|
+
module DispatchPatch
|
|
48
|
+
def dispatch!
|
|
49
|
+
env = @request.env
|
|
50
|
+
|
|
51
|
+
context = env['datadog.waf.context']
|
|
52
|
+
|
|
53
|
+
return super unless context
|
|
54
|
+
|
|
55
|
+
# TODO: handle exceptions, except for super
|
|
56
|
+
|
|
57
|
+
request_return, request_response = Instrumentation.gateway.push('sinatra.request.dispatch', request) do
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if request_response && request_response.any? { |action, _event| action == :block }
|
|
62
|
+
self.response = ::Sinatra::Response.new([Datadog::AppSec::Assets.blocked],
|
|
63
|
+
403,
|
|
64
|
+
{ 'Content-Type' => 'text/html' })
|
|
65
|
+
request_return = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
request_return
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Hook into Base#route_eval, which
|
|
73
|
+
# path params are returned by pattern.params in process_route, then
|
|
74
|
+
# merged with normal params, so we get both
|
|
75
|
+
module RoutePatch
|
|
76
|
+
def process_route(*)
|
|
77
|
+
env = @request.env
|
|
78
|
+
|
|
79
|
+
context = env['datadog.waf.context']
|
|
80
|
+
|
|
81
|
+
return super unless context
|
|
82
|
+
|
|
83
|
+
# process_route is called repeatedly until a route is found.
|
|
84
|
+
# Until then, params has no route params.
|
|
85
|
+
# Capture normal params.
|
|
86
|
+
base_params = params
|
|
87
|
+
|
|
88
|
+
super do |*args|
|
|
89
|
+
# This block is called only once the route is found.
|
|
90
|
+
# At this point params has both route params and normal params.
|
|
91
|
+
route_params = params.each.with_object({}) { |(k, v), h| h[k] = v unless base_params.key?(k) }
|
|
92
|
+
|
|
93
|
+
Instrumentation.gateway.push('sinatra.request.routed', [request, route_params])
|
|
94
|
+
|
|
95
|
+
# TODO: handle block
|
|
96
|
+
|
|
97
|
+
yield(*args)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
45
102
|
# Patcher for AppSec on Sinatra
|
|
46
103
|
module Patcher
|
|
47
104
|
include Datadog::AppSec::Contrib::Patcher
|
|
@@ -57,9 +114,11 @@ module Datadog
|
|
|
57
114
|
end
|
|
58
115
|
|
|
59
116
|
def patch
|
|
117
|
+
Gateway::Watcher.watch
|
|
60
118
|
patch_default_middlewares
|
|
119
|
+
patch_dispatch
|
|
120
|
+
patch_route
|
|
61
121
|
setup_security
|
|
62
|
-
|
|
63
122
|
Patcher.instance_variable_set(:@patched, true)
|
|
64
123
|
end
|
|
65
124
|
|
|
@@ -70,6 +129,14 @@ module Datadog
|
|
|
70
129
|
def patch_default_middlewares
|
|
71
130
|
::Sinatra::Base.singleton_class.prepend(DefaultMiddlewarePatch)
|
|
72
131
|
end
|
|
132
|
+
|
|
133
|
+
def patch_dispatch
|
|
134
|
+
::Sinatra::Base.prepend(DispatchPatch)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def patch_route
|
|
138
|
+
::Sinatra::Base.prepend(RoutePatch)
|
|
139
|
+
end
|
|
73
140
|
end
|
|
74
141
|
end
|
|
75
142
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AppSec
|
|
5
|
+
module Contrib
|
|
6
|
+
module Sinatra
|
|
7
|
+
module Reactive
|
|
8
|
+
# Dispatch data from a Rack request to the WAF context
|
|
9
|
+
module Routed
|
|
10
|
+
def self.publish(op, data)
|
|
11
|
+
_request, route_params = data
|
|
12
|
+
|
|
13
|
+
catch(:block) do
|
|
14
|
+
op.publish('sinatra.request.route_params', route_params)
|
|
15
|
+
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.subscribe(op, waf_context)
|
|
21
|
+
addresses = [
|
|
22
|
+
'sinatra.request.route_params',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
op.subscribe(*addresses) do |*values|
|
|
26
|
+
Datadog.logger.debug { "reacted to #{addresses.inspect}: #{values.inspect}" }
|
|
27
|
+
path_params = values[0]
|
|
28
|
+
|
|
29
|
+
waf_args = {
|
|
30
|
+
'server.request.path_params' => path_params,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
waf_timeout = Datadog::AppSec.settings.waf_timeout
|
|
34
|
+
action, result = waf_context.run(waf_args, waf_timeout)
|
|
35
|
+
|
|
36
|
+
Datadog.logger.debug { "WAF TIMEOUT: #{result.inspect}" } if result.timeout
|
|
37
|
+
|
|
38
|
+
# TODO: encapsulate return array in a type
|
|
39
|
+
case action
|
|
40
|
+
when :monitor
|
|
41
|
+
Datadog.logger.debug { "WAF: #{result.inspect}" }
|
|
42
|
+
yield [action, result, false]
|
|
43
|
+
when :block
|
|
44
|
+
Datadog.logger.debug { "WAF: #{result.inspect}" }
|
|
45
|
+
yield [action, result, true]
|
|
46
|
+
throw(:block, [action, result, true])
|
|
47
|
+
when :good
|
|
48
|
+
Datadog.logger.debug { "WAF OK: #{result.inspect}" }
|
|
49
|
+
when :invalid_call
|
|
50
|
+
Datadog.logger.debug { "WAF CALL ERROR: #{result.inspect}" }
|
|
51
|
+
when :invalid_rule, :invalid_flow, :no_rule
|
|
52
|
+
Datadog.logger.debug { "WAF RULE ERROR: #{result.inspect}" }
|
|
53
|
+
else
|
|
54
|
+
Datadog.logger.debug { "WAF UNKNOWN: #{action.inspect} #{result.inspect}" }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/datadog/appsec/event.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# typed: false
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
require 'datadog/appsec/contrib/rack/request'
|
|
4
6
|
require 'datadog/appsec/contrib/rack/response'
|
|
5
7
|
require 'datadog/appsec/rate_limiter'
|
|
@@ -36,13 +38,21 @@ module Datadog
|
|
|
36
38
|
Content-Language
|
|
37
39
|
].map!(&:downcase).freeze
|
|
38
40
|
|
|
41
|
+
# Record events for a trace
|
|
42
|
+
#
|
|
43
|
+
# This is expected to be called only once per trace for the rate limiter
|
|
44
|
+
# to properly apply
|
|
39
45
|
def self.record(*events)
|
|
46
|
+
# ensure rate limiter is called only when there are events to record
|
|
47
|
+
return if events.empty?
|
|
48
|
+
|
|
40
49
|
Datadog::AppSec::RateLimiter.limit(:traces) do
|
|
41
50
|
record_via_span(*events)
|
|
42
51
|
end
|
|
43
52
|
end
|
|
44
53
|
|
|
45
54
|
# rubocop:disable Metrics/AbcSize
|
|
55
|
+
# rubocop:disable Metrics/MethodLength
|
|
46
56
|
def self.record_via_span(*events)
|
|
47
57
|
events.group_by { |e| e[:trace] }.each do |trace, event_group|
|
|
48
58
|
unless trace
|
|
@@ -58,30 +68,34 @@ module Datadog
|
|
|
58
68
|
|
|
59
69
|
span.set_tag('appsec.event', 'true') if span
|
|
60
70
|
|
|
61
|
-
request = event[:request]
|
|
62
|
-
response = event[:response]
|
|
63
|
-
|
|
64
71
|
# TODO: assume HTTP request context for now
|
|
65
|
-
request_headers = AppSec::Contrib::Rack::Request.headers(request).select do |k, _|
|
|
66
|
-
ALLOWED_REQUEST_HEADERS.include?(k.downcase)
|
|
67
|
-
end
|
|
68
|
-
response_headers = AppSec::Contrib::Rack::Response.headers(response).select do |k, _|
|
|
69
|
-
ALLOWED_RESPONSE_HEADERS.include?(k.downcase)
|
|
70
|
-
end
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (request = event[:request])
|
|
74
|
+
request_headers = AppSec::Contrib::Rack::Request.headers(request).select do |k, _|
|
|
75
|
+
ALLOWED_REQUEST_HEADERS.include?(k.downcase)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
request_headers.each do |header, value|
|
|
79
|
+
tags["http.request.headers.#{header}"] = value
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
tags['http.host'] = request.host
|
|
83
|
+
tags['http.useragent'] = request.user_agent
|
|
84
|
+
tags['network.client.ip'] = request.ip
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
tags["http.response.headers.#{header}"] = value
|
|
86
|
+
# tags['actor.ip'] = request.ip # TODO: uses client IP resolution algorithm
|
|
78
87
|
end
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
if (response = event[:response])
|
|
90
|
+
response_headers = AppSec::Contrib::Rack::Response.headers(response).select do |k, _|
|
|
91
|
+
ALLOWED_RESPONSE_HEADERS.include?(k.downcase)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
response_headers.each do |header, value|
|
|
95
|
+
tags["http.response.headers.#{header}"] = value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
83
98
|
|
|
84
|
-
# tags['actor.ip'] = request.ip # TODO: uses client IP resolution algorithm
|
|
85
99
|
tags['_dd.origin'] = 'appsec'
|
|
86
100
|
|
|
87
101
|
# accumulate triggers
|
|
@@ -100,6 +114,7 @@ module Datadog
|
|
|
100
114
|
end
|
|
101
115
|
end
|
|
102
116
|
end
|
|
117
|
+
# rubocop:enable Metrics/MethodLength
|
|
103
118
|
# rubocop:enable Metrics/AbcSize
|
|
104
119
|
end
|
|
105
120
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# typed: false
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
3
|
require 'datadog/appsec/configuration'
|
|
5
4
|
|
|
6
5
|
module Datadog
|
|
@@ -25,8 +24,6 @@ module Datadog
|
|
|
25
24
|
# Merges {Datadog::AppSec::Configuration::Settings} and {Datadog::AppSec::Configuration::DSL}
|
|
26
25
|
# into a single read/write object.
|
|
27
26
|
class AppSecAdapter
|
|
28
|
-
extend Forwardable
|
|
29
|
-
|
|
30
27
|
def initialize(settings)
|
|
31
28
|
@settings = settings
|
|
32
29
|
end
|