Skip to content

Commit dc19805

Browse files
committed
Introduce separate AppSec gateway events for identity namespace
1 parent a1f38df commit dc19805

File tree

10 files changed

+121
-138
lines changed

10 files changed

+121
-138
lines changed

lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def record_successful_signin(context, resource)
6363
# and because of that we will trigger an additional event even
6464
# if it was already done via the SDK
6565
AppSec::Instrumentation.gateway.push(
66-
'identity.set_user', {id: id, login: login, framework: 'devise', event_type: 'login_success'}
66+
'identity.devise.login_success', {id: id, login: login}
6767
)
6868
end
6969

@@ -81,7 +81,7 @@ def record_failed_signin(context, resource)
8181
context.span[Ext::TAG_LOGIN_FAILURE_USR_EXISTS] ||= 'false'
8282

8383
AppSec::Instrumentation.gateway.push(
84-
'identity.login_failure', {login: login, framework: 'devise'}
84+
'identity.devise.login_failure', {login: login}
8585
)
8686

8787
return
@@ -100,7 +100,7 @@ def record_failed_signin(context, resource)
100100
context.span[Ext::TAG_LOGIN_FAILURE_USR_EXISTS] ||= 'true'
101101

102102
AppSec::Instrumentation.gateway.push(
103-
'identity.login_failure', {id: id, login: login, framework: 'devise'}
103+
'identity.devise.login_failure', {id: id, login: login}
104104
)
105105
end
106106
end

lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def record_successful_signup(context, resource)
5959
# and because of that we will trigger an additional event even
6060
# if it was already done via the SDK
6161
AppSec::Instrumentation.gateway.push(
62-
'identity.set_user', {id: id, login: login, framework: 'devise', event_type: 'signup'}
62+
'identity.devise.signup', {id: id, login: login}
6363
)
6464
end
6565
end

lib/datadog/appsec/contrib/devise/tracking_middleware.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ def call(env)
4545
user_session_id = context.span[Ext::TAG_SESSION_ID] || session_id
4646

4747
AppSec::Instrumentation.gateway.push(
48-
'identity.set_user',
49-
{id: user_id, session_id: user_session_id, framework: 'devise', event_type: 'authenticated_request'}
48+
'identity.devise.authenticated_request',
49+
{id: user_id, session_id: user_session_id}
5050
)
5151
end
5252

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

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,51 +13,57 @@ class << self
1313
def watch
1414
gateway = Instrumentation.gateway
1515

16-
watch_set_user(gateway)
17-
watch_login_failure(gateway)
16+
watch_user_lifecycle(gateway)
17+
watch_authenticated_request(gateway)
1818
end
1919

20-
def watch_set_user(gateway = Instrumentation.gateway)
21-
gateway.watch('identity.set_user') do |stack, user_info|
22-
event_type = user_info[:event_type]
23-
report_missing_user_telemetry(user_info, event_type) if event_type
20+
def watch_user_lifecycle(gateway = Instrumentation.gateway)
21+
%w[
22+
identity.devise.login_success
23+
identity.devise.login_failure
24+
identity.devise.signup
25+
].each do |event_name|
26+
gateway.watch(event_name) do |stack, user_info|
27+
_, framework, event_type = event_name.split('.')
28+
tags = {framework: framework, event_type: event_type}
2429

25-
stack.call(user_info)
26-
end
27-
end
30+
if user_info[:login].nil?
31+
AppSec.telemetry.inc(
32+
Ext::TELEMETRY_METRICS_NAMESPACE,
33+
'instrum.user_auth.missing_user_login',
34+
1,
35+
tags: tags,
36+
)
37+
end
2838

29-
def watch_login_failure(gateway = Instrumentation.gateway)
30-
gateway.watch('identity.login_failure') do |stack, user_info|
31-
report_missing_user_telemetry(user_info, 'login_failure')
39+
if user_info[:id].nil? && user_info[:login].nil?
40+
AppSec.telemetry.inc(
41+
Ext::TELEMETRY_METRICS_NAMESPACE,
42+
'instrum.user_auth.missing_user_id',
43+
1,
44+
tags: tags,
45+
)
46+
end
3247

33-
stack.call(user_info)
48+
stack.call(user_info)
49+
end
3450
end
3551
end
3652

37-
private
53+
def watch_authenticated_request(gateway = Instrumentation.gateway)
54+
gateway.watch('identity.devise.authenticated_request') do |stack, user_info|
55+
tags = {framework: 'devise', event_type: 'authenticated_request'}
3856

39-
def report_missing_user_telemetry(user_info, event_type)
40-
tags = {framework: user_info[:framework], event_type: event_type}
57+
if user_info[:id].nil?
58+
AppSec.telemetry.inc(
59+
Ext::TELEMETRY_METRICS_NAMESPACE,
60+
'instrum.user_auth.missing_user_id',
61+
1,
62+
tags: tags,
63+
)
64+
end
4165

42-
missing_login = user_info[:login].nil?
43-
missing_id = user_info[:id].nil?
44-
45-
if missing_login && event_type != 'authenticated_request'
46-
AppSec.telemetry.inc(
47-
Ext::TELEMETRY_METRICS_NAMESPACE,
48-
'instrum.user_auth.missing_user_login',
49-
1,
50-
tags: tags,
51-
)
52-
end
53-
54-
if missing_id && (event_type == 'authenticated_request' || missing_login)
55-
AppSec.telemetry.inc(
56-
Ext::TELEMETRY_METRICS_NAMESPACE,
57-
'instrum.user_auth.missing_user_id',
58-
1,
59-
tags: tags,
60-
)
66+
stack.call(user_info)
6167
end
6268
end
6369
end

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

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ module Watcher
1515
EVENT_LOGIN_FAILURE = 'users.login.failure'
1616
WATCHED_LOGIN_EVENTS = [EVENT_LOGIN_SUCCESS, EVENT_LOGIN_FAILURE].freeze
1717

18+
IDENTITY_EVENTS = %w[
19+
identity.sdk.set_user
20+
identity.sdk.login_failure
21+
identity.devise.login_success
22+
identity.devise.login_failure
23+
identity.devise.signup
24+
identity.devise.authenticated_request
25+
].freeze
26+
1827
class << self
1928
def watch
2029
gateway = Instrumentation.gateway
@@ -24,33 +33,35 @@ def watch
2433
end
2534

2635
def watch_user_id(gateway = Instrumentation.gateway)
27-
gateway.watch('identity.set_user') do |stack, user_info|
28-
context = AppSec.active_context
29-
30-
if user_info[:id].nil? && user_info[:login].nil? && user_info[:session_id].nil?
31-
Datadog.logger.debug { 'AppSec: skipping WAF check because no user information was provided' }
32-
next stack.call(user_info)
33-
end
34-
35-
persistent_data = {}
36-
persistent_data['usr.id'] = user_info[:id] if user_info[:id]
37-
persistent_data['usr.login'] = user_info[:login] if user_info[:login]
38-
persistent_data['usr.session_id'] = user_info[:session_id] if user_info[:session_id]
39-
40-
result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
41-
42-
if result.match? || result.attributes.any?
43-
context.events.push(
44-
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
45-
)
36+
IDENTITY_EVENTS.each do |event_name|
37+
gateway.watch(event_name) do |stack, user_info|
38+
context = AppSec.active_context
39+
40+
if user_info[:id].nil? && user_info[:login].nil? && user_info[:session_id].nil?
41+
Datadog.logger.debug { 'AppSec: skipping WAF check because no user information was provided' }
42+
next stack.call(user_info)
43+
end
44+
45+
persistent_data = {}
46+
persistent_data['usr.id'] = user_info[:id] if user_info[:id]
47+
persistent_data['usr.login'] = user_info[:login] if user_info[:login]
48+
persistent_data['usr.session_id'] = user_info[:session_id] if user_info[:session_id]
49+
50+
result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
51+
52+
if result.match? || result.attributes.any?
53+
context.events.push(
54+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
55+
)
56+
end
57+
58+
if result.match?
59+
AppSec::Event.tag(context, result)
60+
AppSec::ActionsHandler.handle(result.actions)
61+
end
62+
63+
stack.call(user_info)
4664
end
47-
48-
if result.match?
49-
AppSec::Event.tag(context, result)
50-
AppSec::ActionsHandler.handle(result.actions)
51-
end
52-
53-
stack.call(user_info)
5465
end
5566
end
5667

lib/datadog/kit/appsec/events/v2.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ def track_user_login_success(login, user_or_id = nil, metadata = {})
7070

7171
# NOTE: This is a fallback for the case when we don't have an ID,
7272
# but need to trigger WAF.
73-
user_info = {login: login, framework: 'sdk', event_type: 'login_success'}
74-
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user_info)
73+
::Datadog::AppSec::Instrumentation.gateway.push('identity.sdk.set_user', {login: login})
7574
end
7675

7776
# Attach user login failure information to the service entry span
@@ -121,9 +120,7 @@ def track_user_login_failure(login, user_exists = false, metadata = {})
121120
record_event_telemetry_metric(LOGIN_FAILURE_EVENT)
122121
::Datadog::AppSec::Instrumentation.gateway.push('appsec.events.user_lifecycle', LOGIN_FAILURE_EVENT)
123122

124-
user_info = {login: login, framework: 'sdk', event_type: 'login_failure'}
125-
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user_info)
126-
::Datadog::AppSec::Instrumentation.gateway.push('identity.login_failure', user_info)
123+
::Datadog::AppSec::Instrumentation.gateway.push('identity.sdk.login_failure', {login: login})
127124
end
128125

129126
private

lib/datadog/kit/identity.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ def set_user(
7070
if Datadog::AppSec.active_context
7171
active_span.set_tag('_dd.appsec.user.collection_mode', 'sdk')
7272

73-
user_info = {id: id, login: others[:login], session_id: session_id, framework: 'sdk'}
74-
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user_info)
73+
::Datadog::AppSec::Instrumentation.gateway.push(
74+
'identity.sdk.set_user', {id: id, login: others[:login], session_id: session_id}
75+
)
7576
end
7677
end
7778
end

spec/datadog/appsec/integration/contrib/devise/signin_single_user_tracking_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def index
119119
allow(Datadog::AppSec::Instrumentation).to receive(:gateway).and_return(gateway)
120120
Datadog::AppSec::Monitor::Gateway::TelemetryWatcher.watch
121121

122-
allow(Datadog::AppSec.telemetry).to receive(:inc).and_call_original
122+
allow(Datadog::AppSec.telemetry).to receive(:inc)
123123

124124
# NOTE: Don't reach the agent in any way
125125
allow_any_instance_of(Datadog::Tracing::Transport::HTTP::Client).to receive(:send_request)

spec/datadog/appsec/monitor/gateway/telemetry_watcher_spec.rb

Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,104 +12,72 @@
1212
allow(telemetry).to receive(:inc)
1313
end
1414

15-
describe '.watch_set_user' do
16-
before { described_class.watch_set_user(gateway) }
15+
describe '.watch_user_lifecycle' do
16+
before { described_class.watch_user_lifecycle(gateway) }
1717

18-
%w[login_success signup].each do |event_type|
19-
context "with #{event_type} event_type" do
18+
%w[
19+
identity.devise.login_success
20+
identity.devise.login_failure
21+
identity.devise.signup
22+
].each do |event_name|
23+
_, framework, event_type = event_name.split('.')
24+
25+
context "with #{event_name}" do
2026
it 'reports missing_user_login when login is nil' do
2127
expect(telemetry).to receive(:inc).with(
2228
'appsec', 'instrum.user_auth.missing_user_login', 1,
23-
tags: {framework: 'devise', event_type: event_type},
29+
tags: {framework: framework, event_type: event_type},
2430
)
2531

26-
gateway.push('identity.set_user', {id: '123', framework: 'devise', event_type: event_type})
32+
gateway.push(event_name, {id: '123'})
2733
end
2834

2935
it 'reports both metrics when login and id are nil' do
3036
expect(telemetry).to receive(:inc).with(
3137
'appsec', 'instrum.user_auth.missing_user_login', 1,
32-
tags: {framework: 'devise', event_type: event_type},
38+
tags: {framework: framework, event_type: event_type},
3339
)
3440
expect(telemetry).to receive(:inc).with(
3541
'appsec', 'instrum.user_auth.missing_user_id', 1,
36-
tags: {framework: 'devise', event_type: event_type},
42+
tags: {framework: framework, event_type: event_type},
3743
)
3844

39-
gateway.push('identity.set_user', {framework: 'devise', event_type: event_type})
45+
gateway.push(event_name, {})
4046
end
4147

4248
it 'does not report any telemetry when login is present' do
4349
expect(telemetry).not_to receive(:inc)
4450

45-
gateway.push('identity.set_user', {login: 'alice', framework: 'devise', event_type: event_type})
51+
gateway.push(event_name, {login: 'alice'})
4652
end
4753
end
4854
end
49-
50-
context 'with authenticated_request event_type' do
51-
it 'reports missing_user_id when id is nil' do
52-
expect(telemetry).to receive(:inc).with(
53-
'appsec', 'instrum.user_auth.missing_user_id', 1,
54-
tags: {framework: 'devise', event_type: 'authenticated_request'},
55-
)
56-
57-
gateway.push('identity.set_user', {framework: 'devise', event_type: 'authenticated_request'})
58-
end
59-
60-
it 'does not report missing_user_login even when login is nil' do
61-
expect(telemetry).not_to receive(:inc).with(
62-
'appsec', 'instrum.user_auth.missing_user_login', anything, anything,
63-
)
64-
65-
gateway.push('identity.set_user', {framework: 'devise', event_type: 'authenticated_request'})
66-
end
67-
68-
it 'does not report any telemetry when id is present' do
69-
expect(telemetry).not_to receive(:inc)
70-
71-
gateway.push('identity.set_user', {id: '123', framework: 'devise', event_type: 'authenticated_request'})
72-
end
73-
end
74-
75-
context 'when event_type is not set' do
76-
it 'does not report any telemetry' do
77-
expect(telemetry).not_to receive(:inc)
78-
79-
gateway.push('identity.set_user', {id: '123', framework: 'sdk'})
80-
end
81-
end
8255
end
8356

84-
describe '.watch_login_failure' do
85-
before { described_class.watch_login_failure(gateway) }
57+
describe '.watch_authenticated_request' do
58+
before { described_class.watch_authenticated_request(gateway) }
8659

87-
it 'reports missing_user_login when login is nil' do
60+
it 'reports missing_user_id when id is nil' do
8861
expect(telemetry).to receive(:inc).with(
89-
'appsec', 'instrum.user_auth.missing_user_login', 1,
90-
tags: {framework: 'devise', event_type: 'login_failure'},
62+
'appsec', 'instrum.user_auth.missing_user_id', 1,
63+
tags: {framework: 'devise', event_type: 'authenticated_request'},
9164
)
9265

93-
gateway.push('identity.login_failure', {id: '123', framework: 'devise'})
66+
gateway.push('identity.devise.authenticated_request', {})
9467
end
9568

96-
it 'reports both metrics when login and id are nil' do
97-
expect(telemetry).to receive(:inc).with(
98-
'appsec', 'instrum.user_auth.missing_user_login', 1,
99-
tags: {framework: 'devise', event_type: 'login_failure'},
100-
)
101-
expect(telemetry).to receive(:inc).with(
102-
'appsec', 'instrum.user_auth.missing_user_id', 1,
103-
tags: {framework: 'devise', event_type: 'login_failure'},
69+
it 'does not report missing_user_login even when login is nil' do
70+
expect(telemetry).not_to receive(:inc).with(
71+
'appsec', 'instrum.user_auth.missing_user_login', anything, anything,
10472
)
10573

106-
gateway.push('identity.login_failure', {framework: 'devise'})
74+
gateway.push('identity.devise.authenticated_request', {})
10775
end
10876

109-
it 'does not report any telemetry when login is present' do
77+
it 'does not report any telemetry when id is present' do
11078
expect(telemetry).not_to receive(:inc)
11179

112-
gateway.push('identity.login_failure', {login: 'alice', framework: 'devise'})
80+
gateway.push('identity.devise.authenticated_request', {id: '123'})
11381
end
11482
end
11583
end

0 commit comments

Comments
 (0)