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

New automatic user event collection #4674

Merged
merged 93 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
023828c
lint
simon-id Sep 11, 2024
98090fb
update config
simon-id Sep 11, 2024
5cc7274
add new RC capability
simon-id Sep 11, 2024
3343878
Merge branch 'master' into new_user_collection
simon-id Sep 11, 2024
4e127de
add RC handler
simon-id Sep 12, 2024
b4daf8f
fix require path
simon-id Sep 12, 2024
5f85b8e
add setCollectionMode()
simon-id Sep 12, 2024
1b074ad
use setCollectionMode() in appsec index
simon-id Sep 12, 2024
2da5bef
Merge branch 'master' into new_user_collection
simon-id Sep 17, 2024
5105006
cleanup
simon-id Oct 20, 2024
65c680b
update typings
simon-id Oct 20, 2024
23f965d
cleanup
simon-id Oct 20, 2024
b5b336a
RC config update
simon-id Oct 20, 2024
53a7791
push everything
simon-id Oct 30, 2024
2b7d2e6
simplify passport strategies instrumentation
simon-id Oct 30, 2024
24c2828
fixes
simon-id Oct 30, 2024
cdb89f6
simplify code
simon-id Oct 30, 2024
32edfae
cleanup
simon-id Oct 30, 2024
b5aa0d4
delete passport.js
simon-id Oct 30, 2024
1f30a3c
Update packages/datadog-instrumentations/src/passport-utils.js
simon-id Oct 30, 2024
9fee2bc
update verify subscriber
simon-id Oct 30, 2024
be3115a
rollback config changes
simon-id Oct 30, 2024
38b4419
fix config
simon-id Oct 30, 2024
40c9df1
add blocking for passport strategies
simon-id Oct 30, 2024
e34bb72
update typings and docs
simon-id Oct 30, 2024
d255702
update appsec index
simon-id Oct 30, 2024
b6d4584
update RC
simon-id Oct 30, 2024
75782f9
Merge branch 'master' into new_user_collection
simon-id Oct 30, 2024
4ff8379
push some stuff
simon-id Oct 30, 2024
4a566a8
cleanup
simon-id Oct 30, 2024
882bc8b
cleanup
simon-id Oct 30, 2024
d4345ef
add new usr.login waf address
simon-id Oct 31, 2024
be4a06c
commit some stuff
simon-id Oct 31, 2024
cc86e3a
pass abort controller for blocking
simon-id Nov 1, 2024
ce604c1
change config default
simon-id Nov 15, 2024
56c30e8
handle duplicate RC confs for auto_user_instrum.mode
simon-id Nov 15, 2024
1d3773a
refactor sdk/track_event.js to only be used by the SDK
simon-id Nov 15, 2024
91a59db
remove some comments
simon-id Nov 19, 2024
faea3aa
pass login to WAF in SDK login success event
simon-id Nov 19, 2024
fe70c84
add comments
simon-id Nov 19, 2024
e32f78e
add framework name to passport strategy instrum
simon-id Nov 20, 2024
806ab23
add framework name and waf handleResults() to onPassportVerify()
simon-id Nov 20, 2024
69be031
Merge branch 'master' into new_user_collection
simon-id Nov 20, 2024
1e1bb47
finally commit trackLogin()
simon-id Nov 21, 2024
8eac055
fix tag override condition
simon-id Nov 21, 2024
a444cfe
add telemetry function
simon-id Nov 21, 2024
187b42f
Merge branch 'master' into new_user_collection
simon-id Nov 21, 2024
72a4a8b
Merge branch 'master' into new_user_collection
simon-id Nov 22, 2024
9fa94f6
move user_tracking into a file instead of a subfolder
simon-id Nov 22, 2024
d51e434
Merge branch 'master' into new_user_collection
simon-id Nov 22, 2024
7e3374d
Use addresses enum instead of hardcoded business logic events
simon-id Nov 25, 2024
c099a2a
Update default value of collection mode in typings
simon-id Nov 25, 2024
96cb722
Add string check in getUserId()
simon-id Nov 25, 2024
0a347e8
Merge branch 'master' into new_user_collection
simon-id Nov 26, 2024
85ad6f2
fix existing RC tests
simon-id Nov 26, 2024
70dcbab
add tests for RC collection mode
simon-id Nov 26, 2024
ff72271
fix env var ordering
simon-id Nov 26, 2024
7338bd6
cleanup track_event.spec.js
simon-id Nov 29, 2024
b71ed06
do not export or test trackEvent()
simon-id Nov 29, 2024
166bc23
update test, cleanup, and add missing coverage
simon-id Nov 29, 2024
a93b79a
change ordering of code to match tests
simon-id Nov 29, 2024
a83c4fb
delete passport-utils tests because it's useless
simon-id Nov 29, 2024
6cf7ef2
fix test to correctly use passReqToCallback
simon-id Nov 29, 2024
29c0d62
update passport-local tests
simon-id Nov 29, 2024
afafb8a
update tests for passpot-http
simon-id Dec 2, 2024
956cb9b
allow empty login strings
simon-id Dec 2, 2024
62ae42b
update TS tests
simon-id Dec 2, 2024
375f688
update telemetry tests
simon-id Dec 2, 2024
5a2b1cd
move res definition to add res inside req
simon-id Dec 5, 2024
4a5701a
update appsec/index.js tests
simon-id Dec 5, 2024
13c66a7
update config tests
simon-id Dec 5, 2024
950610b
it's spelled `length`
simon-id Dec 5, 2024
e809b50
Merge branch 'master' into new_user_collection
simon-id Dec 5, 2024
fd15e9d
Remove newline from password in http basic error test
simon-id Dec 8, 2024
e9ceb27
add ASM prefix to logs
simon-id Dec 8, 2024
3728bce
Update packages/dd-trace/src/appsec/remote_config/index.js
simon-id Dec 8, 2024
36d3a1d
Merge branch 'master' into new_user_collection
simon-id Dec 8, 2024
5fb5c81
Update packages/dd-trace/src/appsec/telemetry.js
simon-id Dec 8, 2024
8fe1c2e
fix wrong function name
simon-id Dec 8, 2024
98cae84
rename `incrementMissingUserLogin` to `incrementMissingUserLoginMetric`
simon-id Dec 8, 2024
baa29a6
add error log when RC config is rejected
simon-id Dec 8, 2024
d882589
fix inconsequential typo
simon-id Dec 8, 2024
2562a48
lint
simon-id Dec 8, 2024
7d5ef29
add user_tracking tests
simon-id Dec 11, 2024
3325149
delete old passport tests
simon-id Dec 11, 2024
1effb29
Merge branch 'master' into new_user_collection
simon-id Dec 11, 2024
18659f1
Update packages/dd-trace/test/appsec/user_tracking.spec.js
simon-id Dec 11, 2024
134c561
lint
simon-id Dec 12, 2024
1df40e2
add SDK tag for login alias
simon-id Dec 12, 2024
5fe9404
Merge branch 'master' into new_user_collection
simon-id Dec 12, 2024
172da58
lint again omg
simon-id Dec 12, 2024
b1ba64a
Merge branch 'master' into new_user_collection
simon-id Dec 13, 2024
a752423
Update packages/dd-trace/src/appsec/sdk/track_event.js
simon-id Dec 13, 2024
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
2 changes: 1 addition & 1 deletion docs/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ tracer.init({
blockedTemplateJson: './blocked.json',
blockedTemplateGraphql: './blockedgraphql.json',
eventTracking: {
mode: 'safe'
mode: 'anon'
},
apiSecurity: {
enabled: true,
Expand Down
22 changes: 17 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,12 +655,24 @@ declare namespace tracer {
*/
eventTracking?: {
/**
* Controls the automated user event tracking mode. Possible values are disabled, safe and extended.
* On safe mode, any detected Personally Identifiable Information (PII) about the user will be redacted from the event.
* On extended mode, no redaction will take place.
* @default 'safe'
* Controls the automated user tracking mode for user IDs and logins collections. Possible values:
* * 'anonymous': will hash user IDs and user logins before collecting them
* * 'anon': alias for 'anonymous'
* * 'safe': deprecated alias for 'anonymous'
*
* * 'identification': will collect user IDs and logins without redaction
* * 'ident': alias for 'identification'
* * 'extended': deprecated alias for 'identification'
*
* * 'disabled': will not collect user IDs and logins
*
* Unknown values will be considered as 'disabled'
* @default 'identification'
*/
mode?: 'safe' | 'extended' | 'disabled'
mode?:
'anonymous' | 'anon' | 'safe' |
'identification' | 'ident' | 'extended' |
'disabled'
},
/**
* Configuration for Api Security
Expand Down
16 changes: 2 additions & 14 deletions packages/datadog-instrumentations/src/passport-http.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
'use strict'

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

addHook({
name: 'passport-http',
file: 'lib/passport-http/strategies/basic.js',
versions: ['>=0.3.0']
}, BasicStrategy => {
return shimmer.wrapFunction(BasicStrategy, BasicStrategy => function () {
const type = 'http'

if (typeof arguments[0] === 'function') {
arguments[0] = wrapVerify(arguments[0], false, type)
} else {
arguments[1] = wrapVerify(arguments[1], (arguments[0] && arguments[0].passReqToCallback), type)
}
return BasicStrategy.apply(this, arguments)
})
})
}, strategyHook)
16 changes: 2 additions & 14 deletions packages/datadog-instrumentations/src/passport-local.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
'use strict'

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

addHook({
name: 'passport-local',
file: 'lib/strategy.js',
versions: ['>=1.0.0']
}, Strategy => {
return shimmer.wrapFunction(Strategy, Strategy => function () {
const type = 'local'

if (typeof arguments[0] === 'function') {
arguments[0] = wrapVerify(arguments[0], false, type)
} else {
arguments[1] = wrapVerify(arguments[1], (arguments[0] && arguments[0].passReqToCallback), type)
}
return Strategy.apply(this, arguments)
})
})
}, strategyHook)
62 changes: 43 additions & 19 deletions packages/datadog-instrumentations/src/passport-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,57 @@ const { channel } = require('./helpers/instrument')

const passportVerifyChannel = channel('datadog:passport:verify:finish')

function wrapVerifiedAndPublish (username, password, verified, type) {
if (!passportVerifyChannel.hasSubscribers) {
return verified
}
function wrapVerifiedAndPublish (framework, username, verified) {
return shimmer.wrapFunction(verified, function wrapVerified (verified) {
return function wrappedVerified (err, user) {
// if there is an error, it's neither an auth success nor a failure
if (!err) {
const abortController = new AbortController()

passportVerifyChannel.publish({ framework, login: username, user, success: !!user, abortController })

if (abortController.signal.aborted) return
}

// eslint-disable-next-line n/handle-callback-err
return shimmer.wrapFunction(verified, verified => function (err, user, info) {
const credentials = { type, username }
passportVerifyChannel.publish({ credentials, user })
return verified.apply(this, arguments)
return verified.apply(this, arguments)
}
})
}

function wrapVerify (verify, passReq, type) {
if (passReq) {
return function (req, username, password, verified) {
arguments[3] = wrapVerifiedAndPublish(username, password, verified, type)
return verify.apply(this, arguments)
function wrapVerify (verify) {
return function wrappedVerify (req, username, password, verified) {
simon-id marked this conversation as resolved.
Show resolved Hide resolved
if (passportVerifyChannel.hasSubscribers) {
const framework = `passport-${this.name}`

// replace the callback with our own wrapper to get the result
if (this._passReqToCallback) {
arguments[3] = wrapVerifiedAndPublish(framework, arguments[1], arguments[3])
} else {
arguments[2] = wrapVerifiedAndPublish(framework, arguments[0], arguments[2])
}
}
} else {
return function (username, password, verified) {
arguments[2] = wrapVerifiedAndPublish(username, password, verified, type)
return verify.apply(this, arguments)

return verify.apply(this, arguments)
}
}

function wrapStrategy (Strategy) {
return function wrappedStrategy () {
// verify function can be either the first or second argument
if (typeof arguments[0] === 'function') {
arguments[0] = wrapVerify(arguments[0])
} else {
arguments[1] = wrapVerify(arguments[1])
}

return Strategy.apply(this, arguments)
}
}

function strategyHook (Strategy) {
return shimmer.wrapFunction(Strategy, wrapStrategy)
}

module.exports = {
wrapVerify
strategyHook
}
115 changes: 86 additions & 29 deletions packages/datadog-instrumentations/test/passport-http.spec.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
'use strict'

const agent = require('../../dd-trace/test/plugins/agent')
const axios = require('axios')
const axios = require('axios').create({ validateStatus: null })
const dc = require('dc-polyfill')
const { storage } = require('../../datadog-core')

withVersions('passport-http', 'passport-http', version => {
describe('passport-http instrumentation', () => {
const passportVerifyChannel = dc.channel('datadog:passport:verify:finish')
let port, server, subscriberStub

before(() => {
return agent.load(['express', 'passport', 'passport-http'], { client: false })
return agent.load(['http', 'express', 'passport', 'passport-http'], { client: false })
})

before((done) => {
Expand All @@ -19,7 +20,17 @@ withVersions('passport-http', 'passport-http', version => {
const BasicStrategy = require(`../../../versions/passport-http@${version}`).get().BasicStrategy
const app = express()

passport.use(new BasicStrategy((username, password, done) => {
function validateUser (req, username, password, done) {
// support with or without passReqToCallback
if (typeof done !== 'function') {
done = password
password = username
username = req
}

// simulate db error
if (username === 'error') return done('error')

const users = [{
_id: 1,
username: 'test',
Expand All @@ -35,7 +46,18 @@ withVersions('passport-http', 'passport-http', version => {
return done(null, user)
}
}
))

passport.use('basic', new BasicStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: false
}, validateUser))

passport.use('basic-withreq', new BasicStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true
}, validateUser))

app.use(passport.initialize())
app.use(express.json())
Expand All @@ -44,16 +66,14 @@ withVersions('passport-http', 'passport-http', version => {
passport.authenticate('basic', {
successRedirect: '/grant',
failureRedirect: '/deny',
passReqToCallback: false,
session: false
})
)

app.post('/req',
passport.authenticate('basic', {
app.get('/req',
passport.authenticate('basic-withreq', {
successRedirect: '/grant',
failureRedirect: '/deny',
passReqToCallback: true,
session: false
})
)
Expand All @@ -66,9 +86,7 @@ withVersions('passport-http', 'passport-http', version => {
res.send('Denied')
})

passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) {
subscriberStub(arguments[0])
})
passportVerifyChannel.subscribe((data) => subscriberStub(data))

server = app.listen(0, () => {
port = server.address().port
Expand All @@ -85,6 +103,18 @@ withVersions('passport-http', 'passport-http', version => {
return agent.close({ ritmReset: false })
})

it('should not call subscriber when an error occurs', async () => {
const res = await axios.get(`http://localhost:${port}/`, {
headers: {
// error:1234
Authorization: 'Basic ZXJyb3I6MTIzNA=='
}
})

expect(res.status).to.equal(500)
expect(subscriberStub).to.not.be.called
})

it('should call subscriber with proper arguments on success', async () => {
const res = await axios.get(`http://localhost:${port}/`, {
headers: {
Expand All @@ -95,16 +125,17 @@ withVersions('passport-http', 'passport-http', version => {

expect(res.status).to.equal(200)
expect(res.data).to.equal('Granted')
expect(subscriberStub).to.be.calledOnceWithExactly(
{
credentials: { type: 'http', username: 'test' },
user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' }
}
)
expect(subscriberStub).to.be.calledOnceWithExactly({
framework: 'passport-basic',
uurien marked this conversation as resolved.
Show resolved Hide resolved
login: 'test',
user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' },
success: true,
abortController: new AbortController()
})
})

it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => {
const res = await axios.get(`http://localhost:${port}/`, {
const res = await axios.get(`http://localhost:${port}/req`, {
headers: {
// test:1234
Authorization: 'Basic dGVzdDoxMjM0'
Expand All @@ -113,12 +144,13 @@ withVersions('passport-http', 'passport-http', version => {

expect(res.status).to.equal(200)
expect(res.data).to.equal('Granted')
expect(subscriberStub).to.be.calledOnceWithExactly(
{
credentials: { type: 'http', username: 'test' },
user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' }
}
)
expect(subscriberStub).to.be.calledOnceWithExactly({
framework: 'passport-basic',
login: 'test',
user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' },
success: true,
abortController: new AbortController()
})
})

it('should call subscriber with proper arguments on failure', async () => {
Expand All @@ -131,12 +163,37 @@ withVersions('passport-http', 'passport-http', version => {

expect(res.status).to.equal(200)
expect(res.data).to.equal('Denied')
expect(subscriberStub).to.be.calledOnceWithExactly(
{
credentials: { type: 'http', username: 'test' },
user: false
expect(subscriberStub).to.be.calledOnceWithExactly({
framework: 'passport-basic',
login: 'test',
user: false,
success: false,
abortController: new AbortController()
})
})

it('should block when subscriber aborts', async () => {
subscriberStub = sinon.spy(({ abortController }) => {
storage.getStore().req.res.writeHead(403).end('Blocked')
abortController.abort()
})

const res = await axios.get(`http://localhost:${port}/`, {
headers: {
// test:1234
Authorization: 'Basic dGVzdDoxMjM0'
}
)
})

expect(res.status).to.equal(403)
expect(res.data).to.equal('Blocked')
expect(subscriberStub).to.be.calledOnceWithExactly({
framework: 'passport-basic',
login: 'test',
user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' },
success: true,
abortController: new AbortController()
})
})
})
})
Loading
Loading