dd-trace 4.13.1 → 4.15.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/index.d.ts +4 -4
- package/package.json +3 -3
- package/packages/datadog-instrumentations/src/graphql.js +5 -4
- package/packages/datadog-instrumentations/src/mocha.js +3 -0
- package/packages/datadog-instrumentations/src/mysql.js +39 -1
- package/packages/datadog-plugin-cypress/src/plugin.js +32 -5
- package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
- package/packages/dd-trace/src/appsec/addresses.js +1 -0
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +28 -32
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
- package/packages/dd-trace/src/appsec/index.js +24 -2
- package/packages/dd-trace/src/appsec/recommended.json +107 -8
- package/packages/dd-trace/src/appsec/reporter.js +24 -0
- package/packages/dd-trace/src/appsec/telemetry.js +132 -0
- package/packages/dd-trace/src/appsec/waf/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +12 -4
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +11 -14
- package/packages/dd-trace/src/dogstatsd.js +45 -5
- package/packages/dd-trace/src/plugins/tracing.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
- package/packages/dd-trace/src/proxy.js +1 -11
- package/packages/dd-trace/src/runtime_metrics.js +1 -32
- package/packages/dd-trace/src/util.js +1 -1
package/index.d.ts
CHANGED
|
@@ -649,7 +649,7 @@ export declare interface DogStatsD {
|
|
|
649
649
|
* Increments a metric by the specified value, optionally specifying tags.
|
|
650
650
|
* @param {string} stat The dot-separated metric name.
|
|
651
651
|
* @param {number} value The amount to increment the stat by.
|
|
652
|
-
* @param {[tag:string]:string|number} tags Tags to pass along, such as `
|
|
652
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
|
|
653
653
|
*/
|
|
654
654
|
increment(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
655
655
|
|
|
@@ -657,7 +657,7 @@ export declare interface DogStatsD {
|
|
|
657
657
|
* Decrements a metric by the specified value, optionally specifying tags.
|
|
658
658
|
* @param {string} stat The dot-separated metric name.
|
|
659
659
|
* @param {number} value The amount to decrement the stat by.
|
|
660
|
-
* @param {[tag:string]:string|number} tags Tags to pass along, such as `
|
|
660
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
|
|
661
661
|
*/
|
|
662
662
|
decrement(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
663
663
|
|
|
@@ -665,7 +665,7 @@ export declare interface DogStatsD {
|
|
|
665
665
|
* Sets a distribution value, optionally specifying tags.
|
|
666
666
|
* @param {string} stat The dot-separated metric name.
|
|
667
667
|
* @param {number} value The amount to increment the stat by.
|
|
668
|
-
* @param {[tag:string]:string|number} tags Tags to pass along, such as `
|
|
668
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
|
|
669
669
|
*/
|
|
670
670
|
distribution(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
671
671
|
|
|
@@ -673,7 +673,7 @@ export declare interface DogStatsD {
|
|
|
673
673
|
* Sets a gauge value, optionally specifying tags.
|
|
674
674
|
* @param {string} stat The dot-separated metric name.
|
|
675
675
|
* @param {number} value The amount to increment the stat by.
|
|
676
|
-
* @param {[tag:string]:string|number} tags Tags to pass along, such as `
|
|
676
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
|
|
677
677
|
*/
|
|
678
678
|
gauge(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
679
679
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"test:appsec": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"",
|
|
19
19
|
"test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec",
|
|
20
20
|
"test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
|
|
21
|
-
"test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/
|
|
21
|
+
"test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" -- npm run test:appsec:plugins",
|
|
22
22
|
"test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentelemetry,opentracing,plugins,service-naming,telemetry}/**/*.spec.js\"",
|
|
23
23
|
"test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
|
|
24
24
|
"test:instrumentations": "mocha --colors -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@datadog/native-appsec": "^3.2.0",
|
|
71
|
-
"@datadog/native-iast-rewriter": "2.
|
|
71
|
+
"@datadog/native-iast-rewriter": "2.1.3",
|
|
72
72
|
"@datadog/native-iast-taint-tracking": "1.5.0",
|
|
73
73
|
"@datadog/native-metrics": "^2.0.0",
|
|
74
74
|
"@datadog/pprof": "3.2.0",
|
|
@@ -188,7 +188,7 @@ function wrapExecute (execute) {
|
|
|
188
188
|
executeErrorCh.publish(error)
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
finishExecuteCh.publish({ res, args })
|
|
191
|
+
finishExecuteCh.publish({ res, args, context })
|
|
192
192
|
})
|
|
193
193
|
})
|
|
194
194
|
}
|
|
@@ -205,7 +205,7 @@ function wrapResolve (resolve) {
|
|
|
205
205
|
|
|
206
206
|
if (!context) return resolve.apply(this, arguments)
|
|
207
207
|
|
|
208
|
-
const field = assertField(context, info)
|
|
208
|
+
const field = assertField(context, info, args)
|
|
209
209
|
|
|
210
210
|
return callInAsyncScope(resolve, field.asyncResource, this, arguments, (err) => {
|
|
211
211
|
updateFieldCh.publish({ field, info, err })
|
|
@@ -250,7 +250,7 @@ function pathToArray (path) {
|
|
|
250
250
|
return flattened.reverse()
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
function assertField (context, info) {
|
|
253
|
+
function assertField (context, info, args) {
|
|
254
254
|
const pathInfo = info && info.path
|
|
255
255
|
|
|
256
256
|
const path = pathToArray(pathInfo)
|
|
@@ -272,7 +272,8 @@ function assertField (context, info) {
|
|
|
272
272
|
childResource.runInAsyncScope(() => {
|
|
273
273
|
startResolveCh.publish({
|
|
274
274
|
info,
|
|
275
|
-
context
|
|
275
|
+
context,
|
|
276
|
+
args
|
|
276
277
|
})
|
|
277
278
|
})
|
|
278
279
|
|
|
@@ -441,6 +441,9 @@ addHook({
|
|
|
441
441
|
file: 'lib/cli/run-helpers.js'
|
|
442
442
|
}, (run) => {
|
|
443
443
|
shimmer.wrap(run, 'runMocha', runMocha => async function () {
|
|
444
|
+
if (!testStartCh.hasSubscribers) {
|
|
445
|
+
return runMocha.apply(this, arguments)
|
|
446
|
+
}
|
|
444
447
|
const mocha = arguments[0]
|
|
445
448
|
/**
|
|
446
449
|
* This attaches `run` to the global context, which we'll call after
|
|
@@ -17,7 +17,7 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
|
|
|
17
17
|
return query.apply(this, arguments)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const sql = arguments[0].sql
|
|
20
|
+
const sql = arguments[0].sql || arguments[0]
|
|
21
21
|
const conf = this.config
|
|
22
22
|
const payload = { sql, conf }
|
|
23
23
|
|
|
@@ -66,9 +66,47 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
|
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
|
|
69
|
+
const startPoolQueryCh = channel('datadog:mysql:pool:query:start')
|
|
70
|
+
const finishPoolQueryCh = channel('datadog:mysql:pool:query:finish')
|
|
71
|
+
|
|
69
72
|
shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) {
|
|
70
73
|
arguments[0] = AsyncResource.bind(cb)
|
|
71
74
|
return getConnection.apply(this, arguments)
|
|
72
75
|
})
|
|
76
|
+
|
|
77
|
+
shimmer.wrap(Pool.prototype, 'query', query => function () {
|
|
78
|
+
if (!startPoolQueryCh.hasSubscribers) {
|
|
79
|
+
return query.apply(this, arguments)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
83
|
+
|
|
84
|
+
const sql = arguments[0].sql || arguments[0]
|
|
85
|
+
|
|
86
|
+
return asyncResource.runInAsyncScope(() => {
|
|
87
|
+
startPoolQueryCh.publish({ sql })
|
|
88
|
+
|
|
89
|
+
const finish = asyncResource.bind(function () {
|
|
90
|
+
finishPoolQueryCh.publish()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const cb = arguments[arguments.length - 1]
|
|
94
|
+
if (typeof cb === 'function') {
|
|
95
|
+
arguments[arguments.length - 1] = shimmer.wrap(cb, function () {
|
|
96
|
+
finish()
|
|
97
|
+
return cb.apply(this, arguments)
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const retval = query.apply(this, arguments)
|
|
102
|
+
|
|
103
|
+
if (retval && retval.then) {
|
|
104
|
+
retval.then(finish).catch(finish)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return retval
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
73
111
|
return Pool
|
|
74
112
|
})
|
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
26
26
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
27
27
|
const log = require('../../dd-trace/src/log')
|
|
28
|
+
const NoopTracer = require('../../dd-trace/src/noop/tracer')
|
|
28
29
|
|
|
29
30
|
const TEST_FRAMEWORK_NAME = 'cypress'
|
|
30
31
|
|
|
@@ -119,10 +120,32 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
|
|
|
119
120
|
})
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
const noopTask = {
|
|
124
|
+
'dd:testSuiteStart': () => {
|
|
125
|
+
return null
|
|
126
|
+
},
|
|
127
|
+
'dd:beforeEach': () => {
|
|
128
|
+
return {}
|
|
129
|
+
},
|
|
130
|
+
'dd:afterEach': () => {
|
|
131
|
+
return null
|
|
132
|
+
},
|
|
133
|
+
'dd:addTags': () => {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
module.exports = (on, config) => {
|
|
123
139
|
let isTestsSkipped = false
|
|
124
140
|
const skippedTests = []
|
|
125
141
|
const tracer = require('../../dd-trace')
|
|
142
|
+
|
|
143
|
+
// The tracer was not init correctly for whatever reason (such as invalid DD_SITE)
|
|
144
|
+
if (tracer._tracer instanceof NoopTracer) {
|
|
145
|
+
// We still need to register these tasks or the support file will fail
|
|
146
|
+
return on('task', noopTask)
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
|
|
127
150
|
|
|
128
151
|
const {
|
|
@@ -328,12 +351,16 @@ module.exports = (on, config) => {
|
|
|
328
351
|
}
|
|
329
352
|
|
|
330
353
|
return new Promise(resolve => {
|
|
331
|
-
|
|
332
|
-
|
|
354
|
+
const exporter = tracer._tracer._exporter
|
|
355
|
+
if (!exporter) {
|
|
356
|
+
return resolve(null)
|
|
357
|
+
}
|
|
358
|
+
if (exporter.flush) {
|
|
359
|
+
exporter.flush(() => {
|
|
333
360
|
resolve(null)
|
|
334
361
|
})
|
|
335
|
-
} else {
|
|
336
|
-
|
|
362
|
+
} else if (exporter._writer) {
|
|
363
|
+
exporter._writer.flush(() => {
|
|
337
364
|
resolve(null)
|
|
338
365
|
})
|
|
339
366
|
}
|
|
@@ -375,7 +402,7 @@ module.exports = (on, config) => {
|
|
|
375
402
|
'dd:afterEach': ({ test, coverage }) => {
|
|
376
403
|
const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
|
|
377
404
|
if (activeSpan) {
|
|
378
|
-
if (coverage && tracer._tracer._exporter
|
|
405
|
+
if (coverage && isCodeCoverageEnabled && tracer._tracer._exporter && tracer._tracer._exporter.exportCoverage) {
|
|
379
406
|
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
380
407
|
const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir))
|
|
381
408
|
const { _traceId, _spanId } = testSuiteSpan.context()
|
|
@@ -8,13 +8,14 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
8
8
|
static get id () { return 'graphql' }
|
|
9
9
|
static get operation () { return 'resolve' }
|
|
10
10
|
|
|
11
|
-
start ({ info, context }) {
|
|
11
|
+
start ({ info, context, args }) {
|
|
12
12
|
const path = getPath(info, this.config)
|
|
13
13
|
|
|
14
14
|
if (!shouldInstrument(this.config, path)) return
|
|
15
|
-
|
|
16
15
|
const computedPathString = path.join('.')
|
|
17
16
|
|
|
17
|
+
addResolver(context, info, args)
|
|
18
|
+
|
|
18
19
|
if (this.config.collapse) {
|
|
19
20
|
if (!context[collapsedPathSym]) {
|
|
20
21
|
context[collapsedPathSym] = {}
|
|
@@ -108,4 +109,28 @@ function withCollapse (responsePathAsArray) {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
function addResolver (context, info, args) {
|
|
113
|
+
if (info.rootValue && !info.rootValue[info.fieldName]) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!context.resolvers) {
|
|
118
|
+
context.resolvers = {}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const resolvers = context.resolvers
|
|
122
|
+
|
|
123
|
+
if (!resolvers[info.fieldName]) {
|
|
124
|
+
if (args && Object.keys(args).length) {
|
|
125
|
+
resolvers[info.fieldName] = [args]
|
|
126
|
+
} else {
|
|
127
|
+
resolvers[info.fieldName] = []
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
if (args && Object.keys(args).length) {
|
|
131
|
+
resolvers[info.fieldName].push(args)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
111
136
|
module.exports = GraphQLResolvePlugin
|
|
@@ -12,6 +12,7 @@ module.exports = {
|
|
|
12
12
|
HTTP_INCOMING_RESPONSE_CODE: 'server.response.status',
|
|
13
13
|
HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
|
|
14
14
|
// TODO: 'server.response.trailers',
|
|
15
|
+
HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
|
|
15
16
|
|
|
16
17
|
HTTP_CLIENT_IP: 'http.client_ip',
|
|
17
18
|
|
|
@@ -5,6 +5,7 @@ const dc = require('../../../diagnostics_channel')
|
|
|
5
5
|
// TODO: use TBD naming convention
|
|
6
6
|
module.exports = {
|
|
7
7
|
bodyParser: dc.channel('datadog:body-parser:read:finish'),
|
|
8
|
+
graphqlFinishExecute: dc.channel('apm:graphql:execute:finish'),
|
|
8
9
|
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
|
|
9
10
|
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
|
|
10
11
|
passportVerify: dc.channel('datadog:passport:verify:finish'),
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
3
3
|
const { LDAP_INJECTION } = require('../vulnerabilities')
|
|
4
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
5
|
+
|
|
6
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('ldapjs-promise')
|
|
4
7
|
|
|
5
8
|
class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
6
9
|
constructor () {
|
|
@@ -10,6 +13,10 @@ class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
10
13
|
onConfigure () {
|
|
11
14
|
this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
|
|
12
15
|
}
|
|
16
|
+
|
|
17
|
+
_getExcludedPaths () {
|
|
18
|
+
return EXCLUDED_PATHS
|
|
19
|
+
}
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
module.exports = new LdapInjectionAnalyzer()
|
|
@@ -8,7 +8,7 @@ const { getIastContext } = require('../iast-context')
|
|
|
8
8
|
const { addVulnerability } = require('../vulnerability-reporter')
|
|
9
9
|
const { getNodeModulesPaths } = require('../path-line')
|
|
10
10
|
|
|
11
|
-
const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize', 'pg-pool')
|
|
11
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool')
|
|
12
12
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
@@ -20,37 +20,33 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
20
20
|
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
21
21
|
this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
|
|
22
22
|
|
|
23
|
-
this.addSub(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.addSub('datadog:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (store && store.pgPoolParentStore) {
|
|
51
|
-
storage.enterWith(store.pgPoolParentStore)
|
|
52
|
-
}
|
|
53
|
-
})
|
|
23
|
+
this.addSub(
|
|
24
|
+
'datadog:sequelize:query:start',
|
|
25
|
+
({ sql, dialect }) => this.getStoreAndAnalyze(sql, dialect.toUpperCase())
|
|
26
|
+
)
|
|
27
|
+
this.addSub('datadog:sequelize:query:finish', () => this.returnToParentStore())
|
|
28
|
+
|
|
29
|
+
this.addSub('datadog:pg:pool:query:start', ({ query }) => this.getStoreAndAnalyze(query.text, 'POSTGRES'))
|
|
30
|
+
this.addSub('datadog:pg:pool:query:finish', () => this.returnToParentStore())
|
|
31
|
+
|
|
32
|
+
this.addSub('datadog:mysql:pool:query:start', ({ sql }) => this.getStoreAndAnalyze(sql, 'MYSQL'))
|
|
33
|
+
this.addSub('datadog:mysql:pool:query:finish', () => this.returnToParentStore())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getStoreAndAnalyze (query, dialect) {
|
|
37
|
+
const parentStore = storage.getStore()
|
|
38
|
+
if (parentStore) {
|
|
39
|
+
this.analyze(query, dialect, parentStore)
|
|
40
|
+
|
|
41
|
+
storage.enterWith({ ...parentStore, sqlAnalyzed: true, sqlParentStore: parentStore })
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
returnToParentStore () {
|
|
46
|
+
const store = storage.getStore()
|
|
47
|
+
if (store && store.sqlParentStore) {
|
|
48
|
+
storage.enterWith(store.sqlParentStore)
|
|
49
|
+
}
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
_getEvidence (value, iastContext, dialect) {
|
|
@@ -6,6 +6,7 @@ const { addVulnerability } = require('../vulnerability-reporter')
|
|
|
6
6
|
const { getIastContext } = require('../iast-context')
|
|
7
7
|
const overheadController = require('../overhead-controller')
|
|
8
8
|
const { SinkIastPlugin } = require('../iast-plugin')
|
|
9
|
+
const { getOriginalPathAndLineFromSourceMap } = require('../taint-tracking/rewriter')
|
|
9
10
|
|
|
10
11
|
class Analyzer extends SinkIastPlugin {
|
|
11
12
|
constructor (type) {
|
|
@@ -25,8 +26,9 @@ class Analyzer extends SinkIastPlugin {
|
|
|
25
26
|
const evidence = this._getEvidence(value, context)
|
|
26
27
|
const location = this._getLocation(value)
|
|
27
28
|
if (!this._isExcluded(location)) {
|
|
29
|
+
const locationSourceMap = this._replaceLocationFromSourceMap(location)
|
|
28
30
|
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
29
|
-
const vulnerability = this._createVulnerability(this._type, evidence, spanId,
|
|
31
|
+
const vulnerability = this._createVulnerability(this._type, evidence, spanId, locationSourceMap)
|
|
30
32
|
addVulnerability(context, vulnerability)
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -47,6 +49,22 @@ class Analyzer extends SinkIastPlugin {
|
|
|
47
49
|
return getFirstNonDDPathAndLine(this._getExcludedPaths())
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
_replaceLocationFromSourceMap (location) {
|
|
53
|
+
if (location) {
|
|
54
|
+
const { path, line, column } = getOriginalPathAndLineFromSourceMap(location)
|
|
55
|
+
if (path) {
|
|
56
|
+
location.path = path
|
|
57
|
+
}
|
|
58
|
+
if (line) {
|
|
59
|
+
location.line = line
|
|
60
|
+
}
|
|
61
|
+
if (column) {
|
|
62
|
+
location.column = column
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return location
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
_getExcludedPaths () {}
|
|
51
69
|
|
|
52
70
|
_isInvalidContext (store, iastContext) {
|
|
@@ -47,6 +47,7 @@ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPat
|
|
|
47
47
|
return {
|
|
48
48
|
path: path.relative(process.cwd(), filepath),
|
|
49
49
|
line: callsite.getLineNumber(),
|
|
50
|
+
column: callsite.getColumnNumber(),
|
|
50
51
|
isInternal: !path.isAbsolute(filepath)
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -10,12 +10,51 @@ const { getRewriteFunction } = require('./rewriter-telemetry')
|
|
|
10
10
|
|
|
11
11
|
let rewriter
|
|
12
12
|
let getPrepareStackTrace
|
|
13
|
+
|
|
14
|
+
let getRewriterOriginalPathAndLineFromSourceMap = function (path, line, column) {
|
|
15
|
+
return { path, line, column }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isFlagPresent (flag) {
|
|
19
|
+
return process.env.NODE_OPTIONS?.includes(flag) ||
|
|
20
|
+
process.execArgv?.some(arg => arg.includes(flag))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getGetOriginalPathAndLineFromSourceMapFunction (chainSourceMap, getOriginalPathAndLineFromSourceMap) {
|
|
24
|
+
if (chainSourceMap) {
|
|
25
|
+
return function (path, line, column) {
|
|
26
|
+
// if --enable-source-maps is present stacktraces of the rewritten files contain the original path, file and
|
|
27
|
+
// column because the sourcemap chaining is done during the rewriting process so we can skip it
|
|
28
|
+
if (isPrivateModule(path) && isNotLibraryFile(path)) {
|
|
29
|
+
return { path, line, column }
|
|
30
|
+
} else {
|
|
31
|
+
return getOriginalPathAndLineFromSourceMap(path, line, column)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
return getOriginalPathAndLineFromSourceMap
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
function getRewriter (telemetryVerbosity) {
|
|
14
40
|
if (!rewriter) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
try {
|
|
42
|
+
const iastRewriter = require('@datadog/native-iast-rewriter')
|
|
43
|
+
const Rewriter = iastRewriter.Rewriter
|
|
44
|
+
getPrepareStackTrace = iastRewriter.getPrepareStackTrace
|
|
45
|
+
|
|
46
|
+
const chainSourceMap = isFlagPresent('--enable-source-maps')
|
|
47
|
+
const getOriginalPathAndLineFromSourceMap = iastRewriter.getOriginalPathAndLineFromSourceMap
|
|
48
|
+
if (getOriginalPathAndLineFromSourceMap) {
|
|
49
|
+
getRewriterOriginalPathAndLineFromSourceMap =
|
|
50
|
+
getGetOriginalPathAndLineFromSourceMapFunction(chainSourceMap, getOriginalPathAndLineFromSourceMap)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
rewriter = new Rewriter({ csiMethods, telemetryVerbosity: getName(telemetryVerbosity), chainSourceMap })
|
|
54
|
+
} catch (e) {
|
|
55
|
+
iastLog.error('Unable to initialize TaintTracking Rewriter')
|
|
56
|
+
.errorAndPublish(e)
|
|
57
|
+
}
|
|
19
58
|
}
|
|
20
59
|
return rewriter
|
|
21
60
|
}
|
|
@@ -74,6 +113,10 @@ function disableRewriter () {
|
|
|
74
113
|
Error.prepareStackTrace = originalPrepareStackTrace
|
|
75
114
|
}
|
|
76
115
|
|
|
116
|
+
function getOriginalPathAndLineFromSourceMap ({ path, line, column }) {
|
|
117
|
+
return getRewriterOriginalPathAndLineFromSourceMap(path, line, column)
|
|
118
|
+
}
|
|
119
|
+
|
|
77
120
|
module.exports = {
|
|
78
|
-
enableRewriter, disableRewriter
|
|
121
|
+
enableRewriter, disableRewriter, getOriginalPathAndLineFromSourceMap
|
|
79
122
|
}
|
|
@@ -4,15 +4,17 @@ const log = require('../log')
|
|
|
4
4
|
const RuleManager = require('./rule_manager')
|
|
5
5
|
const remoteConfig = require('./remote_config')
|
|
6
6
|
const {
|
|
7
|
+
bodyParser,
|
|
8
|
+
graphqlFinishExecute,
|
|
7
9
|
incomingHttpRequestStart,
|
|
8
10
|
incomingHttpRequestEnd,
|
|
9
|
-
bodyParser,
|
|
10
11
|
passportVerify,
|
|
11
12
|
queryParser
|
|
12
13
|
} = require('./channels')
|
|
13
14
|
const waf = require('./waf')
|
|
14
15
|
const addresses = require('./addresses')
|
|
15
16
|
const Reporter = require('./reporter')
|
|
17
|
+
const appsecTelemetry = require('./telemetry')
|
|
16
18
|
const web = require('../plugins/util/web')
|
|
17
19
|
const { extractIp } = require('../plugins/util/ip_extractor')
|
|
18
20
|
const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
|
|
@@ -35,10 +37,13 @@ function enable (_config) {
|
|
|
35
37
|
|
|
36
38
|
Reporter.setRateLimit(_config.appsec.rateLimit)
|
|
37
39
|
|
|
40
|
+
appsecTelemetry.enable(_config.telemetry)
|
|
41
|
+
|
|
38
42
|
incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
|
|
39
43
|
incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
|
|
40
44
|
bodyParser.subscribe(onRequestBodyParsed)
|
|
41
45
|
queryParser.subscribe(onRequestQueryParsed)
|
|
46
|
+
graphqlFinishExecute.subscribe(onGraphqlFinishExecute)
|
|
42
47
|
|
|
43
48
|
if (_config.appsec.eventTracking.enabled) {
|
|
44
49
|
passportVerify.subscribe(onPassportVerify)
|
|
@@ -158,6 +163,20 @@ function onPassportVerify ({ credentials, user }) {
|
|
|
158
163
|
passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode)
|
|
159
164
|
}
|
|
160
165
|
|
|
166
|
+
function onGraphqlFinishExecute ({ context }) {
|
|
167
|
+
const store = storage.getStore()
|
|
168
|
+
const req = store?.req
|
|
169
|
+
|
|
170
|
+
if (!req) return
|
|
171
|
+
|
|
172
|
+
const resolvers = context?.resolvers
|
|
173
|
+
|
|
174
|
+
if (!resolvers || typeof resolvers !== 'object') return
|
|
175
|
+
|
|
176
|
+
// Don't collect blocking result because it only works in monitor mode.
|
|
177
|
+
waf.run({ [addresses.HTTP_INCOMING_GRAPHQL_RESOLVERS]: resolvers }, req)
|
|
178
|
+
}
|
|
179
|
+
|
|
161
180
|
function handleResults (actions, req, res, rootSpan, abortController) {
|
|
162
181
|
if (!actions || !req || !res || !rootSpan || !abortController) return
|
|
163
182
|
|
|
@@ -172,12 +191,15 @@ function disable () {
|
|
|
172
191
|
|
|
173
192
|
RuleManager.clearAllRules()
|
|
174
193
|
|
|
194
|
+
appsecTelemetry.disable()
|
|
195
|
+
|
|
175
196
|
remoteConfig.disableWafUpdate()
|
|
176
197
|
|
|
177
198
|
// Channel#unsubscribe() is undefined for non active channels
|
|
199
|
+
if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
|
|
200
|
+
if (graphqlFinishExecute.hasSubscribers) graphqlFinishExecute.unsubscribe(onGraphqlFinishExecute)
|
|
178
201
|
if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
|
|
179
202
|
if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
|
|
180
|
-
if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
|
|
181
203
|
if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
|
|
182
204
|
if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
|
|
183
205
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "2.2",
|
|
3
3
|
"metadata": {
|
|
4
|
-
"rules_version": "1.7.
|
|
4
|
+
"rules_version": "1.7.2"
|
|
5
5
|
},
|
|
6
6
|
"rules": [
|
|
7
7
|
{
|
|
@@ -1743,7 +1743,10 @@
|
|
|
1743
1743
|
"sys/hypervisor",
|
|
1744
1744
|
"sys/kernel",
|
|
1745
1745
|
"sys/module",
|
|
1746
|
-
"sys/power"
|
|
1746
|
+
"sys/power",
|
|
1747
|
+
"windows\\win.ini",
|
|
1748
|
+
"default\\ntuser.dat",
|
|
1749
|
+
"/var/run/secrets/kubernetes.io/serviceaccount"
|
|
1747
1750
|
]
|
|
1748
1751
|
},
|
|
1749
1752
|
"operator": "phrase_match"
|
|
@@ -2312,7 +2315,8 @@
|
|
|
2312
2315
|
}
|
|
2313
2316
|
],
|
|
2314
2317
|
"transformers": [
|
|
2315
|
-
"lowercase"
|
|
2318
|
+
"lowercase",
|
|
2319
|
+
"cmdLine"
|
|
2316
2320
|
]
|
|
2317
2321
|
},
|
|
2318
2322
|
{
|
|
@@ -2950,7 +2954,7 @@
|
|
|
2950
2954
|
"address": "grpc.server.request.message"
|
|
2951
2955
|
}
|
|
2952
2956
|
],
|
|
2953
|
-
"regex": "
|
|
2957
|
+
"regex": "\\bon(?:d(?:r(?:ag(?:en(?:ter|d)|leave|start|over)?|op)|urationchange|blclick)|s(?:e(?:ek(?:ing|ed)|arch|lect)|u(?:spend|bmit)|talled|croll|how)|m(?:ouse(?:(?:lea|mo)ve|o(?:ver|ut)|enter|down|up)|essage)|p(?:a(?:ge(?:hide|show)|(?:st|us)e)|lay(?:ing)?|rogress|aste|ointer(?:cancel|down|enter|leave|move|out|over|rawupdate|up))|c(?:anplay(?:through)?|o(?:ntextmenu|py)|hange|lick|ut)|a(?:nimation(?:iteration|start|end)|(?:fterprin|bor)t|uxclick|fterscriptexecute)|t(?:o(?:uch(?:cancel|start|move|end)|ggle)|imeupdate)|f(?:ullscreen(?:change|error)|ocus(?:out|in)?|inish)|(?:(?:volume|hash)chang|o(?:ff|n)lin)e|b(?:efore(?:unload|print)|lur)|load(?:ed(?:meta)?data|start|end)?|r(?:es(?:ize|et)|atechange)|key(?:press|down|up)|w(?:aiting|heel)|in(?:valid|put)|e(?:nded|rror)|unload)[\\s\\x0B\\x09\\x0C\\x3B\\x2C\\x28\\x3B]*?=[^=]",
|
|
2954
2958
|
"options": {
|
|
2955
2959
|
"min_length": 8
|
|
2956
2960
|
}
|
|
@@ -4636,6 +4640,46 @@
|
|
|
4636
4640
|
],
|
|
4637
4641
|
"transformers": []
|
|
4638
4642
|
},
|
|
4643
|
+
{
|
|
4644
|
+
"id": "dog-913-008",
|
|
4645
|
+
"name": "Netsparker OOB domain",
|
|
4646
|
+
"tags": {
|
|
4647
|
+
"type": "commercial_scanner",
|
|
4648
|
+
"category": "attack_attempt",
|
|
4649
|
+
"tool_name": "Netsparker",
|
|
4650
|
+
"confidence": "0"
|
|
4651
|
+
},
|
|
4652
|
+
"conditions": [
|
|
4653
|
+
{
|
|
4654
|
+
"parameters": {
|
|
4655
|
+
"inputs": [
|
|
4656
|
+
{
|
|
4657
|
+
"address": "server.request.query"
|
|
4658
|
+
},
|
|
4659
|
+
{
|
|
4660
|
+
"address": "server.request.body"
|
|
4661
|
+
},
|
|
4662
|
+
{
|
|
4663
|
+
"address": "server.request.path_params"
|
|
4664
|
+
},
|
|
4665
|
+
{
|
|
4666
|
+
"address": "server.request.headers.no_cookies"
|
|
4667
|
+
},
|
|
4668
|
+
{
|
|
4669
|
+
"address": "grpc.server.request.message"
|
|
4670
|
+
}
|
|
4671
|
+
],
|
|
4672
|
+
"regex": "\\b(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)r87(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)(?:me|com)\\b",
|
|
4673
|
+
"options": {
|
|
4674
|
+
"case_sensitive": false,
|
|
4675
|
+
"min_length": 7
|
|
4676
|
+
}
|
|
4677
|
+
},
|
|
4678
|
+
"operator": "match_regex"
|
|
4679
|
+
}
|
|
4680
|
+
],
|
|
4681
|
+
"transformers": []
|
|
4682
|
+
},
|
|
4639
4683
|
{
|
|
4640
4684
|
"id": "dog-931-001",
|
|
4641
4685
|
"name": "RFI: URL Payload to well known RFI target",
|
|
@@ -4699,6 +4743,56 @@
|
|
|
4699
4743
|
],
|
|
4700
4744
|
"transformers": []
|
|
4701
4745
|
},
|
|
4746
|
+
{
|
|
4747
|
+
"id": "dog-941-001",
|
|
4748
|
+
"name": "XSS in source property",
|
|
4749
|
+
"tags": {
|
|
4750
|
+
"type": "xss",
|
|
4751
|
+
"category": "attack_attempt",
|
|
4752
|
+
"confidence": "0"
|
|
4753
|
+
},
|
|
4754
|
+
"conditions": [
|
|
4755
|
+
{
|
|
4756
|
+
"parameters": {
|
|
4757
|
+
"inputs": [
|
|
4758
|
+
{
|
|
4759
|
+
"address": "server.request.headers.no_cookies",
|
|
4760
|
+
"key_path": [
|
|
4761
|
+
"user-agent"
|
|
4762
|
+
]
|
|
4763
|
+
},
|
|
4764
|
+
{
|
|
4765
|
+
"address": "server.request.headers.no_cookies",
|
|
4766
|
+
"key_path": [
|
|
4767
|
+
"referer"
|
|
4768
|
+
]
|
|
4769
|
+
},
|
|
4770
|
+
{
|
|
4771
|
+
"address": "server.request.query"
|
|
4772
|
+
},
|
|
4773
|
+
{
|
|
4774
|
+
"address": "server.request.body"
|
|
4775
|
+
},
|
|
4776
|
+
{
|
|
4777
|
+
"address": "server.request.path_params"
|
|
4778
|
+
},
|
|
4779
|
+
{
|
|
4780
|
+
"address": "grpc.server.request.message"
|
|
4781
|
+
}
|
|
4782
|
+
],
|
|
4783
|
+
"regex": "<(?:iframe|esi:include)(?:(?:\\s|/)*\\w+=[\"'\\w]+)*(?:\\s|/)*src(?:doc)?=[\"']?(?:data:|javascript:|http:|//)[^\\s'\"]+['\"]?",
|
|
4784
|
+
"options": {
|
|
4785
|
+
"min_length": 14
|
|
4786
|
+
}
|
|
4787
|
+
},
|
|
4788
|
+
"operator": "match_regex"
|
|
4789
|
+
}
|
|
4790
|
+
],
|
|
4791
|
+
"transformers": [
|
|
4792
|
+
"removeNulls",
|
|
4793
|
+
"urlDecodeUni"
|
|
4794
|
+
]
|
|
4795
|
+
},
|
|
4702
4796
|
{
|
|
4703
4797
|
"id": "dog-942-001",
|
|
4704
4798
|
"name": "Blind XSS callback domains",
|
|
@@ -5428,12 +5522,14 @@
|
|
|
5428
5522
|
"address": "grpc.server.request.message"
|
|
5429
5523
|
}
|
|
5430
5524
|
],
|
|
5431
|
-
"regex": "(?i)[&|]\\s*cat\\s
|
|
5525
|
+
"regex": "(?i)[&|]\\s*cat\\s*\\/etc\\/[\\w\\.\\/]*passwd\\s*[&|]"
|
|
5432
5526
|
},
|
|
5433
5527
|
"operator": "match_regex"
|
|
5434
5528
|
}
|
|
5435
5529
|
],
|
|
5436
|
-
"transformers": [
|
|
5530
|
+
"transformers": [
|
|
5531
|
+
"cmdLine"
|
|
5532
|
+
]
|
|
5437
5533
|
},
|
|
5438
5534
|
{
|
|
5439
5535
|
"id": "sqr-000-010",
|
|
@@ -7014,7 +7110,10 @@
|
|
|
7014
7110
|
]
|
|
7015
7111
|
}
|
|
7016
7112
|
],
|
|
7017
|
-
"regex": "mozilla/4\\.0 \\(compatible(; msie 6\\.0; win32)?\\)"
|
|
7113
|
+
"regex": "mozilla/4\\.0 \\(compatible(; msie (?:6\\.0; win32|4\\.0; Windows NT))?\\)",
|
|
7114
|
+
"options": {
|
|
7115
|
+
"case_sensitive": false
|
|
7116
|
+
}
|
|
7018
7117
|
},
|
|
7019
7118
|
"operator": "match_regex"
|
|
7020
7119
|
}
|
|
@@ -7076,4 +7175,4 @@
|
|
|
7076
7175
|
"transformers": []
|
|
7077
7176
|
}
|
|
7078
7177
|
]
|
|
7079
|
-
}
|
|
7178
|
+
}
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
const Limiter = require('../rate_limiter')
|
|
4
4
|
const { storage } = require('../../../datadog-core')
|
|
5
5
|
const web = require('../plugins/util/web')
|
|
6
|
+
const {
|
|
7
|
+
incrementWafInitMetric,
|
|
8
|
+
updateWafRequestsMetricTags,
|
|
9
|
+
incrementWafUpdatesMetric,
|
|
10
|
+
incrementWafRequestsMetric
|
|
11
|
+
} = require('./telemetry')
|
|
6
12
|
|
|
7
13
|
// default limiter, configurable with setRateLimit()
|
|
8
14
|
let limiter = new Limiter(100)
|
|
@@ -63,6 +69,18 @@ function formatHeaderName (name) {
|
|
|
63
69
|
.toLowerCase()
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
function reportWafInit (wafVersion, rulesInfo) {
|
|
73
|
+
metricsQueue.set('_dd.appsec.waf.version', wafVersion)
|
|
74
|
+
|
|
75
|
+
metricsQueue.set('_dd.appsec.event_rules.loaded', rulesInfo.loaded)
|
|
76
|
+
metricsQueue.set('_dd.appsec.event_rules.error_count', rulesInfo.failed)
|
|
77
|
+
if (rulesInfo.failed) metricsQueue.set('_dd.appsec.event_rules.errors', JSON.stringify(rulesInfo.errors))
|
|
78
|
+
|
|
79
|
+
metricsQueue.set('manual.keep', 'true')
|
|
80
|
+
|
|
81
|
+
incrementWafInitMetric(wafVersion, rulesInfo.version)
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
function reportMetrics (metrics) {
|
|
67
85
|
// TODO: metrics should be incremental, there already is an RFC to report metrics
|
|
68
86
|
const store = storage.getStore()
|
|
@@ -80,6 +98,8 @@ function reportMetrics (metrics) {
|
|
|
80
98
|
if (metrics.rulesVersion) {
|
|
81
99
|
rootSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
|
|
82
100
|
}
|
|
101
|
+
|
|
102
|
+
updateWafRequestsMetricTags(metrics, store.req)
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
function reportAttack (attackData) {
|
|
@@ -132,6 +152,8 @@ function finishRequest (req, res) {
|
|
|
132
152
|
metricsQueue.clear()
|
|
133
153
|
}
|
|
134
154
|
|
|
155
|
+
incrementWafRequestsMetric(req)
|
|
156
|
+
|
|
135
157
|
if (!rootSpan.context()._tags['appsec.event']) return
|
|
136
158
|
|
|
137
159
|
const newTags = filterHeaders(res.getHeaders(), RESPONSE_HEADERS_PASSLIST, 'http.response.headers.')
|
|
@@ -151,8 +173,10 @@ module.exports = {
|
|
|
151
173
|
metricsQueue,
|
|
152
174
|
filterHeaders,
|
|
153
175
|
formatHeaderName,
|
|
176
|
+
reportWafInit,
|
|
154
177
|
reportMetrics,
|
|
155
178
|
reportAttack,
|
|
179
|
+
reportWafUpdate: incrementWafUpdatesMetric,
|
|
156
180
|
finishRequest,
|
|
157
181
|
setRateLimit
|
|
158
182
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const telemetryMetrics = require('../telemetry/metrics')
|
|
4
|
+
|
|
5
|
+
const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
|
|
6
|
+
|
|
7
|
+
const DD_TELEMETRY_WAF_RESULT_TAGS = Symbol('_dd.appsec.telemetry.waf.result.tags')
|
|
8
|
+
|
|
9
|
+
const tags = {
|
|
10
|
+
REQUEST_BLOCKED: 'request_blocked',
|
|
11
|
+
RULE_TRIGGERED: 'rule_triggered',
|
|
12
|
+
WAF_TIMEOUT: 'waf_timeout',
|
|
13
|
+
WAF_VERSION: 'waf_version',
|
|
14
|
+
EVENT_RULES_VERSION: 'event_rules_version'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const metricsStoreMap = new WeakMap()
|
|
18
|
+
|
|
19
|
+
let enabled = false
|
|
20
|
+
|
|
21
|
+
function enable (telemetryConfig) {
|
|
22
|
+
enabled = telemetryConfig?.enabled && telemetryConfig.metrics
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function disable () {
|
|
26
|
+
enabled = false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getStore (req) {
|
|
30
|
+
let store = metricsStoreMap.get(req)
|
|
31
|
+
if (!store) {
|
|
32
|
+
store = {}
|
|
33
|
+
metricsStoreMap.set(req, store)
|
|
34
|
+
}
|
|
35
|
+
return store
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getVersionsTags (wafVersion, rulesVersion) {
|
|
39
|
+
return {
|
|
40
|
+
[tags.WAF_VERSION]: wafVersion,
|
|
41
|
+
[tags.EVENT_RULES_VERSION]: rulesVersion
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function trackWafDurations (metrics, versionsTags) {
|
|
46
|
+
if (metrics.duration) {
|
|
47
|
+
appsecMetrics.distribution('waf.duration', versionsTags).track(metrics.duration)
|
|
48
|
+
}
|
|
49
|
+
if (metrics.durationExt) {
|
|
50
|
+
appsecMetrics.distribution('waf.duration_ext', versionsTags).track(metrics.durationExt)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getOrCreateMetricTags ({ wafVersion, rulesVersion }, req, versionsTags) {
|
|
55
|
+
const store = getStore(req)
|
|
56
|
+
|
|
57
|
+
let metricTags = store[DD_TELEMETRY_WAF_RESULT_TAGS]
|
|
58
|
+
if (!metricTags) {
|
|
59
|
+
metricTags = {
|
|
60
|
+
[tags.REQUEST_BLOCKED]: false,
|
|
61
|
+
[tags.RULE_TRIGGERED]: false,
|
|
62
|
+
[tags.WAF_TIMEOUT]: false,
|
|
63
|
+
|
|
64
|
+
...versionsTags
|
|
65
|
+
}
|
|
66
|
+
store[DD_TELEMETRY_WAF_RESULT_TAGS] = metricTags
|
|
67
|
+
}
|
|
68
|
+
return metricTags
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function updateWafRequestsMetricTags (metrics, req) {
|
|
72
|
+
if (!req || !enabled) return
|
|
73
|
+
|
|
74
|
+
const versionsTags = getVersionsTags(metrics.wafVersion, metrics.rulesVersion)
|
|
75
|
+
|
|
76
|
+
trackWafDurations(metrics, versionsTags)
|
|
77
|
+
|
|
78
|
+
const metricTags = getOrCreateMetricTags(metrics, req, versionsTags)
|
|
79
|
+
|
|
80
|
+
const { blockTriggered, ruleTriggered, wafTimeout } = metrics
|
|
81
|
+
|
|
82
|
+
if (blockTriggered) {
|
|
83
|
+
metricTags[tags.REQUEST_BLOCKED] = blockTriggered
|
|
84
|
+
}
|
|
85
|
+
if (ruleTriggered) {
|
|
86
|
+
metricTags[tags.RULE_TRIGGERED] = ruleTriggered
|
|
87
|
+
}
|
|
88
|
+
if (wafTimeout) {
|
|
89
|
+
metricTags[tags.WAF_TIMEOUT] = wafTimeout
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return metricTags
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function incrementWafInitMetric (wafVersion, rulesVersion) {
|
|
96
|
+
if (!enabled) return
|
|
97
|
+
|
|
98
|
+
const versionsTags = getVersionsTags(wafVersion, rulesVersion)
|
|
99
|
+
|
|
100
|
+
appsecMetrics.count('waf.init', versionsTags).inc()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function incrementWafUpdatesMetric (wafVersion, rulesVersion) {
|
|
104
|
+
if (!enabled) return
|
|
105
|
+
|
|
106
|
+
const versionsTags = getVersionsTags(wafVersion, rulesVersion)
|
|
107
|
+
|
|
108
|
+
appsecMetrics.count('waf.updates', versionsTags).inc()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function incrementWafRequestsMetric (req) {
|
|
112
|
+
if (!req || !enabled) return
|
|
113
|
+
|
|
114
|
+
const store = getStore(req)
|
|
115
|
+
|
|
116
|
+
const metricTags = store[DD_TELEMETRY_WAF_RESULT_TAGS]
|
|
117
|
+
if (metricTags) {
|
|
118
|
+
appsecMetrics.count('waf.requests', metricTags).inc()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
metricsStoreMap.delete(req)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
enable,
|
|
126
|
+
disable,
|
|
127
|
+
|
|
128
|
+
updateWafRequestsMetricTags,
|
|
129
|
+
incrementWafInitMetric,
|
|
130
|
+
incrementWafUpdatesMetric,
|
|
131
|
+
incrementWafRequestsMetric
|
|
132
|
+
}
|
|
@@ -39,7 +39,7 @@ function update (newRules) {
|
|
|
39
39
|
if (!waf.wafManager) throw new Error('Cannot update disabled WAF')
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
|
-
waf.wafManager.
|
|
42
|
+
waf.wafManager.update(newRules)
|
|
43
43
|
} catch (err) {
|
|
44
44
|
log.error('Could not apply rules from remote config')
|
|
45
45
|
throw err
|
|
@@ -4,11 +4,12 @@ const log = require('../../log')
|
|
|
4
4
|
const Reporter = require('../reporter')
|
|
5
5
|
|
|
6
6
|
class WAFContextWrapper {
|
|
7
|
-
constructor (ddwafContext, requiredAddresses, wafTimeout, rulesInfo) {
|
|
7
|
+
constructor (ddwafContext, requiredAddresses, wafTimeout, rulesInfo, wafVersion) {
|
|
8
8
|
this.ddwafContext = ddwafContext
|
|
9
9
|
this.requiredAddresses = requiredAddresses
|
|
10
10
|
this.wafTimeout = wafTimeout
|
|
11
|
-
this.
|
|
11
|
+
this.rulesVersion = rulesInfo.version
|
|
12
|
+
this.wafVersion = wafVersion
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
run (params) {
|
|
@@ -32,13 +33,20 @@ class WAFContextWrapper {
|
|
|
32
33
|
|
|
33
34
|
const end = process.hrtime.bigint()
|
|
34
35
|
|
|
36
|
+
const ruleTriggered = !!result.data && result.data !== '[]'
|
|
37
|
+
const blockTriggered = result.actions?.includes('block')
|
|
38
|
+
|
|
35
39
|
Reporter.reportMetrics({
|
|
36
40
|
duration: result.totalRuntime / 1e3,
|
|
37
41
|
durationExt: parseInt(end - start) / 1e3,
|
|
38
|
-
rulesVersion: this.
|
|
42
|
+
rulesVersion: this.rulesVersion,
|
|
43
|
+
ruleTriggered,
|
|
44
|
+
blockTriggered,
|
|
45
|
+
wafVersion: this.wafVersion,
|
|
46
|
+
wafTimeout: result.timeout
|
|
39
47
|
})
|
|
40
48
|
|
|
41
|
-
if (
|
|
49
|
+
if (ruleTriggered) {
|
|
42
50
|
Reporter.reportAttack(result.data)
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -11,7 +11,9 @@ class WAFManager {
|
|
|
11
11
|
this.config = config
|
|
12
12
|
this.wafTimeout = config.wafTimeout
|
|
13
13
|
this.ddwaf = this._loadDDWAF(rules)
|
|
14
|
-
this.
|
|
14
|
+
this.ddwafVersion = this.ddwaf.constructor.version()
|
|
15
|
+
|
|
16
|
+
Reporter.reportWafInit(this.ddwafVersion, this.ddwaf.rulesInfo)
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
_loadDDWAF (rules) {
|
|
@@ -28,18 +30,6 @@ class WAFManager {
|
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
_reportMetrics () {
|
|
32
|
-
Reporter.metricsQueue.set('_dd.appsec.waf.version', this.ddwaf.constructor.version())
|
|
33
|
-
|
|
34
|
-
const { loaded, failed, errors } = this.ddwaf.rulesInfo
|
|
35
|
-
|
|
36
|
-
Reporter.metricsQueue.set('_dd.appsec.event_rules.loaded', loaded)
|
|
37
|
-
Reporter.metricsQueue.set('_dd.appsec.event_rules.error_count', failed)
|
|
38
|
-
if (failed) Reporter.metricsQueue.set('_dd.appsec.event_rules.errors', JSON.stringify(errors))
|
|
39
|
-
|
|
40
|
-
Reporter.metricsQueue.set('manual.keep', 'true')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
33
|
getWAFContext (req) {
|
|
44
34
|
let wafContext = contexts.get(req)
|
|
45
35
|
|
|
@@ -48,7 +38,8 @@ class WAFManager {
|
|
|
48
38
|
this.ddwaf.createContext(),
|
|
49
39
|
this.ddwaf.requiredAddresses,
|
|
50
40
|
this.wafTimeout,
|
|
51
|
-
this.ddwaf.rulesInfo
|
|
41
|
+
this.ddwaf.rulesInfo,
|
|
42
|
+
this.ddwafVersion
|
|
52
43
|
)
|
|
53
44
|
contexts.set(req, wafContext)
|
|
54
45
|
}
|
|
@@ -56,6 +47,12 @@ class WAFManager {
|
|
|
56
47
|
return wafContext
|
|
57
48
|
}
|
|
58
49
|
|
|
50
|
+
update (newRules) {
|
|
51
|
+
this.ddwaf.update(newRules)
|
|
52
|
+
|
|
53
|
+
Reporter.reportWafUpdate(this.ddwafVersion, this.ddwaf.rulesInfo.version)
|
|
54
|
+
}
|
|
55
|
+
|
|
59
56
|
destroy () {
|
|
60
57
|
if (this.ddwaf) {
|
|
61
58
|
this.ddwaf.dispose()
|
|
@@ -5,6 +5,7 @@ const request = require('./exporters/common/request')
|
|
|
5
5
|
const dgram = require('dgram')
|
|
6
6
|
const isIP = require('net').isIP
|
|
7
7
|
const log = require('./log')
|
|
8
|
+
const { URL, format } = require('url')
|
|
8
9
|
|
|
9
10
|
const MAX_BUFFER_SIZE = 1024 // limit from the agent
|
|
10
11
|
|
|
@@ -13,9 +14,7 @@ const TYPE_GAUGE = 'g'
|
|
|
13
14
|
const TYPE_DISTRIBUTION = 'd'
|
|
14
15
|
|
|
15
16
|
class DogStatsDClient {
|
|
16
|
-
constructor (options) {
|
|
17
|
-
options = options || {}
|
|
18
|
-
|
|
17
|
+
constructor (options = {}) {
|
|
19
18
|
if (options.metricsProxyUrl) {
|
|
20
19
|
this._httpOptions = {
|
|
21
20
|
url: options.metricsProxyUrl.toString(),
|
|
@@ -50,6 +49,8 @@ class DogStatsDClient {
|
|
|
50
49
|
flush () {
|
|
51
50
|
const queue = this._enqueue()
|
|
52
51
|
|
|
52
|
+
log.debug(`Flushing ${queue.length} metrics via ${this._httpOptions ? 'HTTP' : 'UDP'}`)
|
|
53
|
+
|
|
53
54
|
if (this._queue.length === 0) return
|
|
54
55
|
|
|
55
56
|
this._queue = []
|
|
@@ -141,6 +142,44 @@ class DogStatsDClient {
|
|
|
141
142
|
|
|
142
143
|
return socket
|
|
143
144
|
}
|
|
145
|
+
|
|
146
|
+
static generateClientConfig (config = {}) {
|
|
147
|
+
const tags = []
|
|
148
|
+
|
|
149
|
+
if (config.tags) {
|
|
150
|
+
Object.keys(config.tags)
|
|
151
|
+
.filter(key => typeof config.tags[key] === 'string')
|
|
152
|
+
.filter(key => {
|
|
153
|
+
// Skip runtime-id unless enabled as cardinality may be too high
|
|
154
|
+
if (key !== 'runtime-id') return true
|
|
155
|
+
return (config.experimental && config.experimental.runtimeId)
|
|
156
|
+
})
|
|
157
|
+
.forEach(key => {
|
|
158
|
+
// https://docs.datadoghq.com/tagging/#defining-tags
|
|
159
|
+
const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_')
|
|
160
|
+
|
|
161
|
+
tags.push(`${key}:${value}`)
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const clientConfig = {
|
|
166
|
+
host: config.dogstatsd.hostname,
|
|
167
|
+
port: config.dogstatsd.port,
|
|
168
|
+
tags
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (config.url) {
|
|
172
|
+
clientConfig.metricsProxyUrl = config.url
|
|
173
|
+
} else if (config.port) {
|
|
174
|
+
clientConfig.metricsProxyUrl = new URL(format({
|
|
175
|
+
protocol: 'http:',
|
|
176
|
+
hostname: config.hostname || 'localhost',
|
|
177
|
+
port: config.port
|
|
178
|
+
}))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return clientConfig
|
|
182
|
+
}
|
|
144
183
|
}
|
|
145
184
|
|
|
146
185
|
class NoopDogStatsDClient {
|
|
@@ -155,8 +194,9 @@ class NoopDogStatsDClient {
|
|
|
155
194
|
|
|
156
195
|
// This is a simplified user-facing proxy to the underlying DogStatsDClient instance
|
|
157
196
|
class CustomMetrics {
|
|
158
|
-
constructor (
|
|
159
|
-
|
|
197
|
+
constructor (config) {
|
|
198
|
+
const clientConfig = DogStatsDClient.generateClientConfig(config)
|
|
199
|
+
this.dogstatsd = new DogStatsDClient(clientConfig)
|
|
160
200
|
}
|
|
161
201
|
|
|
162
202
|
increment (stat, value = 1, tags) {
|
|
@@ -139,13 +139,13 @@ function removeInvalidMetadata (metadata) {
|
|
|
139
139
|
return Object.keys(metadata).reduce((filteredTags, tag) => {
|
|
140
140
|
if (tag === GIT_REPOSITORY_URL) {
|
|
141
141
|
if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
|
|
142
|
-
log.error(
|
|
142
|
+
log.error(`Repository URL is not a valid repository URL: ${metadata[GIT_REPOSITORY_URL]}.`)
|
|
143
143
|
return filteredTags
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
if (tag === GIT_COMMIT_SHA) {
|
|
147
147
|
if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
|
|
148
|
-
log.error(
|
|
148
|
+
log.error(`Git commit SHA must be a full-length git SHA: ${metadata[GIT_COMMIT_SHA]}.`)
|
|
149
149
|
return filteredTags
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -6,9 +6,11 @@ const dc = require('../../../../diagnostics_channel')
|
|
|
6
6
|
const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
|
|
7
7
|
const { WEB } = require('../../../../../ext/types')
|
|
8
8
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
|
+
const telemetryMetrics = require('../../telemetry/metrics')
|
|
9
10
|
|
|
10
11
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
11
12
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
13
|
+
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
12
14
|
|
|
13
15
|
let kSampleCount
|
|
14
16
|
|
|
@@ -168,6 +170,16 @@ class NativeWallProfiler {
|
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
_reportV8bug (maybeBug) {
|
|
174
|
+
const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
|
|
175
|
+
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
|
|
176
|
+
this._logger?.warn(`Wall profiler: ${maybeBug ? 'possible ' : ''}v8 profiler stuck event loop detected.`)
|
|
177
|
+
// report as runtime metric (can be removed in the future when telemetry is mature)
|
|
178
|
+
runtimeMetrics.increment(`runtime.node.profiler.${metric}`, tag, true)
|
|
179
|
+
// report as telemetry metric
|
|
180
|
+
profilerTelemetryMetrics.count(metric, [tag]).inc()
|
|
181
|
+
}
|
|
182
|
+
|
|
171
183
|
_stop (restart) {
|
|
172
184
|
if (!this._started) return
|
|
173
185
|
if (this._codeHotspotsEnabled) {
|
|
@@ -178,12 +190,8 @@ class NativeWallProfiler {
|
|
|
178
190
|
const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
|
|
179
191
|
if (restart) {
|
|
180
192
|
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
181
|
-
if (v8BugDetected
|
|
182
|
-
this.
|
|
183
|
-
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_maybe_stuck_event_loop', undefined, true)
|
|
184
|
-
} else if (v8BugDetected === 2) {
|
|
185
|
-
this._logger?.warn('Wall profiler: v8 profiler stuck event loop detected.')
|
|
186
|
-
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_stuck_event_loop', undefined, true)
|
|
193
|
+
if (v8BugDetected !== 0) {
|
|
194
|
+
this._reportV8bug(v8BugDetected === 1)
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
return profile
|
|
@@ -30,17 +30,7 @@ class Tracer extends NoopProxy {
|
|
|
30
30
|
|
|
31
31
|
if (config.dogstatsd) {
|
|
32
32
|
// Custom Metrics
|
|
33
|
-
this.dogstatsd = new dogstatsd.CustomMetrics(
|
|
34
|
-
host: config.dogstatsd.hostname,
|
|
35
|
-
port: config.dogstatsd.port,
|
|
36
|
-
tags: [
|
|
37
|
-
// these are the Runtime Metrics default tags
|
|
38
|
-
// Python also uses these as default Custom Metrics tags
|
|
39
|
-
`service:${config.tags.service}`,
|
|
40
|
-
`env:${config.tags.env}`,
|
|
41
|
-
`version:${config.tags.version}`
|
|
42
|
-
]
|
|
43
|
-
})
|
|
33
|
+
this.dogstatsd = new dogstatsd.CustomMetrics(config)
|
|
44
34
|
|
|
45
35
|
setInterval(() => {
|
|
46
36
|
this.dogstatsd.flush()
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// TODO: capture every second and flush every 10 seconds
|
|
4
4
|
|
|
5
|
-
const { URL, format } = require('url')
|
|
6
5
|
const v8 = require('v8')
|
|
7
6
|
const os = require('os')
|
|
8
7
|
const { DogStatsDClient } = require('./dogstatsd')
|
|
@@ -27,21 +26,7 @@ reset()
|
|
|
27
26
|
|
|
28
27
|
module.exports = {
|
|
29
28
|
start (config) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
Object.keys(config.tags)
|
|
33
|
-
.filter(key => typeof config.tags[key] === 'string')
|
|
34
|
-
.filter(key => {
|
|
35
|
-
// Skip runtime-id unless enabled as cardinality may be too high
|
|
36
|
-
if (key !== 'runtime-id') return true
|
|
37
|
-
return (config.experimental && config.experimental.runtimeId)
|
|
38
|
-
})
|
|
39
|
-
.forEach(key => {
|
|
40
|
-
// https://docs.datadoghq.com/tagging/#defining-tags
|
|
41
|
-
const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_')
|
|
42
|
-
|
|
43
|
-
tags.push(`${key}:${value}`)
|
|
44
|
-
})
|
|
29
|
+
const clientConfig = DogStatsDClient.generateClientConfig(config)
|
|
45
30
|
|
|
46
31
|
try {
|
|
47
32
|
nativeMetrics = require('@datadog/native-metrics')
|
|
@@ -51,22 +36,6 @@ module.exports = {
|
|
|
51
36
|
nativeMetrics = null
|
|
52
37
|
}
|
|
53
38
|
|
|
54
|
-
const clientConfig = {
|
|
55
|
-
host: config.dogstatsd.hostname,
|
|
56
|
-
port: config.dogstatsd.port,
|
|
57
|
-
tags
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (config.url) {
|
|
61
|
-
clientConfig.metricsProxyUrl = config.url
|
|
62
|
-
} else if (config.port) {
|
|
63
|
-
clientConfig.metricsProxyUrl = new URL(format({
|
|
64
|
-
protocol: 'http:',
|
|
65
|
-
hostname: config.hostname || 'localhost',
|
|
66
|
-
port: config.port
|
|
67
|
-
}))
|
|
68
|
-
}
|
|
69
|
-
|
|
70
39
|
client = new DogStatsDClient(clientConfig)
|
|
71
40
|
|
|
72
41
|
time = process.hrtime()
|
|
@@ -66,7 +66,7 @@ function globMatch (pattern, subject) {
|
|
|
66
66
|
function calculateDDBasePath (dirname) {
|
|
67
67
|
const dirSteps = dirname.split(path.sep)
|
|
68
68
|
const packagesIndex = dirSteps.lastIndexOf('packages')
|
|
69
|
-
return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
|
|
69
|
+
return dirSteps.slice(0, packagesIndex + 1).join(path.sep) + path.sep
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
module.exports = {
|