dd-trace 2.30.1 → 2.32.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 -1
- package/esbuild.js +3 -0
- package/index.d.ts +10 -9
- package/package.json +12 -12
- package/packages/datadog-core/src/storage/async_resource.js +1 -1
- package/packages/datadog-esbuild/index.js +9 -2
- package/packages/datadog-instrumentations/src/body-parser.js +15 -9
- package/packages/datadog-instrumentations/src/cucumber.js +11 -1
- package/packages/datadog-instrumentations/src/express.js +32 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/http/server.js +2 -1
- package/packages/datadog-instrumentations/src/jest.js +6 -3
- package/packages/datadog-instrumentations/src/mocha.js +19 -2
- package/packages/datadog-instrumentations/src/playwright.js +5 -2
- package/packages/datadog-plugin-amqp10/src/consumer.js +1 -3
- package/packages/datadog-plugin-amqp10/src/producer.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +4 -3
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -3
- package/packages/datadog-plugin-cucumber/src/index.js +6 -4
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -1
- package/packages/datadog-plugin-cypress/src/support.js +4 -0
- package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -3
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -3
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -3
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http/src/server.js +2 -2
- package/packages/datadog-plugin-http2/src/server.js +0 -5
- package/packages/datadog-plugin-jest/src/index.js +10 -5
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -4
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -3
- package/packages/datadog-plugin-mocha/src/index.js +9 -4
- package/packages/datadog-plugin-playwright/src/index.js +6 -5
- package/packages/datadog-plugin-redis/src/index.js +16 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-rhea/src/producer.js +1 -5
- package/packages/dd-trace/src/appsec/addresses.js +0 -3
- package/packages/dd-trace/src/appsec/blocked_templates.js +2 -9
- package/packages/dd-trace/src/appsec/blocking.js +1 -1
- package/packages/dd-trace/src/appsec/{gateway/channels.js → channels.js} +4 -4
- package/packages/dd-trace/src/appsec/iast/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +21 -13
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/logs.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +87 -79
- package/packages/dd-trace/src/appsec/recommended.json +448 -121
- package/packages/dd-trace/src/appsec/remote_config/apply_states.js +7 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +30 -11
- package/packages/dd-trace/src/appsec/remote_config/manager.js +33 -12
- package/packages/dd-trace/src/appsec/reporter.js +27 -58
- package/packages/dd-trace/src/appsec/rule_manager.js +160 -32
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -12
- package/packages/dd-trace/src/appsec/waf/index.js +75 -0
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +57 -0
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +66 -0
- package/packages/dd-trace/src/config.js +18 -1
- package/packages/dd-trace/src/dcitm.js +2 -0
- package/packages/dd-trace/src/encode/0.4.js +12 -4
- package/packages/dd-trace/src/encode/tags-processors.js +40 -68
- package/packages/dd-trace/src/exporters/common/request.js +2 -2
- package/packages/dd-trace/src/format.js +2 -1
- package/packages/dd-trace/src/iitm.js +1 -1
- package/packages/dd-trace/src/log/channels.js +11 -12
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
- package/packages/dd-trace/src/plugin_manager.js +3 -1
- package/packages/dd-trace/src/plugins/client.js +3 -2
- package/packages/dd-trace/src/plugins/consumer.js +17 -2
- package/packages/dd-trace/src/plugins/inbound.js +7 -0
- package/packages/dd-trace/src/plugins/{outgoing.js → outbound.js} +2 -2
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/producer.js +17 -2
- package/packages/dd-trace/src/plugins/server.js +2 -2
- package/packages/dd-trace/src/plugins/tracing.js +11 -0
- package/packages/dd-trace/src/plugins/util/test.js +19 -1
- package/packages/dd-trace/src/profiling/profilers/cpu.js +1 -1
- package/packages/dd-trace/src/ritm.js +1 -1
- package/packages/dd-trace/src/service-naming/index.js +41 -0
- package/packages/dd-trace/src/service-naming/schemas/definition.js +28 -0
- package/packages/dd-trace/src/service-naming/schemas/index.js +6 -0
- package/packages/dd-trace/src/service-naming/schemas/v0.js +66 -0
- package/packages/dd-trace/src/service-naming/schemas/v1.js +58 -0
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +1 -1
- package/packages/diagnostics_channel/index.js +3 -0
- package/packages/diagnostics_channel/src/index.js +57 -0
- package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +0 -137
- package/packages/dd-trace/src/appsec/callbacks/index.js +0 -7
- package/packages/dd-trace/src/appsec/gateway/als.js +0 -6
- package/packages/dd-trace/src/appsec/gateway/engine/engine.js +0 -140
- package/packages/dd-trace/src/appsec/gateway/engine/index.js +0 -51
- package/packages/dd-trace/src/appsec/gateway/engine/runner.js +0 -42
- package/packages/dd-trace/src/instrumenter.js +0 -203
- package/packages/dd-trace/src/loader.js +0 -131
- package/packages/dd-trace/src/plugins/incoming.js +0 -7
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const RemoteConfigManager = require('./manager')
|
|
4
4
|
const RemoteConfigCapabilities = require('./capabilities')
|
|
5
|
-
const RuleManager = require('../rule_manager')
|
|
6
5
|
|
|
7
6
|
let rc
|
|
8
7
|
|
|
@@ -32,28 +31,48 @@ function enable (config) {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
function
|
|
36
|
-
if (rc && appsecConfig && appsecConfig.
|
|
34
|
+
function enableWafUpdate (appsecConfig) {
|
|
35
|
+
if (rc && appsecConfig && !appsecConfig.customRulesProvided) {
|
|
36
|
+
// dirty require to make startup faster for serverless
|
|
37
|
+
const RuleManager = require('../rule_manager')
|
|
38
|
+
|
|
37
39
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_IP_BLOCKING, true)
|
|
38
40
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_USER_BLOCKING, true)
|
|
39
|
-
|
|
41
|
+
// TODO: we should have a different capability for rule override
|
|
42
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_DD_RULES, true)
|
|
43
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXCLUSIONS, true)
|
|
44
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_REQUEST_BLOCKING, true)
|
|
45
|
+
|
|
46
|
+
rc.on('ASM_DATA', noop)
|
|
47
|
+
rc.on('ASM_DD', noop)
|
|
48
|
+
rc.on('ASM', noop)
|
|
49
|
+
|
|
50
|
+
rc.on(RemoteConfigManager.kPreUpdate, RuleManager.updateWafFromRC)
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
function
|
|
54
|
+
function disableWafUpdate () {
|
|
44
55
|
if (rc) {
|
|
56
|
+
const RuleManager = require('../rule_manager')
|
|
57
|
+
|
|
45
58
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_IP_BLOCKING, false)
|
|
46
59
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_USER_BLOCKING, false)
|
|
47
|
-
rc.
|
|
60
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_DD_RULES, false)
|
|
61
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXCLUSIONS, false)
|
|
62
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_REQUEST_BLOCKING, false)
|
|
63
|
+
|
|
64
|
+
rc.off('ASM_DATA', noop)
|
|
65
|
+
rc.off('ASM_DD', noop)
|
|
66
|
+
rc.off('ASM', noop)
|
|
67
|
+
|
|
68
|
+
rc.off(RemoteConfigManager.kPreUpdate, RuleManager.updateWafFromRC)
|
|
48
69
|
}
|
|
49
70
|
}
|
|
50
71
|
|
|
51
|
-
function
|
|
52
|
-
RuleManager.updateAsmData(action, ruleData, ruleId)
|
|
53
|
-
}
|
|
72
|
+
function noop () {}
|
|
54
73
|
|
|
55
74
|
module.exports = {
|
|
56
75
|
enable,
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
enableWafUpdate,
|
|
77
|
+
disableWafUpdate
|
|
59
78
|
}
|
|
@@ -6,14 +6,19 @@ const Scheduler = require('./scheduler')
|
|
|
6
6
|
const tracerVersion = require('../../../../../package.json').version
|
|
7
7
|
const request = require('../../exporters/common/request')
|
|
8
8
|
const log = require('../../log')
|
|
9
|
+
const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
|
|
9
10
|
|
|
10
11
|
const clientId = uuid()
|
|
11
12
|
|
|
12
13
|
const DEFAULT_CAPABILITY = Buffer.alloc(1).toString('base64') // 0x00
|
|
13
14
|
|
|
15
|
+
const kPreUpdate = Symbol('kPreUpdate')
|
|
16
|
+
|
|
14
17
|
// There MUST NOT exist separate instances of RC clients in a tracer making separate ClientGetConfigsRequest
|
|
15
18
|
// with their own separated Client.ClientState.
|
|
16
19
|
class RemoteConfigManager extends EventEmitter {
|
|
20
|
+
static get kPreUpdate () { return kPreUpdate }
|
|
21
|
+
|
|
17
22
|
constructor (config) {
|
|
18
23
|
super()
|
|
19
24
|
|
|
@@ -78,9 +83,11 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
78
83
|
on (event, listener) {
|
|
79
84
|
super.on(event, listener)
|
|
80
85
|
|
|
81
|
-
this.
|
|
86
|
+
this.updateProducts()
|
|
82
87
|
|
|
83
|
-
this.
|
|
88
|
+
if (this.state.client.products.length) {
|
|
89
|
+
this.scheduler.start()
|
|
90
|
+
}
|
|
84
91
|
|
|
85
92
|
return this
|
|
86
93
|
}
|
|
@@ -88,7 +95,7 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
88
95
|
off (event, listener) {
|
|
89
96
|
super.off(event, listener)
|
|
90
97
|
|
|
91
|
-
this.
|
|
98
|
+
this.updateProducts()
|
|
92
99
|
|
|
93
100
|
if (!this.state.client.products.length) {
|
|
94
101
|
this.scheduler.stop()
|
|
@@ -97,6 +104,10 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
97
104
|
return this
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
updateProducts () {
|
|
108
|
+
this.state.client.products = this.eventNames().filter(e => typeof e === 'string')
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
poll (cb) {
|
|
101
112
|
request(JSON.stringify(this.state), this.requestOptions, (err, data, statusCode) => {
|
|
102
113
|
// 404 means RC is disabled, ignore it
|
|
@@ -181,7 +192,7 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
181
192
|
product,
|
|
182
193
|
id,
|
|
183
194
|
version: meta.custom.v,
|
|
184
|
-
apply_state:
|
|
195
|
+
apply_state: UNACKNOWLEDGED,
|
|
185
196
|
apply_error: '',
|
|
186
197
|
length: meta.length,
|
|
187
198
|
hashes: meta.hashes,
|
|
@@ -194,6 +205,8 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
if (toUnapply.length || toApply.length || toModify.length) {
|
|
208
|
+
this.emit(RemoteConfigManager.kPreUpdate, { toUnapply, toApply, toModify })
|
|
209
|
+
|
|
197
210
|
this.dispatch(toUnapply, 'unapply')
|
|
198
211
|
this.dispatch(toApply, 'apply')
|
|
199
212
|
this.dispatch(toModify, 'modify')
|
|
@@ -221,14 +234,22 @@ class RemoteConfigManager extends EventEmitter {
|
|
|
221
234
|
|
|
222
235
|
dispatch (list, action) {
|
|
223
236
|
for (const item of list) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
// TODO: we need a way to tell if unapply configs were handled by kPreUpdate or not, because they're always
|
|
238
|
+
// emitted unlike the apply and modify configs
|
|
239
|
+
|
|
240
|
+
// in case the item was already handled by kPreUpdate
|
|
241
|
+
if (item.apply_state === UNACKNOWLEDGED || action === 'unapply') {
|
|
242
|
+
try {
|
|
243
|
+
// TODO: do we want to pass old and new config ?
|
|
244
|
+
const hadListeners = this.emit(item.product, action, item.file, item.id)
|
|
245
|
+
|
|
246
|
+
if (hadListeners) {
|
|
247
|
+
item.apply_state = ACKNOWLEDGED
|
|
248
|
+
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
item.apply_state = ERROR
|
|
251
|
+
item.apply_error = err.toString()
|
|
252
|
+
}
|
|
232
253
|
}
|
|
233
254
|
|
|
234
255
|
if (action === 'unapply') {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const addresses = require('./addresses')
|
|
4
3
|
const Limiter = require('../rate_limiter')
|
|
4
|
+
const { storage } = require('../../../datadog-core')
|
|
5
5
|
const web = require('../plugins/util/web')
|
|
6
6
|
|
|
7
7
|
// default limiter, configurable with setRateLimit()
|
|
8
8
|
let limiter = new Limiter(100)
|
|
9
9
|
|
|
10
|
+
// TODO: use precomputed maps instead
|
|
10
11
|
const REQUEST_HEADERS_PASSLIST = [
|
|
11
12
|
'accept',
|
|
12
13
|
'accept-encoding',
|
|
@@ -37,28 +38,6 @@ const RESPONSE_HEADERS_PASSLIST = [
|
|
|
37
38
|
|
|
38
39
|
const metricsQueue = new Map()
|
|
39
40
|
|
|
40
|
-
function resolveHTTPRequest (context) {
|
|
41
|
-
if (!context) return {}
|
|
42
|
-
|
|
43
|
-
const headers = context.resolve(addresses.HTTP_INCOMING_HEADERS)
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
remote_ip: context.resolve(addresses.HTTP_INCOMING_REMOTE_IP),
|
|
47
|
-
headers: filterHeaders(headers, REQUEST_HEADERS_PASSLIST, 'http.request.headers.')
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function resolveHTTPResponse (context) {
|
|
52
|
-
if (!context) return {}
|
|
53
|
-
|
|
54
|
-
const headers = context.resolve(addresses.HTTP_INCOMING_RESPONSE_HEADERS)
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
endpoint: context.resolve(addresses.HTTP_INCOMING_ENDPOINT),
|
|
58
|
-
headers: filterHeaders(headers, RESPONSE_HEADERS_PASSLIST, 'http.response.headers.')
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
41
|
function filterHeaders (headers, passlist, prefix) {
|
|
63
42
|
const result = {}
|
|
64
43
|
|
|
@@ -68,7 +47,7 @@ function filterHeaders (headers, passlist, prefix) {
|
|
|
68
47
|
const headerName = passlist[i]
|
|
69
48
|
|
|
70
49
|
if (headers[headerName]) {
|
|
71
|
-
result[`${prefix}${formatHeaderName(headerName)}`] = headers[headerName]
|
|
50
|
+
result[`${prefix}${formatHeaderName(headerName)}`] = '' + headers[headerName]
|
|
72
51
|
}
|
|
73
52
|
}
|
|
74
53
|
|
|
@@ -84,10 +63,11 @@ function formatHeaderName (name) {
|
|
|
84
63
|
.toLowerCase()
|
|
85
64
|
}
|
|
86
65
|
|
|
87
|
-
function reportMetrics (metrics
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
66
|
+
function reportMetrics (metrics) {
|
|
67
|
+
// TODO: metrics should be incremental, there already is an RFC to report metrics
|
|
68
|
+
const store = storage.getStore()
|
|
69
|
+
const rootSpan = store && store.req && web.root(store.req)
|
|
70
|
+
if (!rootSpan) return
|
|
91
71
|
|
|
92
72
|
if (metrics.duration) {
|
|
93
73
|
rootSpan.setTag('_dd.appsec.waf.duration', metrics.duration)
|
|
@@ -102,16 +82,17 @@ function reportMetrics (metrics, store) {
|
|
|
102
82
|
}
|
|
103
83
|
}
|
|
104
84
|
|
|
105
|
-
function reportAttack (attackData
|
|
106
|
-
const
|
|
85
|
+
function reportAttack (attackData) {
|
|
86
|
+
const store = storage.getStore()
|
|
87
|
+
const req = store && store.req
|
|
107
88
|
const rootSpan = web.root(req)
|
|
108
|
-
if (!rootSpan) return
|
|
89
|
+
if (!rootSpan) return
|
|
109
90
|
|
|
110
91
|
const currentTags = rootSpan.context()._tags
|
|
111
92
|
|
|
112
|
-
const newTags =
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
const newTags = filterHeaders(req.headers, REQUEST_HEADERS_PASSLIST, 'http.request.headers.')
|
|
94
|
+
|
|
95
|
+
newTags['appsec.event'] = 'true'
|
|
115
96
|
|
|
116
97
|
if (limiter.isAllowed()) {
|
|
117
98
|
newTags['manual.keep'] = 'true' // TODO: figure out how to keep appsec traces with sampling revamp
|
|
@@ -126,32 +107,24 @@ function reportAttack (attackData, store) {
|
|
|
126
107
|
|
|
127
108
|
// merge JSON arrays without parsing them
|
|
128
109
|
if (currentJson) {
|
|
129
|
-
newTags['_dd.appsec.json'] = currentJson.slice(0, -2) + ',' + attackData.slice(1
|
|
110
|
+
newTags['_dd.appsec.json'] = currentJson.slice(0, -2) + ',' + attackData.slice(1) + '}'
|
|
130
111
|
} else {
|
|
131
112
|
newTags['_dd.appsec.json'] = '{"triggers":' + attackData + '}'
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const resolvedRequest = resolveHTTPRequest(context)
|
|
138
|
-
|
|
139
|
-
Object.assign(newTags, resolvedRequest.headers)
|
|
140
|
-
|
|
141
|
-
const ua = resolvedRequest.headers['http.request.headers.user-agent']
|
|
142
|
-
if (ua) {
|
|
143
|
-
newTags['http.useragent'] = ua
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
newTags['network.client.ip'] = resolvedRequest.remote_ip
|
|
115
|
+
const ua = newTags['http.request.headers.user-agent']
|
|
116
|
+
if (ua) {
|
|
117
|
+
newTags['http.useragent'] = ua
|
|
147
118
|
}
|
|
148
119
|
|
|
120
|
+
newTags['network.client.ip'] = req.socket.remoteAddress
|
|
121
|
+
|
|
149
122
|
rootSpan.addTags(newTags)
|
|
150
123
|
}
|
|
151
124
|
|
|
152
|
-
function finishRequest (req,
|
|
125
|
+
function finishRequest (req, res) {
|
|
153
126
|
const rootSpan = web.root(req)
|
|
154
|
-
if (!rootSpan) return
|
|
127
|
+
if (!rootSpan) return
|
|
155
128
|
|
|
156
129
|
if (metricsQueue.size) {
|
|
157
130
|
rootSpan.addTags(Object.fromEntries(metricsQueue))
|
|
@@ -159,14 +132,12 @@ function finishRequest (req, context) {
|
|
|
159
132
|
metricsQueue.clear()
|
|
160
133
|
}
|
|
161
134
|
|
|
162
|
-
if (!
|
|
163
|
-
|
|
164
|
-
const resolvedResponse = resolveHTTPResponse(context)
|
|
135
|
+
if (!rootSpan.context()._tags['appsec.event']) return
|
|
165
136
|
|
|
166
|
-
const newTags =
|
|
137
|
+
const newTags = filterHeaders(res.getHeaders(), RESPONSE_HEADERS_PASSLIST, 'http.response.headers.')
|
|
167
138
|
|
|
168
|
-
if (
|
|
169
|
-
newTags['http.endpoint'] =
|
|
139
|
+
if (req.route && typeof req.route.path === 'string') {
|
|
140
|
+
newTags['http.endpoint'] = req.route.path
|
|
170
141
|
}
|
|
171
142
|
|
|
172
143
|
rootSpan.addTags(newTags)
|
|
@@ -178,8 +149,6 @@ function setRateLimit (rateLimit) {
|
|
|
178
149
|
|
|
179
150
|
module.exports = {
|
|
180
151
|
metricsQueue,
|
|
181
|
-
resolveHTTPRequest,
|
|
182
|
-
resolveHTTPResponse,
|
|
183
152
|
filterHeaders,
|
|
184
153
|
formatHeaderName,
|
|
185
154
|
reportMetrics,
|
|
@@ -1,48 +1,175 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const waf = require('./waf')
|
|
4
|
+
const { ACKNOWLEDGED, ERROR } = require('./remote_config/apply_states')
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
let defaultRules
|
|
7
|
+
|
|
8
|
+
let appliedRulesData = new Map()
|
|
9
|
+
let appliedRulesetId
|
|
10
|
+
let appliedRulesOverride = new Map()
|
|
11
|
+
let appliedExclusions = new Map()
|
|
8
12
|
|
|
9
13
|
function applyRules (rules, config) {
|
|
10
|
-
|
|
14
|
+
defaultRules = rules
|
|
15
|
+
|
|
16
|
+
waf.init(rules, config)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function updateWafFromRC ({ toUnapply, toApply, toModify }) {
|
|
20
|
+
const batch = new Set()
|
|
21
|
+
|
|
22
|
+
const newRulesData = new SpyMap(appliedRulesData)
|
|
23
|
+
let newRuleset
|
|
24
|
+
let newRulesetId
|
|
25
|
+
const newRulesOverride = new SpyMap(appliedRulesOverride)
|
|
26
|
+
const newExclusions = new SpyMap(appliedExclusions)
|
|
27
|
+
|
|
28
|
+
for (const item of toUnapply) {
|
|
29
|
+
const { product, id } = item
|
|
30
|
+
|
|
31
|
+
if (product === 'ASM_DATA') {
|
|
32
|
+
newRulesData.delete(id)
|
|
33
|
+
} else if (product === 'ASM_DD') {
|
|
34
|
+
if (appliedRulesetId === id) {
|
|
35
|
+
newRuleset = defaultRules
|
|
36
|
+
newRulesetId = null
|
|
37
|
+
}
|
|
38
|
+
} else if (product === 'ASM') {
|
|
39
|
+
newRulesOverride.delete(id)
|
|
40
|
+
newExclusions.delete(id)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const item of [...toApply, ...toModify]) {
|
|
45
|
+
const { product, id, file } = item
|
|
46
|
+
|
|
47
|
+
if (product === 'ASM_DATA') {
|
|
48
|
+
if (file && file.rules_data && file.rules_data.length) {
|
|
49
|
+
newRulesData.set(id, file.rules_data)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
batch.add(item)
|
|
53
|
+
} else if (product === 'ASM_DD') {
|
|
54
|
+
if (appliedRulesetId && appliedRulesetId !== id) {
|
|
55
|
+
item.apply_state = ERROR
|
|
56
|
+
item.apply_error = 'Multiple ruleset received in ASM_DD'
|
|
57
|
+
} else {
|
|
58
|
+
if (file && file.rules && file.rules.length) {
|
|
59
|
+
const { version, metadata, rules } = file
|
|
60
|
+
|
|
61
|
+
newRuleset = { version, metadata, rules }
|
|
62
|
+
newRulesetId = id
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
batch.add(item)
|
|
66
|
+
}
|
|
67
|
+
} else if (product === 'ASM') {
|
|
68
|
+
if (file && file.rules_override && file.rules_override.length) {
|
|
69
|
+
newRulesOverride.set(id, file.rules_override)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (file && file.exclusions && file.exclusions.length) {
|
|
73
|
+
newExclusions.set(id, file.exclusions)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
batch.add(item)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
11
79
|
|
|
12
|
-
|
|
13
|
-
|
|
80
|
+
let newApplyState = ACKNOWLEDGED
|
|
81
|
+
let newApplyError
|
|
14
82
|
|
|
15
|
-
|
|
83
|
+
if (newRulesData.modified || newRuleset || newRulesOverride.modified || newExclusions.modified) {
|
|
84
|
+
const payload = newRuleset || {}
|
|
85
|
+
|
|
86
|
+
if (newRulesData.modified) {
|
|
87
|
+
payload.rules_data = mergeRulesData(newRulesData)
|
|
88
|
+
}
|
|
89
|
+
if (newRulesOverride.modified) {
|
|
90
|
+
payload.rules_override = concatArrays(newRulesOverride)
|
|
91
|
+
}
|
|
92
|
+
if (newExclusions.modified) {
|
|
93
|
+
payload.exclusions = concatArrays(newExclusions)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
waf.update(payload)
|
|
98
|
+
|
|
99
|
+
if (newRulesData.modified) {
|
|
100
|
+
appliedRulesData = newRulesData
|
|
101
|
+
}
|
|
102
|
+
if (newRuleset) {
|
|
103
|
+
appliedRulesetId = newRulesetId
|
|
104
|
+
}
|
|
105
|
+
if (newRulesOverride.modified) {
|
|
106
|
+
appliedRulesOverride = newRulesOverride
|
|
107
|
+
}
|
|
108
|
+
if (newExclusions.modified) {
|
|
109
|
+
appliedExclusions = newExclusions
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
newApplyState = ERROR
|
|
113
|
+
newApplyError = err.toString()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const config of batch) {
|
|
118
|
+
config.apply_state = newApplyState
|
|
119
|
+
if (newApplyError) config.apply_error = newApplyError
|
|
120
|
+
}
|
|
16
121
|
}
|
|
17
122
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
123
|
+
// A Map with a new prop `modified`, a bool that indicates if the Map was modified
|
|
124
|
+
class SpyMap extends Map {
|
|
125
|
+
constructor (iterable) {
|
|
126
|
+
super(iterable)
|
|
127
|
+
this.modified = false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
set (key, value) {
|
|
131
|
+
this.modified = true
|
|
132
|
+
return super.set(key, value)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
delete (key) {
|
|
136
|
+
const result = super.delete(key)
|
|
137
|
+
if (result) this.modified = true
|
|
138
|
+
return result
|
|
23
139
|
}
|
|
24
140
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
141
|
+
clear () {
|
|
142
|
+
this.modified = false
|
|
143
|
+
return super.clear()
|
|
28
144
|
}
|
|
29
145
|
}
|
|
30
146
|
|
|
31
|
-
function
|
|
147
|
+
function concatArrays (files) {
|
|
148
|
+
return Array.from(files.values()).flat()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
ASM_DATA Merge strategy:
|
|
153
|
+
The merge should be based on the id and type. For any duplicate items, the longer expiration should be taken.
|
|
154
|
+
As a result, multiple Rule Data may use the same DATA_ID and DATA_TYPE. In this case, all values are considered part
|
|
155
|
+
of a set and are merged. For instance, a denylist customized by environment may use a global Rule Data for all
|
|
156
|
+
environments and a Rule Data per environment
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
function mergeRulesData (files) {
|
|
32
160
|
const mergedRulesData = new Map()
|
|
33
|
-
for (const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const key = `${rulesData.id}+${rulesData.type}`
|
|
161
|
+
for (const [, file] of files) {
|
|
162
|
+
for (const ruleData of file) {
|
|
163
|
+
const key = `${ruleData.id}+${ruleData.type}`
|
|
37
164
|
if (mergedRulesData.has(key)) {
|
|
38
165
|
const existingRulesData = mergedRulesData.get(key)
|
|
39
|
-
|
|
166
|
+
ruleData.data.reduce(rulesReducer, existingRulesData.data)
|
|
40
167
|
} else {
|
|
41
|
-
mergedRulesData.set(key, copyRulesData(
|
|
168
|
+
mergedRulesData.set(key, copyRulesData(ruleData))
|
|
42
169
|
}
|
|
43
170
|
}
|
|
44
171
|
}
|
|
45
|
-
return
|
|
172
|
+
return Array.from(mergedRulesData.values())
|
|
46
173
|
}
|
|
47
174
|
|
|
48
175
|
function rulesReducer (existingEntries, rulesDataEntry) {
|
|
@@ -69,19 +196,20 @@ function copyRulesData (rulesData) {
|
|
|
69
196
|
}
|
|
70
197
|
return copy
|
|
71
198
|
}
|
|
199
|
+
|
|
72
200
|
function clearAllRules () {
|
|
73
|
-
|
|
201
|
+
waf.destroy()
|
|
74
202
|
|
|
75
|
-
|
|
76
|
-
callback.clear()
|
|
203
|
+
defaultRules = null
|
|
77
204
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
205
|
+
appliedRulesData.clear()
|
|
206
|
+
appliedRulesetId = null
|
|
207
|
+
appliedRulesOverride.clear()
|
|
208
|
+
appliedExclusions.clear()
|
|
81
209
|
}
|
|
82
210
|
|
|
83
211
|
module.exports = {
|
|
84
212
|
applyRules,
|
|
85
|
-
|
|
86
|
-
|
|
213
|
+
updateWafFromRC,
|
|
214
|
+
clearAllRules
|
|
87
215
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { USER_ID } = require('../addresses')
|
|
4
|
-
const
|
|
4
|
+
const waf = require('../waf')
|
|
5
5
|
const { getRootSpan } = require('./utils')
|
|
6
6
|
const { block } = require('../blocking')
|
|
7
7
|
const { storage } = require('../../../../datadog-core')
|
|
@@ -9,19 +9,11 @@ const { setUserTags } = require('./set_user')
|
|
|
9
9
|
const log = require('../../log')
|
|
10
10
|
|
|
11
11
|
function isUserBlocked (user) {
|
|
12
|
-
const
|
|
12
|
+
const actions = waf.run({ [USER_ID]: user.id })
|
|
13
13
|
|
|
14
|
-
if (!
|
|
15
|
-
return false
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
for (const entry of results) {
|
|
19
|
-
if (entry && entry.includes('block')) {
|
|
20
|
-
return true
|
|
21
|
-
}
|
|
22
|
-
}
|
|
14
|
+
if (!actions) return false
|
|
23
15
|
|
|
24
|
-
return
|
|
16
|
+
return actions.includes('block')
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
function checkUserAndSetUser (tracer, user) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../../../datadog-core')
|
|
4
|
+
const log = require('../../log')
|
|
5
|
+
|
|
6
|
+
const waf = {
|
|
7
|
+
wafManager: null,
|
|
8
|
+
init,
|
|
9
|
+
destroy,
|
|
10
|
+
update,
|
|
11
|
+
run: noop,
|
|
12
|
+
disposeContext: noop
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function init (rules, config) {
|
|
16
|
+
destroy()
|
|
17
|
+
|
|
18
|
+
// dirty require to make startup faster for serverless
|
|
19
|
+
const WAFManager = require('./waf_manager')
|
|
20
|
+
|
|
21
|
+
waf.wafManager = new WAFManager(rules, config)
|
|
22
|
+
|
|
23
|
+
waf.run = run
|
|
24
|
+
waf.disposeContext = disposeContext
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function destroy () {
|
|
28
|
+
if (waf.wafManager) {
|
|
29
|
+
waf.wafManager.destroy()
|
|
30
|
+
waf.wafManager = null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
waf.run = noop
|
|
34
|
+
waf.disposeContext = noop
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function update (newRules) {
|
|
38
|
+
// TODO: check race conditions between Appsec enable/disable and WAF updates, the whole RC state management in general
|
|
39
|
+
if (!waf.wafManager) throw new Error('Cannot update disabled WAF')
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
waf.wafManager.ddwaf.update(newRules)
|
|
43
|
+
} catch (err) {
|
|
44
|
+
log.error('Could not apply rules from remote config')
|
|
45
|
+
throw err
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function run (data, req) {
|
|
50
|
+
if (!req) {
|
|
51
|
+
const store = storage.getStore()
|
|
52
|
+
if (!store || !store.req) {
|
|
53
|
+
log.warn('Request object not available in waf.run')
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
req = store.req
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const wafContext = waf.wafManager.getWAFContext(req)
|
|
61
|
+
|
|
62
|
+
return wafContext.run(data)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function disposeContext (req) {
|
|
66
|
+
const wafContext = waf.wafManager.getWAFContext(req)
|
|
67
|
+
|
|
68
|
+
if (wafContext && !wafContext.ddwafContext.disposed) {
|
|
69
|
+
wafContext.dispose()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function noop () {}
|
|
74
|
+
|
|
75
|
+
module.exports = waf
|