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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -16
  3. data/CHANGELOG.md +31 -2
  4. data/LICENSE-3rdparty.csv +3 -2
  5. data/README.md +2 -2
  6. data/ddtrace.gemspec +12 -3
  7. data/docs/GettingStarted.md +19 -2
  8. data/docs/ProfilingDevelopment.md +8 -8
  9. data/docs/UpgradeGuide.md +3 -3
  10. data/ext/ddtrace_profiling_loader/ddtrace_profiling_loader.c +118 -0
  11. data/ext/ddtrace_profiling_loader/extconf.rb +53 -0
  12. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +31 -5
  13. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +0 -8
  14. data/ext/ddtrace_profiling_native_extension/collectors_stack.c +278 -0
  15. data/ext/ddtrace_profiling_native_extension/extconf.rb +70 -100
  16. data/ext/ddtrace_profiling_native_extension/libddprof_helpers.h +13 -0
  17. data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +186 -0
  18. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +579 -7
  19. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +30 -0
  20. data/ext/ddtrace_profiling_native_extension/profiling.c +7 -0
  21. data/ext/ddtrace_profiling_native_extension/stack_recorder.c +139 -0
  22. data/ext/ddtrace_profiling_native_extension/stack_recorder.h +28 -0
  23. data/lib/datadog/appsec/autoload.rb +2 -2
  24. data/lib/datadog/appsec/configuration/settings.rb +19 -0
  25. data/lib/datadog/appsec/configuration.rb +8 -0
  26. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +76 -33
  27. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -0
  28. data/lib/datadog/appsec/contrib/rack/patcher.rb +0 -1
  29. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +64 -0
  30. data/lib/datadog/appsec/contrib/rack/request.rb +6 -0
  31. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +41 -0
  32. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +60 -5
  33. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +81 -0
  34. data/lib/datadog/appsec/contrib/rails/patcher.rb +34 -1
  35. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +68 -0
  36. data/lib/datadog/appsec/contrib/rails/request.rb +33 -0
  37. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +124 -0
  38. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +69 -2
  39. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +63 -0
  40. data/lib/datadog/appsec/event.rb +33 -18
  41. data/lib/datadog/appsec/extensions.rb +0 -3
  42. data/lib/datadog/appsec/processor.rb +45 -2
  43. data/lib/datadog/appsec/rate_limiter.rb +5 -0
  44. data/lib/datadog/appsec/reactive/operation.rb +0 -1
  45. data/lib/datadog/ci/ext/environment.rb +21 -7
  46. data/lib/datadog/core/configuration/agent_settings_resolver.rb +1 -1
  47. data/lib/datadog/core/configuration/components.rb +22 -4
  48. data/lib/datadog/core/configuration/settings.rb +3 -3
  49. data/lib/datadog/core/configuration.rb +7 -5
  50. data/lib/datadog/core/environment/cgroup.rb +3 -1
  51. data/lib/datadog/core/environment/container.rb +2 -1
  52. data/lib/datadog/core/environment/variable_helpers.rb +26 -2
  53. data/lib/datadog/core/logging/ext.rb +11 -0
  54. data/lib/datadog/core/metrics/client.rb +15 -5
  55. data/lib/datadog/core/runtime/metrics.rb +1 -1
  56. data/lib/datadog/core/workers/async.rb +3 -1
  57. data/lib/datadog/core/workers/runtime_metrics.rb +0 -3
  58. data/lib/datadog/core.rb +6 -0
  59. data/lib/datadog/kit/enable_core_dumps.rb +50 -0
  60. data/lib/datadog/kit/identity.rb +63 -0
  61. data/lib/datadog/kit.rb +11 -0
  62. data/lib/datadog/opentracer/tracer.rb +0 -2
  63. data/lib/datadog/profiling/collectors/old_stack.rb +298 -0
  64. data/lib/datadog/profiling/collectors/stack.rb +6 -287
  65. data/lib/datadog/profiling/encoding/profile.rb +0 -1
  66. data/lib/datadog/profiling/ext.rb +1 -1
  67. data/lib/datadog/profiling/flush.rb +1 -1
  68. data/lib/datadog/profiling/load_native_extension.rb +22 -0
  69. data/lib/datadog/profiling/recorder.rb +1 -1
  70. data/lib/datadog/profiling/scheduler.rb +1 -1
  71. data/lib/datadog/profiling/stack_recorder.rb +33 -0
  72. data/lib/datadog/profiling/tag_builder.rb +48 -0
  73. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  74. data/lib/datadog/profiling/tasks/setup.rb +6 -4
  75. data/lib/datadog/profiling.rb +29 -27
  76. data/lib/datadog/tracing/buffer.rb +9 -3
  77. data/lib/datadog/tracing/contrib/action_view/patcher.rb +0 -1
  78. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
  79. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  80. data/lib/datadog/tracing/contrib/active_record/vendor/connection_specification.rb +1 -1
  81. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +4 -2
  82. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +10 -3
  83. data/lib/datadog/tracing/contrib/dalli/patcher.rb +0 -1
  84. data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +0 -1
  85. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +9 -3
  86. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +38 -2
  87. data/lib/datadog/tracing/contrib/ethon/patcher.rb +0 -1
  88. data/lib/datadog/tracing/contrib/extensions.rb +0 -2
  89. data/lib/datadog/tracing/contrib/faraday/patcher.rb +0 -1
  90. data/lib/datadog/tracing/contrib/grape/patcher.rb +0 -1
  91. data/lib/datadog/tracing/contrib/graphql/patcher.rb +0 -1
  92. data/lib/datadog/tracing/contrib/grpc/patcher.rb +0 -1
  93. data/lib/datadog/tracing/contrib/kafka/patcher.rb +0 -1
  94. data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +2 -1
  95. data/lib/datadog/tracing/contrib/qless/patcher.rb +0 -1
  96. data/lib/datadog/tracing/contrib/que/patcher.rb +0 -1
  97. data/lib/datadog/tracing/contrib/racecar/patcher.rb +0 -1
  98. data/lib/datadog/tracing/contrib/rails/log_injection.rb +3 -16
  99. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  100. data/lib/datadog/tracing/contrib/rake/patcher.rb +0 -1
  101. data/lib/datadog/tracing/contrib/redis/patcher.rb +0 -1
  102. data/lib/datadog/tracing/contrib/resque/patcher.rb +0 -1
  103. data/lib/datadog/tracing/contrib/rest_client/patcher.rb +0 -1
  104. data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +2 -1
  105. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +1 -0
  106. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +20 -1
  107. data/lib/datadog/tracing/contrib/sinatra/framework.rb +11 -0
  108. data/lib/datadog/tracing/contrib/sinatra/patcher.rb +0 -1
  109. data/lib/datadog/tracing/contrib/sneakers/patcher.rb +0 -1
  110. data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +0 -1
  111. data/lib/datadog/tracing/event.rb +2 -1
  112. data/lib/datadog/tracing/sampling/priority_sampler.rb +4 -5
  113. data/lib/datadog/tracing/sampling/rule.rb +12 -6
  114. data/lib/datadog/tracing/sampling/rule_sampler.rb +3 -5
  115. data/lib/datadog/tracing/span_operation.rb +2 -3
  116. data/lib/datadog/tracing/trace_operation.rb +0 -1
  117. data/lib/ddtrace/transport/http/client.rb +2 -1
  118. data/lib/ddtrace/transport/http/response.rb +34 -4
  119. data/lib/ddtrace/transport/io/client.rb +3 -1
  120. data/lib/ddtrace/version.rb +1 -1
  121. data/lib/ddtrace.rb +1 -0
  122. 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
@@ -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
- request_headers.each do |header, value|
73
- tags["http.request.headers.#{header}"] = value
74
- end
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
- response_headers.each do |header, value|
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
- tags['http.host'] = request.host
81
- tags['http.useragent'] = request.user_agent
82
- tags['network.client.ip'] = request.ip
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