dd-trace 5.29.1 → 5.30.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/package.json +1 -1
- package/packages/datadog-instrumentations/src/fs.js +3 -0
- package/packages/dd-trace/src/appsec/addresses.js +1 -0
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +13 -6
- package/packages/dd-trace/src/appsec/rasp/lfi.js +3 -1
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +3 -1
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +3 -1
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
- package/packages/dd-trace/src/appsec/reporter.js +3 -3
- package/packages/dd-trace/src/appsec/telemetry.js +7 -2
- package/packages/dd-trace/src/appsec/waf/index.js +2 -2
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -2
- package/packages/dd-trace/src/log/index.js +12 -2
- package/packages/dd-trace/src/log/writer.js +4 -3
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +7 -3
- package/packages/dd-trace/src/profiling/profilers/event_plugins/fs.js +49 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +23 -2
package/package.json
CHANGED
|
@@ -13,6 +13,9 @@ const errorChannel = channel('apm:fs:operation:error')
|
|
|
13
13
|
const ddFhSym = Symbol('ddFileHandle')
|
|
14
14
|
let kHandle, kDirReadPromisified, kDirClosePromisified
|
|
15
15
|
|
|
16
|
+
// Update packages/dd-trace/src/profiling/profilers/event_plugins/fs.js if you make changes to param names in any of
|
|
17
|
+
// the following objects.
|
|
18
|
+
|
|
16
19
|
const paramsByMethod = {
|
|
17
20
|
access: ['path', 'mode'],
|
|
18
21
|
appendFile: ['path', 'data', 'options'],
|
|
@@ -25,19 +25,26 @@ function disable () {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function analyzeCommandInjection ({ file, fileArgs, shell, abortController }) {
|
|
28
|
-
if (!file
|
|
28
|
+
if (!file) return
|
|
29
29
|
|
|
30
30
|
const store = storage.getStore()
|
|
31
31
|
const req = store?.req
|
|
32
32
|
if (!req) return
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
34
|
+
const persistent = {}
|
|
35
|
+
const raspRule = { type: RULE_TYPES.COMMAND_INJECTION }
|
|
36
|
+
const params = fileArgs ? [file, ...fileArgs] : file
|
|
37
|
+
|
|
38
|
+
if (shell) {
|
|
39
|
+
persistent[addresses.SHELL_COMMAND] = params
|
|
40
|
+
raspRule.variant = 'shell'
|
|
41
|
+
} else {
|
|
42
|
+
const commandParams = Array.isArray(params) ? params : [params]
|
|
43
|
+
persistent[addresses.EXEC_COMMAND] = commandParams
|
|
44
|
+
raspRule.variant = 'exec'
|
|
38
45
|
}
|
|
39
46
|
|
|
40
|
-
const result = waf.run({ persistent }, req,
|
|
47
|
+
const result = waf.run({ persistent }, req, raspRule)
|
|
41
48
|
|
|
42
49
|
const res = store?.res
|
|
43
50
|
handleResult(result, req, res, abortController, config)
|
|
@@ -58,7 +58,9 @@ function analyzeLfi (ctx) {
|
|
|
58
58
|
[FS_OPERATION_PATH]: path
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const
|
|
61
|
+
const raspRule = { type: RULE_TYPES.LFI }
|
|
62
|
+
|
|
63
|
+
const result = waf.run({ persistent }, req, raspRule)
|
|
62
64
|
handleResult(result, req, res, ctx.abortController, config)
|
|
63
65
|
})
|
|
64
66
|
}
|
|
@@ -72,7 +72,9 @@ function analyzeSqlInjection (query, dbSystem, abortController) {
|
|
|
72
72
|
[addresses.DB_SYSTEM]: dbSystem
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const raspRule = { type: RULE_TYPES.SQL_INJECTION }
|
|
76
|
+
|
|
77
|
+
const result = waf.run({ persistent }, req, raspRule)
|
|
76
78
|
|
|
77
79
|
handleResult(result, req, res, abortController, config)
|
|
78
80
|
}
|
|
@@ -29,7 +29,9 @@ function analyzeSsrf (ctx) {
|
|
|
29
29
|
[addresses.HTTP_OUTGOING_URL]: outgoingUrl
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const raspRule = { type: RULE_TYPES.SSRF }
|
|
33
|
+
|
|
34
|
+
const result = waf.run({ persistent }, req, raspRule)
|
|
33
35
|
|
|
34
36
|
const res = store?.res
|
|
35
37
|
handleResult(result, req, res, ctx.abortController, config)
|
|
@@ -101,6 +101,7 @@ function enableWafUpdate (appsecConfig) {
|
|
|
101
101
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, true)
|
|
102
102
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_LFI, true)
|
|
103
103
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SHI, true)
|
|
104
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_CMDI, true)
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
// TODO: delete noop handlers and kPreUpdate and replace with batched handlers
|
|
@@ -133,6 +134,7 @@ function disableWafUpdate () {
|
|
|
133
134
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, false)
|
|
134
135
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_LFI, false)
|
|
135
136
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SHI, false)
|
|
137
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_CMDI, false)
|
|
136
138
|
|
|
137
139
|
rc.removeProductHandler('ASM_DATA')
|
|
138
140
|
rc.removeProductHandler('ASM_DD')
|
|
@@ -101,7 +101,7 @@ function reportWafInit (wafVersion, rulesVersion, diagnosticsRules = {}) {
|
|
|
101
101
|
incrementWafInitMetric(wafVersion, rulesVersion)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function reportMetrics (metrics,
|
|
104
|
+
function reportMetrics (metrics, raspRule) {
|
|
105
105
|
const store = storage.getStore()
|
|
106
106
|
const rootSpan = store?.req && web.root(store.req)
|
|
107
107
|
if (!rootSpan) return
|
|
@@ -109,8 +109,8 @@ function reportMetrics (metrics, raspRuleType) {
|
|
|
109
109
|
if (metrics.rulesVersion) {
|
|
110
110
|
rootSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
|
|
111
111
|
}
|
|
112
|
-
if (
|
|
113
|
-
updateRaspRequestsMetricTags(metrics, store.req,
|
|
112
|
+
if (raspRule) {
|
|
113
|
+
updateRaspRequestsMetricTags(metrics, store.req, raspRule)
|
|
114
114
|
} else {
|
|
115
115
|
updateWafRequestsMetricTags(metrics, store.req)
|
|
116
116
|
}
|
|
@@ -79,7 +79,7 @@ function getOrCreateMetricTags (store, versionsTags) {
|
|
|
79
79
|
return metricTags
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function updateRaspRequestsMetricTags (metrics, req,
|
|
82
|
+
function updateRaspRequestsMetricTags (metrics, req, raspRule) {
|
|
83
83
|
if (!req) return
|
|
84
84
|
|
|
85
85
|
const store = getStore(req)
|
|
@@ -89,7 +89,12 @@ function updateRaspRequestsMetricTags (metrics, req, raspRuleType) {
|
|
|
89
89
|
|
|
90
90
|
if (!enabled) return
|
|
91
91
|
|
|
92
|
-
const tags = { rule_type:
|
|
92
|
+
const tags = { rule_type: raspRule.type, waf_version: metrics.wafVersion }
|
|
93
|
+
|
|
94
|
+
if (raspRule.variant) {
|
|
95
|
+
tags.rule_variant = raspRule.variant
|
|
96
|
+
}
|
|
97
|
+
|
|
93
98
|
appsecMetrics.count('rasp.rule.eval', tags).inc(1)
|
|
94
99
|
|
|
95
100
|
if (metrics.wafTimeout) {
|
|
@@ -46,7 +46,7 @@ function update (newRules) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function run (data, req,
|
|
49
|
+
function run (data, req, raspRule) {
|
|
50
50
|
if (!req) {
|
|
51
51
|
const store = storage.getStore()
|
|
52
52
|
if (!store || !store.req) {
|
|
@@ -59,7 +59,7 @@ function run (data, req, raspRuleType) {
|
|
|
59
59
|
|
|
60
60
|
const wafContext = waf.wafManager.getWAFContext(req)
|
|
61
61
|
|
|
62
|
-
return wafContext.run(data,
|
|
62
|
+
return wafContext.run(data, raspRule)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function disposeContext (req) {
|
|
@@ -21,7 +21,7 @@ class WAFContextWrapper {
|
|
|
21
21
|
this.knownAddresses = knownAddresses
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
run ({ persistent, ephemeral },
|
|
24
|
+
run ({ persistent, ephemeral }, raspRule) {
|
|
25
25
|
if (this.ddwafContext.disposed) {
|
|
26
26
|
log.warn('[ASM] Calling run on a disposed context')
|
|
27
27
|
return
|
|
@@ -87,7 +87,7 @@ class WAFContextWrapper {
|
|
|
87
87
|
blockTriggered,
|
|
88
88
|
wafVersion: this.wafVersion,
|
|
89
89
|
wafTimeout: result.timeout
|
|
90
|
-
},
|
|
90
|
+
}, raspRule)
|
|
91
91
|
|
|
92
92
|
if (ruleTriggered) {
|
|
93
93
|
Reporter.reportAttack(JSON.stringify(result.events))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const coalesce = require('koalas')
|
|
4
|
+
const { inspect } = require('util')
|
|
4
5
|
const { isTrue } = require('../util')
|
|
5
6
|
const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
|
|
6
7
|
const logWriter = require('./writer')
|
|
@@ -59,9 +60,18 @@ const log = {
|
|
|
59
60
|
trace (...args) {
|
|
60
61
|
if (traceChannel.hasSubscribers) {
|
|
61
62
|
const logRecord = {}
|
|
63
|
+
|
|
62
64
|
Error.captureStackTrace(logRecord, this.trace)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
|
|
66
|
+
const fn = logRecord.stack.split('\n')[1].replace(/^\s+at ([^\s]+) .+/, '$1')
|
|
67
|
+
const params = args.map(a => {
|
|
68
|
+
return a && a.hasOwnProperty('toString') && typeof a.toString === 'function'
|
|
69
|
+
? a.toString()
|
|
70
|
+
: inspect(a, { depth: 3, breakLength: Infinity, compact: true })
|
|
71
|
+
}).join(', ')
|
|
72
|
+
const formatted = logRecord.stack.replace('Error: ', `Trace: ${fn}(${params})`)
|
|
73
|
+
|
|
74
|
+
traceChannel.publish(Log.parse(formatted))
|
|
65
75
|
}
|
|
66
76
|
return this
|
|
67
77
|
},
|
|
@@ -4,7 +4,6 @@ const { storage } = require('../../../datadog-core')
|
|
|
4
4
|
const { LogChannel } = require('./channels')
|
|
5
5
|
const { Log } = require('./log')
|
|
6
6
|
const defaultLogger = {
|
|
7
|
-
trace: msg => console.trace(msg), /* eslint-disable-line no-console */
|
|
8
7
|
debug: msg => console.debug(msg), /* eslint-disable-line no-console */
|
|
9
8
|
info: msg => console.info(msg), /* eslint-disable-line no-console */
|
|
10
9
|
warn: msg => console.warn(msg), /* eslint-disable-line no-console */
|
|
@@ -91,8 +90,10 @@ function onDebug (log) {
|
|
|
91
90
|
|
|
92
91
|
function onTrace (log) {
|
|
93
92
|
const { formatted, cause } = getErrorLog(log)
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
// Using logger.debug() because not all loggers have trace level,
|
|
94
|
+
// and console.trace() has a completely different meaning.
|
|
95
|
+
if (formatted) withNoop(() => logger.debug(formatted))
|
|
96
|
+
if (cause) withNoop(() => logger.debug(cause))
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
function error (...args) {
|
|
@@ -107,7 +107,7 @@ class DatadogSpan {
|
|
|
107
107
|
|
|
108
108
|
toString () {
|
|
109
109
|
const spanContext = this.context()
|
|
110
|
-
const resourceName = spanContext._tags['resource.name']
|
|
110
|
+
const resourceName = spanContext._tags['resource.name'] || ''
|
|
111
111
|
const resource = resourceName.length > 100
|
|
112
112
|
? `${resourceName.substring(0, 97)}...`
|
|
113
113
|
: resourceName
|
|
@@ -32,11 +32,11 @@ class EventPlugin extends TracingPlugin {
|
|
|
32
32
|
if (!store) return
|
|
33
33
|
|
|
34
34
|
const { startEvent, startTime, error } = store
|
|
35
|
-
if (error) {
|
|
36
|
-
return // don't emit perf events for failed operations
|
|
35
|
+
if (error || this.ignoreEvent(startEvent)) {
|
|
36
|
+
return // don't emit perf events for failed operations or ignored events
|
|
37
37
|
}
|
|
38
|
-
const duration = performance.now() - startTime
|
|
39
38
|
|
|
39
|
+
const duration = performance.now() - startTime
|
|
40
40
|
const event = {
|
|
41
41
|
entryType: this.entryType,
|
|
42
42
|
startTime,
|
|
@@ -53,6 +53,10 @@ class EventPlugin extends TracingPlugin {
|
|
|
53
53
|
|
|
54
54
|
this.eventHandler(this.extendEvent(event, startEvent))
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
ignoreEvent () {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
module.exports = EventPlugin
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const EventPlugin = require('./event')
|
|
2
|
+
|
|
3
|
+
// Values taken from parameter names in datadog-instrumentations/src/fs.js.
|
|
4
|
+
// Known param names that are disallowed because they can be strings and have arbitrary sizes:
|
|
5
|
+
// 'data'
|
|
6
|
+
// Known param names that are disallowed because they are never a string or number:
|
|
7
|
+
// 'buffer', 'buffers', 'listener'
|
|
8
|
+
const allowedParams = new Set([
|
|
9
|
+
'atime', 'dest',
|
|
10
|
+
'existingPath', 'fd', 'file',
|
|
11
|
+
'flag', 'gid', 'len',
|
|
12
|
+
'length', 'mode', 'mtime',
|
|
13
|
+
'newPath', 'offset', 'oldPath',
|
|
14
|
+
'operation', 'options', 'path',
|
|
15
|
+
'position', 'prefix', 'src',
|
|
16
|
+
'target', 'type', 'uid'
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
class FilesystemPlugin extends EventPlugin {
|
|
20
|
+
static get id () {
|
|
21
|
+
return 'fs'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static get operation () {
|
|
25
|
+
return 'operation'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static get entryType () {
|
|
29
|
+
return 'fs'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ignoreEvent (event) {
|
|
33
|
+
// Don't care about sync events, they show up in the event loop samples anyway
|
|
34
|
+
return event.operation?.endsWith('Sync')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
extendEvent (event, detail) {
|
|
38
|
+
const d = { ...detail }
|
|
39
|
+
Object.entries(d).forEach(([k, v]) => {
|
|
40
|
+
if (!(allowedParams.has(k) && (typeof v === 'string' || typeof v === 'number'))) {
|
|
41
|
+
delete d[k]
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
event.detail = d
|
|
45
|
+
|
|
46
|
+
return event
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
module.exports = FilesystemPlugin
|
|
@@ -133,11 +133,32 @@ class NetDecorator {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
class FilesystemDecorator {
|
|
137
|
+
constructor (stringTable) {
|
|
138
|
+
this.stringTable = stringTable
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
decorateSample (sampleInput, item) {
|
|
142
|
+
const labels = sampleInput.label
|
|
143
|
+
const stringTable = this.stringTable
|
|
144
|
+
Object.entries(item.detail).forEach(([k, v]) => {
|
|
145
|
+
switch (typeof v) {
|
|
146
|
+
case 'string':
|
|
147
|
+
labels.push(labelFromStrStr(stringTable, k, v))
|
|
148
|
+
break
|
|
149
|
+
case 'number':
|
|
150
|
+
labels.push(new Label({ key: stringTable.dedup(k), num: v }))
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
136
156
|
// Keys correspond to PerformanceEntry.entryType, values are constructor
|
|
137
157
|
// functions for type-specific decorators.
|
|
138
158
|
const decoratorTypes = {
|
|
139
|
-
|
|
159
|
+
fs: FilesystemDecorator,
|
|
140
160
|
dns: DNSDecorator,
|
|
161
|
+
gc: GCDecorator,
|
|
141
162
|
net: NetDecorator
|
|
142
163
|
}
|
|
143
164
|
|
|
@@ -255,7 +276,7 @@ class NodeApiEventSource {
|
|
|
255
276
|
|
|
256
277
|
class DatadogInstrumentationEventSource {
|
|
257
278
|
constructor (eventHandler, eventFilter) {
|
|
258
|
-
this.plugins = ['dns_lookup', 'dns_lookupservice', 'dns_resolve', 'dns_reverse', 'net'].map(m => {
|
|
279
|
+
this.plugins = ['dns_lookup', 'dns_lookupservice', 'dns_resolve', 'dns_reverse', 'fs', 'net'].map(m => {
|
|
259
280
|
const Plugin = require(`./event_plugins/${m}`)
|
|
260
281
|
return new Plugin(eventHandler, eventFilter)
|
|
261
282
|
})
|