Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic userID tracking and blocking + automatic session tracking #4670

Draft
wants to merge 16 commits into
base: new_user_collection
Choose a base branch
from
1 change: 1 addition & 0 deletions packages/datadog-instrumentations/src/helpers/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ module.exports = {
oracledb: () => require('../oracledb'),
openai: () => require('../openai'),
paperplane: () => require('../paperplane'),
passport: () => require('../passport'),
'passport-http': () => require('../passport-http'),
'passport-local': () => require('../passport-local'),
pg: () => require('../pg'),
Expand Down
85 changes: 85 additions & 0 deletions packages/datadog-instrumentations/src/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict'

const shimmer = require('../../datadog-shimmer')
const { addHook } = require('./helpers/instrument')

/* TODO: test with:
passport-jwt JWTs
can be used both for login events, or as a session, that complicates things it think
maybe instrument this lib directly, and ofc only send the events after it was verified
@nestjs/passport
pasport-local
passport-oauth2
passport-google-oauth20
passport-custom
passport-http
passport-http-bearer
koa-passport
*/

function wrapDone (done) {
// eslint-disable-next-line n/handle-callback-err
return function wrappedDone (err, user) {
if (user) {
const abortController = new AbortController()

// express-session middleware sets req.sessionID, it's required to use passport sessions anyway so might as well use it ?

Check failure on line 26 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

This line has a length of 127. Maximum allowed is 120
// what if session IDs are using rolling sessions or always changing or something idk ?
channel.publish({ req, user, sessionId: req.sessionID, abortController })

Check failure on line 28 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

'channel' is not defined

Check failure on line 28 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

'req' is not defined

Check failure on line 28 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

'req' is not defined

if (abortController.signal.aborted) return
}

return done.apply(this, arguments)
}
}

function wrapDeserializeUser (deserializeUser) {
return function wrappedDeserializeUser (fn, req, done) {
if (typeof req === 'function') {
done = req
// req = storage.getStore().get('req')
arguments[1] = wrapDone(done)
} else {
arguments[2] = wrapDone(done)
}

return deserializeUser.apply(this, arguments)
}
}


Check failure on line 51 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

More than 1 blank line not allowed
const { block } = require('../../dd-trace/src/appsec/blocking')
const { getRootSpan } = require('../../dd-trace/src/appsec/sdk/utils')

addHook({
name: 'passport',
file: 'lib/authenticator.js',
versions: ['>=0.3.0'] // TODO
}, Authenticator => {
shimmer.wrap(Authenticator.prototype, 'deserializeUser', wrapDeserializeUser)

shimmer.wrap(Authenticator.prototype, 'authenticate', function wrapAuthenticate (authenticate) {
return function wrappedAuthenticate (name) {
const middleware = authenticate.apply(this, arguments)

const strategy = this._strategy(name)

strategy._verify

return function wrappedMiddleware (req, res, next) {
return middleware(req, res, function wrappedNext (err) {

Check failure on line 71 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

Expected error to be handled
console.log('NEW', req.user)

Check failure on line 72 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
if (req.user?.name === 'bitch') {

Check failure on line 73 in packages/datadog-instrumentations/src/passport.js

View workflow job for this annotation

GitHub Actions / lint

Block must not be padded by blank lines

return block(req, res, getRootSpan(global._ddtrace))
}

return next.apply(this, arguments)
})
}
}
})

return Authenticator
})
12 changes: 12 additions & 0 deletions packages/dd-trace/src/appsec/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@
Reporter.finishRequest(req, res)
}

function onPassportDeserializeUser ({ req, user, sessionId, abordController }) {

Check failure on line 183 in packages/dd-trace/src/appsec/index.js

View workflow job for this annotation

GitHub Actions / lint

'onPassportDeserializeUser' is defined but never used
UserTracking.trackUser(user, abortController)

Check failure on line 184 in packages/dd-trace/src/appsec/index.js

View workflow job for this annotation

GitHub Actions / lint

'abortController' is not defined

if (sessionId && typeof sessionId === 'string') {
const results = waf.run({
persistent: {
'usr.session_id': sessionId
}
})
}
}

function onPassportVerify ({ login, user, success }) {
const store = storage.getStore()
const rootSpan = store?.req && web.root(store.req)
Expand Down
35 changes: 30 additions & 5 deletions packages/dd-trace/src/appsec/sdk/set_user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const { getRootSpan } = require('./utils')
const log = require('../../log')
const waf = require('../waf')

function setUserTags (user, rootSpan) {
for (const k of Object.keys(user)) {
Expand All @@ -23,16 +24,40 @@ function setUser (tracer, user) {

// must get user ID with USER_ID_FIELDS

// _dd.appsec.user.collection_mode: collectionMode // sdk/ident/anon
setTags({
'usr.id': userId,
'_dd.appsec.user.collection_mode': 'sdk/ident/anon'
})

/*
When the user monitoring SDK is available and in use, the following applies:
The usr.id must be set to the value provided through the user monitoring SDK.
The span tag _dd.appsec.user.collection_mode must be set to sdk.
This effectively means that the automated user ID collection mechanism must not overwrite the aforementioned span tags, while the user monitoring SDK must overwrite them if present.
*/


setUserTags(user, rootSpan)

/*
User IDs generated through the SDK must now be provided to libddwaf as a persistent addresses.
If the user monitoring SDK has already resulted in a call to libddwaf before any automated instrumentation or collection method has been executed, no extra call should be made.
If the automated instrumentation or collection method has resulted in a call to libddwaf before the user monitoring SDK has been executed, a second call must be performed with the user ID obtained through the SDK.
When a user provides their own session ID through the use of the SDK using the session_id key:
If libddwaf hasn’t already been called with the usr.session_id address, it should be called with the provided session_id and further calls through the automated collection method should be inhibited.
If libddwaf has already been called with the usr.session_id address, it should be called again.
*/
if (user.session_id && typeof user.session_id === 'string') {
persistent['usr.session_id'] = user.session_id
}


setUserTags(user, rootSpan)

// If the automated instrumentation or collection method has resulted in a call to libddwaf before the user monitoring SDK has been executed, a second call must be performed with the user ID obtained through the SDK.
// will the second call trigger tho ? make some edge case tests
const results = waf.run({
persistent: {
[USER_ID]: userId
}
})

}

module.exports = {
Expand Down
25 changes: 25 additions & 0 deletions packages/dd-trace/src/appsec/user_tracking/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,31 @@ function passportTrackEvent (credentials, passportUser, rootSpan) {
}
}


function trackUser (user, abortController) {
if (!collectionMode || collectionMode === 'disabled') return

const userId = getUserId(user)

if (!userId) {

}

// don't override tags if already set by "sdk" but still add the _dd.appsec.usr.id
rootSpan.addTags({
'usr.id': userId,
'_dd.appsec.usr.id': userId, // always AND only send when automated
'_dd.appsec.user.collection_mode': collectionMode // short or long form ?
})

// If the user monitoring SDK has already resulted in a call to libddwaf before any automated instrumentation or collection method has been executed, no extra call should be made.
const results = waf.run({
persistent: {
[USER_ID]: userID
}
})
}

module.exports = {
setCollectionMode,
trackLogin
Expand Down
Loading