dd-trace 3.1.0 → 3.3.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 +2 -0
- package/README.md +4 -0
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +2 -1
- package/index.d.ts +43 -20
- package/package.json +6 -4
- package/packages/datadog-instrumentations/src/crypto.js +32 -0
- package/packages/datadog-instrumentations/src/grpc/server.js +15 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +136 -14
- package/packages/datadog-instrumentations/src/mocha.js +77 -31
- package/packages/datadog-instrumentations/src/net.js +13 -0
- package/packages/datadog-instrumentations/src/next.js +7 -3
- package/packages/datadog-plugin-jest/src/index.js +106 -6
- package/packages/datadog-plugin-mocha/src/index.js +15 -7
- package/packages/datadog-plugin-mongodb-core/src/index.js +19 -10
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +4 -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-cipher-analyzer.js +27 -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 +121 -0
- package/packages/dd-trace/src/appsec/recommended.json +1144 -275
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -3
- package/packages/dd-trace/src/config.js +90 -10
- package/packages/dd-trace/src/constants.js +9 -1
- package/packages/dd-trace/src/encode/0.4.js +7 -1
- package/packages/dd-trace/src/encode/0.5.js +7 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +2 -2
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -20
- 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 +9 -5
- 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/index.js +3 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
- package/packages/dd-trace/src/plugins/util/ip_blocklist.js +51 -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/packages/dd-trace/src/plugins/util/redis.js +0 -74
- package/packages/dd-trace/src/plugins/util/tx.js +0 -75
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
const { SpanStatsEncoder } = require('../../encode/span-stats')
|
|
3
|
+
|
|
4
|
+
const pkg = require('../../../../../package.json')
|
|
5
|
+
|
|
6
|
+
const BaseWriter = require('../common/writer')
|
|
7
|
+
const request = require('../common/request')
|
|
8
|
+
const log = require('../../log')
|
|
9
|
+
|
|
10
|
+
class Writer extends BaseWriter {
|
|
11
|
+
constructor ({ url }) {
|
|
12
|
+
super(...arguments)
|
|
13
|
+
this._url = url
|
|
14
|
+
this._encoder = new SpanStatsEncoder(this)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_sendPayload (data, _, done) {
|
|
18
|
+
makeRequest(data, this._url, (err, res) => {
|
|
19
|
+
if (err) {
|
|
20
|
+
log.error(err)
|
|
21
|
+
done()
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
log.debug(`Response from the intake: ${res}`)
|
|
25
|
+
done()
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeRequest (data, url, cb) {
|
|
31
|
+
const options = {
|
|
32
|
+
path: '/v0.6/stats',
|
|
33
|
+
method: 'PUT',
|
|
34
|
+
headers: {
|
|
35
|
+
'Datadog-Meta-Lang': 'javascript',
|
|
36
|
+
'Datadog-Meta-Tracer-Version': pkg.version,
|
|
37
|
+
'Content-Type': 'application/msgpack'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
options.protocol = url.protocol
|
|
42
|
+
options.hostname = url.hostname
|
|
43
|
+
options.port = url.port
|
|
44
|
+
|
|
45
|
+
log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
|
|
46
|
+
|
|
47
|
+
request(data, options, (err, res) => {
|
|
48
|
+
cb(err, res)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
Writer
|
|
54
|
+
}
|
|
@@ -12,6 +12,7 @@ const SAMPLING_AGENT_DECISION = constants.SAMPLING_AGENT_DECISION
|
|
|
12
12
|
const MEASURED = tags.MEASURED
|
|
13
13
|
const ORIGIN_KEY = constants.ORIGIN_KEY
|
|
14
14
|
const HOSTNAME_KEY = constants.HOSTNAME_KEY
|
|
15
|
+
const TOP_LEVEL_KEY = constants.TOP_LEVEL_KEY
|
|
15
16
|
|
|
16
17
|
const map = {
|
|
17
18
|
'service.name': 'service',
|
|
@@ -110,6 +111,7 @@ function extractRootTags (trace, span) {
|
|
|
110
111
|
addTag({}, trace.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
|
|
111
112
|
addTag({}, trace.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
|
|
112
113
|
addTag({}, trace.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
|
|
114
|
+
addTag({}, trace.metrics, TOP_LEVEL_KEY, 1)
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
function extractChunkTags (trace, span) {
|
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const semver = require('semver')
|
|
4
4
|
const logger = require('./log')
|
|
5
|
+
const { addHook } = require('import-in-the-middle')
|
|
6
|
+
const dc = require('diagnostics_channel')
|
|
5
7
|
|
|
6
8
|
if (semver.satisfies(process.versions.node, '>=14.13.1')) {
|
|
9
|
+
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
10
|
+
addHook((name, namespace) => {
|
|
11
|
+
if (moduleLoadStartChannel.hasSubscribers) {
|
|
12
|
+
moduleLoadStartChannel.publish({
|
|
13
|
+
filename: name,
|
|
14
|
+
module: namespace
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
})
|
|
7
18
|
module.exports = require('import-in-the-middle')
|
|
8
19
|
} else {
|
|
9
20
|
logger.warn('ESM is not fully supported by this version of Node.js, ' +
|
|
@@ -11,6 +11,7 @@ const traceKey = 'x-datadog-trace-id'
|
|
|
11
11
|
const spanKey = 'x-datadog-parent-id'
|
|
12
12
|
const originKey = 'x-datadog-origin'
|
|
13
13
|
const samplingKey = 'x-datadog-sampling-priority'
|
|
14
|
+
const tagsKey = 'x-datadog-tags'
|
|
14
15
|
const baggagePrefix = 'ot-baggage-'
|
|
15
16
|
const b3TraceKey = 'x-b3-traceid'
|
|
16
17
|
const b3TraceExpr = /^([0-9a-f]{16}){1,2}$/i
|
|
@@ -23,6 +24,8 @@ const b3HeaderKey = 'b3'
|
|
|
23
24
|
const sqsdHeaderHey = 'x-aws-sqsd-attr-_datadog'
|
|
24
25
|
const b3HeaderExpr = /^(([0-9a-f]{16}){1,2}-[0-9a-f]{16}(-[01d](-[0-9a-f]{16})?)?|[01d])$/i
|
|
25
26
|
const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`)
|
|
27
|
+
const tagKeyExpr = /^_dd\.p\.[\x21-\x2b\x2d-\x7e]+$/ // ASCII minus spaces and commas
|
|
28
|
+
const tagValueExpr = /^[\x20-\x2b\x2d-\x7e]*$/ // ASCII minus commas
|
|
26
29
|
const ddKeys = [traceKey, spanKey, samplingKey, originKey]
|
|
27
30
|
const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey]
|
|
28
31
|
const logKeys = ddKeys.concat(b3Keys)
|
|
@@ -43,6 +46,7 @@ class TextMapPropagator {
|
|
|
43
46
|
this._injectBaggageItems(spanContext, carrier)
|
|
44
47
|
this._injectB3(spanContext, carrier)
|
|
45
48
|
this._injectTraceparent(spanContext, carrier)
|
|
49
|
+
this._injectTags(spanContext, carrier)
|
|
46
50
|
|
|
47
51
|
log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
|
|
48
52
|
}
|
|
@@ -79,6 +83,35 @@ class TextMapPropagator {
|
|
|
79
83
|
})
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
_injectTags (spanContext, carrier) {
|
|
87
|
+
const trace = spanContext._trace
|
|
88
|
+
|
|
89
|
+
if (this._config.tagsHeaderMaxLength === 0) {
|
|
90
|
+
log.debug('Trace tag propagation is disabled, skipping injection.')
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const tags = []
|
|
95
|
+
|
|
96
|
+
for (const key in trace.tags) {
|
|
97
|
+
if (!trace.tags[key] || !key.startsWith('_dd.p.')) continue
|
|
98
|
+
if (!this._validateTagKey(key) || !this._validateTagValue(trace.tags[key])) {
|
|
99
|
+
log.error('Trace tags from span are invalid, skipping injection.')
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
tags.push(`${key}=${trace.tags[key]}`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const header = tags.join(',')
|
|
107
|
+
|
|
108
|
+
if (header.length > this._config.tagsHeaderMaxLength) {
|
|
109
|
+
log.error('Trace tags from span are too large, skipping injection.')
|
|
110
|
+
} else if (header) {
|
|
111
|
+
carrier[tagsKey] = header
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
82
115
|
_injectB3 (spanContext, carrier) {
|
|
83
116
|
if (!this._config.experimental.b3) return
|
|
84
117
|
|
|
@@ -118,6 +151,7 @@ class TextMapPropagator {
|
|
|
118
151
|
this._extractOrigin(carrier, spanContext)
|
|
119
152
|
this._extractBaggageItems(carrier, spanContext)
|
|
120
153
|
this._extractSamplingPriority(carrier, spanContext)
|
|
154
|
+
this._extractTags(carrier, spanContext)
|
|
121
155
|
}
|
|
122
156
|
|
|
123
157
|
return spanContext
|
|
@@ -273,6 +307,43 @@ class TextMapPropagator {
|
|
|
273
307
|
}
|
|
274
308
|
}
|
|
275
309
|
|
|
310
|
+
_extractTags (carrier, spanContext) {
|
|
311
|
+
if (!carrier[tagsKey]) return
|
|
312
|
+
|
|
313
|
+
const trace = spanContext._trace
|
|
314
|
+
|
|
315
|
+
if (this._config.tagsHeaderMaxLength === 0) {
|
|
316
|
+
log.debug('Trace tag propagation is disabled, skipping extraction.')
|
|
317
|
+
} else if (carrier[tagsKey].length > this._config.tagsHeaderMaxLength) {
|
|
318
|
+
log.error('Trace tags from carrier are too large, skipping extraction.')
|
|
319
|
+
} else {
|
|
320
|
+
const pairs = carrier[tagsKey].split(',')
|
|
321
|
+
const tags = {}
|
|
322
|
+
|
|
323
|
+
for (const pair of pairs) {
|
|
324
|
+
const [key, ...rest] = pair.split('=')
|
|
325
|
+
const value = rest.join('=')
|
|
326
|
+
|
|
327
|
+
if (!this._validateTagKey(key) || !this._validateTagValue(value)) {
|
|
328
|
+
log.error('Trace tags from carrier are invalid, skipping extraction.')
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
tags[key] = value
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
Object.assign(trace.tags, tags)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
_validateTagKey (key) {
|
|
340
|
+
return tagKeyExpr.test(key)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
_validateTagValue (value) {
|
|
344
|
+
return tagValueExpr.test(value)
|
|
345
|
+
}
|
|
346
|
+
|
|
276
347
|
_getPriority (sampled, debug) {
|
|
277
348
|
if (debug) {
|
|
278
349
|
return USER_KEEP
|
|
@@ -28,7 +28,7 @@ class DatadogTracer {
|
|
|
28
28
|
this._tags = config.tags
|
|
29
29
|
this._logInjection = config.logInjection
|
|
30
30
|
this._debug = config.debug
|
|
31
|
-
this._prioritySampler = new PrioritySampler(config.env, config.
|
|
31
|
+
this._prioritySampler = new PrioritySampler(config.env, config.sampler)
|
|
32
32
|
this._exporter = new Exporter(config, this._prioritySampler)
|
|
33
33
|
this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
|
|
34
34
|
this._url = this._exporter._url
|
|
@@ -119,9 +119,11 @@ module.exports = class PluginManager {
|
|
|
119
119
|
const {
|
|
120
120
|
logInjection,
|
|
121
121
|
serviceMapping,
|
|
122
|
-
experimental,
|
|
123
122
|
queryStringObfuscation,
|
|
124
|
-
|
|
123
|
+
clientIpHeaderDisabled,
|
|
124
|
+
clientIpHeader,
|
|
125
|
+
isIntelligentTestRunnerEnabled,
|
|
126
|
+
experimental
|
|
125
127
|
} = this._tracerConfig
|
|
126
128
|
|
|
127
129
|
const sharedConfig = {}
|
|
@@ -134,6 +136,14 @@ module.exports = class PluginManager {
|
|
|
134
136
|
sharedConfig.queryStringObfuscation = queryStringObfuscation
|
|
135
137
|
}
|
|
136
138
|
|
|
139
|
+
if (clientIpHeaderDisabled !== undefined) {
|
|
140
|
+
sharedConfig.clientIpHeaderDisabled = clientIpHeaderDisabled
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (clientIpHeader !== undefined) {
|
|
144
|
+
sharedConfig.clientIpHeader = clientIpHeader
|
|
145
|
+
}
|
|
146
|
+
|
|
137
147
|
if (experimental) {
|
|
138
148
|
sharedConfig.isAgentlessEnabled = experimental.exporter === 'datadog'
|
|
139
149
|
}
|
|
@@ -7,6 +7,7 @@ module.exports = {
|
|
|
7
7
|
get '@google-cloud/pubsub' () { return require('../../../datadog-plugin-google-cloud-pubsub/src') },
|
|
8
8
|
get '@grpc/grpc-js' () { return require('../../../datadog-plugin-grpc/src') },
|
|
9
9
|
get '@hapi/hapi' () { return require('../../../datadog-plugin-hapi/src') },
|
|
10
|
+
get '@jest/core' () { return require('../../../datadog-plugin-jest/src') },
|
|
10
11
|
get '@koa/router' () { return require('../../../datadog-plugin-koa/src') },
|
|
11
12
|
get '@node-redis/client' () { return require('../../../datadog-plugin-redis/src') },
|
|
12
13
|
get 'amqp10' () { return require('../../../datadog-plugin-amqp10/src') },
|
|
@@ -29,6 +30,8 @@ module.exports = {
|
|
|
29
30
|
get 'http2' () { return require('../../../datadog-plugin-http2/src') },
|
|
30
31
|
get 'https' () { return require('../../../datadog-plugin-http/src') },
|
|
31
32
|
get 'ioredis' () { return require('../../../datadog-plugin-ioredis/src') },
|
|
33
|
+
get 'jest-circus' () { return require('../../../datadog-plugin-jest/src') },
|
|
34
|
+
get 'jest-config' () { return require('../../../datadog-plugin-jest/src') },
|
|
32
35
|
get 'jest-environment-node' () { return require('../../../datadog-plugin-jest/src') },
|
|
33
36
|
get 'jest-environment-jsdom' () { return require('../../../datadog-plugin-jest/src') },
|
|
34
37
|
get 'jest-jasmine2' () { return require('../../../datadog-plugin-jest/src') },
|
|
@@ -9,25 +9,32 @@ const hasOwn = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
|
|
|
9
9
|
function messageProxy (message, holder) {
|
|
10
10
|
return new Proxy(message, {
|
|
11
11
|
get (target, p, receiver) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return Object.prototype.toString.call(target).slice(8, -1)
|
|
15
|
-
case 'dd':
|
|
16
|
-
return holder.dd
|
|
17
|
-
default:
|
|
18
|
-
return Reflect.get(target, p, receiver)
|
|
12
|
+
if (p === Symbol.toStringTag) {
|
|
13
|
+
return Object.prototype.toString.call(target).slice(8, -1)
|
|
19
14
|
}
|
|
15
|
+
|
|
16
|
+
if (shouldOverride(target, p)) {
|
|
17
|
+
return holder.dd
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return Reflect.get(target, p, receiver)
|
|
20
21
|
},
|
|
21
22
|
ownKeys (target) {
|
|
22
23
|
const ownKeys = Reflect.ownKeys(target)
|
|
23
|
-
return hasOwn(target, 'dd')
|
|
24
|
+
return hasOwn(target, 'dd') || !Reflect.isExtensible(target)
|
|
25
|
+
? ownKeys
|
|
26
|
+
: ['dd', ...ownKeys]
|
|
24
27
|
},
|
|
25
28
|
getOwnPropertyDescriptor (target, p) {
|
|
26
|
-
return Reflect.getOwnPropertyDescriptor(p
|
|
29
|
+
return Reflect.getOwnPropertyDescriptor(shouldOverride(target, p) ? holder : target, p)
|
|
27
30
|
}
|
|
28
31
|
})
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
function shouldOverride (target, p) {
|
|
35
|
+
return p === 'dd' && !Reflect.has(target, p) && Reflect.isExtensible(target)
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
module.exports = class LogPlugin extends Plugin {
|
|
32
39
|
constructor (...args) {
|
|
33
40
|
super(...args)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const semver = require('semver')
|
|
4
|
+
|
|
5
|
+
if (semver.satisfies(process.version, '>=14.18.0')) {
|
|
6
|
+
const net = require('net')
|
|
7
|
+
|
|
8
|
+
module.exports = net.BlockList
|
|
9
|
+
} else {
|
|
10
|
+
const ipaddr = require('ipaddr.js')
|
|
11
|
+
|
|
12
|
+
module.exports = class BlockList {
|
|
13
|
+
constructor () {
|
|
14
|
+
this.v4Ranges = []
|
|
15
|
+
this.v6Ranges = []
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
addSubnet (net, prefix, type) {
|
|
19
|
+
this[type === 'ipv4' ? 'v4Ranges' : 'v6Ranges'].push(ipaddr.parseCIDR(`${net}/${prefix}`))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
check (address, type) {
|
|
23
|
+
try {
|
|
24
|
+
let ip = ipaddr.parse(address)
|
|
25
|
+
|
|
26
|
+
type = ip.kind()
|
|
27
|
+
|
|
28
|
+
if (type === 'ipv6') {
|
|
29
|
+
for (const range of this.v6Ranges) {
|
|
30
|
+
if (ip.match(range)) return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (ip.isIPv4MappedAddress()) {
|
|
34
|
+
ip = ip.toIPv4Address()
|
|
35
|
+
type = ip.kind()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (type === 'ipv4') {
|
|
40
|
+
for (const range of this.v4Ranges) {
|
|
41
|
+
if (ip.match(range)) return true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return false
|
|
46
|
+
} catch {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const net = require('net')
|
|
3
4
|
const uniq = require('lodash.uniq')
|
|
4
5
|
const analyticsSampler = require('../../analytics_sampler')
|
|
5
6
|
const FORMAT_HTTP_HEADERS = 'http_headers'
|
|
@@ -8,6 +9,7 @@ const tags = require('../../../../../ext/tags')
|
|
|
8
9
|
const types = require('../../../../../ext/types')
|
|
9
10
|
const kinds = require('../../../../../ext/kinds')
|
|
10
11
|
const urlFilter = require('./urlfilter')
|
|
12
|
+
const BlockList = require('./ip_blocklist')
|
|
11
13
|
const { incomingHttpRequestEnd } = require('../../appsec/gateway/channels')
|
|
12
14
|
|
|
13
15
|
const WEB = types.WEB
|
|
@@ -24,12 +26,25 @@ const HTTP_ROUTE = tags.HTTP_ROUTE
|
|
|
24
26
|
const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS
|
|
25
27
|
const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS
|
|
26
28
|
const HTTP_USERAGENT = tags.HTTP_USERAGENT
|
|
29
|
+
const HTTP_CLIENT_IP = tags.HTTP_CLIENT_IP
|
|
27
30
|
const MANUAL_DROP = tags.MANUAL_DROP
|
|
28
31
|
|
|
29
32
|
const HTTP2_HEADER_AUTHORITY = ':authority'
|
|
30
33
|
const HTTP2_HEADER_SCHEME = ':scheme'
|
|
31
34
|
const HTTP2_HEADER_PATH = ':path'
|
|
32
35
|
|
|
36
|
+
const ipHeaderList = [
|
|
37
|
+
'x-forwarded-for',
|
|
38
|
+
'x-real-ip',
|
|
39
|
+
'client-ip',
|
|
40
|
+
'x-forwarded',
|
|
41
|
+
'x-cluster-client-ip',
|
|
42
|
+
'forwarded-for',
|
|
43
|
+
'forwarded',
|
|
44
|
+
'via',
|
|
45
|
+
'true-client-ip'
|
|
46
|
+
]
|
|
47
|
+
|
|
33
48
|
const contexts = new WeakMap()
|
|
34
49
|
const ends = new WeakMap()
|
|
35
50
|
|
|
@@ -337,6 +352,42 @@ const web = {
|
|
|
337
352
|
|
|
338
353
|
return `${path}?${qs}`
|
|
339
354
|
},
|
|
355
|
+
|
|
356
|
+
extractIp (context) {
|
|
357
|
+
const { req, config } = context
|
|
358
|
+
|
|
359
|
+
if (config.clientIpHeaderDisabled) return
|
|
360
|
+
|
|
361
|
+
const headers = req.headers
|
|
362
|
+
|
|
363
|
+
if (config.clientIpHeader) {
|
|
364
|
+
const header = headers[config.clientIpHeader]
|
|
365
|
+
if (!header) return
|
|
366
|
+
|
|
367
|
+
return findFirstIp(header)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const foundHeaders = []
|
|
371
|
+
|
|
372
|
+
for (let i = 0; i < ipHeaderList.length; i++) {
|
|
373
|
+
if (headers[ipHeaderList[i]]) {
|
|
374
|
+
foundHeaders.push(ipHeaderList[i])
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (foundHeaders.length === 1) {
|
|
379
|
+
const header = headers[foundHeaders[0]]
|
|
380
|
+
const firstIp = findFirstIp(header)
|
|
381
|
+
|
|
382
|
+
if (firstIp) return firstIp
|
|
383
|
+
} else if (foundHeaders.length > 1) {
|
|
384
|
+
log.error(`Cannot find client IP: multiple IP headers detected ${foundHeaders}`)
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return req.socket && req.socket.remoteAddress
|
|
389
|
+
},
|
|
390
|
+
|
|
340
391
|
wrapWriteHead (context) {
|
|
341
392
|
const { req, res } = context
|
|
342
393
|
const writeHead = res.writeHead
|
|
@@ -392,7 +443,8 @@ function addAllowHeaders (req, res, headers) {
|
|
|
392
443
|
'x-datadog-parent-id',
|
|
393
444
|
'x-datadog-sampled', // Deprecated, but still accept it in case it's sent.
|
|
394
445
|
'x-datadog-sampling-priority',
|
|
395
|
-
'x-datadog-trace-id'
|
|
446
|
+
'x-datadog-trace-id',
|
|
447
|
+
'x-datadog-tags'
|
|
396
448
|
]
|
|
397
449
|
|
|
398
450
|
for (const header of contextHeaders) {
|
|
@@ -434,7 +486,8 @@ function addRequestTags (context) {
|
|
|
434
486
|
[HTTP_METHOD]: req.method,
|
|
435
487
|
[SPAN_KIND]: SERVER,
|
|
436
488
|
[SPAN_TYPE]: WEB,
|
|
437
|
-
[HTTP_USERAGENT]: req.headers['user-agent']
|
|
489
|
+
[HTTP_USERAGENT]: req.headers['user-agent'],
|
|
490
|
+
[HTTP_CLIENT_IP]: web.extractIp(context)
|
|
438
491
|
})
|
|
439
492
|
|
|
440
493
|
addHeaders(context)
|
|
@@ -502,6 +555,51 @@ function getProtocol (req) {
|
|
|
502
555
|
return 'http'
|
|
503
556
|
}
|
|
504
557
|
|
|
558
|
+
const privateCIDRs = [
|
|
559
|
+
'127.0.0.0/8',
|
|
560
|
+
'10.0.0.0/8',
|
|
561
|
+
'172.16.0.0/12',
|
|
562
|
+
'192.168.0.0/16',
|
|
563
|
+
'169.254.0.0/16',
|
|
564
|
+
'::1/128',
|
|
565
|
+
'fec0::/10',
|
|
566
|
+
'fe80::/10',
|
|
567
|
+
'fc00::/7',
|
|
568
|
+
'fd00::/8'
|
|
569
|
+
]
|
|
570
|
+
|
|
571
|
+
const privateIPMatcher = new BlockList()
|
|
572
|
+
|
|
573
|
+
for (const cidr of privateCIDRs) {
|
|
574
|
+
const [ address, prefix ] = cidr.split('/')
|
|
575
|
+
|
|
576
|
+
privateIPMatcher.addSubnet(address, parseInt(prefix), net.isIPv6(address) ? 'ipv6' : 'ipv4')
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function findFirstIp (str) {
|
|
580
|
+
let firstPrivateIp
|
|
581
|
+
const splitted = str.split(',')
|
|
582
|
+
|
|
583
|
+
for (let i = 0; i < splitted.length; i++) {
|
|
584
|
+
const chunk = splitted[i].trim()
|
|
585
|
+
|
|
586
|
+
// TODO: strip port and interface data ?
|
|
587
|
+
|
|
588
|
+
const type = net.isIP(chunk)
|
|
589
|
+
if (!type) continue
|
|
590
|
+
|
|
591
|
+
if (!privateIPMatcher.check(chunk, type === 6 ? 'ipv6' : 'ipv4')) {
|
|
592
|
+
// it's public, return it immediately
|
|
593
|
+
return chunk
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// it's private, only save the first one found
|
|
597
|
+
if (!firstPrivateIp) firstPrivateIp = chunk
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return firstPrivateIp
|
|
601
|
+
}
|
|
602
|
+
|
|
505
603
|
function getHeadersToRecord (config) {
|
|
506
604
|
if (Array.isArray(config.headers)) {
|
|
507
605
|
try {
|
|
@@ -6,9 +6,14 @@ const ext = require('../../../ext')
|
|
|
6
6
|
const { setSamplingRules } = require('./startup-log')
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
+
SAMPLING_MECHANISM_DEFAULT,
|
|
10
|
+
SAMPLING_MECHANISM_AGENT,
|
|
11
|
+
SAMPLING_MECHANISM_RULE,
|
|
12
|
+
SAMPLING_MECHANISM_MANUAL,
|
|
9
13
|
SAMPLING_RULE_DECISION,
|
|
10
14
|
SAMPLING_LIMIT_DECISION,
|
|
11
|
-
SAMPLING_AGENT_DECISION
|
|
15
|
+
SAMPLING_AGENT_DECISION,
|
|
16
|
+
DECISION_MAKER_KEY
|
|
12
17
|
} = require('./constants')
|
|
13
18
|
|
|
14
19
|
const SERVICE_NAME = ext.tags.SERVICE_NAME
|
|
@@ -45,6 +50,7 @@ class PrioritySampler {
|
|
|
45
50
|
const context = this._getContext(span)
|
|
46
51
|
const root = context._trace.started[0]
|
|
47
52
|
|
|
53
|
+
// TODO: remove the decision maker tag when priority is less than AUTO_KEEP
|
|
48
54
|
if (context._sampling.priority !== undefined) return
|
|
49
55
|
if (!root) return // noop span
|
|
50
56
|
|
|
@@ -52,9 +58,14 @@ class PrioritySampler {
|
|
|
52
58
|
|
|
53
59
|
if (this.validate(tag)) {
|
|
54
60
|
context._sampling.priority = tag
|
|
61
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_MANUAL
|
|
55
62
|
} else if (auto) {
|
|
56
63
|
context._sampling.priority = this._getPriorityFromAuto(root)
|
|
64
|
+
} else {
|
|
65
|
+
return
|
|
57
66
|
}
|
|
67
|
+
|
|
68
|
+
this._addDecisionMaker(root)
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
update (rates) {
|
|
@@ -115,6 +126,7 @@ class PrioritySampler {
|
|
|
115
126
|
|
|
116
127
|
_getPriorityByRule (context, rule) {
|
|
117
128
|
context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
|
|
129
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_RULE
|
|
118
130
|
|
|
119
131
|
return rule.sampler.isSampled(context) && this._isSampledByRateLimit(context) ? USER_KEEP : USER_REJECT
|
|
120
132
|
}
|
|
@@ -133,10 +145,33 @@ class PrioritySampler {
|
|
|
133
145
|
|
|
134
146
|
context._trace[SAMPLING_AGENT_DECISION] = sampler.rate()
|
|
135
147
|
|
|
148
|
+
if (sampler === defaultSampler) {
|
|
149
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
|
|
150
|
+
} else {
|
|
151
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
|
|
152
|
+
}
|
|
153
|
+
|
|
136
154
|
return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT
|
|
137
155
|
}
|
|
138
156
|
|
|
157
|
+
_addDecisionMaker (span) {
|
|
158
|
+
const context = span.context()
|
|
159
|
+
const trace = context._trace
|
|
160
|
+
const priority = context._sampling.priority
|
|
161
|
+
const mechanism = context._sampling.mechanism
|
|
162
|
+
|
|
163
|
+
if (priority >= AUTO_KEEP) {
|
|
164
|
+
if (!trace.tags[DECISION_MAKER_KEY]) {
|
|
165
|
+
trace.tags[DECISION_MAKER_KEY] = `-${mechanism}`
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
delete trace.tags[DECISION_MAKER_KEY]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
139
172
|
_normalizeRules (rules, sampleRate) {
|
|
173
|
+
rules = [].concat(rules || [])
|
|
174
|
+
|
|
140
175
|
return rules
|
|
141
176
|
.concat({ sampleRate })
|
|
142
177
|
.map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
|
|
@@ -48,6 +48,9 @@ class Tracer extends NoopProxy {
|
|
|
48
48
|
if (config.appsec.enabled) {
|
|
49
49
|
require('./appsec').enable(config)
|
|
50
50
|
}
|
|
51
|
+
if (config.iast.enabled) {
|
|
52
|
+
require('./appsec/iast').enable(config)
|
|
53
|
+
}
|
|
51
54
|
|
|
52
55
|
this._tracer = new DatadogTracer(config)
|
|
53
56
|
this._pluginManager.configure(config)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const Module = require('module')
|
|
5
5
|
const parse = require('module-details-from-path')
|
|
6
|
+
const dc = require('diagnostics_channel')
|
|
6
7
|
|
|
7
8
|
const origRequire = Module.prototype.require
|
|
8
9
|
|
|
@@ -14,7 +15,7 @@ let moduleHooks = Object.create(null)
|
|
|
14
15
|
let cache = Object.create(null)
|
|
15
16
|
let patching = Object.create(null)
|
|
16
17
|
let patchedRequire = null
|
|
17
|
-
|
|
18
|
+
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
18
19
|
function Hook (modules, options, onrequire) {
|
|
19
20
|
if (!(this instanceof Hook)) return new Hook(modules, options, onrequire)
|
|
20
21
|
if (typeof modules === 'function') {
|
|
@@ -78,6 +79,14 @@ function Hook (modules, options, onrequire) {
|
|
|
78
79
|
// so the patching mark can be cleaned up.
|
|
79
80
|
delete patching[filename]
|
|
80
81
|
|
|
82
|
+
if (moduleLoadStartChannel.hasSubscribers) {
|
|
83
|
+
moduleLoadStartChannel.publish({
|
|
84
|
+
filename,
|
|
85
|
+
module: exports,
|
|
86
|
+
request
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
81
90
|
if (core) {
|
|
82
91
|
hooks = moduleHooks[filename]
|
|
83
92
|
if (!hooks) return exports // abort if module name isn't on whitelist
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const log = require('./log')
|
|
4
4
|
const format = require('./format')
|
|
5
5
|
|
|
6
|
+
const { SpanStatsProcessor } = require('./span_stats')
|
|
7
|
+
|
|
6
8
|
const startedSpans = new WeakSet()
|
|
7
9
|
const finishedSpans = new WeakSet()
|
|
8
10
|
|
|
@@ -11,6 +13,8 @@ class SpanProcessor {
|
|
|
11
13
|
this._exporter = exporter
|
|
12
14
|
this._prioritySampler = prioritySampler
|
|
13
15
|
this._config = config
|
|
16
|
+
|
|
17
|
+
this._stats = new SpanStatsProcessor(config)
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
process (span) {
|
|
@@ -26,7 +30,9 @@ class SpanProcessor {
|
|
|
26
30
|
|
|
27
31
|
for (const span of started) {
|
|
28
32
|
if (span._duration !== undefined) {
|
|
29
|
-
|
|
33
|
+
const formattedSpan = format(span)
|
|
34
|
+
this._stats.onSpanFinished(formattedSpan)
|
|
35
|
+
formatted.push(formattedSpan)
|
|
30
36
|
} else {
|
|
31
37
|
active.push(span)
|
|
32
38
|
}
|