Skip to content

Commit 4ea0c70

Browse files
authored
Merge pull request #2642 from DataDog/appsec-block-user-when-ID-matches
[APPSEC-8112] Appsec block request when user ID matches rules data
2 parents cf135e6 + 3b09f87 commit 4ea0c70

File tree

9 files changed

+149
-4
lines changed

9 files changed

+149
-4
lines changed

lib/datadog/appsec/contrib/rack/request_middleware.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require 'json'
44

5+
require_relative '../../ext'
56
require_relative '../../instrumentation/gateway'
67
require_relative '../../processor'
78
require_relative '../../response'
@@ -38,8 +39,10 @@ def call(env)
3839

3940
add_appsec_tags(processor, active_trace, active_span, env)
4041

41-
request_return, request_response = Instrumentation.gateway.push('rack.request', request) do
42-
@app.call(env)
42+
request_return, request_response = catch(::Datadog::AppSec::Ext::INTERRUPT) do
43+
Instrumentation.gateway.push('rack.request', request) do
44+
@app.call(env)
45+
end
4346
end
4447

4548
if request_response && request_response.any? { |action, _event| action == :block }

lib/datadog/appsec/ext.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# typed: false
2+
# frozen_string_literal: true
3+
4+
module Datadog
5+
module AppSec
6+
module Ext
7+
INTERRUPT = :datadog_appsec_interrupt
8+
end
9+
end
10+
end

lib/datadog/appsec/monitor/gateway/watcher.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# typed: false
22
# frozen_string_literal: true
33

4+
require_relative '../../ext'
45
require_relative '../../instrumentation/gateway'
56
require_relative '../../reactive/operation'
67
require_relative '../reactive/set_user'
@@ -48,7 +49,7 @@ def watch_user_id(gateway = Instrumentation.gateway)
4849
_result, block = Monitor::Reactive::SetUser.publish(op, user)
4950
end
5051

51-
next [nil, [[:block, event]]] if block
52+
throw(Datadog::AppSec::Ext::INTERRUPT, [nil, [:block, event]]) if block
5253

5354
ret, res = stack.call(user)
5455

lib/datadog/kit/identity.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ def self.set_user(trace, id:, email: nil, name: nil, session_id: nil, role: nil,
5656
others.each do |k, v|
5757
trace.set_tag("usr.#{k}", v) unless v.nil?
5858
end
59+
60+
if Datadog.configuration.appsec.enabled
61+
user = OpenStruct.new(id: id)
62+
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
63+
end
5964
end
6065
# rubocop:enable Metrics/PerceivedComplexity
6166
# rubocop:enable Metrics/CyclomaticComplexity

sig/datadog/appsec/ext.rbs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module Datadog
2+
module AppSec
3+
module Ext
4+
INTERRUPT: Symbol
5+
end
6+
end
7+
end

spec/datadog/appsec/contrib/rack/integration_test_spec.rb

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
let(:appsec_enabled) { true }
2323
let(:tracing_enabled) { true }
2424
let(:appsec_ip_denylist) { nil }
25+
let(:appsec_user_id_denylist) { nil }
2526
let(:appsec_ruleset) { :recommended }
2627

2728
let(:crs_942_100) do
@@ -133,11 +134,15 @@
133134
c.appsec.enabled = appsec_enabled
134135
c.appsec.instrument :rack
135136
c.appsec.ip_denylist = appsec_ip_denylist
137+
c.appsec.user_id_denylist = appsec_user_id_denylist
136138
c.appsec.ruleset = appsec_ruleset
137139
end
138140
end
139141

140-
after { Datadog.registry[:rack].reset_configuration! }
142+
after do
143+
Datadog::AppSec.settings.send(:reset!)
144+
Datadog.registry[:rack].reset_configuration!
145+
end
141146

142147
context 'for an application' do
143148
# TODO: also test without Tracing: it should run without trace transport
@@ -293,6 +298,15 @@
293298
end
294299
)
295300
end
301+
302+
map '/set_user' do
303+
run(
304+
proc do |_env|
305+
Datadog::Kit::Identity.set_user(Datadog::Tracing.active_trace, id: 'blocked-user-id')
306+
[200, { 'Content-Type' => 'text/html' }, ['OK']]
307+
end
308+
)
309+
end
296310
end
297311
end
298312

@@ -382,6 +396,26 @@
382396
it_behaves_like 'a trace with AppSec events'
383397
end
384398
end
399+
400+
context 'with user blocking ID' do
401+
let(:url) { '/set_user' }
402+
403+
it { is_expected.to be_ok }
404+
405+
it_behaves_like 'a GET 200 span'
406+
it_behaves_like 'a trace with AppSec tags'
407+
it_behaves_like 'a trace without AppSec events'
408+
409+
context 'with an event-triggering user ID' do
410+
let(:appsec_user_id_denylist) { ['blocked-user-id'] }
411+
412+
it { is_expected.to be_forbidden }
413+
414+
it_behaves_like 'a GET 403 span'
415+
it_behaves_like 'a trace with AppSec tags'
416+
it_behaves_like 'a trace with AppSec events'
417+
end
418+
end
385419
end
386420

387421
describe 'POST request' do

spec/datadog/appsec/contrib/rails/integration_test_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
let(:appsec_enabled) { true }
3333
let(:tracing_enabled) { true }
3434
let(:appsec_ip_denylist) { nil }
35+
let(:appsec_user_id_denylist) { nil }
3536
let(:appsec_ruleset) { :recommended }
3637

3738
let(:crs_942_100) do
@@ -89,12 +90,18 @@
8990
c.appsec.enabled = appsec_enabled
9091
c.appsec.instrument :rails
9192
c.appsec.ip_denylist = appsec_ip_denylist
93+
c.appsec.user_id_denylist = appsec_user_id_denylist
9294
c.appsec.ruleset = appsec_ruleset
9395

9496
# TODO: test with c.appsec.instrument :rack
9597
end
9698
end
9799

100+
after do
101+
Datadog::AppSec.settings.send(:reset!)
102+
Datadog.registry[:rails].reset_configuration!
103+
end
104+
98105
context 'for an application' do
99106
include_context 'Rails test application'
100107

@@ -118,6 +125,11 @@
118125
def success
119126
head :ok
120127
end
128+
129+
def set_user
130+
Datadog::Kit::Identity.set_user(Datadog::Tracing.active_trace, id: 'blocked-user-id')
131+
head :ok
132+
end
121133
end
122134
)
123135
end
@@ -244,6 +256,7 @@ def success
244256
{
245257
'/success' => 'test#success',
246258
[:post, '/success'] => 'test#success',
259+
'/set_user' => 'test#set_user',
247260
}
248261
end
249262

@@ -347,6 +360,26 @@ def success
347360
it_behaves_like 'a trace with AppSec tags'
348361
it_behaves_like 'a trace with AppSec events'
349362
end
363+
364+
context 'with user blocking ID' do
365+
let(:url) { '/set_user' }
366+
367+
it { is_expected.to be_ok }
368+
369+
it_behaves_like 'a GET 200 span'
370+
it_behaves_like 'a trace with AppSec tags'
371+
it_behaves_like 'a trace without AppSec events'
372+
373+
context 'with an event-triggering user ID' do
374+
let(:appsec_user_id_denylist) { ['blocked-user-id'] }
375+
376+
it { is_expected.to be_forbidden }
377+
378+
it_behaves_like 'a GET 403 span'
379+
it_behaves_like 'a trace with AppSec tags'
380+
it_behaves_like 'a trace with AppSec events'
381+
end
382+
end
350383
end
351384

352385
describe 'POST request' do

spec/datadog/appsec/contrib/sinatra/integration_test_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
let(:appsec_enabled) { true }
4545
let(:tracing_enabled) { true }
4646
let(:appsec_ip_denylist) { nil }
47+
let(:appsec_user_id_denylist) { nil }
4748
let(:appsec_ruleset) { :recommended }
4849

4950
let(:crs_942_100) do
@@ -101,13 +102,15 @@
101102
c.appsec.enabled = appsec_enabled
102103
c.appsec.instrument :sinatra
103104
c.appsec.ip_denylist = appsec_ip_denylist
105+
c.appsec.user_id_denylist = appsec_user_id_denylist
104106
c.appsec.ruleset = appsec_ruleset
105107

106108
# TODO: test with c.appsec.instrument :rack
107109
end
108110
end
109111

110112
after do
113+
Datadog::AppSec.settings.send(:reset!)
111114
Datadog.registry[:rack].reset_configuration!
112115
Datadog.registry[:sinatra].reset_configuration!
113116
end
@@ -254,6 +257,11 @@
254257
post '/success' do
255258
'ok'
256259
end
260+
261+
get '/set_user' do
262+
Datadog::Kit::Identity.set_user(Datadog::Tracing.active_trace, id: 'blocked-user-id')
263+
'ok'
264+
end
257265
end
258266
end
259267

@@ -359,6 +367,26 @@
359367
it_behaves_like 'a trace with AppSec tags'
360368
it_behaves_like 'a trace with AppSec events'
361369
end
370+
371+
context 'with user blocking ID' do
372+
let(:url) { '/set_user' }
373+
374+
it { is_expected.to be_ok }
375+
376+
it_behaves_like 'a GET 200 span'
377+
it_behaves_like 'a trace with AppSec tags'
378+
it_behaves_like 'a trace without AppSec events'
379+
380+
context 'with an event-triggering user ID' do
381+
let(:appsec_user_id_denylist) { ['blocked-user-id'] }
382+
383+
it { is_expected.to be_forbidden }
384+
385+
it_behaves_like 'a GET 403 span'
386+
it_behaves_like 'a trace with AppSec tags'
387+
it_behaves_like 'a trace with AppSec events'
388+
end
389+
end
362390
end
363391

364392
describe 'POST request' do

spec/datadog/kit/identity_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,5 +230,29 @@
230230
expect { described_class.set_user(trace_op, id: 42, foo: 42) }.to raise_error(TypeError)
231231
end
232232
end
233+
234+
context 'appsec' do
235+
after { Datadog.configuration.appsec.send(:reset!) }
236+
237+
context 'when is enabled' do
238+
it 'instruments the user information to appsec' do
239+
Datadog.configuration.appsec.enabled = true
240+
user = OpenStruct.new(id: '42')
241+
expect_any_instance_of(Datadog::AppSec::Instrumentation::Gateway).to receive(:push).with(
242+
'identity.set_user',
243+
user
244+
)
245+
described_class.set_user(trace_op, id: '42')
246+
end
247+
end
248+
249+
context 'when is disabled' do
250+
it 'does not instrument the user information to appsec' do
251+
Datadog.configuration.appsec.enabled = false
252+
expect_any_instance_of(Datadog::AppSec::Instrumentation::Gateway).to_not receive(:push).with('identity.set_user')
253+
described_class.set_user(trace_op, id: '42')
254+
end
255+
end
256+
end
233257
end
234258
end

0 commit comments

Comments
 (0)