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

[nodejs] Automated User Tracking and Blocking #3747

Merged
merged 65 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
61fd4e5
update manifest
simon-id Dec 24, 2024
f6ee13f
install express-session
simon-id Dec 24, 2024
0310f6d
use express-session
simon-id Dec 24, 2024
06fbb85
rewrite auth.js
simon-id Dec 24, 2024
ec84141
move auth middleware up
simon-id Dec 26, 2024
0a8fa6c
Merge branch 'main' into nodejs_user_tracking
simon-id Dec 26, 2024
00f237a
use feature branch
simon-id Dec 26, 2024
d808ea5
update manifest
simon-id Dec 27, 2024
5522626
use ASM_DATA instead of ASM_DD for blacklists
simon-id Dec 27, 2024
64ec6ac
add missing_feature
simon-id Dec 27, 2024
72afdcf
Merge branch 'use_asm_data' into nodejs_user_tracking
simon-id Dec 30, 2024
7a2cfba
add temp note
simon-id Jan 15, 2025
a474386
add sdk blocking to weblog
simon-id Jan 17, 2025
294c917
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 18, 2025
14c176f
cleanup
simon-id Jan 18, 2025
7de4a0b
session endpoints
simon-id Jan 18, 2025
f805ac0
enable fingerptint tests
simon-id Jan 18, 2025
373c08f
wrong PR
simon-id Jan 18, 2025
2b9616c
cleanup
simon-id Jan 20, 2025
841e41d
cleanup
simon-id Jan 20, 2025
e71f6ee
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 20, 2025
dbca6f6
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 21, 2025
b5c7a78
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 21, 2025
7348164
Update utils/scripts/load-binary.sh
simon-id Jan 23, 2025
748cf9f
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 23, 2025
39c9734
add before/after sdk call param
simon-id Jan 23, 2025
adaae79
add IAST retroactive exclusion for express-session weak hash vuln
simon-id Jan 24, 2025
302e2cc
update manifest
simon-id Jan 24, 2025
20b2050
fix semver method
simon-id Jan 24, 2025
8d59a58
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 24, 2025
cba62f5
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 26, 2025
a7429d6
put back feature branch
simon-id Jan 26, 2025
8bba8a7
add express-session dep to typescript weblog
simon-id Jan 26, 2025
d6f7a3b
Update manifests/nodejs.yml
simon-id Jan 26, 2025
27c7d0a
mirror changes in typescript weblog
simon-id Jan 26, 2025
6943a8a
lint
simon-id Jan 26, 2025
51fd22b
add passportjs types
simon-id Jan 26, 2025
d4a5906
fix
simon-id Jan 26, 2025
1635dfa
fix types
simon-id Jan 26, 2025
6e92bed
fix
simon-id Jan 26, 2025
d3b4991
fix for express5
simon-id Jan 26, 2025
7bb8044
ts fix
simon-id Jan 26, 2025
a5d7baa
another typescript fix
simon-id Jan 26, 2025
dcb74f7
add semver dep to typescript weblog
simon-id Jan 26, 2025
46d0af3
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 27, 2025
1b2f5eb
fix for misdesigned test
simon-id Jan 29, 2025
c49970a
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 29, 2025
8478252
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 30, 2025
202d853
Update utils/build/docker/nodejs/express4-typescript/iast/exclusions.ts
simon-id Jan 30, 2025
08904d0
Update manifests/nodejs.yml
simon-id Jan 30, 2025
9289141
Update utils/build/docker/nodejs/express/iast/exclusions.js
simon-id Jan 30, 2025
44c3760
increase DD_IAST_MAX_CONTEXT_OPERATIONS for all node weblogs
simon-id Jan 31, 2025
fc8554d
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 31, 2025
43190ef
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 31, 2025
e23fcf4
test saveUninitialized: false
simon-id Jan 31, 2025
ae7e0b7
mirror fix in ts weblog
simon-id Jan 31, 2025
d88616d
push all the fixes
simon-id Jan 31, 2025
f50c0fa
remove unecessary fixes
simon-id Jan 31, 2025
a6882bf
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 31, 2025
9a7fcf5
turns out we actually need it
simon-id Jan 31, 2025
2303435
one last fix
simon-id Jan 31, 2025
284b53b
Merge branch 'main' into nodejs_user_tracking
simon-id Jan 31, 2025
2fc8d2a
Merge branch 'main' into nodejs_user_tracking
simon-id Feb 1, 2025
a5b6271
trigger CI
simon-id Feb 3, 2025
04c4cac
trigger CI
simon-id Feb 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ refs:
- &ref_5_27_0 '>=5.27.0 || ^4.51.0'
- &ref_5_29_0 '>=5.29.0 || ^4.53.0' # express 5 support
- &ref_5_30_0 '>=5.30.0 || ^4.54.0'
- &ref_5_32_0 '>=5.32.0'
- &ref_5_32_0 '>=5.32.0' # keep the anchor as we may backport stuff to v4 later
- &ref_5_33_0 '>=5.33.0'

tests/:
Expand Down Expand Up @@ -530,21 +530,25 @@ tests/:
Test_V2_Login_Events_RC: irrelevant
Test_V3_Auto_User_Instrum_Mode_Capability: *ref_5_29_0
Test_V3_Login_Events:
'*': *ref_5_29_0
nextjs: missing_feature
'*': *ref_5_29_0
nextjs: missing_feature
Test_V3_Login_Events_Anon:
'*': *ref_5_29_0
nextjs: missing_feature
'*': *ref_5_29_0
nextjs: missing_feature
Test_V3_Login_Events_Blocking:
'*': *ref_5_29_0
nextjs: missing_feature
'*': *ref_5_29_0
nextjs: missing_feature
Test_V3_Login_Events_RC:
'*': *ref_5_29_0
nextjs: missing_feature
'*': *ref_5_29_0
nextjs: missing_feature
test_automated_user_and_session_tracking.py:
Test_Automated_Session_Blocking: missing_feature
Test_Automated_User_Blocking: missing_feature
Test_Automated_User_Tracking: missing_feature
Test_Automated_User_Blocking:
'*': *ref_5_33_0
nextjs: missing_feature
Test_Automated_User_Tracking:
'*': *ref_5_33_0
simon-id marked this conversation as resolved.
Show resolved Hide resolved
simon-id marked this conversation as resolved.
Show resolved Hide resolved
nextjs: missing_feature
test_blocking_addresses.py:
Test_BlockingGraphqlResolvers:
'*': *ref_4_22_0
Expand Down Expand Up @@ -660,15 +664,15 @@ tests/:
Test_Debugger_Expression_Language: missing_feature (feature not implented)
test_debugger_pii.py:
Test_Debugger_PII_Redaction:
"*": irrelevant
'*': irrelevant
express4: v5.32.0
test_debugger_probe_snapshot.py:
Test_Debugger_Probe_Snaphots:
"*": irrelevant
'*': irrelevant
express4: v5.32.0
test_debugger_probe_status.py:
Test_Debugger_Probe_Statuses:
"*": irrelevant
'*': irrelevant
express4: v5.32.0
test_debugger_symdb.py:
Test_Debugger_SymDb: missing_feature (feature not implented)
Expand Down
13 changes: 10 additions & 3 deletions utils/build/docker/nodejs/express/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ const tracer = require('dd-trace').init({
flushInterval: 5000
})

require('./iast/exclusions')

const { promisify } = require('util')
const app = require('express')()
const axios = require('axios')
const fs = require('fs')
const passport = require('passport')
const crypto = require('crypto')
const pino = require('pino')

Expand Down Expand Up @@ -42,8 +43,16 @@ app.use(require('body-parser').json())
app.use(require('body-parser').urlencoded({ extended: true }))
app.use(require('express-xml-bodyparser')())
app.use(require('cookie-parser')())
app.use(require('express-session')({
secret: 'secret',
resave: false,
rolling: true,
saveUninitialized: true
}))
iast.initMiddlewares(app)

require('./auth')(app, tracer)

app.get('/', (req, res) => {
console.log('Received a request')
res.send('Hello\n')
Expand Down Expand Up @@ -458,8 +467,6 @@ iast.initRoutes(app, tracer)

di.initRoutes(app)

require('./auth')(app, passport, tracer)

// try to flush as much stuff as possible from the library
app.get('/flush', (req, res) => {
// doesn't have a callback :(
Expand Down
152 changes: 91 additions & 61 deletions utils/build/docker/nodejs/express/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const passport = require('passport')
const { Strategy: LocalStrategy } = require('passport-local')
const { BasicStrategy } = require('passport-http')

Expand All @@ -18,81 +19,110 @@ const users = [
}
]

module.exports = function (app, passport, tracer) {
passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' },
(username, password, done) => {
const user = users.find(user => (user.username === username) && (user.password === password))
if (!user) {
return done(null, false)
} else {
return done(null, user)
}
})
)

passport.use(new BasicStrategy((username, password, done) => {
const user = users.find(user => (user.username === username) && (user.password === password))
if (!user) {
return done(null, false)
} else {
return done(null, user)
}
}
))
function findUser (fields) {
return users.find((user) => {
return Object.entries(fields).every(([field, value]) => user[field] === value)
})
}

function handleAuthentication (req, res, next, err, user, info) {
module.exports = function (app, tracer) {
function shouldSdkBlock (req, res) {
const event = req.query.sdk_event
const userId = req.query.sdk_user || 'sdk_user'
const userMail = req.query.sdk_mail || 'system_tests_user@system_tests_user.com'
const exists = req.query.sdk_user_exists === 'true'

if (err) {
console.error('unexpected login error', err)
return next(err)
}
if (!user) {
if (event === 'failure') {
tracer.appsec.trackUserLoginFailureEvent(userId, exists, { metadata0: 'value0', metadata1: 'value1' })
}
res.statusCode = req.user ? 200 : 401

res.sendStatus(401)
if (event === 'failure') {
tracer.appsec.trackUserLoginFailureEvent(userId, exists, { metadata0: 'value0', metadata1: 'value1' })

res.statusCode = 401
} else if (event === 'success') {
tracer.appsec.trackUserLoginSuccessEvent(
{
id: userId,
email: userMail,
name: 'system_tests_user'
},
{
metadata0: 'value0',
metadata1: 'value1'
}
)

res.sendStatus(200)
} else {
res.sendStatus(200)
const sdkUser = {
id: userId,
email: userMail,
name: 'system_tests_user'
}

tracer.appsec.trackUserLoginSuccessEvent(sdkUser, { metadata0: 'value0', metadata1: 'value1' })

const isUserBlocked = tracer.appsec.isUserBlocked(sdkUser)
if (isUserBlocked && tracer.appsec.blockRequest(req, res)) {
return true
}

res.statusCode = 200
}
}

function getStrategy (req, res, next) {
const auth = req.query && req.query.auth
if (auth === 'local') {
return passport.authenticate('local', { session: false }, function (err, user, info) {
handleAuthentication(req, res, next, err, user, info)
})(req, res, next)
app.use(passport.initialize())
app.use(passport.session())

passport.serializeUser((user, done) => {
done(null, user.id)
})

passport.deserializeUser((id, done) => {
const user = findUser({ id })

done(null, user)
})

passport.use(new LocalStrategy((username, password, done) => {
const user = findUser({ username, password })

done(null, user)
}))

passport.use(new BasicStrategy((username, password, done) => {
const user = findUser({ username, password })

done(null, user)
}))

// rewrite url depending on which strategy to use
app.all('/login', (req, res, next) => {
if (req.query.sdk_trigger === 'before' && shouldSdkBlock(req, res)) {
return
}

let newRoute

switch (req.query?.auth) {
case 'basic':
newRoute = '/login/basic'
break

case 'local':
default:
newRoute = '/login/local'
}

req.url = req.url.replace('/login', newRoute)

next()
})

app.use('/login/local', passport.authenticate('local', { failWithError: true }), handleError)
app.use('/login/basic', passport.authenticate('basic', { failWithError: true }), handleError)

// only stop if unexpected error
function handleError (err, req, res, next) {
if (err?.name !== 'AuthenticationError') {
console.error('unexpected login error', err)
next(err)
} else {
return passport.authenticate('basic', { session: false }, function (err, user, info) {
handleAuthentication(req, res, next, err, user, info)
})(req, res, next)
next()
}
}

app.use(passport.initialize())
app.all('/login',
getStrategy,
(req, res, next) => {
res.sendStatus(200)
// callback for all strategies to run SDK
app.all('/login/*', (req, res) => {
if (req.query.sdk_trigger !== 'before' && shouldSdkBlock(req, res)) {
return
}
)

res.sendStatus(res.statusCode || 200)
})
}
14 changes: 14 additions & 0 deletions utils/build/docker/nodejs/express/iast/exclusions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

const semver = require('semver')
const version = require('dd-trace/package.json').version

if (semver.satisfies(version, '<5.33.0')) {
simon-id marked this conversation as resolved.
Show resolved Hide resolved
const WeakHashAnalyzer = require('dd-trace/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js')
const original = WeakHashAnalyzer._isExcluded

WeakHashAnalyzer._isExcluded = function wrappedIsExcluded (location) {
if (location.path.includes('express-session/index.js')) return true
else return original.apply(this, arguments)
}
}
Loading
Loading