diff --git a/packages/datadog-instrumentations/src/express-session.js b/packages/datadog-instrumentations/src/express-session.js new file mode 100644 index 00000000000..c7e810415e3 --- /dev/null +++ b/packages/datadog-instrumentations/src/express-session.js @@ -0,0 +1,41 @@ +'use strict' + +const shimmer = require('../../datadog-shimmer') +const { channel, addHook } = require('./helpers/instrument') + +const sessionMiddlewareFinishCh = channel('datadog:express-session:middleware:finish') + +function wrapSessionMiddleware (sessionMiddleware) { + return function wrappedSessionMiddleware (req, res, next) { + shimmer.wrap(arguments, 2, function wrapNext (next) { + return function wrappedNext () { + if (sessionMiddlewareFinishCh.hasSubscribers) { + const abortController = new AbortController() + + sessionMiddlewareFinishCh.publish({ req, res, sessionId: req.sessionID, abortController }) + + if (abortController.signal.aborted) return + } + + return next.apply(this, arguments) + } + }) + + return sessionMiddleware.apply(this, arguments) + } +} + +function wrapSession (session) { + return function wrappedSession () { + const sessionMiddleware = session.apply(this, arguments) + + return shimmer.wrapFunction(sessionMiddleware, wrapSessionMiddleware) + } +} + +addHook({ + name: 'express-session', + versions: ['>=1.0.1'] +}, session => { + return shimmer.wrapFunction(session, wrapSession) +}) diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js index fbe72ad143d..cb886df4a47 100644 --- a/packages/datadog-instrumentations/src/helpers/hooks.js +++ b/packages/datadog-instrumentations/src/helpers/hooks.js @@ -47,6 +47,7 @@ module.exports = { elasticsearch: () => require('../elasticsearch'), express: () => require('../express'), 'express-mongo-sanitize': () => require('../express-mongo-sanitize'), + 'express-session': () => require('../express-session'), fastify: () => require('../fastify'), 'find-my-way': () => require('../find-my-way'), fs: () => require('../fs'), diff --git a/packages/dd-trace/src/appsec/addresses.js b/packages/dd-trace/src/appsec/addresses.js index 20290baf9c4..f7073af3fd0 100644 --- a/packages/dd-trace/src/appsec/addresses.js +++ b/packages/dd-trace/src/appsec/addresses.js @@ -22,6 +22,7 @@ module.exports = { USER_ID: 'usr.id', USER_LOGIN: 'usr.login', + USER_SESSION_ID: 'usr.session_id', WAF_CONTEXT_PROCESSOR: 'waf.context.processor', diff --git a/packages/dd-trace/src/appsec/channels.js b/packages/dd-trace/src/appsec/channels.js index 1fe8d632041..1a94e651b37 100644 --- a/packages/dd-trace/src/appsec/channels.js +++ b/packages/dd-trace/src/appsec/channels.js @@ -15,6 +15,7 @@ module.exports = { incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'), passportVerify: dc.channel('datadog:passport:verify:finish'), passportUser: dc.channel('datadog:passport:deserializeUser:finish'), + expressSession: dc.channel('datadog:express-session:middleware:finish'), queryParser: dc.channel('datadog:query:read:finish'), setCookieChannel: dc.channel('datadog:iast:set-cookie'), nextBodyParsed: dc.channel('apm:next:body-parsed'), diff --git a/packages/dd-trace/src/appsec/index.js b/packages/dd-trace/src/appsec/index.js index 9c948290525..023c32d6863 100644 --- a/packages/dd-trace/src/appsec/index.js +++ b/packages/dd-trace/src/appsec/index.js @@ -11,6 +11,7 @@ const { incomingHttpRequestEnd, passportVerify, passportUser, + expressSession, queryParser, nextBodyParsed, nextQueryParsed, @@ -69,6 +70,7 @@ function enable (_config) { incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator) passportVerify.subscribe(onPassportVerify) // possible optimization: only subscribe if collection mode is enabled passportUser.subscribe(onPassportDeserializeUser) + expressSession.subscribe(onExpressSession) queryParser.subscribe(onRequestQueryParsed) nextBodyParsed.subscribe(onRequestBodyParsed) nextQueryParsed.subscribe(onRequestQueryParsed) @@ -213,6 +215,23 @@ function onPassportDeserializeUser ({ user, abortController }) { handleResults(results, store.req, store.req.res, rootSpan, abortController) } +function onExpressSession ({ req, res, sessionId, abortController }) { + const rootSpan = web.root(req) + if (!rootSpan) { + log.warn('[ASM] No rootSpan found in onExpressSession') + return + } + + // do not call this if the SDK already called it + const results = waf.run({ + persistent: { + [addresses.USER_SESSION_ID]: sessionId + } + }, req) + + handleResults(results, req, res, rootSpan, abortController) +} + function onRequestQueryParsed ({ req, res, query, abortController }) { if (!query || typeof query !== 'object') return @@ -327,6 +346,7 @@ function disable () { if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator) if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify) if (passportUser.hasSubscribers) passportUser.unsubscribe(onPassportDeserializeUser) + if (expressSession.hasSubscribers) expressSession.unsubscribe(onExpressSession) if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed) if (nextBodyParsed.hasSubscribers) nextBodyParsed.unsubscribe(onRequestBodyParsed) if (nextQueryParsed.hasSubscribers) nextQueryParsed.unsubscribe(onRequestQueryParsed) diff --git a/packages/dd-trace/src/appsec/remote_config/capabilities.js b/packages/dd-trace/src/appsec/remote_config/capabilities.js index 5057d38de43..2cde1a751df 100644 --- a/packages/dd-trace/src/appsec/remote_config/capabilities.js +++ b/packages/dd-trace/src/appsec/remote_config/capabilities.js @@ -24,6 +24,7 @@ module.exports = { APM_TRACING_SAMPLE_RULES: 1n << 29n, ASM_AUTO_USER_INSTRUM_MODE: 1n << 31n, ASM_ENDPOINT_FINGERPRINT: 1n << 32n, + ASM_SESSION_FINGERPRINT: 1n << 33n, // should we only send this if app uses express-session ? ASM_NETWORK_FINGERPRINT: 1n << 34n, ASM_HEADER_FINGERPRINT: 1n << 35n, ASM_RASP_CMDI: 1n << 37n diff --git a/packages/dd-trace/src/appsec/sdk/set_user.js b/packages/dd-trace/src/appsec/sdk/set_user.js index ccae859d832..2d8cfff7df3 100644 --- a/packages/dd-trace/src/appsec/sdk/set_user.js +++ b/packages/dd-trace/src/appsec/sdk/set_user.js @@ -23,6 +23,10 @@ function setUser (tracer, user) { return } + if (user.session_id && typeof user.session_id === 'string') { + persistent['usr.session_id'] = user.session_id + } + setUserTags(user, rootSpan) rootSpan.setTag('_dd.appsec.user.collection_mode', 'sdk')