datadog 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +2 -5
- data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/lib/datadog/appsec/actions_handler.rb +27 -0
- data/lib/datadog/appsec/component.rb +14 -8
- data/lib/datadog/appsec/configuration/settings.rb +9 -0
- data/lib/datadog/appsec/context.rb +28 -8
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -5
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +15 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +5 -4
- data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -13
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -8
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -26
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +1 -1
- data/lib/datadog/appsec/ext.rb +6 -1
- data/lib/datadog/appsec/metrics/collector.rb +38 -0
- data/lib/datadog/appsec/metrics/exporter.rb +35 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
- data/lib/datadog/appsec/metrics.rb +13 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +5 -4
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +1 -1
- data/lib/datadog/appsec/processor.rb +4 -3
- data/lib/datadog/appsec/response.rb +18 -80
- data/lib/datadog/appsec/security_engine/result.rb +67 -0
- data/lib/datadog/appsec/security_engine/runner.rb +88 -0
- data/lib/datadog/appsec/security_engine.rb +9 -0
- data/lib/datadog/appsec.rb +14 -5
- data/lib/datadog/di/component.rb +2 -0
- data/lib/datadog/di/probe_notification_builder.rb +6 -0
- data/lib/datadog/di/redactor.rb +0 -1
- data/lib/datadog/di/remote.rb +26 -5
- data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/extensions.rb +15 -3
- data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
- data/lib/datadog/version.rb +1 -1
- metadata +32 -18
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/processor/context.rb +0 -107
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'metrics'
|
4
|
+
|
3
5
|
module Datadog
|
4
6
|
module AppSec
|
5
7
|
# This class accumulates the context over the request life-cycle and exposes
|
@@ -7,10 +9,7 @@ module Datadog
|
|
7
9
|
class Context
|
8
10
|
ActiveContextError = Class.new(StandardError)
|
9
11
|
|
10
|
-
attr_reader :trace, :span
|
11
|
-
|
12
|
-
# NOTE: This is an intermediate state and will be changed
|
13
|
-
attr_reader :waf_runner
|
12
|
+
attr_reader :trace, :span, :events
|
14
13
|
|
15
14
|
class << self
|
16
15
|
def activate(context)
|
@@ -34,16 +33,37 @@ module Datadog
|
|
34
33
|
def initialize(trace, span, security_engine)
|
35
34
|
@trace = trace
|
36
35
|
@span = span
|
36
|
+
@events = []
|
37
37
|
@security_engine = security_engine
|
38
|
-
@waf_runner = security_engine.
|
38
|
+
@waf_runner = security_engine.new_runner
|
39
|
+
@metrics = Metrics::Collector.new
|
39
40
|
end
|
40
41
|
|
41
42
|
def run_waf(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
|
42
|
-
@waf_runner.run(persistent_data, ephemeral_data, timeout)
|
43
|
+
result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
|
44
|
+
|
45
|
+
@metrics.record_waf(result)
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
|
50
|
+
result = @waf_runner.run(persistent_data, ephemeral_data, timeout)
|
51
|
+
|
52
|
+
Metrics::Telemetry.report_rasp(type, result)
|
53
|
+
@metrics.record_rasp(result)
|
54
|
+
|
55
|
+
result
|
43
56
|
end
|
44
57
|
|
45
|
-
def
|
46
|
-
@waf_runner.run(
|
58
|
+
def extract_schema
|
59
|
+
@waf_runner.run({ 'waf.context.processor' => { 'extract-schema' => true } }, {})
|
60
|
+
end
|
61
|
+
|
62
|
+
def export_metrics
|
63
|
+
return if @span.nil?
|
64
|
+
|
65
|
+
Metrics::Exporter.export_waf_metrics(@metrics.waf, @span)
|
66
|
+
Metrics::Exporter.export_rasp_metrics(@metrics.rasp, @span)
|
47
67
|
end
|
48
68
|
|
49
69
|
def finalize
|
@@ -9,6 +9,8 @@ module Datadog
|
|
9
9
|
module_function
|
10
10
|
|
11
11
|
def detect_sql_injection(sql, adapter_name)
|
12
|
+
return unless AppSec.rasp_enabled?
|
13
|
+
|
12
14
|
context = AppSec.active_context
|
13
15
|
return unless context
|
14
16
|
|
@@ -25,7 +27,7 @@ module Datadog
|
|
25
27
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
26
28
|
result = context.run_rasp(Ext::RASP_SQLI, {}, ephemeral_data, waf_timeout)
|
27
29
|
|
28
|
-
if result.
|
30
|
+
if result.match?
|
29
31
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
30
32
|
|
31
33
|
event = {
|
@@ -35,7 +37,9 @@ module Datadog
|
|
35
37
|
sql: sql,
|
36
38
|
actions: result.actions
|
37
39
|
}
|
38
|
-
context.
|
40
|
+
context.events << event
|
41
|
+
|
42
|
+
ActionsHandler.handle(result.actions)
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
@@ -16,16 +16,10 @@ module Datadog
|
|
16
16
|
|
17
17
|
gateway_multiplex = Gateway::Multiplex.new(multiplex)
|
18
18
|
|
19
|
-
multiplex_return,
|
19
|
+
multiplex_return, _gateway_multiplex = Instrumentation.gateway.push('graphql.multiplex', gateway_multiplex) do
|
20
20
|
super
|
21
21
|
end
|
22
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
23
|
multiplex_return
|
30
24
|
end
|
31
25
|
end
|
@@ -22,7 +22,6 @@ module Datadog
|
|
22
22
|
# This time we don't throw but use next
|
23
23
|
def watch_multiplex(gateway = Instrumentation.gateway)
|
24
24
|
gateway.watch('graphql.multiplex', :appsec) do |stack, gateway_multiplex|
|
25
|
-
block = false
|
26
25
|
event = nil
|
27
26
|
context = AppSec::Context.active
|
28
27
|
engine = AppSec::Reactive::Engine.new
|
@@ -38,14 +37,14 @@ module Datadog
|
|
38
37
|
}
|
39
38
|
|
40
39
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
41
|
-
context.
|
40
|
+
context.events << event
|
41
|
+
|
42
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
+
GraphQL::Reactive::Multiplex.publish(engine, gateway_multiplex)
|
45
46
|
end
|
46
47
|
|
47
|
-
next [nil, [[:block, event]]] if block
|
48
|
-
|
49
48
|
stack.call(gateway_multiplex.arguments)
|
50
49
|
end
|
51
50
|
end
|
@@ -32,7 +32,7 @@ module Datadog
|
|
32
32
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
33
33
|
result = context.run_waf(persistent_data, {}, waf_timeout)
|
34
34
|
|
35
|
-
next
|
35
|
+
next unless result.match?
|
36
36
|
|
37
37
|
yield result
|
38
38
|
throw(:block, true) unless result.actions.empty?
|
@@ -30,7 +30,7 @@ module Datadog
|
|
30
30
|
engine = AppSec::Reactive::Engine.new
|
31
31
|
|
32
32
|
Rack::Reactive::Request.subscribe(engine, context) do |result|
|
33
|
-
if result.
|
33
|
+
if result.match?
|
34
34
|
# TODO: should this hash be an Event instance instead?
|
35
35
|
event = {
|
36
36
|
waf_result: result,
|
@@ -43,12 +43,13 @@ module Datadog
|
|
43
43
|
# We want to keep the trace in case of security event
|
44
44
|
context.trace.keep! if context.trace
|
45
45
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
46
|
-
context.
|
46
|
+
context.events << event
|
47
|
+
|
48
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
|
-
|
51
|
-
throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
|
52
|
+
Rack::Reactive::Request.publish(engine, gateway_request)
|
52
53
|
|
53
54
|
stack.call(gateway_request.request)
|
54
55
|
end
|
@@ -61,7 +62,7 @@ module Datadog
|
|
61
62
|
engine = AppSec::Reactive::Engine.new
|
62
63
|
|
63
64
|
Rack::Reactive::Response.subscribe(engine, context) do |result|
|
64
|
-
if result.
|
65
|
+
if result.match?
|
65
66
|
# TODO: should this hash be an Event instance instead?
|
66
67
|
event = {
|
67
68
|
waf_result: result,
|
@@ -74,12 +75,13 @@ module Datadog
|
|
74
75
|
# We want to keep the trace in case of security event
|
75
76
|
context.trace.keep! if context.trace
|
76
77
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
77
|
-
context.
|
78
|
+
context.events << event
|
79
|
+
|
80
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
78
81
|
end
|
79
82
|
end
|
80
83
|
|
81
|
-
|
82
|
-
throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
|
84
|
+
Rack::Reactive::Response.publish(engine, gateway_response)
|
83
85
|
|
84
86
|
stack.call(gateway_response.response)
|
85
87
|
end
|
@@ -92,7 +94,7 @@ module Datadog
|
|
92
94
|
engine = AppSec::Reactive::Engine.new
|
93
95
|
|
94
96
|
Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
|
95
|
-
if result.
|
97
|
+
if result.match?
|
96
98
|
# TODO: should this hash be an Event instance instead?
|
97
99
|
event = {
|
98
100
|
waf_result: result,
|
@@ -105,12 +107,13 @@ module Datadog
|
|
105
107
|
# We want to keep the trace in case of security event
|
106
108
|
context.trace.keep! if context.trace
|
107
109
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
108
|
-
context.
|
110
|
+
context.events << event
|
111
|
+
|
112
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
109
113
|
end
|
110
114
|
end
|
111
115
|
|
112
|
-
|
113
|
-
throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
|
116
|
+
Rack::Reactive::RequestBody.publish(engine, gateway_request)
|
114
117
|
|
115
118
|
stack.call(gateway_request.request)
|
116
119
|
end
|
@@ -55,7 +55,7 @@ module Datadog
|
|
55
55
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
56
56
|
result = context.run_waf(persistent_data, {}, waf_timeout)
|
57
57
|
|
58
|
-
next
|
58
|
+
next unless result.match?
|
59
59
|
|
60
60
|
yield result
|
61
61
|
throw(:block, true) unless result.actions.empty?
|
@@ -33,7 +33,7 @@ module Datadog
|
|
33
33
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
34
34
|
result = context.run_waf(persistent_data, {}, waf_timeout)
|
35
35
|
|
36
|
-
next
|
36
|
+
next unless result.match?
|
37
37
|
|
38
38
|
yield result
|
39
39
|
throw(:block, true) unless result.actions.empty?
|
@@ -39,7 +39,7 @@ module Datadog
|
|
39
39
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
40
40
|
result = context.run_waf(persistent_data, {}, waf_timeout)
|
41
41
|
|
42
|
-
next
|
42
|
+
next unless result.match?
|
43
43
|
|
44
44
|
yield result
|
45
45
|
throw(:block, true) unless result.actions.empty?
|
@@ -24,15 +24,15 @@ module Datadog
|
|
24
24
|
# TODO: handle exceptions, except for @app.call
|
25
25
|
|
26
26
|
http_response = nil
|
27
|
-
|
28
|
-
http_response, = Instrumentation.gateway.push('rack.request.body', Gateway::Request.new(env)) do
|
27
|
+
interrupt_params = catch(::Datadog::AppSec::Ext::INTERRUPT) do
|
28
|
+
http_response, _request = Instrumentation.gateway.push('rack.request.body', Gateway::Request.new(env)) do
|
29
29
|
@app.call(env)
|
30
30
|
end
|
31
31
|
|
32
32
|
nil
|
33
33
|
end
|
34
34
|
|
35
|
-
return AppSec::Response.
|
35
|
+
return AppSec::Response.from_interrupt_params(interrupt_params, env['HTTP_ACCEPT']).to_rack if interrupt_params
|
36
36
|
|
37
37
|
http_response
|
38
38
|
end
|
@@ -76,8 +76,8 @@ module Datadog
|
|
76
76
|
gateway_request = Gateway::Request.new(env)
|
77
77
|
gateway_response = nil
|
78
78
|
|
79
|
-
|
80
|
-
http_response, = Instrumentation.gateway.push('rack.request', gateway_request) do
|
79
|
+
interrupt_params = catch(::Datadog::AppSec::Ext::INTERRUPT) do
|
80
|
+
http_response, _gateway_request = Instrumentation.gateway.push('rack.request', gateway_request) do
|
81
81
|
@app.call(env)
|
82
82
|
end
|
83
83
|
|
@@ -90,27 +90,29 @@ module Datadog
|
|
90
90
|
nil
|
91
91
|
end
|
92
92
|
|
93
|
-
|
93
|
+
if interrupt_params
|
94
|
+
http_response = AppSec::Response.from_interrupt_params(interrupt_params, env['HTTP_ACCEPT']).to_rack
|
95
|
+
end
|
94
96
|
|
95
|
-
if
|
96
|
-
ctx.
|
97
|
+
if AppSec.api_security_enabled?
|
98
|
+
ctx.events << {
|
97
99
|
trace: ctx.trace,
|
98
100
|
span: ctx.span,
|
99
|
-
waf_result:
|
101
|
+
waf_result: ctx.extract_schema,
|
100
102
|
}
|
101
103
|
end
|
102
104
|
|
103
|
-
ctx.
|
105
|
+
ctx.events.each do |e|
|
104
106
|
e[:response] ||= gateway_response
|
105
107
|
e[:request] ||= gateway_request
|
106
108
|
end
|
107
109
|
|
108
|
-
AppSec::Event.record(ctx.span, *ctx.
|
110
|
+
AppSec::Event.record(ctx.span, *ctx.events)
|
109
111
|
|
110
112
|
http_response
|
111
113
|
ensure
|
112
114
|
if ctx
|
113
|
-
|
115
|
+
ctx.export_metrics
|
114
116
|
Datadog::AppSec::Context.deactivate
|
115
117
|
end
|
116
118
|
end
|
@@ -198,19 +200,6 @@ module Datadog
|
|
198
200
|
end
|
199
201
|
end
|
200
202
|
|
201
|
-
def add_waf_runtime_tags(context)
|
202
|
-
span = context.span
|
203
|
-
context = context.waf_runner
|
204
|
-
|
205
|
-
return unless span && context
|
206
|
-
|
207
|
-
span.set_tag('_dd.appsec.waf.timeouts', context.timeouts)
|
208
|
-
|
209
|
-
# these tags expect time in us
|
210
|
-
span.set_tag('_dd.appsec.waf.duration', context.time_ns / 1000.0)
|
211
|
-
span.set_tag('_dd.appsec.waf.duration_ext', context.time_ext_ns / 1000.0)
|
212
|
-
end
|
213
|
-
|
214
203
|
def to_rack_header(header)
|
215
204
|
@rack_headers[header] ||= Datadog::Tracing::Contrib::Rack::Header.to_rack_header(header)
|
216
205
|
end
|
@@ -26,7 +26,7 @@ module Datadog
|
|
26
26
|
engine = AppSec::Reactive::Engine.new
|
27
27
|
|
28
28
|
Rails::Reactive::Action.subscribe(engine, context) do |result|
|
29
|
-
if result.
|
29
|
+
if result.match?
|
30
30
|
# TODO: should this hash be an Event instance instead?
|
31
31
|
event = {
|
32
32
|
waf_result: result,
|
@@ -39,12 +39,13 @@ module Datadog
|
|
39
39
|
# We want to keep the trace in case of security event
|
40
40
|
context.trace.keep! if context.trace
|
41
41
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
42
|
-
context.
|
42
|
+
context.events << event
|
43
|
+
|
44
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
|
47
|
-
next [nil, [[:block, event]]] if block
|
48
|
+
Rails::Reactive::Action.publish(engine, gateway_request)
|
48
49
|
|
49
50
|
stack.call(gateway_request.request)
|
50
51
|
end
|
@@ -80,22 +80,12 @@ module Datadog
|
|
80
80
|
# TODO: handle exceptions, except for super
|
81
81
|
|
82
82
|
gateway_request = Gateway::Request.new(request)
|
83
|
-
request_return, request_response = Instrumentation.gateway.push('rails.request.action', gateway_request) do
|
84
|
-
super
|
85
|
-
end
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
if blocked_event
|
90
|
-
@_response = AppSec::Response.negotiate(
|
91
|
-
env,
|
92
|
-
blocked_event.last[:actions]
|
93
|
-
).to_action_dispatch_response
|
94
|
-
request_return = @_response.body
|
95
|
-
end
|
84
|
+
http_response, _gateway_request = Instrumentation.gateway.push('rails.request.action', gateway_request) do
|
85
|
+
super
|
96
86
|
end
|
97
87
|
|
98
|
-
|
88
|
+
http_response
|
99
89
|
end
|
100
90
|
end
|
101
91
|
|
@@ -39,7 +39,7 @@ module Datadog
|
|
39
39
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
40
40
|
result = context.run_waf(persistent_data, {}, waf_timeout)
|
41
41
|
|
42
|
-
next
|
42
|
+
next unless result.match?
|
43
43
|
|
44
44
|
yield result
|
45
45
|
throw(:block, true) unless result.actions.empty?
|
@@ -28,7 +28,7 @@ module Datadog
|
|
28
28
|
engine = AppSec::Reactive::Engine.new
|
29
29
|
|
30
30
|
Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
|
31
|
-
if result.
|
31
|
+
if result.match?
|
32
32
|
# TODO: should this hash be an Event instance instead?
|
33
33
|
event = {
|
34
34
|
waf_result: result,
|
@@ -41,12 +41,13 @@ module Datadog
|
|
41
41
|
# We want to keep the trace in case of security event
|
42
42
|
context.trace.keep! if context.trace
|
43
43
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
44
|
-
context.
|
44
|
+
context.events << event
|
45
|
+
|
46
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
|
49
|
-
next [nil, [[:block, event]]] if block
|
50
|
+
Rack::Reactive::RequestBody.publish(engine, gateway_request)
|
50
51
|
|
51
52
|
stack.call(gateway_request.request)
|
52
53
|
end
|
@@ -59,7 +60,7 @@ module Datadog
|
|
59
60
|
engine = AppSec::Reactive::Engine.new
|
60
61
|
|
61
62
|
Sinatra::Reactive::Routed.subscribe(engine, context) do |result|
|
62
|
-
if result.
|
63
|
+
if result.match?
|
63
64
|
# TODO: should this hash be an Event instance instead?
|
64
65
|
event = {
|
65
66
|
waf_result: result,
|
@@ -72,12 +73,13 @@ module Datadog
|
|
72
73
|
# We want to keep the trace in case of security event
|
73
74
|
context.trace.keep! if context.trace
|
74
75
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
75
|
-
context.
|
76
|
+
context.events << event
|
77
|
+
|
78
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
76
79
|
end
|
77
80
|
end
|
78
81
|
|
79
|
-
|
80
|
-
next [nil, [[:block, event]]] if block
|
82
|
+
Sinatra::Reactive::Routed.publish(engine, [gateway_request, gateway_route_params])
|
81
83
|
|
82
84
|
stack.call(gateway_request.request)
|
83
85
|
end
|
@@ -6,7 +6,6 @@ require_relative '../patcher'
|
|
6
6
|
require_relative '../../response'
|
7
7
|
require_relative '../rack/request_middleware'
|
8
8
|
require_relative 'framework'
|
9
|
-
require_relative 'ext'
|
10
9
|
require_relative 'gateway/watcher'
|
11
10
|
require_relative 'gateway/route_params'
|
12
11
|
require_relative 'gateway/request'
|
@@ -62,17 +61,8 @@ module Datadog
|
|
62
61
|
|
63
62
|
gateway_request = Gateway::Request.new(env)
|
64
63
|
|
65
|
-
request_return,
|
66
|
-
|
67
|
-
catch(Datadog::AppSec::Contrib::Sinatra::Ext::ROUTE_INTERRUPT) { super }
|
68
|
-
end
|
69
|
-
|
70
|
-
if request_response
|
71
|
-
blocked_event = request_response.find { |action, _options| action == :block }
|
72
|
-
if blocked_event
|
73
|
-
self.response = AppSec::Response.negotiate(env, blocked_event.last[:actions]).to_sinatra_response
|
74
|
-
request_return = nil
|
75
|
-
end
|
64
|
+
request_return, _gateway_request = Instrumentation.gateway.push('sinatra.request.dispatch', gateway_request) do
|
65
|
+
super
|
76
66
|
end
|
77
67
|
|
78
68
|
request_return
|
@@ -103,20 +93,7 @@ module Datadog
|
|
103
93
|
gateway_request = Gateway::Request.new(env)
|
104
94
|
gateway_route_params = Gateway::RouteParams.new(route_params)
|
105
95
|
|
106
|
-
|
107
|
-
'sinatra.request.routed',
|
108
|
-
[gateway_request, gateway_route_params]
|
109
|
-
)
|
110
|
-
|
111
|
-
if request_response
|
112
|
-
blocked_event = request_response.find { |action, _options| action == :block }
|
113
|
-
if blocked_event
|
114
|
-
self.response = AppSec::Response.negotiate(env, blocked_event.last[:actions]).to_sinatra_response
|
115
|
-
|
116
|
-
# interrupt request and return response to dispatch! for consistency
|
117
|
-
throw(Datadog::AppSec::Contrib::Sinatra::Ext::ROUTE_INTERRUPT, response)
|
118
|
-
end
|
119
|
-
end
|
96
|
+
Instrumentation.gateway.push('sinatra.request.routed', [gateway_request, gateway_route_params])
|
120
97
|
|
121
98
|
yield(*args)
|
122
99
|
end
|
@@ -34,7 +34,7 @@ module Datadog
|
|
34
34
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
35
35
|
result = context.run_waf(persistent_data, {}, waf_timeout)
|
36
36
|
|
37
|
-
next
|
37
|
+
next unless result.match?
|
38
38
|
|
39
39
|
yield result
|
40
40
|
throw(:block, true) unless result.actions.empty?
|
data/lib/datadog/appsec/ext.rb
CHANGED
@@ -3,7 +3,10 @@
|
|
3
3
|
module Datadog
|
4
4
|
module AppSec
|
5
5
|
module Ext
|
6
|
-
RASP_SQLI =
|
6
|
+
RASP_SQLI = 'sql_injection'
|
7
|
+
RASP_LFI = 'lfi'
|
8
|
+
RASP_SSRF = 'ssrf'
|
9
|
+
|
7
10
|
INTERRUPT = :datadog_appsec_interrupt
|
8
11
|
CONTEXT_KEY = 'datadog.appsec.context'
|
9
12
|
ACTIVE_CONTEXT_KEY = :datadog_appsec_active_context
|
@@ -11,6 +14,8 @@ module Datadog
|
|
11
14
|
TAG_APPSEC_ENABLED = '_dd.appsec.enabled'
|
12
15
|
TAG_APM_ENABLED = '_dd.apm.enabled'
|
13
16
|
TAG_DISTRIBUTED_APPSEC_EVENT = '_dd.p.appsec'
|
17
|
+
|
18
|
+
TELEMETRY_METRICS_NAMESPACE = 'appsec'
|
14
19
|
end
|
15
20
|
end
|
16
21
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module Metrics
|
6
|
+
# A class responsible for collecting WAF and RASP call metrics.
|
7
|
+
class Collector
|
8
|
+
Store = Struct.new(:evals, :timeouts, :duration_ns, :duration_ext_ns, keyword_init: true)
|
9
|
+
|
10
|
+
attr_reader :waf, :rasp
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@waf = Store.new(evals: 0, timeouts: 0, duration_ns: 0, duration_ext_ns: 0)
|
15
|
+
@rasp = Store.new(evals: 0, timeouts: 0, duration_ns: 0, duration_ext_ns: 0)
|
16
|
+
end
|
17
|
+
|
18
|
+
def record_waf(result)
|
19
|
+
@mutex.synchronize do
|
20
|
+
@waf.evals += 1
|
21
|
+
@waf.timeouts += 1 if result.timeout?
|
22
|
+
@waf.duration_ns += result.duration_ns
|
23
|
+
@waf.duration_ext_ns += result.duration_ext_ns
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def record_rasp(result)
|
28
|
+
@mutex.synchronize do
|
29
|
+
@rasp.evals += 1
|
30
|
+
@rasp.timeouts += 1 if result.timeout?
|
31
|
+
@rasp.duration_ns += result.duration_ns
|
32
|
+
@rasp.duration_ext_ns += result.duration_ext_ns
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module Metrics
|
6
|
+
# A class responsible for exporting WAF and RASP call metrics.
|
7
|
+
module Exporter
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def export_waf_metrics(metrics, span)
|
11
|
+
return if metrics.evals.zero?
|
12
|
+
|
13
|
+
span.set_tag('_dd.appsec.waf.timeouts', metrics.timeouts)
|
14
|
+
span.set_tag('_dd.appsec.waf.duration', convert_ns_to_us(metrics.duration_ns))
|
15
|
+
span.set_tag('_dd.appsec.waf.duration_ext', convert_ns_to_us(metrics.duration_ext_ns))
|
16
|
+
end
|
17
|
+
|
18
|
+
def export_rasp_metrics(metrics, span)
|
19
|
+
return if metrics.evals.zero?
|
20
|
+
|
21
|
+
span.set_tag('_dd.appsec.rasp.rule.eval', metrics.evals)
|
22
|
+
span.set_tag('_dd.appsec.rasp.timeout', 1) unless metrics.timeouts.zero?
|
23
|
+
span.set_tag('_dd.appsec.rasp.duration', convert_ns_to_us(metrics.duration_ns))
|
24
|
+
span.set_tag('_dd.appsec.rasp.duration_ext', convert_ns_to_us(metrics.duration_ext_ns))
|
25
|
+
end
|
26
|
+
|
27
|
+
# private
|
28
|
+
|
29
|
+
def convert_ns_to_us(value)
|
30
|
+
value / 1000.0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module Metrics
|
6
|
+
# A class responsible for reporting WAF and RASP telemetry metrics.
|
7
|
+
module Telemetry
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def report_rasp(type, result)
|
11
|
+
return if result.is_a?(SecurityEngine::Result::Error)
|
12
|
+
|
13
|
+
tags = { rule_type: type, waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING }
|
14
|
+
namespace = Ext::TELEMETRY_METRICS_NAMESPACE
|
15
|
+
|
16
|
+
AppSec.telemetry.inc(namespace, 'rasp.rule.eval', 1, tags: tags)
|
17
|
+
AppSec.telemetry.inc(namespace, 'rasp.rule.match', 1, tags: tags) if result.match?
|
18
|
+
AppSec.telemetry.inc(namespace, 'rasp.timeout', 1, tags: tags) if result.timeout?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
# This namespace contains classes related to metrics collection and exportation.
|
6
|
+
module Metrics
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative 'metrics/collector'
|
12
|
+
require_relative 'metrics/exporter'
|
13
|
+
require_relative 'metrics/telemetry'
|