dd-trace 3.18.0 → 3.20.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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
"lint": "node scripts/check_licenses.js && eslint . && yarn audit --groups dependencies",
|
|
16
16
|
"services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
|
|
17
17
|
"test": "SERVICES=* yarn services && mocha --colors --exit --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
|
|
18
|
-
"test:appsec": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec
|
|
19
|
-
"test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec
|
|
20
|
-
"test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec
|
|
21
|
-
"test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/test/appsec
|
|
18
|
+
"test:appsec": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"",
|
|
19
|
+
"test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec",
|
|
20
|
+
"test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
|
|
21
|
+
"test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\" -- npm run test:appsec:plugins",
|
|
22
22
|
"test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentracing,plugins,telemetry}/**/*.spec.js\"",
|
|
23
23
|
"test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
|
|
24
24
|
"test:instrumentations": "mocha --colors -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"node": ">=14"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@datadog/native-appsec": "
|
|
68
|
+
"@datadog/native-appsec": "^3.1.0",
|
|
69
69
|
"@datadog/native-iast-rewriter": "2.0.1",
|
|
70
70
|
"@datadog/native-iast-taint-tracking": "^1.4.0",
|
|
71
71
|
"@datadog/native-metrics": "^1.6.0",
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { AbortController } = require('node-abort-controller') // AbortController is not available in node <15
|
|
4
|
+
const shimmer = require('../../datadog-shimmer')
|
|
5
|
+
const { channel, addHook } = require('./helpers/instrument')
|
|
4
6
|
|
|
5
7
|
const bodyParserReadCh = channel('datadog:body-parser:read:finish')
|
|
6
8
|
|
|
7
|
-
function publishRequestBodyAndNext (
|
|
9
|
+
function publishRequestBodyAndNext (req, res, next) {
|
|
8
10
|
return function () {
|
|
9
|
-
if (bodyParserReadCh.hasSubscribers &&
|
|
10
|
-
|
|
11
|
+
if (bodyParserReadCh.hasSubscribers && req) {
|
|
12
|
+
const abortController = new AbortController()
|
|
13
|
+
|
|
14
|
+
bodyParserReadCh.publish({ req, res, abortController })
|
|
15
|
+
|
|
16
|
+
if (abortController.signal.aborted) return
|
|
11
17
|
}
|
|
18
|
+
|
|
12
19
|
next.apply(this, arguments)
|
|
13
20
|
}
|
|
14
21
|
}
|
|
@@ -16,11 +23,10 @@ function publishRequestBodyAndNext (request, next) {
|
|
|
16
23
|
addHook({
|
|
17
24
|
name: 'body-parser',
|
|
18
25
|
file: 'lib/read.js',
|
|
19
|
-
versions: ['>=1']
|
|
26
|
+
versions: ['>=1.4.0']
|
|
20
27
|
}, read => {
|
|
21
|
-
return function (req, res, next) {
|
|
22
|
-
|
|
23
|
-
arguments[2] = nextResource.bind(publishRequestBodyAndNext(req, next))
|
|
28
|
+
return shimmer.wrap(read, function (req, res, next) {
|
|
29
|
+
arguments[2] = publishRequestBodyAndNext(req, res, next)
|
|
24
30
|
read.apply(this, arguments)
|
|
25
|
-
}
|
|
31
|
+
})
|
|
26
32
|
})
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { createWrapRouterMethod } = require('./router')
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
5
5
|
const { addHook, channel } = require('./helpers/instrument')
|
|
6
|
+
const { AbortController } = require('node-abort-controller')
|
|
6
7
|
|
|
7
8
|
const handleChannel = channel('apm:express:request:handle')
|
|
8
9
|
|
|
@@ -25,3 +26,34 @@ addHook({ name: 'express', versions: ['>=4'] }, express => {
|
|
|
25
26
|
|
|
26
27
|
return express
|
|
27
28
|
})
|
|
29
|
+
|
|
30
|
+
const queryParserReadCh = channel('datadog:query:read:finish')
|
|
31
|
+
|
|
32
|
+
function publishQueryParsedAndNext (req, res, next) {
|
|
33
|
+
return function () {
|
|
34
|
+
if (queryParserReadCh.hasSubscribers && req) {
|
|
35
|
+
const abortController = new AbortController()
|
|
36
|
+
|
|
37
|
+
queryParserReadCh.publish({ req, res, abortController })
|
|
38
|
+
|
|
39
|
+
if (abortController.signal.aborted) return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
next.apply(this, arguments)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
addHook({
|
|
47
|
+
name: 'express',
|
|
48
|
+
versions: ['>=4'],
|
|
49
|
+
file: 'lib/middleware/query.js'
|
|
50
|
+
}, query => {
|
|
51
|
+
return shimmer.wrap(query, function () {
|
|
52
|
+
const queryMiddleware = query.apply(this, arguments)
|
|
53
|
+
|
|
54
|
+
return shimmer.wrap(queryMiddleware, function (req, res, next) {
|
|
55
|
+
arguments[2] = publishQueryParsedAndNext(req, res, next)
|
|
56
|
+
return queryMiddleware.apply(this, arguments)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
mergeCoverage,
|
|
11
11
|
getTestSuitePath,
|
|
12
12
|
fromCoverageMapToCoverage,
|
|
13
|
-
|
|
13
|
+
getCallSites
|
|
14
14
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
15
15
|
|
|
16
16
|
const testStartCh = channel('ci:mocha:test:start')
|
|
@@ -393,9 +393,13 @@ addHook({
|
|
|
393
393
|
file: 'lib/suite.js'
|
|
394
394
|
}, (Suite) => {
|
|
395
395
|
shimmer.wrap(Suite.prototype, 'addTest', addTest => function (test) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
396
|
+
const callSites = getCallSites()
|
|
397
|
+
let startLine
|
|
398
|
+
const testCallSite = callSites.find(site => site.getFileName() === test.file)
|
|
399
|
+
if (testCallSite) {
|
|
400
|
+
startLine = testCallSite.getLineNumber()
|
|
401
|
+
testToStartLine.set(test, startLine)
|
|
402
|
+
}
|
|
399
403
|
return addTest.apply(this, arguments)
|
|
400
404
|
})
|
|
401
405
|
return Suite
|
|
@@ -30,9 +30,8 @@ function wrapQuery (query) {
|
|
|
30
30
|
const callbackResource = new AsyncResource('bound-anonymous-fn')
|
|
31
31
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
32
32
|
const processId = this.processID
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
33
|
+
|
|
34
|
+
let pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
|
|
36
35
|
|
|
37
36
|
return asyncResource.runInAsyncScope(() => {
|
|
38
37
|
startCh.publish({
|
|
@@ -41,7 +40,7 @@ function wrapQuery (query) {
|
|
|
41
40
|
processId
|
|
42
41
|
})
|
|
43
42
|
|
|
44
|
-
arguments[0] = pgQuery
|
|
43
|
+
arguments[0] = pgQuery
|
|
45
44
|
|
|
46
45
|
const finish = asyncResource.bind(function (error) {
|
|
47
46
|
if (error) {
|
|
@@ -72,6 +72,9 @@ function getRootDir (playwrightRunner) {
|
|
|
72
72
|
if (playwrightRunner._configDir) {
|
|
73
73
|
return playwrightRunner._configDir
|
|
74
74
|
}
|
|
75
|
+
if (playwrightRunner._config && playwrightRunner._config.config) {
|
|
76
|
+
return playwrightRunner._config.config.rootDir
|
|
77
|
+
}
|
|
75
78
|
return process.cwd()
|
|
76
79
|
}
|
|
77
80
|
|
|
@@ -211,10 +214,20 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
211
214
|
})
|
|
212
215
|
|
|
213
216
|
const runAllTestsReturn = await runAllTests.apply(this, arguments)
|
|
217
|
+
|
|
218
|
+
Object.values(remainingTestsByFile).forEach(tests => {
|
|
219
|
+
// `tests` should normally be empty, but if it isn't,
|
|
220
|
+
// there were tests that did not go through `testBegin` or `testEnd`,
|
|
221
|
+
// because they were skipped
|
|
222
|
+
tests.forEach(test => {
|
|
223
|
+
testBeginHandler(test)
|
|
224
|
+
testEndHandler(test, 'skip')
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
214
228
|
const sessionStatus = runAllTestsReturn.status || runAllTestsReturn
|
|
215
229
|
|
|
216
230
|
let onDone
|
|
217
|
-
|
|
218
231
|
const flushWait = new Promise(resolve => {
|
|
219
232
|
onDone = resolve
|
|
220
233
|
})
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Plugin = require('../../dd-trace/src/plugins/plugin')
|
|
4
4
|
const { storage } = require('../../datadog-core')
|
|
5
5
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
6
|
-
const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/
|
|
6
|
+
const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/channels')
|
|
7
7
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
8
8
|
|
|
9
9
|
class HttpServerPlugin extends Plugin {
|
|
@@ -33,7 +33,7 @@ class HttpServerPlugin extends Plugin {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
if (incomingHttpRequestStart.hasSubscribers) {
|
|
36
|
-
incomingHttpRequestStart.publish({ req, res, abortController })
|
|
36
|
+
incomingHttpRequestStart.publish({ req, res, abortController }) // TODO: no need to make a new object here
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
39
|
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
const Plugin = require('../../dd-trace/src/plugins/plugin')
|
|
6
6
|
const { storage } = require('../../datadog-core')
|
|
7
7
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
8
|
-
const { incomingHttpRequestStart } = require('../../dd-trace/src/appsec/gateway/channels')
|
|
9
8
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
10
9
|
|
|
11
10
|
class Http2ServerPlugin extends Plugin {
|
|
@@ -30,10 +29,6 @@ class Http2ServerPlugin extends Plugin {
|
|
|
30
29
|
context.res.writeHead = web.wrapWriteHead(context)
|
|
31
30
|
context.instrumented = true
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
if (incomingHttpRequestStart.hasSubscribers) {
|
|
35
|
-
incomingHttpRequestStart.publish({ req, res })
|
|
36
|
-
}
|
|
37
32
|
})
|
|
38
33
|
|
|
39
34
|
this.addSub('apm:http2:server:request:error', (error) => {
|
|
@@ -9,7 +9,7 @@ class PGPlugin extends DatabasePlugin {
|
|
|
9
9
|
static get system () { return 'postgres' }
|
|
10
10
|
|
|
11
11
|
start ({ params = {}, query, processId }) {
|
|
12
|
-
const service = getServiceName(this
|
|
12
|
+
const service = getServiceName(this, params)
|
|
13
13
|
const originalStatement = query.text
|
|
14
14
|
|
|
15
15
|
this.startSpan('pg.query', {
|
|
@@ -31,12 +31,12 @@ class PGPlugin extends DatabasePlugin {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function getServiceName (
|
|
35
|
-
if (typeof config.service === 'function') {
|
|
36
|
-
return config.service(params)
|
|
34
|
+
function getServiceName (tracer, params) {
|
|
35
|
+
if (typeof tracer.config.service === 'function') {
|
|
36
|
+
return tracer.config.service(params)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return config.service
|
|
39
|
+
return tracer.config.service || `${tracer._tracer._tracer._service}-postgres`
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
module.exports = PGPlugin
|
|
@@ -7,14 +7,11 @@ module.exports = {
|
|
|
7
7
|
// TODO: 'server.request.trailers',
|
|
8
8
|
HTTP_INCOMING_URL: 'server.request.uri.raw',
|
|
9
9
|
HTTP_INCOMING_METHOD: 'server.request.method',
|
|
10
|
-
HTTP_INCOMING_ENDPOINT: 'server.request.framework_endpoint',
|
|
11
10
|
HTTP_INCOMING_PARAMS: 'server.request.path_params',
|
|
12
11
|
HTTP_INCOMING_COOKIES: 'server.request.cookies',
|
|
13
12
|
HTTP_INCOMING_RESPONSE_CODE: 'server.response.status',
|
|
14
13
|
HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
|
|
15
14
|
// TODO: 'server.response.trailers',
|
|
16
|
-
HTTP_INCOMING_REMOTE_IP: 'server.request.client_ip',
|
|
17
|
-
HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port',
|
|
18
15
|
|
|
19
16
|
HTTP_CLIENT_IP: 'http.client_ip',
|
|
20
17
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable max-len */
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
const html = `<!-- Sorry, you
|
|
4
|
+
const html = `<!-- Sorry, you’ve been blocked -->
|
|
5
5
|
<!DOCTYPE html>
|
|
6
6
|
<html lang="en">
|
|
7
7
|
|
|
@@ -102,14 +102,7 @@ const html = `<!-- Sorry, you've been blocked -->
|
|
|
102
102
|
</html>
|
|
103
103
|
`
|
|
104
104
|
|
|
105
|
-
const json = `{
|
|
106
|
-
"errors": [
|
|
107
|
-
{
|
|
108
|
-
"title": "You've been blocked",
|
|
109
|
-
"detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."
|
|
110
|
-
}
|
|
111
|
-
]
|
|
112
|
-
}`
|
|
105
|
+
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."}]}`
|
|
113
106
|
|
|
114
107
|
module.exports = {
|
|
115
108
|
html,
|
|
@@ -19,7 +19,7 @@ function block (req, res, rootSpan, abortController) {
|
|
|
19
19
|
const accept = req.headers.accept && req.headers.accept.split(',').map((str) => str.split(';', 1)[0].trim())
|
|
20
20
|
|
|
21
21
|
if (accept && accept.includes('text/html') && !accept.includes('application/json')) {
|
|
22
|
-
type = 'text/html'
|
|
22
|
+
type = 'text/html; charset=utf-8'
|
|
23
23
|
body = templateHtml
|
|
24
24
|
} else {
|
|
25
25
|
type = 'application/json'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const dc = require('
|
|
3
|
+
const dc = require('../../../diagnostics_channel')
|
|
4
4
|
|
|
5
5
|
// TODO: use TBD naming convention
|
|
6
|
-
// or directly use http plugin's channels
|
|
7
|
-
// when it gets converted to new plugin system
|
|
8
6
|
module.exports = {
|
|
9
7
|
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
|
|
10
|
-
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd')
|
|
8
|
+
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
|
|
9
|
+
bodyParser: dc.channel('datadog:body-parser:read:finish'),
|
|
10
|
+
queryParser: dc.channel('datadog:query:read:finish')
|
|
11
11
|
}
|
|
@@ -12,7 +12,7 @@ class TaintTrackingPlugin extends Plugin {
|
|
|
12
12
|
this._type = 'taint-tracking'
|
|
13
13
|
this.addSub(
|
|
14
14
|
'datadog:body-parser:read:finish',
|
|
15
|
-
({
|
|
15
|
+
({ req }) => this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body')
|
|
16
16
|
)
|
|
17
17
|
this.addSub(
|
|
18
18
|
'datadog:qs:parse:finish',
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
const log = require('../log')
|
|
4
4
|
const RuleManager = require('./rule_manager')
|
|
5
5
|
const remoteConfig = require('./remote_config')
|
|
6
|
-
const {
|
|
7
|
-
|
|
6
|
+
const {
|
|
7
|
+
incomingHttpRequestStart,
|
|
8
|
+
incomingHttpRequestEnd,
|
|
9
|
+
bodyParser,
|
|
10
|
+
queryParser
|
|
11
|
+
} = require('./channels')
|
|
12
|
+
const waf = require('./waf')
|
|
8
13
|
const addresses = require('./addresses')
|
|
9
14
|
const Reporter = require('./reporter')
|
|
10
15
|
const web = require('../plugins/util/web')
|
|
@@ -21,39 +26,25 @@ function enable (_config) {
|
|
|
21
26
|
try {
|
|
22
27
|
setTemplates(_config)
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
enableFromRules(_config, _config.appsec.rules)
|
|
26
|
-
} catch (err) {
|
|
27
|
-
abortEnable(err)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
29
|
+
RuleManager.applyRules(_config.appsec.rules, _config.appsec)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
RuleManager.applyRules(rules, _config.appsec)
|
|
33
|
-
remoteConfig.enableAsmData(_config.appsec)
|
|
31
|
+
remoteConfig.enableWafUpdate(_config.appsec)
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
Reporter.setRateLimit(_config.appsec.rateLimit)
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Gateway.manager.addresses.add(addresses.HTTP_INCOMING_HEADERS)
|
|
42
|
-
Gateway.manager.addresses.add(addresses.HTTP_INCOMING_ENDPOINT)
|
|
43
|
-
Gateway.manager.addresses.add(addresses.HTTP_INCOMING_RESPONSE_HEADERS)
|
|
44
|
-
Gateway.manager.addresses.add(addresses.HTTP_INCOMING_REMOTE_IP)
|
|
45
|
-
|
|
46
|
-
isEnabled = true
|
|
47
|
-
config = _config
|
|
48
|
-
}
|
|
35
|
+
incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
|
|
36
|
+
incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
|
|
37
|
+
bodyParser.subscribe(onRequestBodyParsed)
|
|
38
|
+
queryParser.subscribe(onRequestQueryParsed)
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
isEnabled = true
|
|
41
|
+
config = _config
|
|
42
|
+
} catch (err) {
|
|
43
|
+
log.error('Unable to start AppSec')
|
|
44
|
+
log.error(err)
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
remoteConfig.disableAsmData()
|
|
46
|
+
disable()
|
|
47
|
+
}
|
|
57
48
|
}
|
|
58
49
|
|
|
59
50
|
function incomingHttpStartTranslator ({ req, res, abortController }) {
|
|
@@ -68,78 +59,92 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
|
|
|
68
59
|
[HTTP_CLIENT_IP]: clientIp
|
|
69
60
|
})
|
|
70
61
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
store.set('req', req)
|
|
74
|
-
store.set('res', res)
|
|
62
|
+
const requestHeaders = Object.assign({}, req.headers)
|
|
63
|
+
delete requestHeaders.cookie
|
|
75
64
|
|
|
76
|
-
const
|
|
65
|
+
const payload = {
|
|
66
|
+
[addresses.HTTP_INCOMING_URL]: req.url,
|
|
67
|
+
[addresses.HTTP_INCOMING_HEADERS]: requestHeaders,
|
|
68
|
+
[addresses.HTTP_INCOMING_METHOD]: req.method
|
|
69
|
+
}
|
|
77
70
|
|
|
78
71
|
if (clientIp) {
|
|
79
|
-
|
|
80
|
-
[addresses.HTTP_CLIENT_IP]: clientIp
|
|
81
|
-
}, context)
|
|
82
|
-
|
|
83
|
-
if (!results || !abortController) return
|
|
84
|
-
|
|
85
|
-
for (const entry of results) {
|
|
86
|
-
if (entry && entry.includes('block')) {
|
|
87
|
-
block(req, res, rootSpan, abortController)
|
|
88
|
-
break
|
|
89
|
-
}
|
|
90
|
-
}
|
|
72
|
+
payload[addresses.HTTP_CLIENT_IP] = clientIp
|
|
91
73
|
}
|
|
92
|
-
}
|
|
93
74
|
|
|
94
|
-
|
|
95
|
-
const context = Gateway.getContext()
|
|
96
|
-
if (!context) return
|
|
75
|
+
const actions = waf.run(payload, req)
|
|
97
76
|
|
|
98
|
-
|
|
99
|
-
|
|
77
|
+
handleResults(actions, req, res, rootSpan, abortController)
|
|
78
|
+
}
|
|
100
79
|
|
|
80
|
+
function incomingHttpEndTranslator ({ req, res }) {
|
|
101
81
|
// TODO: this doesn't support headers sent with res.writeHead()
|
|
102
|
-
const responseHeaders = Object.assign({},
|
|
82
|
+
const responseHeaders = Object.assign({}, res.getHeaders())
|
|
103
83
|
delete responseHeaders['set-cookie']
|
|
104
84
|
|
|
105
85
|
const payload = {
|
|
106
|
-
[addresses.
|
|
107
|
-
[addresses.HTTP_INCOMING_HEADERS]: requestHeaders,
|
|
108
|
-
[addresses.HTTP_INCOMING_METHOD]: data.req.method,
|
|
109
|
-
[addresses.HTTP_INCOMING_REMOTE_IP]: data.req.socket.remoteAddress,
|
|
110
|
-
[addresses.HTTP_INCOMING_REMOTE_PORT]: data.req.socket.remotePort,
|
|
111
|
-
[addresses.HTTP_INCOMING_RESPONSE_CODE]: data.res.statusCode,
|
|
86
|
+
[addresses.HTTP_INCOMING_RESPONSE_CODE]: res.statusCode,
|
|
112
87
|
[addresses.HTTP_INCOMING_RESPONSE_HEADERS]: responseHeaders
|
|
113
88
|
}
|
|
114
89
|
|
|
115
|
-
//
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (data.req.query && typeof data.req.query === 'object') {
|
|
121
|
-
payload[addresses.HTTP_INCOMING_QUERY] = data.req.query
|
|
90
|
+
// we need to keep this to support other body parsers
|
|
91
|
+
// TODO: no need to analyze it if it was already done by the body-parser hook
|
|
92
|
+
if (req.body !== undefined && req.body !== null) {
|
|
93
|
+
payload[addresses.HTTP_INCOMING_BODY] = req.body
|
|
122
94
|
}
|
|
123
95
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (data.req.params && typeof data.req.params === 'object') {
|
|
129
|
-
payload[addresses.HTTP_INCOMING_PARAMS] = data.req.params
|
|
96
|
+
// TODO: temporary express instrumentation, will use express plugin later
|
|
97
|
+
if (req.params && typeof req.params === 'object') {
|
|
98
|
+
payload[addresses.HTTP_INCOMING_PARAMS] = req.params
|
|
130
99
|
}
|
|
131
100
|
|
|
132
|
-
if (
|
|
101
|
+
if (req.cookies && typeof req.cookies === 'object') {
|
|
133
102
|
payload[addresses.HTTP_INCOMING_COOKIES] = {}
|
|
134
103
|
|
|
135
|
-
for (const k of Object.keys(
|
|
136
|
-
payload[addresses.HTTP_INCOMING_COOKIES][k] = [
|
|
104
|
+
for (const k of Object.keys(req.cookies)) {
|
|
105
|
+
payload[addresses.HTTP_INCOMING_COOKIES][k] = [req.cookies[k]]
|
|
137
106
|
}
|
|
138
107
|
}
|
|
139
108
|
|
|
140
|
-
|
|
109
|
+
waf.run(payload, req)
|
|
110
|
+
|
|
111
|
+
waf.disposeContext(req)
|
|
112
|
+
|
|
113
|
+
Reporter.finishRequest(req, res)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function onRequestBodyParsed ({ req, res, abortController }) {
|
|
117
|
+
const rootSpan = web.root(req)
|
|
118
|
+
if (!rootSpan) return
|
|
119
|
+
|
|
120
|
+
if (req.body === undefined || req.body === null) return
|
|
121
|
+
|
|
122
|
+
const results = waf.run({
|
|
123
|
+
[addresses.HTTP_INCOMING_BODY]: req.body
|
|
124
|
+
}, req)
|
|
141
125
|
|
|
142
|
-
|
|
126
|
+
handleResults(results, req, res, rootSpan, abortController)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function onRequestQueryParsed ({ req, res, abortController }) {
|
|
130
|
+
const rootSpan = web.root(req)
|
|
131
|
+
if (!rootSpan) return
|
|
132
|
+
|
|
133
|
+
if (!req.query || typeof req.query !== 'object') return
|
|
134
|
+
|
|
135
|
+
const results = waf.run({
|
|
136
|
+
[addresses.HTTP_INCOMING_QUERY]: req.query
|
|
137
|
+
}, req)
|
|
138
|
+
|
|
139
|
+
handleResults(results, req, res, rootSpan, abortController)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleResults (actions, req, res, rootSpan, abortController) {
|
|
143
|
+
if (!actions || !req || !res || !rootSpan || !abortController) return
|
|
144
|
+
|
|
145
|
+
if (actions.includes('block')) {
|
|
146
|
+
block(req, res, rootSpan, abortController)
|
|
147
|
+
}
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
function disable () {
|
|
@@ -147,11 +152,14 @@ function disable () {
|
|
|
147
152
|
config = null
|
|
148
153
|
|
|
149
154
|
RuleManager.clearAllRules()
|
|
150
|
-
|
|
155
|
+
|
|
156
|
+
remoteConfig.disableWafUpdate()
|
|
151
157
|
|
|
152
158
|
// Channel#unsubscribe() is undefined for non active channels
|
|
153
159
|
if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
|
|
154
160
|
if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
|
|
161
|
+
if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
|
|
162
|
+
if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
module.exports = {
|