dd-trace 2.31.0 → 2.33.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 +6 -6
- package/packages/datadog-instrumentations/src/body-parser.js +15 -9
- package/packages/datadog-instrumentations/src/express.js +32 -0
- package/packages/datadog-instrumentations/src/http/server.js +2 -1
- package/packages/datadog-instrumentations/src/mocha.js +8 -4
- package/packages/datadog-instrumentations/src/pg.js +3 -4
- package/packages/datadog-instrumentations/src/playwright.js +14 -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-kafkajs/src/consumer.js +2 -0
- package/packages/datadog-plugin-pg/src/index.js +5 -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/taint-tracking/plugin.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 +29 -10
- 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/encode/0.4.js +12 -4
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -4
- package/packages/dd-trace/src/plugins/util/test.js +34 -17
- package/packages/dd-trace/src/telemetry/send-data.js +13 -3
- 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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
const Reporter = require('../reporter')
|
|
5
|
+
|
|
6
|
+
class WAFContextWrapper {
|
|
7
|
+
constructor (ddwafContext, requiredAddresses, wafTimeout, rulesInfo) {
|
|
8
|
+
this.ddwafContext = ddwafContext
|
|
9
|
+
this.requiredAddresses = requiredAddresses
|
|
10
|
+
this.wafTimeout = wafTimeout
|
|
11
|
+
this.rulesInfo = rulesInfo
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
run (params) {
|
|
15
|
+
const inputs = {}
|
|
16
|
+
let someInputAdded = false
|
|
17
|
+
|
|
18
|
+
// TODO: possible optimizaion: only send params that haven't already been sent with same value to this wafContext
|
|
19
|
+
for (const key of Object.keys(params)) {
|
|
20
|
+
if (this.requiredAddresses.has(key)) {
|
|
21
|
+
inputs[key] = params[key]
|
|
22
|
+
someInputAdded = true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!someInputAdded) return
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const start = process.hrtime.bigint()
|
|
30
|
+
|
|
31
|
+
const result = this.ddwafContext.run(inputs, this.wafTimeout)
|
|
32
|
+
|
|
33
|
+
const end = process.hrtime.bigint()
|
|
34
|
+
|
|
35
|
+
Reporter.reportMetrics({
|
|
36
|
+
duration: result.totalRuntime / 1e3,
|
|
37
|
+
durationExt: parseInt(end - start) / 1e3,
|
|
38
|
+
rulesVersion: this.rulesInfo.version
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (result.data && result.data !== '[]') {
|
|
42
|
+
Reporter.reportAttack(result.data)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result.actions
|
|
46
|
+
} catch (err) {
|
|
47
|
+
log.error('Error while running the AppSec WAF')
|
|
48
|
+
log.error(err)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
dispose () {
|
|
53
|
+
this.ddwafContext.dispose()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = WAFContextWrapper
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
const Reporter = require('../reporter')
|
|
5
|
+
const WAFContextWrapper = require('./waf_context_wrapper')
|
|
6
|
+
|
|
7
|
+
const contexts = new WeakMap()
|
|
8
|
+
|
|
9
|
+
class WAFManager {
|
|
10
|
+
constructor (rules, config) {
|
|
11
|
+
this.config = config
|
|
12
|
+
this.wafTimeout = config.wafTimeout
|
|
13
|
+
this.ddwaf = this._loadDDWAF(rules)
|
|
14
|
+
this._reportMetrics()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_loadDDWAF (rules) {
|
|
18
|
+
try {
|
|
19
|
+
// require in `try/catch` because this can throw at require time
|
|
20
|
+
const { DDWAF } = require('@datadog/native-appsec')
|
|
21
|
+
|
|
22
|
+
const { obfuscatorKeyRegex, obfuscatorValueRegex } = this.config
|
|
23
|
+
return new DDWAF(rules, { obfuscatorKeyRegex, obfuscatorValueRegex })
|
|
24
|
+
} catch (err) {
|
|
25
|
+
log.error('AppSec could not load native package. In-app WAF features will not be available.')
|
|
26
|
+
|
|
27
|
+
throw err
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
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
|
+
getWAFContext (req) {
|
|
44
|
+
let wafContext = contexts.get(req)
|
|
45
|
+
|
|
46
|
+
if (!wafContext) {
|
|
47
|
+
wafContext = new WAFContextWrapper(
|
|
48
|
+
this.ddwaf.createContext(),
|
|
49
|
+
this.ddwaf.requiredAddresses,
|
|
50
|
+
this.wafTimeout,
|
|
51
|
+
this.ddwaf.rulesInfo
|
|
52
|
+
)
|
|
53
|
+
contexts.set(req, wafContext)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return wafContext
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
destroy () {
|
|
60
|
+
if (this.ddwaf) {
|
|
61
|
+
this.ddwaf.dispose()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = WAFManager
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const { truncateSpan, normalizeSpan } = require('./tags-processors')
|
|
4
4
|
const Chunk = require('./chunk')
|
|
5
5
|
const log = require('../log')
|
|
6
|
+
const { isTrue } = require('../util')
|
|
7
|
+
const coalesce = require('koalas')
|
|
6
8
|
|
|
7
9
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
8
10
|
|
|
@@ -24,6 +26,10 @@ class AgentEncoder {
|
|
|
24
26
|
this._stringBytes = new Chunk()
|
|
25
27
|
this._writer = writer
|
|
26
28
|
this._reset()
|
|
29
|
+
this._debugEncoding = isTrue(coalesce(
|
|
30
|
+
process.env.DD_TRACE_ENCODING_DEBUG,
|
|
31
|
+
false
|
|
32
|
+
))
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
count () {
|
|
@@ -40,11 +46,13 @@ class AgentEncoder {
|
|
|
40
46
|
|
|
41
47
|
const end = bytes.length
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
if (this._debugEncoding) {
|
|
50
|
+
log.debug(() => {
|
|
51
|
+
const hex = bytes.buffer.subarray(start, end).toString('hex').match(/../g).join(' ')
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
return `Adding encoded trace to buffer: ${hex}`
|
|
54
|
+
})
|
|
55
|
+
}
|
|
48
56
|
|
|
49
57
|
// we can go over the soft limit since the agent has a 50MB hard limit
|
|
50
58
|
if (this._traceBytes.length > this._limit || this._stringBytes.length > this._limit) {
|
|
@@ -129,12 +129,17 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
129
129
|
_encodeEventContent (bytes, content) {
|
|
130
130
|
const keysLength = Object.keys(content).length
|
|
131
131
|
|
|
132
|
+
let totalKeysLength = keysLength
|
|
132
133
|
if (content.meta.test_session_id) {
|
|
133
|
-
|
|
134
|
-
} else {
|
|
135
|
-
this._encodeMapPrefix(bytes, keysLength)
|
|
134
|
+
totalKeysLength = totalKeysLength + 1
|
|
136
135
|
}
|
|
137
|
-
|
|
136
|
+
if (content.meta.test_module_id) {
|
|
137
|
+
totalKeysLength = totalKeysLength + 1
|
|
138
|
+
}
|
|
139
|
+
if (content.meta.test_suite_id) {
|
|
140
|
+
totalKeysLength = totalKeysLength + 1
|
|
141
|
+
}
|
|
142
|
+
this._encodeMapPrefix(bytes, totalKeysLength)
|
|
138
143
|
if (content.type) {
|
|
139
144
|
this._encodeString(bytes, 'type')
|
|
140
145
|
this._encodeString(bytes, content.type)
|
|
@@ -170,15 +175,20 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
170
175
|
this._encodeString(bytes, 'test_session_id')
|
|
171
176
|
this._encodeId(bytes, id(content.meta.test_session_id, 10))
|
|
172
177
|
delete content.meta.test_session_id
|
|
178
|
+
}
|
|
173
179
|
|
|
180
|
+
if (content.meta.test_module_id) {
|
|
174
181
|
this._encodeString(bytes, 'test_module_id')
|
|
175
182
|
this._encodeId(bytes, id(content.meta.test_module_id, 10))
|
|
176
183
|
delete content.meta.test_module_id
|
|
184
|
+
}
|
|
177
185
|
|
|
186
|
+
if (content.meta.test_suite_id) {
|
|
178
187
|
this._encodeString(bytes, 'test_suite_id')
|
|
179
188
|
this._encodeId(bytes, id(content.meta.test_suite_id, 10))
|
|
180
189
|
delete content.meta.test_suite_id
|
|
181
190
|
}
|
|
191
|
+
|
|
182
192
|
this._encodeString(bytes, 'meta')
|
|
183
193
|
this._encodeMap(bytes, content.meta)
|
|
184
194
|
this._encodeString(bytes, 'metrics')
|
|
@@ -50,12 +50,10 @@ const CI_APP_ORIGIN = 'ciapp-test'
|
|
|
50
50
|
const JEST_TEST_RUNNER = 'test.jest.test_runner'
|
|
51
51
|
|
|
52
52
|
const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const TEST_MODULE_ITR_SKIPPING_ENABLED = 'test_module.itr.tests_skipping.enabled'
|
|
56
|
-
const TEST_MODULE_CODE_COVERAGE_ENABLED = 'test_module.code_coverage.enabled'
|
|
53
|
+
const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
|
|
54
|
+
const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
|
|
57
55
|
|
|
58
|
-
const
|
|
56
|
+
const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
|
|
59
57
|
|
|
60
58
|
// jest worker variables
|
|
61
59
|
const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
|
|
@@ -97,17 +95,16 @@ module.exports = {
|
|
|
97
95
|
TEST_SUITE_ID,
|
|
98
96
|
TEST_ITR_TESTS_SKIPPED,
|
|
99
97
|
TEST_MODULE,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
TEST_MODULE_CODE_COVERAGE_ENABLED,
|
|
104
|
-
TEST_CODE_COVERAGE_LINES_TOTAL,
|
|
98
|
+
TEST_ITR_SKIPPING_ENABLED,
|
|
99
|
+
TEST_CODE_COVERAGE_ENABLED,
|
|
100
|
+
TEST_CODE_COVERAGE_LINES_PCT,
|
|
105
101
|
addIntelligentTestRunnerSpanTags,
|
|
106
102
|
getCoveredFilenamesFromCoverage,
|
|
107
103
|
resetCoverage,
|
|
108
104
|
mergeCoverage,
|
|
109
105
|
fromCoverageMapToCoverage,
|
|
110
|
-
getTestLineStart
|
|
106
|
+
getTestLineStart,
|
|
107
|
+
getCallSites
|
|
111
108
|
}
|
|
112
109
|
|
|
113
110
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -316,17 +313,17 @@ function addIntelligentTestRunnerSpanTags (
|
|
|
316
313
|
{ isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
|
|
317
314
|
) {
|
|
318
315
|
testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
|
|
319
|
-
testSessionSpan.setTag(
|
|
320
|
-
testSessionSpan.setTag(
|
|
316
|
+
testSessionSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
|
|
317
|
+
testSessionSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
|
|
321
318
|
|
|
322
319
|
testModuleSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
|
|
323
|
-
testModuleSpan.setTag(
|
|
324
|
-
testModuleSpan.setTag(
|
|
320
|
+
testModuleSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
|
|
321
|
+
testModuleSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
|
|
325
322
|
|
|
326
323
|
// If suites have been skipped we don't want to report the total coverage, as it will be wrong
|
|
327
324
|
if (testCodeCoverageLinesTotal !== undefined && !isSuitesSkipped) {
|
|
328
|
-
testSessionSpan.setTag(
|
|
329
|
-
testModuleSpan.setTag(
|
|
325
|
+
testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
326
|
+
testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
330
327
|
}
|
|
331
328
|
}
|
|
332
329
|
|
|
@@ -399,3 +396,23 @@ function getTestLineStart (err, testSuitePath) {
|
|
|
399
396
|
return null
|
|
400
397
|
}
|
|
401
398
|
}
|
|
399
|
+
|
|
400
|
+
// From https://github.com/felixge/node-stack-trace/blob/ba06dcdb50d465cd440d84a563836e293b360427/index.js#L1
|
|
401
|
+
function getCallSites () {
|
|
402
|
+
const oldLimit = Error.stackTraceLimit
|
|
403
|
+
Error.stackTraceLimit = Infinity
|
|
404
|
+
|
|
405
|
+
const dummy = {}
|
|
406
|
+
|
|
407
|
+
const v8Handler = Error.prepareStackTrace
|
|
408
|
+
Error.prepareStackTrace = function (_, v8StackTrace) {
|
|
409
|
+
return v8StackTrace
|
|
410
|
+
}
|
|
411
|
+
Error.captureStackTrace(dummy)
|
|
412
|
+
|
|
413
|
+
const v8StackTrace = dummy.stack
|
|
414
|
+
Error.prepareStackTrace = v8Handler
|
|
415
|
+
Error.stackTraceLimit = oldLimit
|
|
416
|
+
|
|
417
|
+
return v8StackTrace
|
|
418
|
+
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
const request = require('../exporters/common/request')
|
|
2
2
|
let seqId = 0
|
|
3
|
+
|
|
4
|
+
function getPayload (payload) {
|
|
5
|
+
// Some telemetry endpoints payloads accept collections of elements such as the 'logs' endpoint.
|
|
6
|
+
// 'logs' request type payload is meant to send library logs to Datadog’s backend.
|
|
7
|
+
if (Array.isArray(payload)) {
|
|
8
|
+
return payload
|
|
9
|
+
} else {
|
|
10
|
+
const { logger, tags, serviceMapping, ...trimmedPayload } = payload
|
|
11
|
+
return trimmedPayload
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
function sendData (config, application, host, reqType, payload = {}) {
|
|
4
16
|
const {
|
|
5
17
|
hostname,
|
|
@@ -7,8 +19,6 @@ function sendData (config, application, host, reqType, payload = {}) {
|
|
|
7
19
|
url
|
|
8
20
|
} = config
|
|
9
21
|
|
|
10
|
-
const { logger, tags, serviceMapping, ...trimmedPayload } = payload
|
|
11
|
-
|
|
12
22
|
const options = {
|
|
13
23
|
url,
|
|
14
24
|
hostname,
|
|
@@ -27,7 +37,7 @@ function sendData (config, application, host, reqType, payload = {}) {
|
|
|
27
37
|
tracer_time: Math.floor(Date.now() / 1000),
|
|
28
38
|
runtime_id: config.tags['runtime-id'],
|
|
29
39
|
seq_id: ++seqId,
|
|
30
|
-
payload:
|
|
40
|
+
payload: getPayload(payload),
|
|
31
41
|
application,
|
|
32
42
|
host
|
|
33
43
|
})
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const log = require('../../log')
|
|
4
|
-
const addresses = require('../addresses')
|
|
5
|
-
const Gateway = require('../gateway/engine')
|
|
6
|
-
const Reporter = require('../reporter')
|
|
7
|
-
|
|
8
|
-
const validAddressSet = new Set(Object.values(addresses))
|
|
9
|
-
|
|
10
|
-
// TODO: put reusable code in a base class
|
|
11
|
-
class WAFCallback {
|
|
12
|
-
static loadDDWAF (rules, config) {
|
|
13
|
-
try {
|
|
14
|
-
// require in `try/catch` because this can throw at require time
|
|
15
|
-
const { DDWAF } = require('@datadog/native-appsec')
|
|
16
|
-
|
|
17
|
-
return new DDWAF(rules, config)
|
|
18
|
-
} catch (err) {
|
|
19
|
-
log.error('AppSec could not load native package. In-app WAF features will not be available.')
|
|
20
|
-
|
|
21
|
-
throw err
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
constructor (rules, config) {
|
|
26
|
-
const { wafTimeout, obfuscatorKeyRegex, obfuscatorValueRegex } = config
|
|
27
|
-
|
|
28
|
-
this.ddwaf = WAFCallback.loadDDWAF(rules, { obfuscatorKeyRegex, obfuscatorValueRegex })
|
|
29
|
-
|
|
30
|
-
this.wafTimeout = wafTimeout
|
|
31
|
-
|
|
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
|
-
this.wafContextCache = new WeakMap()
|
|
43
|
-
|
|
44
|
-
// closures are faster than binds
|
|
45
|
-
const self = this
|
|
46
|
-
const method = (params, store) => {
|
|
47
|
-
return self.action(params, store)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// might be its own class with more info later
|
|
51
|
-
const callback = { method }
|
|
52
|
-
|
|
53
|
-
const subscribedAddresses = new Set()
|
|
54
|
-
|
|
55
|
-
for (const rule of rules.rules) {
|
|
56
|
-
for (const condition of rule.conditions) {
|
|
57
|
-
for (const input of condition.parameters.inputs) {
|
|
58
|
-
const address = input.address.split(':', 2)[0]
|
|
59
|
-
|
|
60
|
-
if (!validAddressSet.has(address) || subscribedAddresses.has(address)) continue
|
|
61
|
-
|
|
62
|
-
subscribedAddresses.add(address)
|
|
63
|
-
|
|
64
|
-
Gateway.manager.addSubscription({ addresses: [address], callback })
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
action (params, store) {
|
|
71
|
-
let wafContext
|
|
72
|
-
|
|
73
|
-
if (store) {
|
|
74
|
-
const key = store.get('context')
|
|
75
|
-
|
|
76
|
-
if (key) {
|
|
77
|
-
if (this.wafContextCache.has(key)) {
|
|
78
|
-
wafContext = this.wafContextCache.get(key)
|
|
79
|
-
} else {
|
|
80
|
-
wafContext = this.ddwaf.createContext()
|
|
81
|
-
this.wafContextCache.set(key, wafContext)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!wafContext || wafContext.disposed) {
|
|
87
|
-
wafContext = this.ddwaf.createContext()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// cast status code to string
|
|
91
|
-
if (params[addresses.HTTP_INCOMING_RESPONSE_CODE]) {
|
|
92
|
-
params[addresses.HTTP_INCOMING_RESPONSE_CODE] = params[addresses.HTTP_INCOMING_RESPONSE_CODE] + ''
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
// TODO: possible optimizaion: only send params that haven't already been sent to this wafContext
|
|
97
|
-
const start = process.hrtime.bigint()
|
|
98
|
-
|
|
99
|
-
const result = wafContext.run(params, this.wafTimeout)
|
|
100
|
-
|
|
101
|
-
result.durationExt = parseInt(process.hrtime.bigint() - start)
|
|
102
|
-
|
|
103
|
-
return this.applyResult(result, store)
|
|
104
|
-
} catch (err) {
|
|
105
|
-
log.error('Error while running the AppSec WAF')
|
|
106
|
-
log.error(err)
|
|
107
|
-
} finally {
|
|
108
|
-
wafContext.dispose()
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
applyResult (result, store) {
|
|
113
|
-
Reporter.reportMetrics({
|
|
114
|
-
duration: result.totalRuntime / 1e3,
|
|
115
|
-
durationExt: result.durationExt / 1e3,
|
|
116
|
-
rulesVersion: this.ddwaf.rulesInfo.version
|
|
117
|
-
}, store)
|
|
118
|
-
|
|
119
|
-
if (result.data && result.data !== '[]') {
|
|
120
|
-
Reporter.reportAttack(result.data, store)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return result.actions
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
updateRuleData (ruleData) {
|
|
127
|
-
this.ddwaf.updateRuleData(ruleData)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
clear () {
|
|
131
|
-
this.ddwaf.dispose()
|
|
132
|
-
|
|
133
|
-
this.wafContextCache = new WeakMap()
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
module.exports = WAFCallback
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Runner = require('./runner')
|
|
4
|
-
|
|
5
|
-
const MAX_CONTEXT_SIZE = 1024
|
|
6
|
-
|
|
7
|
-
class SubscriptionManager {
|
|
8
|
-
constructor () {
|
|
9
|
-
this.addressToSubscriptions = new Map()
|
|
10
|
-
this.addresses = new Set()
|
|
11
|
-
this.subscriptions = new Set()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
clear () {
|
|
15
|
-
this.addressToSubscriptions = new Map()
|
|
16
|
-
this.addresses = new Set()
|
|
17
|
-
this.subscriptions = new Set()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
addSubscription (subscription) {
|
|
21
|
-
if (!subscription.addresses.length || this.subscriptions.has(subscription)) return
|
|
22
|
-
|
|
23
|
-
for (let i = 0; i < subscription.addresses.length; ++i) {
|
|
24
|
-
const address = subscription.addresses[i]
|
|
25
|
-
|
|
26
|
-
this.addresses.add(address)
|
|
27
|
-
|
|
28
|
-
const list = this.addressToSubscriptions.get(address)
|
|
29
|
-
|
|
30
|
-
if (list === undefined) {
|
|
31
|
-
this.addressToSubscriptions.set(address, [subscription])
|
|
32
|
-
} else {
|
|
33
|
-
list.push(subscription)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
this.subscriptions.add(subscription)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
matchSubscriptions (newAddresses, allAddresses) {
|
|
41
|
-
const addresses = new Set()
|
|
42
|
-
const subscriptions = new Set()
|
|
43
|
-
const knownSubscriptions = new Set()
|
|
44
|
-
|
|
45
|
-
// TODO: possible optimization: collect matchedSubscriptions on the fly in Context#setValue
|
|
46
|
-
newAddresses.forEach((newAddress) => {
|
|
47
|
-
const matchedSubscriptions = this.addressToSubscriptions.get(newAddress)
|
|
48
|
-
|
|
49
|
-
if (matchedSubscriptions === undefined) return
|
|
50
|
-
|
|
51
|
-
for (let j = 0; j < matchedSubscriptions.length; ++j) {
|
|
52
|
-
const subscription = matchedSubscriptions[j]
|
|
53
|
-
|
|
54
|
-
if (knownSubscriptions.has(subscription) === true) continue
|
|
55
|
-
knownSubscriptions.add(subscription)
|
|
56
|
-
|
|
57
|
-
const isFulfilled = subscription.addresses.every(allAddresses.has, allAddresses)
|
|
58
|
-
|
|
59
|
-
if (isFulfilled === true) {
|
|
60
|
-
for (let k = 0; k < subscription.addresses.length; ++k) {
|
|
61
|
-
addresses.add(subscription.addresses[k])
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
subscriptions.add(subscription)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
return { addresses, subscriptions }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
dispatch (newAddresses, allAddresses, context) {
|
|
73
|
-
const matches = this.matchSubscriptions(newAddresses, allAddresses)
|
|
74
|
-
|
|
75
|
-
// TODO: possible optimization
|
|
76
|
-
// check if matches.subscriptions is empty here instead of in runner.js
|
|
77
|
-
|
|
78
|
-
const params = {}
|
|
79
|
-
|
|
80
|
-
matches.addresses.forEach((address) => {
|
|
81
|
-
params[address] = context.resolve(address)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
return Runner.runSubscriptions(matches.subscriptions, params)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
class Context {
|
|
89
|
-
static setManager (manager) {
|
|
90
|
-
this.manager = manager
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
constructor () {
|
|
94
|
-
// TODO: this probably don't need to be a Map()
|
|
95
|
-
this.store = new Map()
|
|
96
|
-
this.allAddresses = new Set()
|
|
97
|
-
this.newAddresses = new Set()
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
clear () {
|
|
101
|
-
this.store = new Map()
|
|
102
|
-
this.allAddresses = new Set()
|
|
103
|
-
this.newAddresses = new Set()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
setValue (address, value) {
|
|
107
|
-
if (this.allAddresses.size >= MAX_CONTEXT_SIZE) return this
|
|
108
|
-
|
|
109
|
-
// cannot optimize for objects because they're pointers
|
|
110
|
-
if (typeof value !== 'object') {
|
|
111
|
-
const oldValue = this.store.get(address)
|
|
112
|
-
if (oldValue === value) return this
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.store.set(address, value)
|
|
116
|
-
this.allAddresses.add(address)
|
|
117
|
-
this.newAddresses.add(address)
|
|
118
|
-
|
|
119
|
-
return this
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
dispatch () {
|
|
123
|
-
if (this.newAddresses.size === 0) return []
|
|
124
|
-
|
|
125
|
-
const result = Context.manager.dispatch(this.newAddresses, this.allAddresses, this)
|
|
126
|
-
|
|
127
|
-
this.newAddresses.clear()
|
|
128
|
-
|
|
129
|
-
return result
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
resolve (address) {
|
|
133
|
-
return this.store.get(address)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
module.exports = {
|
|
138
|
-
SubscriptionManager,
|
|
139
|
-
Context
|
|
140
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { SubscriptionManager, Context } = require('./engine')
|
|
4
|
-
const als = require('../als')
|
|
5
|
-
|
|
6
|
-
const manager = new SubscriptionManager()
|
|
7
|
-
Context.setManager(manager)
|
|
8
|
-
|
|
9
|
-
function startContext () {
|
|
10
|
-
const store = new Map()
|
|
11
|
-
|
|
12
|
-
store.set('context', new Context())
|
|
13
|
-
|
|
14
|
-
als.enterWith(store)
|
|
15
|
-
|
|
16
|
-
return store
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function getContext () {
|
|
20
|
-
const store = als.getStore()
|
|
21
|
-
|
|
22
|
-
return store && store.get('context')
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function needsAddress (address) {
|
|
26
|
-
return manager.addresses.has(address)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function propagate (data, context = getContext()) {
|
|
30
|
-
if (!context) return
|
|
31
|
-
|
|
32
|
-
const keys = Object.keys(data)
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i < keys.length; ++i) {
|
|
35
|
-
const key = keys[i]
|
|
36
|
-
|
|
37
|
-
if (needsAddress(key)) {
|
|
38
|
-
context.setValue(key, data[key])
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return context.dispatch()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
module.exports = {
|
|
46
|
-
manager,
|
|
47
|
-
startContext,
|
|
48
|
-
getContext,
|
|
49
|
-
needsAddress,
|
|
50
|
-
propagate
|
|
51
|
-
}
|