@newrelic/security-agent 0.2.0 → 0.3.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/CHANGELOG.md +19 -0
- package/README.md +2 -2
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/lib/instrumentation-security/hooks/express/nr-express.js +3 -0
- package/lib/instrumentation-security/hooks/http/nr-http.js +1 -1
- package/lib/instrumentation-security/hooks/mysql/nr-mysql.js +17 -5
- package/lib/instrumentation-security/index.js +19 -0
- package/lib/nr-security-agent/index.js +4 -0
- package/lib/nr-security-agent/lib/core/Auth-headers.js +17 -2
- package/lib/nr-security-agent/lib/core/commonUtils.js +48 -41
- package/lib/nr-security-agent/lib/core/connections/websocket/response/IASTUtils.js +56 -13
- package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +10 -8
- package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +36 -3
- package/lib/nr-security-agent/lib/core/event-stats.js +27 -0
- package/lib/nr-security-agent/lib/core/event.js +1 -0
- package/lib/nr-security-agent/lib/core/health-check.js +45 -2
- package/lib/nr-security-agent/lib/core/logging.js +14 -7
- package/lib/nr-security-agent/lib/core/sec-agent-constants.js +7 -4
- package/lib/nr-security-agent/lib/core/sec-util.js +16 -1
- package/lib/nr-security-agent/lib/core/websocket-client.js +1 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
### v0.3.0 (2023-09-25)
|
|
2
|
+
#### Features
|
|
3
|
+
* Last leg acknowledgement in IAST scanning.
|
|
4
|
+
#### Bug Fixes
|
|
5
|
+
* Fix for mysql query params in security events.
|
|
6
|
+
* Logging update for default log level and status file flags.
|
|
7
|
+
* Update in IAST batch size processing.
|
|
8
|
+
* Disable instrumentation when security enabled flag is set to false
|
|
9
|
+
#### Miscellaneous Chores
|
|
10
|
+
* Readme update
|
|
11
|
+
|
|
12
|
+
### v0.2.1 (2023-08-07)
|
|
13
|
+
#### Bug Fixes
|
|
14
|
+
* Pinned check-disk-space to v3.3.1 to support Node.js v14
|
|
15
|
+
#### Miscellaneous Chores
|
|
16
|
+
* deps: Updated semver to v7.5.4 and @aws-sdk/client-lambda to v3.363.0
|
|
17
|
+
#### Continuous Integration
|
|
18
|
+
* Add Node.js 20.x to CI
|
|
19
|
+
|
|
1
20
|
### v0.2.0 (2023-07-24)
|
|
2
21
|
#### Features
|
|
3
22
|
* IAST data pull implementation
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ The `@newrelic/security-agent` must be explicitly enabled in order to perform IA
|
|
|
42
42
|
The [Developer docs](http://newrelic.github.io/node-newrelic/) for writing instrumentation will be of help. We particularly recommend the [tutorials](https://newrelic.github.io/node-newrelic/tutorial-Instrumentation-Basics.html) and the [shim API](https://newrelic.github.io/node-newrelic/Shim.html) documentation.
|
|
43
43
|
|
|
44
44
|
### Supported Node.js Versions
|
|
45
|
-
- Node version
|
|
45
|
+
- Node version 16.x, 18.x and 20.x
|
|
46
46
|
|
|
47
47
|
### Supported Modules
|
|
48
48
|
|
|
@@ -56,7 +56,7 @@ The [Developer docs](http://newrelic.github.io/node-newrelic/) for writing instr
|
|
|
56
56
|
- [pg](https://www.npmjs.com/package/pg)(7.x and above)
|
|
57
57
|
- [mongodb](https://www.npmjs.com/package/mongodb)(2.x, 3.x and 4.x)
|
|
58
58
|
- [express](https://www.npmjs.com/package/express)(4.x and above)
|
|
59
|
-
- [@koa/router](https://www.npmjs.com/package/@koa/router) (
|
|
59
|
+
- [@koa/router](https://www.npmjs.com/package/@koa/router) (9.x and above)
|
|
60
60
|
- [koa-router](https://www.npmjs.com/package/koa-router)(9.x and above)
|
|
61
61
|
- [@hapi/hapi](https://www.npmjs.com/package/@hapi/hapi)(19.x and above)
|
|
62
62
|
- [fastify](https://www.npmjs.com/package/fastify)(2.x, 3.x and 4.x)
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -75,7 +75,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
|
|
|
75
75
|
|
|
76
76
|
### @aws-sdk/client-lambda
|
|
77
77
|
|
|
78
|
-
This product includes source derived from [@aws-sdk/client-lambda](https://github.com/aws/aws-sdk-js-v3) ([v3.
|
|
78
|
+
This product includes source derived from [@aws-sdk/client-lambda](https://github.com/aws/aws-sdk-js-v3) ([v3.379.1](https://github.com/aws/aws-sdk-js-v3/tree/v3.379.1)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.379.1/LICENSE):
|
|
79
79
|
|
|
80
80
|
```
|
|
81
81
|
Apache License
|
|
@@ -311,7 +311,7 @@ THE SOFTWARE.
|
|
|
311
311
|
|
|
312
312
|
### check-disk-space
|
|
313
313
|
|
|
314
|
-
This product includes source derived from [check-disk-space](https://github.com/Alex-D/check-disk-space) ([v3.
|
|
314
|
+
This product includes source derived from [check-disk-space](https://github.com/Alex-D/check-disk-space) ([v3.3.1](https://github.com/Alex-D/check-disk-space/tree/v3.3.1)), distributed under the [MIT License](https://github.com/Alex-D/check-disk-space/blob/v3.3.1/LICENSE):
|
|
315
315
|
|
|
316
316
|
```
|
|
317
317
|
MIT License
|
|
@@ -684,7 +684,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
684
684
|
|
|
685
685
|
### semver
|
|
686
686
|
|
|
687
|
-
This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.5.
|
|
687
|
+
This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.5.4](https://github.com/npm/node-semver/tree/v7.5.4)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.5.4/LICENSE):
|
|
688
688
|
|
|
689
689
|
```
|
|
690
690
|
The ISC License
|
|
@@ -75,6 +75,9 @@ function wrapExpress4(shim, express) {
|
|
|
75
75
|
* @param {*} req
|
|
76
76
|
*/
|
|
77
77
|
function extractParams(shim, req) {
|
|
78
|
+
if (!requestManager.getRequest(shim)) {
|
|
79
|
+
secUtils.addRequestData(shim, req);
|
|
80
|
+
}
|
|
78
81
|
const segment = shim.getActiveSegment();
|
|
79
82
|
if (segment && segment.transaction) {
|
|
80
83
|
let request = requestManager.getRequestFromId(segment.transaction.id);
|
|
@@ -36,7 +36,7 @@ const semver = require('semver');
|
|
|
36
36
|
const CSEC_SEP = ':IAST:';
|
|
37
37
|
const find = '{{NR_CSEC_VALIDATOR_HOME_TMP}}';
|
|
38
38
|
const CSEC_HOME_TMP_CONST = new RegExp(find, 'g');
|
|
39
|
-
|
|
39
|
+
let CSEC_HOME = NRAgent && NRAgent.config.newrelic_home ? NRAgent.config.newrelic_home : NRAgent && NRAgent.config.logging.filepath ? path.dirname(NRAgent.config.logging.filepath) : require('path').join(process.cwd());
|
|
40
40
|
const CSEC_HOME_TMP = `${CSEC_HOME}/nr-security-home/tmp/language-agent/${process.env.applicationUUID}`
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -177,11 +177,16 @@ function wrapQueryable(shim, queryable, isPoolQuery) {
|
|
|
177
177
|
* @returns
|
|
178
178
|
*/
|
|
179
179
|
function containsOnlyStrings(list) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
try {
|
|
181
|
+
for (let i = 0; i < list.length; i++) {
|
|
182
|
+
if (typeof list[i] !== 'string') {
|
|
183
|
+
list[i] = list[i].toString();
|
|
184
|
+
}
|
|
183
185
|
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return false;
|
|
184
188
|
}
|
|
189
|
+
|
|
185
190
|
return true;
|
|
186
191
|
}
|
|
187
192
|
|
|
@@ -202,6 +207,13 @@ function extractQueryArgs(shim, args) {
|
|
|
202
207
|
} else {
|
|
203
208
|
// query(opts [, values], callback)
|
|
204
209
|
rawQuery = args[0].sql;
|
|
210
|
+
try {
|
|
211
|
+
if (args[0].values && typeof args[0].values == 'object') {
|
|
212
|
+
param = Object.values(args[0].values)
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logger.warn("Not able to extract params for mysql")
|
|
216
|
+
}
|
|
205
217
|
}
|
|
206
218
|
// Then determine the query values and callback parameters.
|
|
207
219
|
if (shim.isArray(args[1])) {
|
|
@@ -210,9 +222,9 @@ function extractQueryArgs(shim, args) {
|
|
|
210
222
|
}
|
|
211
223
|
else if (args[1] && typeof args[1] == 'object') {
|
|
212
224
|
param = args[1];
|
|
213
|
-
param =
|
|
225
|
+
param = Object.values(param)
|
|
214
226
|
}
|
|
215
|
-
if(!containsOnlyStrings(param)){
|
|
227
|
+
if (!containsOnlyStrings(param)) {
|
|
216
228
|
param = [];
|
|
217
229
|
}
|
|
218
230
|
return {
|
|
@@ -12,6 +12,7 @@ logger.info("Started instrumentation using shimmer module");
|
|
|
12
12
|
|
|
13
13
|
newrelic.instrument({
|
|
14
14
|
moduleName: 'child_process',
|
|
15
|
+
isEsm: true,
|
|
15
16
|
onRequire: require('./hooks/native/nr-childProcess'),
|
|
16
17
|
onError: function intrumentErrorHandler(err) {
|
|
17
18
|
logger.error(err.message, err.stack)
|
|
@@ -19,6 +20,7 @@ newrelic.instrument({
|
|
|
19
20
|
})
|
|
20
21
|
newrelic.instrument({
|
|
21
22
|
moduleName: 'http',
|
|
23
|
+
isEsm: true,
|
|
22
24
|
onRequire: require('./hooks/http/nr-http'),
|
|
23
25
|
onError: function intrumentErrorHandler(err) {
|
|
24
26
|
logger.error(err.message, err.stack)
|
|
@@ -27,6 +29,7 @@ newrelic.instrument({
|
|
|
27
29
|
|
|
28
30
|
newrelic.instrument({
|
|
29
31
|
moduleName: 'https',
|
|
32
|
+
isEsm: true,
|
|
30
33
|
onRequire: require('./hooks/http/nr-http'),
|
|
31
34
|
onError: function intrumentErrorHandler(err) {
|
|
32
35
|
logger.error(err.message, err.stack)
|
|
@@ -35,6 +38,7 @@ newrelic.instrument({
|
|
|
35
38
|
|
|
36
39
|
newrelic.instrument({
|
|
37
40
|
moduleName: 'fs',
|
|
41
|
+
isEsm: true,
|
|
38
42
|
onRequire: require('./hooks/fs/nr-fs'),
|
|
39
43
|
onError: function intrumentErrorHandler(err) {
|
|
40
44
|
logger.error(err.message, err.stack)
|
|
@@ -43,6 +47,7 @@ newrelic.instrument({
|
|
|
43
47
|
|
|
44
48
|
newrelic.instrumentWebframework({
|
|
45
49
|
moduleName: 'express',
|
|
50
|
+
isEsm: true,
|
|
46
51
|
onRequire: require('./hooks/express/nr-express'),
|
|
47
52
|
onError: function intrumentErrorHandler(err) {
|
|
48
53
|
logger.error(err.message, err.stack)
|
|
@@ -51,6 +56,7 @@ newrelic.instrumentWebframework({
|
|
|
51
56
|
|
|
52
57
|
newrelic.instrumentDatastore({
|
|
53
58
|
moduleName: 'mysql',
|
|
59
|
+
isEsm: true,
|
|
54
60
|
onRequire: require('./hooks/mysql/nr-mysql'),
|
|
55
61
|
onError: function intrumentErrorHandler(err) {
|
|
56
62
|
logger.error(err.message, err.stack)
|
|
@@ -59,6 +65,7 @@ newrelic.instrumentDatastore({
|
|
|
59
65
|
|
|
60
66
|
newrelic.instrumentDatastore({
|
|
61
67
|
moduleName: 'mysql2',
|
|
68
|
+
isEsm: true,
|
|
62
69
|
onRequire: require('./hooks/mysql/nr-mysql'),
|
|
63
70
|
onError: function intrumentErrorHandler(err) {
|
|
64
71
|
logger.error(err.message, err.stack)
|
|
@@ -67,6 +74,7 @@ newrelic.instrumentDatastore({
|
|
|
67
74
|
|
|
68
75
|
newrelic.instrumentDatastore({
|
|
69
76
|
moduleName: 'mongodb',
|
|
77
|
+
isEsm: true,
|
|
70
78
|
onRequire: require('./hooks/mongodb/nr-mongodb'),
|
|
71
79
|
onError: function intrumentErrorHandler(err) {
|
|
72
80
|
logger.error(err.message, err.stack)
|
|
@@ -75,6 +83,7 @@ newrelic.instrumentDatastore({
|
|
|
75
83
|
|
|
76
84
|
newrelic.instrument({
|
|
77
85
|
moduleName: 'ldapjs',
|
|
86
|
+
isEsm: true,
|
|
78
87
|
onRequire: require('./hooks/ldap/nr-ldap'),
|
|
79
88
|
onError: function intrumentErrorHandler(err) {
|
|
80
89
|
logger.error(err.message, err.stack)
|
|
@@ -83,6 +92,7 @@ newrelic.instrument({
|
|
|
83
92
|
|
|
84
93
|
newrelic.instrument({
|
|
85
94
|
moduleName: 'ldapts',
|
|
95
|
+
isEsm: true,
|
|
86
96
|
onRequire: require('./hooks/ldap/nr-ldap'),
|
|
87
97
|
onError: function intrumentErrorHandler(err) {
|
|
88
98
|
logger.error(err.message, err.stack)
|
|
@@ -92,6 +102,7 @@ newrelic.instrument({
|
|
|
92
102
|
|
|
93
103
|
newrelic.instrument({
|
|
94
104
|
moduleName: 'xpath',
|
|
105
|
+
isEsm: true,
|
|
95
106
|
onRequire: require('./hooks/xpath/nr-xpath'),
|
|
96
107
|
onError: function intrumentErrorHandler(err) {
|
|
97
108
|
logger.error(err.message, err.stack)
|
|
@@ -99,6 +110,7 @@ newrelic.instrument({
|
|
|
99
110
|
})
|
|
100
111
|
newrelic.instrument({
|
|
101
112
|
moduleName: 'xpath.js',
|
|
113
|
+
isEsm: true,
|
|
102
114
|
onRequire: require('./hooks/xpath/nr-xpath'),
|
|
103
115
|
onError: function intrumentErrorHandler(err) {
|
|
104
116
|
logger.error(err.message, err.stack)
|
|
@@ -107,6 +119,7 @@ newrelic.instrument({
|
|
|
107
119
|
|
|
108
120
|
newrelic.instrumentDatastore({
|
|
109
121
|
moduleName: 'pg',
|
|
122
|
+
isEsm: true,
|
|
110
123
|
onRequire: require('./hooks/postgres/nr-postgres'),
|
|
111
124
|
onError: function intrumentErrorHandler(err) {
|
|
112
125
|
logger.error(err.message, err.stack)
|
|
@@ -115,6 +128,7 @@ newrelic.instrumentDatastore({
|
|
|
115
128
|
|
|
116
129
|
newrelic.instrumentWebframework({
|
|
117
130
|
moduleName: 'fastify',
|
|
131
|
+
isEsm: true,
|
|
118
132
|
onRequire: require('./hooks/fastify/nr-fastify'),
|
|
119
133
|
onError: function intrumentErrorHandler(err) {
|
|
120
134
|
logger.error(err.message, err.stack)
|
|
@@ -124,6 +138,7 @@ newrelic.instrumentWebframework({
|
|
|
124
138
|
|
|
125
139
|
newrelic.instrumentWebframework({
|
|
126
140
|
moduleName: 'restify',
|
|
141
|
+
isEsm: true,
|
|
127
142
|
onRequire: require('./hooks/restify/nr-restify'),
|
|
128
143
|
onError: function intrumentErrorHandler(err) {
|
|
129
144
|
logger.error(err.message, err.stack)
|
|
@@ -132,6 +147,7 @@ newrelic.instrumentWebframework({
|
|
|
132
147
|
|
|
133
148
|
newrelic.instrumentWebframework({
|
|
134
149
|
moduleName: '@koa/router',
|
|
150
|
+
isEsm: true,
|
|
135
151
|
onRequire: require('./hooks/koa/@koa/nr-@koa-router'),
|
|
136
152
|
onError: function intrumentErrorHandler(err) {
|
|
137
153
|
logger.error(err.message, err.stack)
|
|
@@ -140,6 +156,7 @@ newrelic.instrumentWebframework({
|
|
|
140
156
|
|
|
141
157
|
newrelic.instrumentWebframework({
|
|
142
158
|
moduleName: 'koa-router',
|
|
159
|
+
isEsm: true,
|
|
143
160
|
onRequire: require('./hooks/koa/@koa/nr-@koa-router'),
|
|
144
161
|
onError: function intrumentErrorHandler(err) {
|
|
145
162
|
logger.error(err.message, err.stack)
|
|
@@ -148,6 +165,7 @@ newrelic.instrumentWebframework({
|
|
|
148
165
|
|
|
149
166
|
newrelic.instrumentWebframework({
|
|
150
167
|
moduleName: '@hapi/hapi',
|
|
168
|
+
isEsm: true,
|
|
151
169
|
onRequire: require('./hooks/hapi/nr-hapi'),
|
|
152
170
|
onError: function intrumentErrorHandler(err) {
|
|
153
171
|
logger.error(err.message, err.stack)
|
|
@@ -156,6 +174,7 @@ newrelic.instrumentWebframework({
|
|
|
156
174
|
|
|
157
175
|
newrelic.instrumentWebframework({
|
|
158
176
|
moduleName: 'director',
|
|
177
|
+
isEsm: true,
|
|
159
178
|
onRequire: require('./hooks/director/nr-director'),
|
|
160
179
|
onError: function intrumentErrorHandler(err) {
|
|
161
180
|
logger.error(err.message, err.stack)
|
|
@@ -159,6 +159,10 @@ function getProcCMDLineSanitized(applicationInfoModule) {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
(() => {
|
|
162
|
+
if(!NRAgent.config.security.enabled){
|
|
163
|
+
logger.warn("security.enabled flag is set to false");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
162
166
|
if (initialize()) {
|
|
163
167
|
require('../instrumentation-security');
|
|
164
168
|
initLogger.info("[STEP-6] => Application instrumentation applied successfully");
|
|
@@ -21,7 +21,7 @@ function AuthHeaders() {
|
|
|
21
21
|
const NRAgent = API.getNRAgent();
|
|
22
22
|
authHeaders = {}
|
|
23
23
|
authHeaders['NR-CSEC-CONNECTION-TYPE'] = 'LANGUAGE_COLLECTOR';
|
|
24
|
-
authHeaders['NR-AGENT-RUN-TOKEN'] =
|
|
24
|
+
authHeaders['NR-AGENT-RUN-TOKEN'] = NRAgent.config.run_id;
|
|
25
25
|
const COLLECTOR_VERSION = appInfo.collectorVersion || '';
|
|
26
26
|
authHeaders['NR-CSEC-VERSION'] = COLLECTOR_VERSION.trim();
|
|
27
27
|
authHeaders['NR-CSEC-COLLECTOR-TYPE'] = 'NODE';
|
|
@@ -29,7 +29,7 @@ function AuthHeaders() {
|
|
|
29
29
|
authHeaders['NR-CSEC-APP-UUID'] = commonUtils.getUUID();
|
|
30
30
|
authHeaders['NR-CSEC-JSON-VERSION'] = appInfo.jsonVersion;
|
|
31
31
|
authHeaders['NR-CSEC-BUILD-NUMBER'] = appInfo.buildNumber;
|
|
32
|
-
authHeaders['NR-ACCOUNT-ID'] =
|
|
32
|
+
authHeaders['NR-ACCOUNT-ID'] = NRAgent.config.account_id;
|
|
33
33
|
authHeaders['NR-LICENSE-KEY'] = NRAgent ? NRAgent.config.license_key : '';
|
|
34
34
|
authHeaders["NR-CSEC-IAST-DATA-TRANSFER-MODE"] = "PULL";
|
|
35
35
|
} catch (error) {
|
|
@@ -49,6 +49,21 @@ AuthHeaders.prototype.constructor = AuthHeaders;
|
|
|
49
49
|
function getInstance() {
|
|
50
50
|
if (!authHeaders) {
|
|
51
51
|
authHeaders = new AuthHeaders();
|
|
52
|
+
try {
|
|
53
|
+
for (const key in authHeaders) {
|
|
54
|
+
if (authHeaders.hasOwnProperty(key)) {
|
|
55
|
+
if (key != 'NR-LICENSE-KEY') {
|
|
56
|
+
logger.info(`Adding WS connection header: ${key} -> ${authHeaders[key]}`)
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
logger.info(`Adding WS connection header: ${key} -> ******`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.error("Error while getting authentication headers");
|
|
66
|
+
}
|
|
52
67
|
}
|
|
53
68
|
return authHeaders;
|
|
54
69
|
}
|
|
@@ -26,13 +26,13 @@ let websocketHealth;
|
|
|
26
26
|
* Utility to generate UUID
|
|
27
27
|
* @returns UUID
|
|
28
28
|
*/
|
|
29
|
-
function getUUID(){
|
|
29
|
+
function getUUID() {
|
|
30
30
|
let uuid = process.env.applicationUUID || uuidV.v4();
|
|
31
31
|
process.env.applicationUUID = uuid;
|
|
32
32
|
return uuid;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function getKindIdPair
|
|
35
|
+
function getKindIdPair(identifier, hostId) {
|
|
36
36
|
const obj = {};
|
|
37
37
|
if (identifier.isContainer) {
|
|
38
38
|
obj.first = 'CONTAINER';
|
|
@@ -54,7 +54,7 @@ function getKindIdPair (identifier, hostId) {
|
|
|
54
54
|
* Utility to check worker thread support
|
|
55
55
|
* @returns
|
|
56
56
|
*/
|
|
57
|
-
function hasWorker
|
|
57
|
+
function hasWorker() {
|
|
58
58
|
let workerFlag = false;
|
|
59
59
|
try {
|
|
60
60
|
require('worker_threads');
|
|
@@ -65,7 +65,7 @@ function hasWorker () {
|
|
|
65
65
|
return workerFlag;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function runtimeSupportsFunctionGenerators
|
|
68
|
+
function runtimeSupportsFunctionGenerators() {
|
|
69
69
|
try {
|
|
70
70
|
// eslint-disable-next-line no-eval
|
|
71
71
|
eval('"use strict"; (function* () {})');
|
|
@@ -76,7 +76,7 @@ function runtimeSupportsFunctionGenerators () {
|
|
|
76
76
|
return process.csecRuntimeSupportsFunctionGenerators;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function runtimeSupportsAsyncFunctionGenerators
|
|
79
|
+
function runtimeSupportsAsyncFunctionGenerators() {
|
|
80
80
|
try {
|
|
81
81
|
// eslint-disable-next-line no-eval
|
|
82
82
|
eval('"use strict"; (async function* () {})');
|
|
@@ -91,7 +91,7 @@ function runtimeSupportsAsyncFunctionGenerators () {
|
|
|
91
91
|
* Utility to create directory and set permissions
|
|
92
92
|
* @param {*} dir
|
|
93
93
|
*/
|
|
94
|
-
function createPathIfNotExist
|
|
94
|
+
function createPathIfNotExist(dir) {
|
|
95
95
|
try {
|
|
96
96
|
if (!fs.existsSync(dir)) {
|
|
97
97
|
fs.mkdirSync(dir, 777, { recursive: true });
|
|
@@ -110,7 +110,7 @@ function createPathIfNotExist (dir) {
|
|
|
110
110
|
* Utility to get OS
|
|
111
111
|
* @returns
|
|
112
112
|
*/
|
|
113
|
-
function getOS
|
|
113
|
+
function getOS() {
|
|
114
114
|
if (process.platform === 'darwin') {
|
|
115
115
|
return 'mac';
|
|
116
116
|
}
|
|
@@ -124,7 +124,7 @@ function getOS () {
|
|
|
124
124
|
/**
|
|
125
125
|
* Utility to create directories
|
|
126
126
|
*/
|
|
127
|
-
function createDirectories
|
|
127
|
+
function createDirectories() {
|
|
128
128
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home`);
|
|
129
129
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs`);
|
|
130
130
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs${SLASH}snapshots`);
|
|
@@ -140,7 +140,7 @@ function createDirectories () {
|
|
|
140
140
|
/**
|
|
141
141
|
* Utility to check init setup and disk space
|
|
142
142
|
*/
|
|
143
|
-
async function initialSetup
|
|
143
|
+
async function initialSetup() {
|
|
144
144
|
try {
|
|
145
145
|
createDirectories();
|
|
146
146
|
} catch (err) {
|
|
@@ -151,7 +151,7 @@ async function initialSetup () {
|
|
|
151
151
|
/**
|
|
152
152
|
* Utility function to check if the logfile is accessible or not.
|
|
153
153
|
*/
|
|
154
|
-
function isLogAccessible
|
|
154
|
+
function isLogAccessible(logFileName) {
|
|
155
155
|
return new Promise((resolve) => {
|
|
156
156
|
if (logFileName === njsAgentConstants.STATUS_LOG_FILE && require('./statusUtils').getBufferedHC().length === 0) {
|
|
157
157
|
resolve('OK');
|
|
@@ -159,7 +159,11 @@ function isLogAccessible (logFileName) {
|
|
|
159
159
|
fs.access(logFileName, fs.constants.F_OK | fs.constants.W_OK, (err) => {
|
|
160
160
|
if (err) {
|
|
161
161
|
resolve('Error');
|
|
162
|
-
}
|
|
162
|
+
}
|
|
163
|
+
else if ((!logs.isLoggerOk || logger.level.levelStr == 'OFF') && logFileName != njsAgentConstants.STATUS_LOG_FILE) {
|
|
164
|
+
resolve('Error');
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
163
167
|
resolve('OK');
|
|
164
168
|
}
|
|
165
169
|
});
|
|
@@ -172,7 +176,7 @@ function isLogAccessible (logFileName) {
|
|
|
172
176
|
* Utility function to get CPU Usage in percentage
|
|
173
177
|
* @param {*} oldUsage
|
|
174
178
|
*/
|
|
175
|
-
function getCpuUsage
|
|
179
|
+
function getCpuUsage() {
|
|
176
180
|
const usageObj = process.cpuUsage();
|
|
177
181
|
const userUsage = usageObj.user / (usageObj.user + usageObj.system);
|
|
178
182
|
return userUsage;
|
|
@@ -183,7 +187,7 @@ function getCpuUsage () {
|
|
|
183
187
|
* @param {*} bytes
|
|
184
188
|
* @param {*} decimals
|
|
185
189
|
*/
|
|
186
|
-
function formatBytesToMB
|
|
190
|
+
function formatBytesToMB(bytes, decimals = 2) {
|
|
187
191
|
return parseFloat((bytes / (1024 * 1024)).toFixed(decimals));
|
|
188
192
|
}
|
|
189
193
|
|
|
@@ -191,34 +195,34 @@ function formatBytesToMB (bytes, decimals = 2) {
|
|
|
191
195
|
* Utility to get diskSpace infor for the give path
|
|
192
196
|
* @param {*} path
|
|
193
197
|
*/
|
|
194
|
-
async function getDiskSpace
|
|
198
|
+
async function getDiskSpace(path) {
|
|
195
199
|
try {
|
|
196
|
-
const x = await checkDiskSpace(path).catch(() => {});
|
|
200
|
+
const x = await checkDiskSpace(path).catch(() => { });
|
|
197
201
|
return x;
|
|
198
202
|
} catch (error) {
|
|
199
|
-
logger.error("Error in getting diskpace:",error)
|
|
203
|
+
logger.error("Error in getting diskpace:", error)
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
/**
|
|
204
208
|
* Utility to get HC stats
|
|
205
209
|
*/
|
|
206
|
-
async function getHCStats
|
|
210
|
+
async function getHCStats() {
|
|
207
211
|
const stats = {};
|
|
208
212
|
try {
|
|
209
213
|
const appInfo = require('./applicationinfo').getInstance();
|
|
210
214
|
const rootDiskSpace = await getDiskSpace(path.parse(process.cwd()).root);
|
|
211
215
|
if (appInfo && appInfo.serverInfo && appInfo.serverInfo.deployedApplications[0] && appInfo.serverInfo.deployedApplications[0].deployedPath) {
|
|
212
216
|
const processDirDiskFreeSpace = await getDiskSpace(appInfo.serverInfo.deployedApplications[0].deployedPath);
|
|
213
|
-
if(processDirDiskFreeSpace){
|
|
214
|
-
|
|
217
|
+
if (processDirDiskFreeSpace) {
|
|
218
|
+
stats.processDirDiskFreeSpaceMB = formatBytesToMB(processDirDiskFreeSpace.free);
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
const memoryObj = process.memoryUsage();
|
|
218
|
-
if(rootDiskSpace){
|
|
222
|
+
if (rootDiskSpace) {
|
|
219
223
|
stats.rootDiskFreeSpaceMB = formatBytesToMB(rootDiskSpace.free);
|
|
220
224
|
}
|
|
221
|
-
|
|
225
|
+
|
|
222
226
|
stats.processHeapUsageMB = formatBytesToMB(memoryObj.heapUsed);
|
|
223
227
|
stats.processRssMB = formatBytesToMB(memoryObj.rss);
|
|
224
228
|
stats.processMaxHeapMB = formatBytesToMB(memoryObj.heapTotal);
|
|
@@ -235,23 +239,23 @@ async function getHCStats () {
|
|
|
235
239
|
/**
|
|
236
240
|
* Utility to check Agent status
|
|
237
241
|
*/
|
|
238
|
-
function isAgentActiveState
|
|
239
|
-
if (AgentStatus.getInstance().getStatus() === AgentStatus.CSECAgentStatus.codes.ACTIVE && (NRAgent ? (NRAgent.config.security.enabled && NRAgent.canCollectData()) : true) && getWSHealthStatus()=='OK') {
|
|
242
|
+
function isAgentActiveState() {
|
|
243
|
+
if (AgentStatus.getInstance().getStatus() === AgentStatus.CSECAgentStatus.codes.ACTIVE && (NRAgent ? (NRAgent.config.security.enabled && NRAgent.canCollectData()) : true) && getWSHealthStatus() == 'OK') {
|
|
240
244
|
return 'OK';
|
|
241
245
|
} else {
|
|
242
246
|
return 'Error';
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
|
|
246
|
-
function iastRestClientStatus
|
|
250
|
+
function iastRestClientStatus() {
|
|
247
251
|
return 'OK';
|
|
248
252
|
}
|
|
249
253
|
/**
|
|
250
254
|
* Utiltity to remove older snapshots
|
|
251
255
|
*/
|
|
252
|
-
function removeOlderSnapshots
|
|
256
|
+
function removeOlderSnapshots() {
|
|
253
257
|
try {
|
|
254
|
-
const basePath = njsAgentConstants.LOG_DIR + 'snapshots'+ SLASH;
|
|
258
|
+
const basePath = njsAgentConstants.LOG_DIR + 'snapshots' + SLASH;
|
|
255
259
|
const directoryContent = fs.readdirSync(basePath);
|
|
256
260
|
const files = directoryContent.filter((filename) => {
|
|
257
261
|
return fs.statSync(`${basePath}${SLASH}${filename}`).isFile();
|
|
@@ -282,7 +286,7 @@ function removeOlderSnapshots () {
|
|
|
282
286
|
/**
|
|
283
287
|
* Utilty to remove older log files.
|
|
284
288
|
*/
|
|
285
|
-
function removeOlderlogfiles
|
|
289
|
+
function removeOlderlogfiles() {
|
|
286
290
|
try {
|
|
287
291
|
const basePath = njsAgentConstants.LOG_DIR;
|
|
288
292
|
const directoryContent = fs.readdirSync(basePath);
|
|
@@ -297,8 +301,11 @@ function removeOlderlogfiles () {
|
|
|
297
301
|
const secondStat = fs.statSync(`${basePath}${SLASH}${b}`);
|
|
298
302
|
return new Date(secondStat.mtime).getTime() - new Date(firstStat.mtime).getTime();
|
|
299
303
|
});
|
|
300
|
-
|
|
301
|
-
|
|
304
|
+
let maxLogfile = process.env.NR_CSEC_DEBUG_LOGFILE_MAX_COUNT;
|
|
305
|
+
if(isNaN(maxLogfile)){
|
|
306
|
+
maxLogfile = 2;
|
|
307
|
+
}
|
|
308
|
+
for (let i = maxLogfile; i < sorted.length; i++) {
|
|
302
309
|
const fileToDelete = basePath + sorted[i];
|
|
303
310
|
try {
|
|
304
311
|
fs.unlink(fileToDelete, (err) => {
|
|
@@ -320,7 +327,7 @@ function removeOlderlogfiles () {
|
|
|
320
327
|
/**
|
|
321
328
|
* utilty to roll over the log file. Not in use
|
|
322
329
|
*/
|
|
323
|
-
function logRollOver
|
|
330
|
+
function logRollOver() {
|
|
324
331
|
const logFileName = `${njsAgentConstants.LOG_DIR}node-security-collector.log`;
|
|
325
332
|
try {
|
|
326
333
|
const stats = fs.statSync(logFileName);
|
|
@@ -338,7 +345,7 @@ function logRollOver () {
|
|
|
338
345
|
if (!fs.existsSync(lockFile)) {
|
|
339
346
|
fs.writeFileSync(lockFile, pid.toString());
|
|
340
347
|
}
|
|
341
|
-
const fileData = fs.readFileSync(lockFile).toString();
|
|
348
|
+
const fileData = fs.readFileSync(lockFile).toString();
|
|
342
349
|
if (pid.toString() === fileData) {
|
|
343
350
|
try {
|
|
344
351
|
const newLogFile = logFileName + '.' + Date.now();
|
|
@@ -360,9 +367,9 @@ function logRollOver () {
|
|
|
360
367
|
* Utility to get validator service endpoint
|
|
361
368
|
* @returns
|
|
362
369
|
*/
|
|
363
|
-
function getValidatorServiceEndpointURL(){
|
|
364
|
-
let validatorServiceEndpointURL = NRAgent ? NRAgent.config.security.validator_service_url: "wss://csec.nr-data.net";
|
|
365
|
-
if(lodash.isEmpty(validatorServiceEndpointURL)){
|
|
370
|
+
function getValidatorServiceEndpointURL() {
|
|
371
|
+
let validatorServiceEndpointURL = NRAgent ? NRAgent.config.security.validator_service_url : "wss://csec.nr-data.net";
|
|
372
|
+
if (lodash.isEmpty(validatorServiceEndpointURL)) {
|
|
366
373
|
validatorServiceEndpointURL = "wss://csec.nr-data.net";
|
|
367
374
|
}
|
|
368
375
|
return validatorServiceEndpointURL
|
|
@@ -372,7 +379,7 @@ function getValidatorServiceEndpointURL(){
|
|
|
372
379
|
* Utility to get security mode
|
|
373
380
|
* @returns
|
|
374
381
|
*/
|
|
375
|
-
function getCSECmode(){
|
|
382
|
+
function getCSECmode() {
|
|
376
383
|
const mode = NRAgent ? NRAgent.config.security.mode : 'IAST';
|
|
377
384
|
return mode
|
|
378
385
|
}
|
|
@@ -381,9 +388,9 @@ function getCSECmode(){
|
|
|
381
388
|
* Utility to get path of CA Cert
|
|
382
389
|
* @returns
|
|
383
390
|
*/
|
|
384
|
-
function getPathOfCACert(){
|
|
391
|
+
function getPathOfCACert() {
|
|
385
392
|
let relativePath = path.resolve(__dirname, '../../resources/cert.pem');
|
|
386
|
-
if(process.platform == 'win32'){
|
|
393
|
+
if (process.platform == 'win32') {
|
|
387
394
|
relativePath = path.resolve(__dirname, '..\\..\\resources\\cert.pem');
|
|
388
395
|
}
|
|
389
396
|
const certPath = NRAgent && NRAgent.config.security.ca_bundle_path ? NRAgent.config.security.ca_bundle_path : relativePath;
|
|
@@ -393,7 +400,7 @@ function getPathOfCACert(){
|
|
|
393
400
|
* Setting ws health status
|
|
394
401
|
* @param {*} status
|
|
395
402
|
*/
|
|
396
|
-
function setWSHealthStatus(status){
|
|
403
|
+
function setWSHealthStatus(status) {
|
|
397
404
|
websocketHealth = status;
|
|
398
405
|
}
|
|
399
406
|
|
|
@@ -401,11 +408,11 @@ function setWSHealthStatus(status){
|
|
|
401
408
|
* Returns value of websocketHealth
|
|
402
409
|
* @returns websocketHealth
|
|
403
410
|
*/
|
|
404
|
-
function getWSHealthStatus(){
|
|
405
|
-
if(!websocketHealth){
|
|
411
|
+
function getWSHealthStatus() {
|
|
412
|
+
if (!websocketHealth) {
|
|
406
413
|
return 'Error';
|
|
407
414
|
}
|
|
408
|
-
else{
|
|
415
|
+
else {
|
|
409
416
|
return websocketHealth;
|
|
410
417
|
}
|
|
411
418
|
}
|
|
@@ -3,20 +3,53 @@
|
|
|
3
3
|
* SPDX-License-Identifier: New Relic Pre-Release
|
|
4
4
|
*/
|
|
5
5
|
const { Agent } = require('../../../agent');
|
|
6
|
-
let
|
|
6
|
+
let pendingRequestIds = new Set();
|
|
7
|
+
let completedRequestsMap = new Map();
|
|
7
8
|
const PolicyManager = require('../../../Policy');
|
|
8
9
|
let batchSize;
|
|
9
|
-
const batchthreshold =
|
|
10
|
-
function
|
|
11
|
-
return [...
|
|
10
|
+
const batchthreshold = 160;
|
|
11
|
+
function getPendingRequestIds() {
|
|
12
|
+
return [...pendingRequestIds];
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
function
|
|
15
|
-
|
|
15
|
+
function addPendingRequestId(id) {
|
|
16
|
+
pendingRequestIds.add(id);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function removeRequestId(id) {
|
|
19
|
-
|
|
20
|
+
completedRequestsMap.delete(id);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function removePendingRequestId(id) {
|
|
24
|
+
pendingRequestIds.delete(id);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// for last leg
|
|
28
|
+
function completedRequestsMapInit(id) {
|
|
29
|
+
completedRequestsMap.set(id, []);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function addCompletedRequests(id, eventId) {
|
|
33
|
+
let data = completedRequestsMap.get(id);
|
|
34
|
+
data.push(eventId);
|
|
35
|
+
completedRequestsMap.set(id, data);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getCompletedRequestsMap() {
|
|
39
|
+
return completedRequestsMap;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function clearCompletedRequestMap(){
|
|
43
|
+
completedRequestsMap.clear();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clearPendingRequestIdSet(){
|
|
47
|
+
pendingRequestIds.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function IASTCleanup(){
|
|
51
|
+
clearCompletedRequestMap();
|
|
52
|
+
clearPendingRequestIdSet();
|
|
20
53
|
}
|
|
21
54
|
|
|
22
55
|
function generateIASTDataRequest() {
|
|
@@ -27,24 +60,34 @@ function generateIASTDataRequest() {
|
|
|
27
60
|
batchSize = policyInstance.data.vulnerabilityScan.iastScan.probing.batchSize;
|
|
28
61
|
}
|
|
29
62
|
}
|
|
30
|
-
if (
|
|
63
|
+
if (getCompletedRequestsMap().size >= batchSize || batchSize < batchthreshold || getPendingRequestIds().length < batchSize) {
|
|
31
64
|
batchSize = (2 * batchSize);
|
|
32
65
|
}
|
|
33
66
|
let object = {};
|
|
34
67
|
object['jsonName'] = 'iast-data-request';
|
|
35
68
|
object['applicationUUID'] = Agent.getAgent().applicationInfo.applicationUUID;
|
|
36
69
|
object["batchSize"] = batchSize ? batchSize : 300;
|
|
37
|
-
object['
|
|
70
|
+
object['pendingRequestIds'] = getPendingRequestIds();
|
|
71
|
+
object['completedRequests'] = Object.fromEntries(getCompletedRequestsMap());
|
|
38
72
|
return object;
|
|
39
|
-
|
|
40
73
|
}
|
|
41
74
|
|
|
42
75
|
|
|
43
76
|
module.exports = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
getPendingRequestIds,
|
|
78
|
+
addPendingRequestId,
|
|
79
|
+
removePendingRequestId,
|
|
47
80
|
generateIASTDataRequest,
|
|
81
|
+
|
|
82
|
+
// for last leg
|
|
83
|
+
addCompletedRequests,
|
|
84
|
+
completedRequestsMapInit,
|
|
85
|
+
getCompletedRequestsMap,
|
|
86
|
+
removeRequestId,
|
|
87
|
+
IASTCleanup
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
48
91
|
}
|
|
49
92
|
|
|
50
93
|
|
package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js
CHANGED
|
@@ -9,13 +9,12 @@ const stringify = require('fast-safe-stringify');
|
|
|
9
9
|
const restClient = require('../../restclient');
|
|
10
10
|
const { Agent } = require('../../../agent');
|
|
11
11
|
const { FuzzFailEvent } = require('../../../FuzzFailEvent');
|
|
12
|
-
const { CSEC_HOME_TMP, COLON, NR_CSEC_FUZZ_REQUEST_ID, SLASH } = require('../../../sec-agent-constants');
|
|
12
|
+
const { CSEC_HOME_TMP, COLON, NR_CSEC_FUZZ_REQUEST_ID, SLASH, CSEC_SEP } = require('../../../sec-agent-constants');
|
|
13
13
|
const LOCALHOST = 'localhost';
|
|
14
14
|
const COLON_SLASH_SLASH = '://';
|
|
15
15
|
const http = require('http');
|
|
16
16
|
const https = require('https');
|
|
17
17
|
const IASTUtil = require('./IASTUtils');
|
|
18
|
-
const CSEC_SEP = ':IAST:';
|
|
19
18
|
|
|
20
19
|
require('dns').setDefaultResultOrder('ipv4first')
|
|
21
20
|
|
|
@@ -46,10 +45,12 @@ function startIASTSchedular() {
|
|
|
46
45
|
iastIntervalConst = setInterval(() => {
|
|
47
46
|
let data = IASTUtil.generateIASTDataRequest();
|
|
48
47
|
let currentTime = Date.now();
|
|
49
|
-
let completedListSize = IASTUtil.
|
|
48
|
+
let completedListSize = IASTUtil.getCompletedRequestsMap().size;
|
|
49
|
+
let pendingListSize = IASTUtil.getPendingRequestIds().length;
|
|
50
50
|
let timeDiffInSeconds = ((currentTime - lastFuzzEventTime) / 1000);
|
|
51
51
|
logger.trace("Time difference since last fuzz request:", timeDiffInSeconds);
|
|
52
52
|
logger.trace("Completed requests so far:", completedListSize);
|
|
53
|
+
logger.trace("Pending requests so far:", pendingListSize);
|
|
53
54
|
|
|
54
55
|
if (timeDiffInSeconds > 5 && additionalCoolDownTime == 0) {
|
|
55
56
|
Agent.getAgent().client.dispatcher(data);
|
|
@@ -83,10 +84,7 @@ function handler(json) {
|
|
|
83
84
|
try {
|
|
84
85
|
fuzzRequest = JSON.parse(rawFuzzRequest);
|
|
85
86
|
fuzzRequest['id'] = json.id;
|
|
86
|
-
|
|
87
|
-
if (completedList.includes(json.id)) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
87
|
+
IASTUtil.addPendingRequestId(json.id);
|
|
90
88
|
} catch (error) {
|
|
91
89
|
logger.error('Parsing exeception in fuzz request: ', error);
|
|
92
90
|
}
|
|
@@ -130,6 +128,10 @@ function handleFuzzRequest(fuzzDetails) {
|
|
|
130
128
|
}
|
|
131
129
|
}
|
|
132
130
|
|
|
131
|
+
config.headers['nr-csec-parent-id'] = fuzzRequest.id;
|
|
132
|
+
|
|
133
|
+
IASTUtil.completedRequestsMapInit(fuzzRequest.id);
|
|
134
|
+
|
|
133
135
|
if(fuzzRequest.headers && fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]){
|
|
134
136
|
logScannedApiId(fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID], fuzzRequest.requestURI)
|
|
135
137
|
}
|
|
@@ -161,7 +163,7 @@ function handleFuzzRequest(fuzzDetails) {
|
|
|
161
163
|
function handleFuzzResponse(response, fuzzDetails) {
|
|
162
164
|
const { rawFuzzRequest, fuzzRequest } = fuzzDetails;
|
|
163
165
|
if (response) {
|
|
164
|
-
IASTUtil.
|
|
166
|
+
IASTUtil.removePendingRequestId(fuzzRequest.id);
|
|
165
167
|
response.then(() => {
|
|
166
168
|
logger.info('Fuzz success: ' + rawFuzzRequest);
|
|
167
169
|
}).catch(() => {
|
|
@@ -9,16 +9,18 @@ const WebSocket = require('ws');
|
|
|
9
9
|
const RingBuffer = require('ringbufferjs');
|
|
10
10
|
const stringify = require('fast-safe-stringify');
|
|
11
11
|
const { EventEmitter } = require('events');
|
|
12
|
-
|
|
12
|
+
const API = require('../../../../../nr-security-api');
|
|
13
|
+
const NRAgent = API.getNRAgent();
|
|
13
14
|
const { Agent } = require('../../agent');
|
|
14
15
|
const logs = require('../../logging');
|
|
15
16
|
const hc = require('../../health-check');
|
|
16
17
|
const fs = require('fs');
|
|
17
18
|
const { promisify } = require('../../sec-util');
|
|
18
19
|
const ResponseHandler = require('./response');
|
|
19
|
-
const { EMPTY_APPLICATION_UUID, LOG_MESSAGES } = require('../../sec-agent-constants');
|
|
20
|
+
const { EMPTY_APPLICATION_UUID, LOG_MESSAGES, CSEC_SEP, NR_CSEC_FUZZ_REQUEST_ID, EXITEVENT,JSON_NAME, RASP } = require('../../sec-agent-constants');
|
|
20
21
|
const statusUtils = require('../../statusUtils');
|
|
21
22
|
const commonUtils = require('../../commonUtils');
|
|
23
|
+
const IASTUtil = require('../websocket/response/IASTUtils');
|
|
22
24
|
|
|
23
25
|
const BUSY = 'busy';
|
|
24
26
|
const IDLE = 'idle';
|
|
@@ -155,6 +157,7 @@ SecWebSocket.prototype.obeyReconnect = function obeyReconnect() {
|
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
initTimeOut = setTimeout(() => {
|
|
160
|
+
IASTUtil.IASTCleanup();
|
|
158
161
|
logger.debug("Terminating ws instance and reconnecting");
|
|
159
162
|
try {
|
|
160
163
|
instance.terminate();
|
|
@@ -162,7 +165,7 @@ SecWebSocket.prototype.obeyReconnect = function obeyReconnect() {
|
|
|
162
165
|
logger.debug("Error while terminating ws instance");
|
|
163
166
|
}
|
|
164
167
|
this.init();
|
|
165
|
-
},
|
|
168
|
+
}, (5 + (Math.floor(Math.random() * 11))) * 1000)
|
|
166
169
|
|
|
167
170
|
}
|
|
168
171
|
|
|
@@ -200,6 +203,18 @@ SecWebSocket.prototype.dispatch = async function dispatch(event) {
|
|
|
200
203
|
if (event.jsonName === APPLICATION_INFO_JSON_NAME) {
|
|
201
204
|
APP_INFO = event;
|
|
202
205
|
}
|
|
206
|
+
if (event.parentId && event.httpRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
|
|
207
|
+
try {
|
|
208
|
+
let apiId = event.httpRequest.headers[NR_CSEC_FUZZ_REQUEST_ID].split(CSEC_SEP)[0]
|
|
209
|
+
if (apiId == event.apiId) {
|
|
210
|
+
IASTUtil.addCompletedRequests(event.parentId, event.id);
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
logger.debug("Error while mapping completedRequests:", error);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
203
218
|
RegExp.prototype.toJSON = RegExp.prototype.toString;
|
|
204
219
|
const eventStr = stringify(event);
|
|
205
220
|
if (!this.instance ||
|
|
@@ -212,6 +227,18 @@ SecWebSocket.prototype.dispatch = async function dispatch(event) {
|
|
|
212
227
|
try {
|
|
213
228
|
await promisify(this.instance, this.instance.send)(eventStr, { mask: true }, () => {
|
|
214
229
|
logger.debug(LOG_MESSAGES.EVENT_SENT + eventStr);
|
|
230
|
+
if (event.jsonName == JSON_NAME.EVENT) {
|
|
231
|
+
if (NRAgent.config.security.mode == RASP) {
|
|
232
|
+
hc.getInstance().raspEventStats.sent++;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
hc.getInstance().iastEventStats.sent++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (event.jsonName == EXITEVENT) {
|
|
239
|
+
hc.getInstance().exitEventStats.sent++;
|
|
240
|
+
}
|
|
241
|
+
|
|
215
242
|
});
|
|
216
243
|
} catch (error) {
|
|
217
244
|
logger.debug(LOG_MESSAGES.ERROR_WHILE_SEND_EVENT, eventStr, error);
|
|
@@ -291,6 +318,12 @@ const handleDispatchFailure = (self, event, eventStr) => {
|
|
|
291
318
|
// so feeding an old applicationinfo JSON would be incorrect
|
|
292
319
|
if (eventStr && event.jsonName !== APPLICATION_INFO_JSON_NAME) {
|
|
293
320
|
hc.getInstance().registerEventDrop();
|
|
321
|
+
if (NRAgent.config.security.mode == "RASP") {
|
|
322
|
+
hc.getInstance().raspEventStats.errorCount++;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
hc.getInstance().iastEventStats.errorCount++;
|
|
326
|
+
}
|
|
294
327
|
}
|
|
295
328
|
};
|
|
296
329
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: New Relic Pre-Release
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let eventStats = undefined;
|
|
7
|
+
function EventStats() {
|
|
8
|
+
this.processed = 0;
|
|
9
|
+
this.sent = 0;
|
|
10
|
+
this.rejected = 0;
|
|
11
|
+
this.errorCount = 0;
|
|
12
|
+
}
|
|
13
|
+
EventStats.prototype.constructor = EventStats;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current instanceof EventStats
|
|
17
|
+
*
|
|
18
|
+
* @returns {EventStats} instance
|
|
19
|
+
*/
|
|
20
|
+
function getInstance() {
|
|
21
|
+
return new EventStats();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
getInstance
|
|
27
|
+
};
|
|
@@ -99,6 +99,7 @@ function SecEvent(fileName, funcName, lineNumber, argument, eid, applicationInfo
|
|
|
99
99
|
}
|
|
100
100
|
if (httpRequest && httpRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
|
|
101
101
|
this.isIASTRequest = true;
|
|
102
|
+
this.parentId = httpRequest.headers['nr-csec-parent-id'];
|
|
102
103
|
}
|
|
103
104
|
else {
|
|
104
105
|
this.isIASTRequest = false;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const { Agent } = require('./agent');
|
|
9
9
|
const { JSON_NAME } = require('./sec-agent-constants');
|
|
10
10
|
const { BasicInfo } = require('./event');
|
|
11
|
+
const EventStats = require('./event-stats');
|
|
11
12
|
|
|
12
13
|
const NODE_JS_LITERAL = 'Node.js:';
|
|
13
14
|
|
|
@@ -39,9 +40,10 @@ module.exports.getInstance = function () {
|
|
|
39
40
|
*
|
|
40
41
|
* @param {ApplicationInfo} info
|
|
41
42
|
*/
|
|
42
|
-
function HC
|
|
43
|
+
function HC(info) {
|
|
43
44
|
BasicInfo.call(this);
|
|
44
45
|
|
|
46
|
+
|
|
45
47
|
this.applicationUUID = info.applicationUUID;
|
|
46
48
|
this.eventType = 'sec_health_check_lc';
|
|
47
49
|
if (NRAgent && NRAgent.config) {
|
|
@@ -58,7 +60,9 @@ function HC (info) {
|
|
|
58
60
|
this.buildNumber = info.buildNumber;
|
|
59
61
|
this.jsonName = JSON_NAME.HC;
|
|
60
62
|
this.pid = info.pid;
|
|
61
|
-
|
|
63
|
+
this.iastEventStats = EventStats.getInstance();
|
|
64
|
+
this.raspEventStats = EventStats.getInstance();
|
|
65
|
+
this.exitEventStats = EventStats.getInstance();
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
/**
|
|
@@ -130,6 +134,45 @@ function HC (info) {
|
|
|
130
134
|
this.httpRequestCount = 0;
|
|
131
135
|
};
|
|
132
136
|
|
|
137
|
+
/**
|
|
138
|
+
* resets the IAST EventStats.
|
|
139
|
+
*/
|
|
140
|
+
this.resetIASTEventStats = function () {
|
|
141
|
+
this.iastEventStats.processed = 0;
|
|
142
|
+
this.iastEventStats.sent = 0;
|
|
143
|
+
this.iastEventStats.rejected = 0;
|
|
144
|
+
this.iastEventStats.errorCount = 0;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* resets the RASP EventStats.
|
|
149
|
+
*/
|
|
150
|
+
this.resetRASPEventStats = function () {
|
|
151
|
+
this.raspEventStats.processed = 0;
|
|
152
|
+
this.raspEventStats.sent = 0;
|
|
153
|
+
this.raspEventStats.rejected = 0;
|
|
154
|
+
this.raspEventStats.errorCount = 0;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* resets the EXIT EventStats.
|
|
159
|
+
*/
|
|
160
|
+
this.resetEXITEventStats = function () {
|
|
161
|
+
this.exitEventStats.processed = 0;
|
|
162
|
+
this.exitEventStats.sent = 0;
|
|
163
|
+
this.exitEventStats.rejected = 0;
|
|
164
|
+
this.exitEventStats.errorCount = 0;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* resets the EventStats.
|
|
169
|
+
*/
|
|
170
|
+
this.resetEventStats = function () {
|
|
171
|
+
this.resetIASTEventStats();
|
|
172
|
+
this.resetRASPEventStats();
|
|
173
|
+
this.resetEXITEventStats();
|
|
174
|
+
};
|
|
175
|
+
|
|
133
176
|
|
|
134
177
|
/**
|
|
135
178
|
* sets gerate hc timestamp.
|
|
@@ -24,7 +24,9 @@ const NRAgent = API.getNRAgent();
|
|
|
24
24
|
const logFileName = `${LOG_DIR}node-security-collector.log`;
|
|
25
25
|
const initLogFileName = `${LOG_DIR}node-security-collector-init.log`;
|
|
26
26
|
let isLoggerOk = true;
|
|
27
|
-
|
|
27
|
+
const validLogLevels = ["trace", "debug", "info", "warn", "error", "mark", "fatal", "off", "all"];
|
|
28
|
+
const sep = require('path').sep;
|
|
29
|
+
const CSEC_ROOT = `${CSEC_HOME}${sep}nr-security-home${sep}`;
|
|
28
30
|
|
|
29
31
|
function createLogPathIfNotExist(dir) {
|
|
30
32
|
try {
|
|
@@ -45,14 +47,17 @@ function createLogPathIfNotExist(dir) {
|
|
|
45
47
|
*/
|
|
46
48
|
function initialize(applicationUUID) {
|
|
47
49
|
try {
|
|
48
|
-
|
|
50
|
+
let logLevel = NRAgent && NRAgent.config && NRAgent.config.logging.enabled ? NRAgent.config.logging.level : 'info';
|
|
49
51
|
const apmLogFilePath = NRAgent && NRAgent.config && NRAgent.config.logging.enabled ? NRAgent.config.logging.filepath : '';
|
|
50
52
|
let appenderType = 'file';
|
|
51
53
|
|
|
52
|
-
if (apmLogFilePath === 'stdout') {
|
|
54
|
+
if (apmLogFilePath === 'stdout' || apmLogFilePath === 'stderr') {
|
|
53
55
|
appenderType = 'stdout';
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
if (logLevel && !validLogLevels.includes(logLevel.toLowerCase())) {
|
|
59
|
+
logLevel = 'info';
|
|
60
|
+
}
|
|
56
61
|
const appenders = {};
|
|
57
62
|
appenders.NR_CSEC = {
|
|
58
63
|
type: appenderType,
|
|
@@ -67,6 +72,7 @@ function initialize(applicationUUID) {
|
|
|
67
72
|
|
|
68
73
|
|
|
69
74
|
createLogPathIfNotExist(CSEC_HOME);
|
|
75
|
+
createLogPathIfNotExist(CSEC_ROOT);
|
|
70
76
|
createLogPathIfNotExist(LOG_DIR);
|
|
71
77
|
|
|
72
78
|
|
|
@@ -117,7 +123,7 @@ function initialize(applicationUUID) {
|
|
|
117
123
|
disableClustering: true
|
|
118
124
|
});
|
|
119
125
|
} catch (error) {
|
|
120
|
-
console.error(
|
|
126
|
+
console.error(`Security agent is unable to write logs into file: ${logFileName}.`, error.message);
|
|
121
127
|
isLoggerOk = false;
|
|
122
128
|
}
|
|
123
129
|
}
|
|
@@ -132,7 +138,7 @@ function setLevel(level = INFO) {
|
|
|
132
138
|
logger.level = level;
|
|
133
139
|
}
|
|
134
140
|
if (!isLoggerOk) {
|
|
135
|
-
logger.level =
|
|
141
|
+
logger.level = "off";
|
|
136
142
|
}
|
|
137
143
|
}
|
|
138
144
|
|
|
@@ -167,7 +173,7 @@ function getLogger(applicationUUID = process.env.applicationUUID) {
|
|
|
167
173
|
} catch (error) {
|
|
168
174
|
}
|
|
169
175
|
initLogger.info("[STEP-2] => Generating unique identifier:", applicationUUID)
|
|
170
|
-
if (NRAgent) {
|
|
176
|
+
if (NRAgent && NRAgent.config.logging.enabled) {
|
|
171
177
|
setLevel(NRAgent.config.logging.level);
|
|
172
178
|
}
|
|
173
179
|
|
|
@@ -196,5 +202,6 @@ module.exports = {
|
|
|
196
202
|
setLevel,
|
|
197
203
|
getLevel,
|
|
198
204
|
getInitLogger,
|
|
199
|
-
reCreateLogger
|
|
205
|
+
reCreateLogger,
|
|
206
|
+
isLoggerOk
|
|
200
207
|
};
|
|
@@ -12,7 +12,7 @@ const API = require('../../../nr-security-api');
|
|
|
12
12
|
const NRAgent = API.getNRAgent();
|
|
13
13
|
const sep = require('path').sep;
|
|
14
14
|
|
|
15
|
-
let CSEC_HOME = NRAgent &&
|
|
15
|
+
let CSEC_HOME = NRAgent && NRAgent.config.newrelic_home ? NRAgent.config.newrelic_home : NRAgent && NRAgent.config.logging.filepath ? path.dirname(NRAgent.config.logging.filepath) : require('path').join(process.cwd());
|
|
16
16
|
const CSEC_GROUP_NAME = NRAgent ? NRAgent.config.security.mode : 'IAST';
|
|
17
17
|
CSEC_HOME = path.resolve(CSEC_HOME);
|
|
18
18
|
const agentStartTime = new Date().toString();
|
|
@@ -49,9 +49,9 @@ module.exports = {
|
|
|
49
49
|
COLLECTOR_CONFIG: 100,
|
|
50
50
|
BLOCKING: 101,
|
|
51
51
|
POLICY_ERROR: 102,
|
|
52
|
-
OBEY_WS_RECONNECT:12,
|
|
53
|
-
ENTER_IAST_COOLDOWN:13,
|
|
54
|
-
IAST_RECORD_DELETE_CONFIRMATION:14
|
|
52
|
+
OBEY_WS_RECONNECT: 12,
|
|
53
|
+
ENTER_IAST_COOLDOWN: 13,
|
|
54
|
+
IAST_RECORD_DELETE_CONFIRMATION: 14
|
|
55
55
|
|
|
56
56
|
},
|
|
57
57
|
JSON_NAME: {
|
|
@@ -105,6 +105,9 @@ module.exports = {
|
|
|
105
105
|
DOUBLE_DOLLAR: '$$',
|
|
106
106
|
LOADING_MODULE: 'Loading module ',
|
|
107
107
|
EXITEVENT: 'exit-event',
|
|
108
|
+
CSEC_SEP: ':IAST:',
|
|
109
|
+
RASP: 'RASP',
|
|
110
|
+
IAST: 'IAST',
|
|
108
111
|
|
|
109
112
|
LOG_MESSAGES: {
|
|
110
113
|
LOADED_CSEC_ENVS_MSG: '[STEP-1][COMPLETE][env] Environment Information Gathering Done.',
|
|
@@ -9,9 +9,10 @@ const { SecEvent } = require('./event');
|
|
|
9
9
|
const logs = require('./logging');
|
|
10
10
|
const { Agent } = require('./agent');
|
|
11
11
|
const shaUtil = require('./sha-size-util');
|
|
12
|
-
const { LOG_MESSAGES, NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR } = require('./sec-agent-constants');
|
|
12
|
+
const { LOG_MESSAGES, NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR, RASP } = require('./sec-agent-constants');
|
|
13
13
|
const API = require('../../../nr-security-api');
|
|
14
14
|
const NRAgent = API.getNRAgent();
|
|
15
|
+
const HC = require('./health-check');
|
|
15
16
|
|
|
16
17
|
const statusUtils = require('./statusUtils');
|
|
17
18
|
|
|
@@ -65,6 +66,12 @@ const sleep = (timeout) => new Promise((resolve) => {
|
|
|
65
66
|
function generateSecEvent(securityMetadata) {
|
|
66
67
|
const traceObject = securityMetadata.traceObject;
|
|
67
68
|
const request = securityMetadata.request;
|
|
69
|
+
if (NRAgent.config.security.mode == RASP) {
|
|
70
|
+
HC.getInstance().raspEventStats.processed++;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
HC.getInstance().iastEventStats.processed++;
|
|
74
|
+
}
|
|
68
75
|
|
|
69
76
|
try {
|
|
70
77
|
let stakTrace = traceObject.stacktrace;
|
|
@@ -95,6 +102,12 @@ function generateSecEvent(securityMetadata) {
|
|
|
95
102
|
} catch (e) {
|
|
96
103
|
statusUtils.addErrortoBuffer(e);
|
|
97
104
|
logger.error("Error in generating event:", e);
|
|
105
|
+
if (NRAgent.config.security.mode == RASP) {
|
|
106
|
+
HC.getInstance().raspEventStats.errorCount++;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
HC.getInstance().iastEventStats.errorCount++;
|
|
110
|
+
}
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
}
|
|
@@ -122,10 +135,12 @@ function generateExitEvent(secEvent) {
|
|
|
122
135
|
exitEvent.executionId = secEvent.id;
|
|
123
136
|
exitEvent.caseType = secEvent.caseType;
|
|
124
137
|
exitEvent.k2RequestIdentifier = secEvent.httpRequest.headers[NR_CSEC_FUZZ_REQUEST_ID];
|
|
138
|
+
HC.getInstance().exitEventStats.processed++;
|
|
125
139
|
sendEvent(exitEvent);
|
|
126
140
|
}
|
|
127
141
|
} catch (error) {
|
|
128
142
|
logger.error('Error in generating exit event', error);
|
|
143
|
+
HC.getInstance().exitEventStats.errorCount++;
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
146
|
}
|
|
@@ -118,6 +118,7 @@ const sendHC = async function () {
|
|
|
118
118
|
hc.getInstance().resetProcessedCount();
|
|
119
119
|
hc.getInstance().resetEventSentCount();
|
|
120
120
|
hc.getInstance().resetHttpRequestCount();
|
|
121
|
+
hc.getInstance().resetEventStats();
|
|
121
122
|
|
|
122
123
|
logger.info('Health Check Status:', JSON.stringify(json));
|
|
123
124
|
statusUtils.addHCtoBuffer(json);
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/security-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "New Relic Security Agent for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"jsonVersion": "1.0
|
|
6
|
+
"jsonVersion": "1.1.0",
|
|
7
7
|
"contributors": [
|
|
8
8
|
{
|
|
9
9
|
"name": "Sumit Suthar",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"url": "https://github.com/newrelic/csec-node-agent.git"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@aws-sdk/client-lambda": "^3.
|
|
38
|
+
"@aws-sdk/client-lambda": "^3.363.0",
|
|
39
39
|
"axios": "0.21.4",
|
|
40
|
-
"check-disk-space": "
|
|
40
|
+
"check-disk-space": "3.3.1",
|
|
41
41
|
"content-type": "^1.0.5",
|
|
42
42
|
"fast-safe-stringify": "^2.1.1",
|
|
43
43
|
"find-package-json": "^1.2.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"pretty-bytes": "^5.6.0",
|
|
52
52
|
"request-ip": "^3.3.0",
|
|
53
53
|
"ringbufferjs": "^2.0.0",
|
|
54
|
-
"semver": "^7.5.
|
|
54
|
+
"semver": "^7.5.4",
|
|
55
55
|
"sync-request": "^6.1.0",
|
|
56
56
|
"unescape": "^1.0.1",
|
|
57
57
|
"unescape-js": "^1.1.4",
|