dd-trace 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -0
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +2 -1
- package/index.d.ts +43 -20
- package/package.json +4 -3
- package/packages/datadog-instrumentations/src/crypto.js +30 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/net.js +13 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +19 -10
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/index.js +20 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +48 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
- package/packages/dd-trace/src/appsec/iast/iast-context.js +50 -0
- package/packages/dd-trace/src/appsec/iast/index.js +59 -0
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +94 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +70 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +113 -0
- package/packages/dd-trace/src/config.js +76 -10
- package/packages/dd-trace/src/constants.js +9 -1
- package/packages/dd-trace/src/encode/span-stats.js +155 -0
- package/packages/dd-trace/src/exporters/agent/index.js +14 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +6 -3
- package/packages/dd-trace/src/exporters/common/request.js +6 -2
- package/packages/dd-trace/src/exporters/span-stats/index.js +20 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +54 -0
- package/packages/dd-trace/src/format.js +2 -0
- package/packages/dd-trace/src/iitm.js +11 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +71 -0
- package/packages/dd-trace/src/opentracing/tracer.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +12 -2
- package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
- package/packages/dd-trace/src/plugins/util/ip_blocklist.js +25 -0
- package/packages/dd-trace/src/plugins/util/web.js +100 -2
- package/packages/dd-trace/src/priority_sampler.js +36 -1
- package/packages/dd-trace/src/proxy.js +3 -0
- package/packages/dd-trace/src/ritm.js +10 -1
- package/packages/dd-trace/src/span_processor.js +7 -1
- package/packages/dd-trace/src/span_stats.js +210 -0
- package/packages/dd-trace/src/telemetry/dependencies.js +83 -0
- package/packages/dd-trace/src/{telemetry.js → telemetry/index.js} +10 -65
- package/packages/dd-trace/src/telemetry/send-data.js +35 -0
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -3,6 +3,7 @@ require,@datadog/native-appsec,Apache license 2.0,Copyright 2018 Datadog Inc.
|
|
|
3
3
|
require,@datadog/native-metrics,Apache license 2.0,Copyright 2018 Datadog Inc.
|
|
4
4
|
require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
|
|
5
5
|
require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
|
|
6
|
+
require,cidr-matcher,MIT,Copyright 2015 Marco Pracucci
|
|
6
7
|
require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
|
|
7
8
|
require,diagnostics_channel,MIT,Copyright 2021 Simon D.
|
|
8
9
|
require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
|
package/ext/tags.d.ts
CHANGED
|
@@ -15,7 +15,8 @@ declare const tags: {
|
|
|
15
15
|
HTTP_ROUTE: 'http.route'
|
|
16
16
|
HTTP_REQUEST_HEADERS: 'http.request.headers'
|
|
17
17
|
HTTP_RESPONSE_HEADERS: 'http.response.headers'
|
|
18
|
-
HTTP_USERAGENT: 'http.useragent'
|
|
18
|
+
HTTP_USERAGENT: 'http.useragent',
|
|
19
|
+
HTTP_CLIENT_IP: 'http.client_ip'
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export = tags
|
package/ext/tags.js
CHANGED
|
@@ -20,7 +20,8 @@ const tags = {
|
|
|
20
20
|
HTTP_ROUTE: 'http.route',
|
|
21
21
|
HTTP_REQUEST_HEADERS: 'http.request.headers',
|
|
22
22
|
HTTP_RESPONSE_HEADERS: 'http.response.headers',
|
|
23
|
-
HTTP_USERAGENT: 'http.useragent'
|
|
23
|
+
HTTP_USERAGENT: 'http.useragent',
|
|
24
|
+
HTTP_CLIENT_IP: 'http.client_ip'
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
// Deprecated
|
package/index.d.ts
CHANGED
|
@@ -267,6 +267,22 @@ export declare interface TracerOptions {
|
|
|
267
267
|
*/
|
|
268
268
|
sampleRate?: number;
|
|
269
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Global rate limit that is applied on the global sample rate and all rules,
|
|
272
|
+
* and controls the ingestion rate limit between the agent and the backend.
|
|
273
|
+
* Defaults to deferring the decision to the agent.
|
|
274
|
+
*/
|
|
275
|
+
rateLimit?: Number,
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Sampling rules to apply to priority samplin. Each rule is a JSON,
|
|
279
|
+
* consisting of `service` and `name`, which are regexes to match against
|
|
280
|
+
* a trace's `service` and `name`, and a corresponding `sampleRate`. If not
|
|
281
|
+
* specified, will defer to global sampling rate for all spans.
|
|
282
|
+
* @default []
|
|
283
|
+
*/
|
|
284
|
+
samplingRules?: SamplingRule[]
|
|
285
|
+
|
|
270
286
|
/**
|
|
271
287
|
* Interval in milliseconds at which the tracer will submit traces to the agent.
|
|
272
288
|
* @default 2000
|
|
@@ -298,7 +314,10 @@ export declare interface TracerOptions {
|
|
|
298
314
|
protocolVersion?: string
|
|
299
315
|
|
|
300
316
|
/**
|
|
301
|
-
*
|
|
317
|
+
* Deprecated in favor of the global versions of the variables provided under this option
|
|
318
|
+
*
|
|
319
|
+
* @deprecated
|
|
320
|
+
* @hidden
|
|
302
321
|
*/
|
|
303
322
|
ingestion?: {
|
|
304
323
|
/**
|
|
@@ -307,7 +326,7 @@ export declare interface TracerOptions {
|
|
|
307
326
|
sampleRate?: number
|
|
308
327
|
|
|
309
328
|
/**
|
|
310
|
-
* Controls the ingestion rate limit between the agent and the backend.
|
|
329
|
+
* Controls the ingestion rate limit between the agent and the backend. Defaults to deferring the decision to the agent.
|
|
311
330
|
*/
|
|
312
331
|
rateLimit?: number
|
|
313
332
|
};
|
|
@@ -333,32 +352,36 @@ export declare interface TracerOptions {
|
|
|
333
352
|
exporter?: 'log' | 'agent'
|
|
334
353
|
|
|
335
354
|
/**
|
|
336
|
-
*
|
|
355
|
+
* Whether to enable the experimental `getRumData` method.
|
|
356
|
+
* @default false
|
|
337
357
|
*/
|
|
338
|
-
|
|
358
|
+
enableGetRumData?: boolean
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Configuration of the IAST. Can be a boolean as an alias to `iast.enabled`.
|
|
362
|
+
*/
|
|
363
|
+
iast?: boolean | {
|
|
339
364
|
/**
|
|
340
|
-
*
|
|
365
|
+
* Whether to enable IAST.
|
|
366
|
+
* @default false
|
|
341
367
|
*/
|
|
342
|
-
|
|
343
|
-
|
|
368
|
+
enabled?: boolean,
|
|
344
369
|
/**
|
|
345
|
-
*
|
|
346
|
-
* @default
|
|
370
|
+
* Controls the percentage of requests that iast will analyze
|
|
371
|
+
* @default 30
|
|
347
372
|
*/
|
|
348
|
-
|
|
349
|
-
|
|
373
|
+
requestSampling?: number,
|
|
350
374
|
/**
|
|
351
|
-
*
|
|
352
|
-
* @default
|
|
375
|
+
* Controls how many request can be analyzing code vulnerabilities at the same time
|
|
376
|
+
* @default 2
|
|
353
377
|
*/
|
|
354
|
-
|
|
378
|
+
maxConcurrentRequests?: number,
|
|
379
|
+
/**
|
|
380
|
+
* Controls how many code vulnerabilities can be detected in the same request
|
|
381
|
+
* @default 2
|
|
382
|
+
*/
|
|
383
|
+
maxContextOperations?: number
|
|
355
384
|
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Whether to enable the experimental `getRumData` method.
|
|
359
|
-
* @default false
|
|
360
|
-
*/
|
|
361
|
-
enableGetRumData?: boolean
|
|
362
385
|
};
|
|
363
386
|
|
|
364
387
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -61,11 +61,12 @@
|
|
|
61
61
|
"@datadog/native-appsec": "^1.2.1",
|
|
62
62
|
"@datadog/native-metrics": "^1.4.2",
|
|
63
63
|
"@datadog/pprof": "^1.0.2",
|
|
64
|
-
"@datadog/sketches-js": "^2.
|
|
64
|
+
"@datadog/sketches-js": "^2.1.0",
|
|
65
|
+
"cidr-matcher": "^2.1.1",
|
|
65
66
|
"crypto-randomuuid": "^1.0.0",
|
|
66
67
|
"diagnostics_channel": "^1.1.0",
|
|
67
68
|
"ignore": "^5.2.0",
|
|
68
|
-
"import-in-the-middle": "^1.3.
|
|
69
|
+
"import-in-the-middle": "^1.3.1",
|
|
69
70
|
"istanbul-lib-coverage": "3.2.0",
|
|
70
71
|
"koalas": "^1.0.2",
|
|
71
72
|
"limiter": "^1.1.4",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
channel,
|
|
5
|
+
addHook
|
|
6
|
+
} = require('./helpers/instrument')
|
|
7
|
+
const shimmer = require('../../datadog-shimmer')
|
|
8
|
+
|
|
9
|
+
const cryptoCh = channel('datadog:crypto:hashing:start')
|
|
10
|
+
|
|
11
|
+
addHook({ name: 'crypto' }, crypto => {
|
|
12
|
+
shimmer.massWrap(
|
|
13
|
+
crypto,
|
|
14
|
+
['createHash', 'createHmac', 'createSign', 'createVerify', 'sign', 'verify'],
|
|
15
|
+
wrapMethod
|
|
16
|
+
)
|
|
17
|
+
return crypto
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function wrapMethod (cryptoMethod) {
|
|
21
|
+
return function () {
|
|
22
|
+
if (cryptoCh.hasSubscribers) {
|
|
23
|
+
if (arguments.length > 0) {
|
|
24
|
+
const algorithm = arguments[0]
|
|
25
|
+
cryptoCh.publish({ algorithm })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return cryptoMethod.apply(this, arguments)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
'cassandra-driver': () => require('../cassandra-driver'),
|
|
18
18
|
'connect': () => require('../connect'),
|
|
19
19
|
'couchbase': () => require('../couchbase'),
|
|
20
|
+
'crypto': () => require('../crypto'),
|
|
20
21
|
'cypress': () => require('../cypress'),
|
|
21
22
|
'dns': () => require('../dns'),
|
|
22
23
|
'elasticsearch': () => require('../elasticsearch'),
|
|
@@ -49,6 +49,19 @@ addHook({ name: 'net' }, net => {
|
|
|
49
49
|
setupListeners(this, 'tcp', asyncResource)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
const emit = this.emit
|
|
53
|
+
this.emit = function (eventName) {
|
|
54
|
+
switch (eventName) {
|
|
55
|
+
case 'ready':
|
|
56
|
+
case 'connect':
|
|
57
|
+
return callbackResource.runInAsyncScope(() => {
|
|
58
|
+
return emit.apply(this, arguments)
|
|
59
|
+
})
|
|
60
|
+
default:
|
|
61
|
+
return emit.apply(this, arguments)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
try {
|
|
53
66
|
return connect.apply(this, arguments)
|
|
54
67
|
} catch (err) {
|
|
@@ -73,21 +73,21 @@ function truncate (input) {
|
|
|
73
73
|
return input.slice(0, Math.min(input.length, 10000))
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function simplify (input) {
|
|
77
|
-
return isBSON(input) ? input.toHexString() : input
|
|
78
|
-
}
|
|
79
|
-
|
|
80
76
|
function shouldSimplify (input) {
|
|
81
|
-
return !isObject(input)
|
|
77
|
+
return !isObject(input)
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
function shouldHide (input) {
|
|
85
|
-
return Buffer.isBuffer(input) || typeof input === 'function'
|
|
81
|
+
return Buffer.isBuffer(input) || typeof input === 'function' || isBinary(input)
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
function limitDepth (input) {
|
|
85
|
+
if (isBSON(input)) {
|
|
86
|
+
input = input.toJSON()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
89
|
if (shouldHide(input)) return '?'
|
|
90
|
-
if (shouldSimplify(input)) return
|
|
90
|
+
if (shouldSimplify(input)) return input
|
|
91
91
|
|
|
92
92
|
const output = {}
|
|
93
93
|
const queue = [{
|
|
@@ -104,11 +104,16 @@ function limitDepth (input) {
|
|
|
104
104
|
for (const key in input) {
|
|
105
105
|
if (typeof input[key] === 'function') continue
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
let child = input[key]
|
|
108
|
+
|
|
109
|
+
if (isBSON(child)) {
|
|
110
|
+
child = child.toJSON()
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
if (depth >= 10 || shouldHide(child)) {
|
|
109
114
|
output[key] = '?'
|
|
110
115
|
} else if (shouldSimplify(child)) {
|
|
111
|
-
output[key] =
|
|
116
|
+
output[key] = child
|
|
112
117
|
} else {
|
|
113
118
|
queue.push({
|
|
114
119
|
input: child,
|
|
@@ -127,7 +132,11 @@ function isObject (val) {
|
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
function isBSON (val) {
|
|
130
|
-
return val && val._bsontype
|
|
135
|
+
return val && val._bsontype && !isBinary(val)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isBinary (val) {
|
|
139
|
+
return val && val._bsontype === 'Binary'
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
module.exports = MongodbCorePlugin
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const analyzers = require('./analyzers')
|
|
4
|
+
|
|
5
|
+
function enableAllAnalyzers () {
|
|
6
|
+
for (const analyzer in analyzers) {
|
|
7
|
+
analyzers[analyzer].configure(true)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function disableAllAnalyzers () {
|
|
12
|
+
for (const analyzer in analyzers) {
|
|
13
|
+
analyzers[analyzer].configure(false)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
enableAllAnalyzers,
|
|
19
|
+
disableAllAnalyzers
|
|
20
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Plugin = require('../../../../src/plugins/plugin')
|
|
4
|
+
const { storage } = require('../../../../../datadog-core')
|
|
5
|
+
const { getFirstNonDDPathAndLine } = require('./../path-line')
|
|
6
|
+
const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
|
|
7
|
+
const { getIastContext } = require('../iast-context')
|
|
8
|
+
const overheadController = require('../overhead-controller')
|
|
9
|
+
|
|
10
|
+
class Analyzer extends Plugin {
|
|
11
|
+
constructor (type) {
|
|
12
|
+
super()
|
|
13
|
+
this._type = type
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_isVulnerable (value, context) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_report (value, context) {
|
|
21
|
+
const evidence = this._getEvidence(value)
|
|
22
|
+
const location = this._getLocation()
|
|
23
|
+
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
24
|
+
const vulnerability = createVulnerability(this._type, evidence, spanId, location)
|
|
25
|
+
addVulnerability(context, vulnerability)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_getEvidence (value) {
|
|
29
|
+
return { value }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_getLocation () {
|
|
33
|
+
return getFirstNonDDPathAndLine()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
analyze (value) {
|
|
37
|
+
const iastContext = getIastContext(storage.getStore())
|
|
38
|
+
if (iastContext && this._isVulnerable(value, iastContext) && this._checkOCE(iastContext)) {
|
|
39
|
+
this._report(value, iastContext)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_checkOCE (context) {
|
|
44
|
+
return overheadController.hasQuota(overheadController.OPERATIONS.REPORT_VULNERABILITY, context)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Analyzer
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const Analyzer = require('./vulnerability-analyzer')
|
|
3
|
+
|
|
4
|
+
const INSECURE_HASH_ALGORITHMS = new Set([
|
|
5
|
+
'md4', 'md4WithRSAEncryption', 'RSA-MD4',
|
|
6
|
+
'RSA-MD5', 'md5', 'md5-sha1', 'ssl3-md5', 'md5WithRSAEncryption',
|
|
7
|
+
'RSA-SHA1', 'RSA-SHA1-2', 'sha1', 'md5-sha1', 'sha1WithRSAEncryption', 'ssl3-sha1'
|
|
8
|
+
].map(algorithm => algorithm.toLowerCase()))
|
|
9
|
+
|
|
10
|
+
class WeakHashAnalyzer extends Analyzer {
|
|
11
|
+
constructor () {
|
|
12
|
+
super('WEAK_HASH')
|
|
13
|
+
this.addSub('datadog:crypto:hashing:start', ({ algorithm }) => this.analyze(algorithm))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_isVulnerable (algorithm) {
|
|
17
|
+
if (typeof algorithm === 'string') {
|
|
18
|
+
return INSECURE_HASH_ALGORITHMS.has(algorithm.toLowerCase())
|
|
19
|
+
}
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = new WeakHashAnalyzer()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const IAST_CONTEXT_KEY = Symbol('_dd.iast.context')
|
|
2
|
+
|
|
3
|
+
function getIastContext (store) {
|
|
4
|
+
return store && store[IAST_CONTEXT_KEY]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* TODO Fix storage problem when the close event is called without
|
|
8
|
+
finish event to remove `topContext` references
|
|
9
|
+
We have to save the context in two places, because
|
|
10
|
+
clean can be called when the storage store is not available
|
|
11
|
+
*/
|
|
12
|
+
function saveIastContext (store, topContext, context) {
|
|
13
|
+
if (store && topContext) {
|
|
14
|
+
store[IAST_CONTEXT_KEY] = context
|
|
15
|
+
topContext[IAST_CONTEXT_KEY] = context
|
|
16
|
+
return store[IAST_CONTEXT_KEY]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* TODO Fix storage problem when the close event is called without
|
|
21
|
+
finish event to remove `topContext` references
|
|
22
|
+
iastContext is currently saved in store and request rootContext
|
|
23
|
+
to fix problems with `close` without `finish` events
|
|
24
|
+
*/
|
|
25
|
+
function cleanIastContext (store, context, iastContext) {
|
|
26
|
+
if (store) {
|
|
27
|
+
if (!iastContext) {
|
|
28
|
+
iastContext = store[IAST_CONTEXT_KEY]
|
|
29
|
+
}
|
|
30
|
+
store[IAST_CONTEXT_KEY] = null
|
|
31
|
+
}
|
|
32
|
+
if (context) {
|
|
33
|
+
if (!iastContext) {
|
|
34
|
+
iastContext = context[IAST_CONTEXT_KEY]
|
|
35
|
+
}
|
|
36
|
+
context[IAST_CONTEXT_KEY] = null
|
|
37
|
+
}
|
|
38
|
+
if (iastContext) {
|
|
39
|
+
Object.keys(iastContext).forEach(key => delete iastContext[key])
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
getIastContext,
|
|
47
|
+
saveIastContext,
|
|
48
|
+
cleanIastContext,
|
|
49
|
+
IAST_CONTEXT_KEY
|
|
50
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { sendVulnerabilities } = require('./vulnerability-reporter')
|
|
2
|
+
const { enableAllAnalyzers, disableAllAnalyzers } = require('./analyzers')
|
|
3
|
+
const web = require('../../plugins/util/web')
|
|
4
|
+
const { storage } = require('../../../../datadog-core')
|
|
5
|
+
const overheadController = require('./overhead-controller')
|
|
6
|
+
const dc = require('diagnostics_channel')
|
|
7
|
+
const { saveIastContext, getIastContext, cleanIastContext } = require('./iast-context')
|
|
8
|
+
|
|
9
|
+
// TODO Change to `apm:http:server:request:[start|close]` when the subscription
|
|
10
|
+
// order of the callbacks can be enforce
|
|
11
|
+
const requestStart = dc.channel('dd-trace:incomingHttpRequestStart')
|
|
12
|
+
const requestClose = dc.channel('dd-trace:incomingHttpRequestEnd')
|
|
13
|
+
|
|
14
|
+
function enable (config) {
|
|
15
|
+
enableAllAnalyzers()
|
|
16
|
+
requestStart.subscribe(onIncomingHttpRequestStart)
|
|
17
|
+
requestClose.subscribe(onIncomingHttpRequestEnd)
|
|
18
|
+
overheadController.configure(config.iast)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function disable () {
|
|
22
|
+
disableAllAnalyzers()
|
|
23
|
+
if (requestStart.hasSubscribers) requestStart.unsubscribe(onIncomingHttpRequestStart)
|
|
24
|
+
if (requestClose.hasSubscribers) requestClose.unsubscribe(onIncomingHttpRequestEnd)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function onIncomingHttpRequestStart (data) {
|
|
28
|
+
if (data && data.req) {
|
|
29
|
+
const store = storage.getStore()
|
|
30
|
+
if (store) {
|
|
31
|
+
const topContext = web.getContext(data.req)
|
|
32
|
+
if (topContext) {
|
|
33
|
+
const rootSpan = topContext.span
|
|
34
|
+
const isRequestAcquired = overheadController.acquireRequest(rootSpan)
|
|
35
|
+
if (isRequestAcquired) {
|
|
36
|
+
const iastContext = saveIastContext(store, topContext, { rootSpan, req: data.req })
|
|
37
|
+
overheadController.initializeRequestContext(iastContext)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function onIncomingHttpRequestEnd (data) {
|
|
45
|
+
if (data && data.req) {
|
|
46
|
+
const store = storage.getStore()
|
|
47
|
+
const iastContext = getIastContext(storage.getStore())
|
|
48
|
+
if (iastContext && iastContext.rootSpan) {
|
|
49
|
+
overheadController.releaseRequest()
|
|
50
|
+
sendVulnerabilities(iastContext, iastContext.rootSpan)
|
|
51
|
+
}
|
|
52
|
+
// TODO web.getContext(data.req) is required when the request is aborted
|
|
53
|
+
if (cleanIastContext(store, web.getContext(data.req), iastContext)) {
|
|
54
|
+
overheadController.releaseRequest()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { enable, disable, onIncomingHttpRequestEnd, onIncomingHttpRequestStart }
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const OVERHEAD_CONTROLLER_CONTEXT_KEY = 'oce'
|
|
4
|
+
const REPORT_VULNERABILITY = 'REPORT_VULNERABILITY'
|
|
5
|
+
|
|
6
|
+
const GLOBAL_OCE_CONTEXT = {}
|
|
7
|
+
let config = {}
|
|
8
|
+
let availableRequest = 0
|
|
9
|
+
const OPERATIONS = {
|
|
10
|
+
REPORT_VULNERABILITY: {
|
|
11
|
+
hasQuota: (context) => {
|
|
12
|
+
const reserved = context && context.tokens && context.tokens[REPORT_VULNERABILITY] > 0
|
|
13
|
+
if (reserved) {
|
|
14
|
+
context.tokens[REPORT_VULNERABILITY]--
|
|
15
|
+
}
|
|
16
|
+
return reserved
|
|
17
|
+
},
|
|
18
|
+
name: REPORT_VULNERABILITY,
|
|
19
|
+
initialTokenBucketSize () {
|
|
20
|
+
return typeof config.maxContextOperations === 'number' ? config.maxContextOperations : 2
|
|
21
|
+
},
|
|
22
|
+
initContext: function (context) {
|
|
23
|
+
context.tokens[REPORT_VULNERABILITY] = this.initialTokenBucketSize()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _getNewContext () {
|
|
29
|
+
const oceContext = {
|
|
30
|
+
tokens: {}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const operation in OPERATIONS) {
|
|
34
|
+
OPERATIONS[operation].initContext(oceContext)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return oceContext
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _getContext (iastContext) {
|
|
41
|
+
if (iastContext && iastContext[OVERHEAD_CONTROLLER_CONTEXT_KEY]) {
|
|
42
|
+
return iastContext[OVERHEAD_CONTROLLER_CONTEXT_KEY]
|
|
43
|
+
}
|
|
44
|
+
return GLOBAL_OCE_CONTEXT
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _resetGlobalContext () {
|
|
48
|
+
Object.assign(GLOBAL_OCE_CONTEXT, _getNewContext())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function acquireRequest (rootSpan) {
|
|
52
|
+
if (availableRequest > 0) {
|
|
53
|
+
const sampling = config && typeof config.requestSampling === 'number'
|
|
54
|
+
? config.requestSampling : 30
|
|
55
|
+
if (rootSpan.context().toSpanId().slice(-2) <= sampling) {
|
|
56
|
+
availableRequest--
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function releaseRequest () {
|
|
64
|
+
if (availableRequest < config.maxConcurrentRequests) {
|
|
65
|
+
availableRequest++
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hasQuota (operation, iastContext) {
|
|
70
|
+
const oceContext = _getContext(iastContext)
|
|
71
|
+
return operation.hasQuota(oceContext)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function initializeRequestContext (iastContext) {
|
|
75
|
+
if (iastContext) iastContext[OVERHEAD_CONTROLLER_CONTEXT_KEY] = _getNewContext()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function configure (cfg) {
|
|
79
|
+
config = cfg
|
|
80
|
+
availableRequest = config.maxConcurrentRequests
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_resetGlobalContext()
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
OVERHEAD_CONTROLLER_CONTEXT_KEY,
|
|
87
|
+
OPERATIONS,
|
|
88
|
+
_resetGlobalContext,
|
|
89
|
+
initializeRequestContext,
|
|
90
|
+
hasQuota,
|
|
91
|
+
acquireRequest,
|
|
92
|
+
releaseRequest,
|
|
93
|
+
configure
|
|
94
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const pathLine = {
|
|
3
|
+
getFirstNonDDPathAndLine,
|
|
4
|
+
getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
|
|
5
|
+
calculateDDBasePath, // Exported only for test purposes
|
|
6
|
+
ddBasePath: calculateDDBasePath(__dirname) // Only for test purposes
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const EXCLUDED_PATHS = [
|
|
10
|
+
'/node_modules/diagnostics_channel'
|
|
11
|
+
]
|
|
12
|
+
const EXCLUDED_PATH_PREFIXES = [
|
|
13
|
+
'node:diagnostics_channel',
|
|
14
|
+
'diagnostics_channel'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
function calculateDDBasePath (dirname) {
|
|
18
|
+
const dirSteps = dirname.split(path.sep)
|
|
19
|
+
const packagesIndex = dirSteps.indexOf('packages')
|
|
20
|
+
return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getCallSiteInfo () {
|
|
24
|
+
const previousPrepareStackTrace = Error.prepareStackTrace
|
|
25
|
+
let callsiteList
|
|
26
|
+
Error.prepareStackTrace = function (_, callsites) {
|
|
27
|
+
callsiteList = callsites
|
|
28
|
+
}
|
|
29
|
+
const e = new Error()
|
|
30
|
+
e.stack
|
|
31
|
+
Error.prepareStackTrace = previousPrepareStackTrace
|
|
32
|
+
return callsiteList
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getFirstNonDDPathAndLineFromCallsites (callsites) {
|
|
36
|
+
if (callsites) {
|
|
37
|
+
for (let i = 0; i < callsites.length; i++) {
|
|
38
|
+
const callsite = callsites[i]
|
|
39
|
+
const path = callsite.getFileName()
|
|
40
|
+
if (!isExcluded(callsite) && path.indexOf(pathLine.ddBasePath) === -1) {
|
|
41
|
+
return {
|
|
42
|
+
path,
|
|
43
|
+
line: callsite.getLineNumber()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isExcluded (callsite) {
|
|
52
|
+
if (callsite.isNative()) return true
|
|
53
|
+
const filename = callsite.getFileName()
|
|
54
|
+
for (let i = 0; i < EXCLUDED_PATHS.length; i++) {
|
|
55
|
+
if (filename.indexOf(EXCLUDED_PATHS[i]) > -1) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < EXCLUDED_PATH_PREFIXES.length; i++) {
|
|
60
|
+
if (filename.indexOf(EXCLUDED_PATH_PREFIXES[i]) === 0) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getFirstNonDDPathAndLine () {
|
|
68
|
+
return getFirstNonDDPathAndLineFromCallsites(getCallSiteInfo())
|
|
69
|
+
}
|
|
70
|
+
module.exports = pathLine
|