dd-trace 3.41.0 → 3.43.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/index.d.ts +29 -0
- package/package.json +7 -6
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
- package/packages/datadog-instrumentations/src/graphql.js +18 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/http/client.js +10 -0
- package/packages/datadog-instrumentations/src/jest.js +11 -5
- package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
- package/packages/datadog-instrumentations/src/next.js +18 -6
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-instrumentations/src/rhea.js +15 -9
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
- package/packages/datadog-plugin-http/src/client.js +19 -2
- package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
- package/packages/datadog-plugin-next/src/index.js +40 -14
- package/packages/dd-trace/src/appsec/activation.js +29 -0
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
- package/packages/dd-trace/src/appsec/blocking.js +95 -43
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/graphql.js +146 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +32 -31
- package/packages/dd-trace/src/appsec/recommended.json +1395 -2
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
- package/packages/dd-trace/src/appsec/reporter.js +19 -0
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
- package/packages/dd-trace/src/config.js +36 -2
- package/packages/dd-trace/src/datastreams/processor.js +107 -12
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +2 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +2 -2
- package/packages/dd-trace/src/plugins/util/test.js +3 -2
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
- package/packages/dd-trace/src/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +8 -0
- package/packages/dd-trace/src/profiling/profiler.js +17 -10
- package/packages/dd-trace/src/profiling/profilers/events.js +181 -83
- package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
- package/packages/dd-trace/src/proxy.js +25 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
- package/packages/dd-trace/src/spanleak.js +98 -0
- package/packages/dd-trace/src/startup-log.js +7 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
- package/packages/dd-trace/src/telemetry/index.js +135 -43
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +47 -5
- package/packages/dd-trace/src/tracer.js +4 -0
- package/scripts/install_plugin_modules.js +11 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../datadog-core')
|
|
4
|
+
const DatabasePlugin = require('../../dd-trace/src/plugins/database')
|
|
5
|
+
|
|
6
|
+
const AEROSPIKE_PEER_SERVICE = 'aerospike.namespace'
|
|
7
|
+
|
|
8
|
+
class AerospikePlugin extends DatabasePlugin {
|
|
9
|
+
static get id () { return 'aerospike' }
|
|
10
|
+
static get operation () { return 'command' }
|
|
11
|
+
static get system () { return 'aerospike' }
|
|
12
|
+
static get prefix () {
|
|
13
|
+
return 'tracing:apm:aerospike:command'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static get peerServicePrecursors () {
|
|
17
|
+
return [AEROSPIKE_PEER_SERVICE]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
bindStart (ctx) {
|
|
21
|
+
const { commandName, commandArgs } = ctx
|
|
22
|
+
const resourceName = commandName.slice(0, commandName.indexOf('Command'))
|
|
23
|
+
const store = storage.getStore()
|
|
24
|
+
const childOf = store ? store.span : null
|
|
25
|
+
const meta = getMeta(resourceName, commandArgs)
|
|
26
|
+
|
|
27
|
+
const span = this.startSpan(this.operationName(), {
|
|
28
|
+
childOf,
|
|
29
|
+
service: this.serviceName({ pluginConfig: this.config }),
|
|
30
|
+
type: 'aerospike',
|
|
31
|
+
kind: 'client',
|
|
32
|
+
resource: resourceName,
|
|
33
|
+
meta
|
|
34
|
+
}, false)
|
|
35
|
+
|
|
36
|
+
ctx.parentStore = store
|
|
37
|
+
ctx.currentStore = { ...store, span }
|
|
38
|
+
|
|
39
|
+
return ctx.currentStore
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
bindAsyncStart (ctx) {
|
|
43
|
+
if (ctx.currentStore) {
|
|
44
|
+
// have to manually trigger peer service calculation when using tracing channel
|
|
45
|
+
this.tagPeerService(ctx.currentStore.span)
|
|
46
|
+
ctx.currentStore.span.finish()
|
|
47
|
+
}
|
|
48
|
+
return ctx.parentStore
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
end (ctx) {
|
|
52
|
+
if (ctx.result) {
|
|
53
|
+
// have to manually trigger peer service calculation when using tracing channel
|
|
54
|
+
this.tagPeerService(ctx.currentStore.span)
|
|
55
|
+
ctx.currentStore.span.finish()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
error (ctx) {
|
|
60
|
+
if (ctx.error) {
|
|
61
|
+
const error = ctx.error
|
|
62
|
+
const span = ctx.currentStore.span
|
|
63
|
+
span.setTag('error', error)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getMeta (resourceName, commandArgs) {
|
|
69
|
+
let meta = {}
|
|
70
|
+
if (resourceName.includes('Index')) {
|
|
71
|
+
const [ns, set, bin, index] = commandArgs
|
|
72
|
+
meta = getMetaForIndex(ns, set, bin, index)
|
|
73
|
+
} else if (resourceName === 'Query') {
|
|
74
|
+
const { ns, set } = commandArgs[2]
|
|
75
|
+
meta = getMetaForQuery({ ns, set })
|
|
76
|
+
} else if (isKeyObject(commandArgs[0])) {
|
|
77
|
+
const { ns, set, key } = commandArgs[0]
|
|
78
|
+
meta = getMetaForKey(ns, set, key)
|
|
79
|
+
}
|
|
80
|
+
return meta
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getMetaForIndex (ns, set, bin, index) {
|
|
84
|
+
return {
|
|
85
|
+
[AEROSPIKE_PEER_SERVICE]: ns,
|
|
86
|
+
'aerospike.setname': set,
|
|
87
|
+
'aerospike.bin': bin,
|
|
88
|
+
'aerospike.index': index
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getMetaForKey (ns, set, key) {
|
|
93
|
+
return {
|
|
94
|
+
'aerospike.key': `${ns}:${set}:${key}`,
|
|
95
|
+
[AEROSPIKE_PEER_SERVICE]: ns,
|
|
96
|
+
'aerospike.setname': set,
|
|
97
|
+
'aerospike.userkey': key
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getMetaForQuery (queryObj) {
|
|
102
|
+
const { ns, set } = queryObj
|
|
103
|
+
return {
|
|
104
|
+
[AEROSPIKE_PEER_SERVICE]: ns,
|
|
105
|
+
'aerospike.setname': set
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isKeyObject (obj) {
|
|
110
|
+
return obj && obj.ns !== undefined && obj.set !== undefined && obj.key !== undefined
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = AerospikePlugin
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const dc = require('dc-polyfill')
|
|
4
5
|
|
|
5
6
|
const collapsedPathSym = Symbol('collapsedPaths')
|
|
6
7
|
|
|
@@ -14,8 +15,6 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
14
15
|
if (!shouldInstrument(this.config, path)) return
|
|
15
16
|
const computedPathString = path.join('.')
|
|
16
17
|
|
|
17
|
-
addResolver(context, info, args)
|
|
18
|
-
|
|
19
18
|
if (this.config.collapse) {
|
|
20
19
|
if (!context[collapsedPathSym]) {
|
|
21
20
|
context[collapsedPathSym] = {}
|
|
@@ -55,6 +54,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
55
54
|
span.setTag(`graphql.variables.${name}`, variables[name])
|
|
56
55
|
})
|
|
57
56
|
}
|
|
57
|
+
|
|
58
|
+
if (this.resolverStartCh.hasSubscribers) {
|
|
59
|
+
this.resolverStartCh.publish({ context, resolverInfo: getResolverInfo(info, args) })
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
constructor (...args) {
|
|
@@ -69,6 +72,8 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
69
72
|
field.finishTime = span._getTime ? span._getTime() : 0
|
|
70
73
|
field.error = field.error || err
|
|
71
74
|
})
|
|
75
|
+
|
|
76
|
+
this.resolverStartCh = dc.channel('datadog:graphql:resolver:start')
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
configure (config) {
|
|
@@ -109,28 +114,31 @@ function withCollapse (responsePathAsArray) {
|
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
117
|
+
function getResolverInfo (info, args) {
|
|
118
|
+
let resolverInfo = null
|
|
119
|
+
const resolverVars = {}
|
|
116
120
|
|
|
117
|
-
if (
|
|
118
|
-
|
|
121
|
+
if (args && Object.keys(args).length) {
|
|
122
|
+
Object.assign(resolverVars, args)
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
} else {
|
|
127
|
-
resolvers[info.fieldName] = []
|
|
125
|
+
const directives = info.fieldNodes[0].directives
|
|
126
|
+
for (const directive of directives) {
|
|
127
|
+
const argList = {}
|
|
128
|
+
for (const argument of directive['arguments']) {
|
|
129
|
+
argList[argument.name.value] = argument.value.value
|
|
128
130
|
}
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
131
|
+
|
|
132
|
+
if (Object.keys(argList).length) {
|
|
133
|
+
resolverVars[directive.name.value] = argList
|
|
132
134
|
}
|
|
133
135
|
}
|
|
136
|
+
|
|
137
|
+
if (Object.keys(resolverVars).length) {
|
|
138
|
+
resolverInfo = { [info.fieldName]: resolverVars }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return resolverInfo
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
module.exports = GraphQLResolvePlugin
|
|
@@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
58
58
|
span._spanContext._trace.record = false
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
if (
|
|
61
|
+
if (this.shouldInjectTraceHeaders(options, uri)) {
|
|
62
62
|
this.tracer.inject(span, HTTP_HEADERS, options.headers)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -71,6 +71,18 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
71
71
|
return message.currentStore
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
shouldInjectTraceHeaders (options, uri) {
|
|
75
|
+
if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!this.config.propagationFilter(uri)) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
bindAsyncStart ({ parentStore }) {
|
|
75
87
|
return parentStore
|
|
76
88
|
}
|
|
@@ -98,7 +110,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
98
110
|
span.finish()
|
|
99
111
|
}
|
|
100
112
|
|
|
101
|
-
error ({ span, error }) {
|
|
113
|
+
error ({ span, error, args, customRequestTimeout }) {
|
|
102
114
|
if (!span) return
|
|
103
115
|
if (error) {
|
|
104
116
|
span.addTags({
|
|
@@ -107,6 +119,11 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
107
119
|
[ERROR_STACK]: error.stack
|
|
108
120
|
})
|
|
109
121
|
} else {
|
|
122
|
+
// conditions for no error:
|
|
123
|
+
// 1. not using a custom agent instance with custom timeout specified
|
|
124
|
+
// 2. no invocation of `req.setTimeout`
|
|
125
|
+
if (!args.options.agent?.options.timeout && !customRequestTimeout) return
|
|
126
|
+
|
|
110
127
|
span.setTag('error', 1)
|
|
111
128
|
}
|
|
112
129
|
}
|
|
@@ -7,6 +7,57 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
|
|
|
7
7
|
static get id () { return 'kafkajs' }
|
|
8
8
|
static get operation () { return 'consume' }
|
|
9
9
|
|
|
10
|
+
constructor () {
|
|
11
|
+
super(...arguments)
|
|
12
|
+
this.addSub('apm:kafkajs:consume:commit', message => this.commit(message))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Transform individual commit details sent by kafkajs' event reporter
|
|
17
|
+
* into actionable backlog items for DSM
|
|
18
|
+
*
|
|
19
|
+
* @typedef {object} ConsumerBacklog
|
|
20
|
+
* @property {number} type
|
|
21
|
+
* @property {string} consumer_group
|
|
22
|
+
* @property {string} topic
|
|
23
|
+
* @property {number} partition
|
|
24
|
+
* @property {number} offset
|
|
25
|
+
*
|
|
26
|
+
* @typedef {object} CommitEventItem
|
|
27
|
+
* @property {string} groupId
|
|
28
|
+
* @property {string} topic
|
|
29
|
+
* @property {number} partition
|
|
30
|
+
* @property {import('kafkajs/utils/long').Long} offset
|
|
31
|
+
*
|
|
32
|
+
* @param {CommitEventItem} commit
|
|
33
|
+
* @returns {ConsumerBacklog}
|
|
34
|
+
*/
|
|
35
|
+
transformCommit (commit) {
|
|
36
|
+
const { groupId, partition, offset, topic } = commit
|
|
37
|
+
return {
|
|
38
|
+
partition,
|
|
39
|
+
topic,
|
|
40
|
+
type: 'kafka_commit',
|
|
41
|
+
offset: Number(offset),
|
|
42
|
+
consumer_group: groupId
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
commit (commitList) {
|
|
47
|
+
if (!this.config.dsmEnabled) return
|
|
48
|
+
const keys = [
|
|
49
|
+
'consumer_group',
|
|
50
|
+
'type',
|
|
51
|
+
'partition',
|
|
52
|
+
'offset',
|
|
53
|
+
'topic'
|
|
54
|
+
]
|
|
55
|
+
for (const commit of commitList.map(this.transformCommit)) {
|
|
56
|
+
if (keys.some(key => !commit.hasOwnProperty(key))) continue
|
|
57
|
+
this.tracer.setOffset(commit)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
10
61
|
start ({ topic, partition, message, groupId }) {
|
|
11
62
|
const childOf = extract(this.tracer, message.headers)
|
|
12
63
|
const span = this.startSpan({
|
|
@@ -11,6 +11,61 @@ class KafkajsProducerPlugin extends ProducerPlugin {
|
|
|
11
11
|
static get operation () { return 'produce' }
|
|
12
12
|
static get peerServicePrecursors () { return [BOOTSTRAP_SERVERS_KEY] }
|
|
13
13
|
|
|
14
|
+
constructor () {
|
|
15
|
+
super(...arguments)
|
|
16
|
+
this.addSub('apm:kafkajs:produce:commit', message => this.commit(message))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Transform individual commit details sent by kafkajs' event reporter
|
|
21
|
+
* into actionable backlog items for DSM
|
|
22
|
+
*
|
|
23
|
+
* @typedef {object} ProducerBacklog
|
|
24
|
+
* @property {number} type
|
|
25
|
+
* @property {string} topic
|
|
26
|
+
* @property {number} partition
|
|
27
|
+
* @property {number} offset
|
|
28
|
+
*
|
|
29
|
+
* @typedef {object} ProducerResponseItem
|
|
30
|
+
* @property {string} topic
|
|
31
|
+
* @property {number} partition
|
|
32
|
+
* @property {import('kafkajs/utils/long').Long} [offset]
|
|
33
|
+
* @property {import('kafkajs/utils/long').Long} [baseOffset]
|
|
34
|
+
*
|
|
35
|
+
* @param {ProducerResponseItem} response
|
|
36
|
+
* @returns {ProducerBacklog}
|
|
37
|
+
*/
|
|
38
|
+
transformProduceResponse (response) {
|
|
39
|
+
// In produce protocol >=v3, the offset key changes from `offset` to `baseOffset`
|
|
40
|
+
const { topicName: topic, partition, offset, baseOffset } = response
|
|
41
|
+
const offsetAsLong = offset || baseOffset
|
|
42
|
+
return {
|
|
43
|
+
type: 'kafka_produce',
|
|
44
|
+
partition,
|
|
45
|
+
offset: offsetAsLong ? Number(offsetAsLong) : undefined,
|
|
46
|
+
topic
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param {ProducerResponseItem[]} commitList
|
|
53
|
+
* @returns {void}
|
|
54
|
+
*/
|
|
55
|
+
commit (commitList) {
|
|
56
|
+
if (!this.config.dsmEnabled) return
|
|
57
|
+
const keys = [
|
|
58
|
+
'type',
|
|
59
|
+
'partition',
|
|
60
|
+
'offset',
|
|
61
|
+
'topic'
|
|
62
|
+
]
|
|
63
|
+
for (const commit of commitList.map(this.transformProduceResponse)) {
|
|
64
|
+
if (keys.some(key => !commit.hasOwnProperty(key))) continue
|
|
65
|
+
this.tracer.setOffset(commit)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
14
69
|
start ({ topic, messages, bootstrapServers }) {
|
|
15
70
|
let pathwayCtx
|
|
16
71
|
const span = this.startSpan({
|
|
@@ -6,6 +6,8 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
|
6
6
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
7
7
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
8
8
|
|
|
9
|
+
const errorPages = ['/404', '/500', '/_error', '/_not-found']
|
|
10
|
+
|
|
9
11
|
class NextPlugin extends ServerPlugin {
|
|
10
12
|
static get id () {
|
|
11
13
|
return 'next'
|
|
@@ -40,6 +42,13 @@ class NextPlugin extends ServerPlugin {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
error ({ span, error }) {
|
|
45
|
+
if (!span) {
|
|
46
|
+
const store = storage.getStore()
|
|
47
|
+
if (!store) return
|
|
48
|
+
|
|
49
|
+
span = store.span
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
this.addError(error, span)
|
|
44
53
|
}
|
|
45
54
|
|
|
@@ -50,10 +59,20 @@ class NextPlugin extends ServerPlugin {
|
|
|
50
59
|
|
|
51
60
|
const span = store.span
|
|
52
61
|
const error = span.context()._tags['error']
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
const requestError = req.error || nextRequest.error
|
|
63
|
+
|
|
64
|
+
if (requestError) {
|
|
65
|
+
// prioritize user-set errors from API routes
|
|
66
|
+
span.setTag('error', requestError)
|
|
67
|
+
web.addError(req, requestError)
|
|
68
|
+
} else if (error) {
|
|
69
|
+
// general error handling
|
|
70
|
+
span.setTag('error', error)
|
|
71
|
+
web.addError(req, requestError || error)
|
|
72
|
+
} else if (!this.config.validateStatus(res.statusCode)) {
|
|
73
|
+
// where there's no error, we still need to validate status
|
|
74
|
+
span.setTag('error', true)
|
|
75
|
+
web.addError(req, true)
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
span.addTags({
|
|
@@ -65,7 +84,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
65
84
|
span.finish()
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
pageLoad ({ page, isAppPath = false }) {
|
|
87
|
+
pageLoad ({ page, isAppPath = false, isStatic = false }) {
|
|
69
88
|
const store = storage.getStore()
|
|
70
89
|
|
|
71
90
|
if (!store) return
|
|
@@ -73,21 +92,28 @@ class NextPlugin extends ServerPlugin {
|
|
|
73
92
|
const span = store.span
|
|
74
93
|
const req = this._requests.get(span)
|
|
75
94
|
|
|
95
|
+
// safeguard against missing req in complicated timeout scenarios
|
|
96
|
+
if (!req) return
|
|
97
|
+
|
|
76
98
|
// Only use error page names if there's not already a name
|
|
77
99
|
const current = span.context()._tags['next.page']
|
|
78
|
-
|
|
100
|
+
const isErrorPage = errorPages.includes(page)
|
|
101
|
+
|
|
102
|
+
if (current && isErrorPage) {
|
|
79
103
|
return
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
// remove ending /route or /page for appDir projects
|
|
83
|
-
if
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
107
|
+
// need to check if not an error page too, as those are marked as app directory
|
|
108
|
+
// in newer versions
|
|
109
|
+
if (isAppPath && !isErrorPage) page = page.substring(0, page.lastIndexOf('/'))
|
|
110
|
+
|
|
111
|
+
// handle static resource
|
|
112
|
+
if (isStatic) {
|
|
113
|
+
page = req.url.includes('_next/static')
|
|
114
|
+
? '/_next/static/*'
|
|
115
|
+
: '/public/*'
|
|
116
|
+
}
|
|
91
117
|
|
|
92
118
|
span.addTags({
|
|
93
119
|
[COMPONENT]: this.constructor.id,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Activation = {
|
|
4
|
+
ONECLICK: 'OneClick',
|
|
5
|
+
ENABLED: 'Enabled',
|
|
6
|
+
DISABLED: 'Disabled',
|
|
7
|
+
|
|
8
|
+
fromConfig (config) {
|
|
9
|
+
switch (config.appsec.enabled) {
|
|
10
|
+
// ASM is activated by an env var DD_APPSEC_ENABLED=true
|
|
11
|
+
case true:
|
|
12
|
+
return Activation.ENABLED
|
|
13
|
+
|
|
14
|
+
// ASM is disabled by an env var DD_APPSEC_ENABLED=false
|
|
15
|
+
case false:
|
|
16
|
+
return Activation.DISABLED
|
|
17
|
+
|
|
18
|
+
// ASM is activated by one click remote config
|
|
19
|
+
case undefined:
|
|
20
|
+
return Activation.ONECLICK
|
|
21
|
+
|
|
22
|
+
// Any other value should never occur
|
|
23
|
+
default:
|
|
24
|
+
return Activation.DISABLED
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = Activation
|
|
@@ -13,8 +13,10 @@ module.exports = {
|
|
|
13
13
|
HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
|
|
14
14
|
// TODO: 'server.response.trailers',
|
|
15
15
|
HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
|
|
16
|
+
HTTP_INCOMING_GRAPHQL_RESOLVER: 'graphql.server.resolver',
|
|
16
17
|
|
|
17
18
|
HTTP_CLIENT_IP: 'http.client_ip',
|
|
18
19
|
|
|
19
|
-
USER_ID: 'usr.id'
|
|
20
|
+
USER_ID: 'usr.id',
|
|
21
|
+
WAF_CONTEXT_PROCESSOR: 'waf.context.processor'
|
|
20
22
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
|
|
5
|
+
let enabled
|
|
6
|
+
let requestSampling
|
|
7
|
+
|
|
8
|
+
function configure ({ apiSecurity }) {
|
|
9
|
+
enabled = apiSecurity.enabled
|
|
10
|
+
setRequestSampling(apiSecurity.requestSampling)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function disable () {
|
|
14
|
+
enabled = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setRequestSampling (sampling) {
|
|
18
|
+
requestSampling = parseRequestSampling(sampling)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseRequestSampling (requestSampling) {
|
|
22
|
+
let parsed = parseFloat(requestSampling)
|
|
23
|
+
|
|
24
|
+
if (isNaN(parsed)) {
|
|
25
|
+
log.warn(`Incorrect API Security request sampling value: ${requestSampling}`)
|
|
26
|
+
|
|
27
|
+
parsed = 0
|
|
28
|
+
} else {
|
|
29
|
+
parsed = Math.min(1, Math.max(0, parsed))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return parsed
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sampleRequest () {
|
|
36
|
+
if (!enabled || !requestSampling) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Math.random() <= requestSampling
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
configure,
|
|
45
|
+
disable,
|
|
46
|
+
setRequestSampling,
|
|
47
|
+
sampleRequest
|
|
48
|
+
}
|
|
@@ -5,7 +5,10 @@ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta n
|
|
|
5
5
|
|
|
6
6
|
const json = `{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}`
|
|
7
7
|
|
|
8
|
+
const graphqlJson = `{"errors":[{"message":"You've been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}`
|
|
9
|
+
|
|
8
10
|
module.exports = {
|
|
9
11
|
html,
|
|
10
|
-
json
|
|
12
|
+
json,
|
|
13
|
+
graphqlJson
|
|
11
14
|
}
|