ddtrace 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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