Skip to content

Commit 59616ae

Browse files
committed
Handle business logic events
* Update system-tests target
1 parent 15a6b5f commit 59616ae

File tree

4 files changed

+140
-2
lines changed

4 files changed

+140
-2
lines changed

.github/workflows/system-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ permissions: {}
2222
env:
2323
REGISTRY: ghcr.io
2424
REPO: ghcr.io/datadog/dd-trace-rb
25-
SYSTEM_TESTS_REF: appsec-57504-enable-session-tracking-and-fingerprinting # This must always be set to `main` on dd-trace-rb's master branch
25+
SYSTEM_TESTS_REF: appsec-57237-ruby-enable-session-fingerprinting # This must always be set to `main` on dd-trace-rb's master branch
2626

2727
jobs:
2828
changes:

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ module Monitor
1010
module Gateway
1111
# Watcher for Apssec internal events
1212
module Watcher
13+
ARBITRARY_VALUE = 'invalid'
14+
EVENT_LOGIN_SUCCESS = 'users.login.success'
15+
EVENT_LOGIN_FAILURE = 'users.login.failure'
16+
WATCHED_LOGIN_EVENTS = [EVENT_LOGIN_SUCCESS, EVENT_LOGIN_FAILURE].freeze
17+
1318
class << self
1419
def watch
1520
gateway = Instrumentation.gateway
1621

1722
watch_user_id(gateway)
23+
watch_user_login(gateway)
1824
end
1925

2026
def watch_user_id(gateway = Instrumentation.gateway)
@@ -47,6 +53,30 @@ def watch_user_id(gateway = Instrumentation.gateway)
4753
stack.call(user)
4854
end
4955
end
56+
57+
def watch_user_login(gateway = Instrumentation.gateway)
58+
gateway.watch('appsec.events.user_lifecycle', :appsec) do |stack, kind|
59+
context = AppSec.active_context
60+
61+
next stack.call(kind) unless WATCHED_LOGIN_EVENTS.include?(kind)
62+
63+
persistent_data = { "server.business_logic.#{kind}" => ARBITRARY_VALUE }
64+
result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
65+
66+
if result.match? || !result.derivatives.empty?
67+
context.events.push(
68+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
69+
)
70+
end
71+
72+
if result.match?
73+
AppSec::Event.tag_and_keep!(context, result)
74+
AppSec::ActionsHandler.handle(result.actions)
75+
end
76+
77+
stack.call(kind)
78+
end
79+
end
5080
end
5181
end
5282
end

sig/datadog/appsec/monitor/gateway/watcher.rbs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@ module Datadog
33
module Monitor
44
module Gateway
55
module Watcher
6+
ARBITRARY_VALUE: ::String
7+
8+
EVENT_LOGIN_SUCCESS: ::String
9+
10+
EVENT_LOGIN_FAILURE: ::String
11+
12+
WATCHED_LOGIN_EVENTS: ::Array[::String]
13+
614
def self.watch: () -> untyped
715

8-
def self.watch_user_id: (?Datadog::AppSec::Instrumentation::Gateway gateway) -> untyped
16+
def self.watch_user_id: (?Instrumentation::Gateway gateway) -> void
17+
18+
def self.watch_user_login: (?Instrumentation::Gateway gateway) -> void
919
end
1020
end
1121
end
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# frozen_string_literal: true
2+
3+
require 'datadog/tracing/contrib/support/spec_helper'
4+
require 'datadog/appsec/spec_helper'
5+
require 'rack/test'
6+
7+
require 'datadog/tracing'
8+
require 'datadog/appsec'
9+
require 'datadog/kit/appsec/events'
10+
11+
RSpec.describe 'Session fingerprint collection for business events' do
12+
include Rack::Test::Methods
13+
14+
before do
15+
Datadog.configure do |c|
16+
c.tracing.enabled = true
17+
18+
c.appsec.enabled = true
19+
c.appsec.instrument :rack
20+
21+
c.appsec.waf_timeout = 10_000_000 # in us
22+
c.appsec.ip_passlist = []
23+
c.appsec.ip_denylist = []
24+
c.appsec.user_id_denylist = []
25+
c.appsec.ruleset = :recommended
26+
c.appsec.api_security.enabled = false
27+
c.appsec.api_security.sample_rate = 0.0
28+
29+
c.remote.enabled = false
30+
end
31+
32+
allow(Datadog::AppSec::Instrumentation).to receive(:gateway).and_return(gateway)
33+
34+
# NOTE: Don't reach the agent in any way
35+
allow_any_instance_of(Datadog::Tracing::Transport::HTTP::Client).to receive(:send_request)
36+
allow_any_instance_of(Datadog::Tracing::Transport::Traces::Transport).to receive(:native_events_supported?)
37+
.and_return(true)
38+
39+
Datadog::AppSec::Monitor::Gateway::Watcher.watch
40+
end
41+
42+
after do
43+
Datadog.configuration.reset!
44+
Datadog.registry[:rack].reset_configuration!
45+
end
46+
47+
let(:gateway) { Datadog::AppSec::Instrumentation::Gateway.new }
48+
49+
let(:http_service_entry_span) do
50+
Datadog::Tracing::Transport::TraceFormatter.format!(trace)
51+
spans.find { |s| s.name == 'rack.request' }
52+
end
53+
54+
let(:app) do
55+
stack = Rack::Builder.new do
56+
use Datadog::Tracing::Contrib::Rack::TraceMiddleware
57+
use Datadog::AppSec::Contrib::Rack::RequestMiddleware
58+
59+
map '/with-track-login-success' do
60+
run(
61+
lambda do |_env|
62+
Datadog::Kit::AppSec::Events.track_login_success(
63+
Datadog::Tracing.active_trace, Datadog::Tracing.active_span, user: { id: '42' }
64+
)
65+
66+
[200, { 'Content-Type' => 'text/html' }, ['OK']]
67+
end
68+
)
69+
end
70+
71+
map '/without-track-login-success' do
72+
run ->(_env) { [200, { 'Content-Type' => 'text/html' }, ['OK']] }
73+
end
74+
end
75+
76+
stack.to_app
77+
end
78+
79+
subject(:response) { last_response }
80+
81+
context 'when business event was pushed' do
82+
before { get('/with-track-login-success') }
83+
84+
it 'collects session fingerprint' do
85+
expect(response).to be_ok
86+
expect(http_service_entry_span.tags).to include('_dd.appsec.fp.session' => String)
87+
end
88+
end
89+
90+
context 'when business event was not pushed' do
91+
before { get('/without-track-login-success') }
92+
93+
it 'does not collect session fingerprint' do
94+
expect(response).to be_ok
95+
expect(http_service_entry_span.tags).not_to have_key('_dd.appsec.fp.session')
96+
end
97+
end
98+
end

0 commit comments

Comments
 (0)