@newrelic/security-agent 1.4.0 → 2.0.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 +29 -0
- package/THIRD_PARTY_NOTICES.md +19 -2
- package/lib/instrumentation-security/core/constants.js +8 -1
- package/lib/instrumentation-security/core/event-constants.js +5 -3
- package/lib/instrumentation-security/core/request-manager.js +3 -1
- package/lib/instrumentation-security/core/sec-utils.js +1 -0
- package/lib/instrumentation-security/hooks/fs/nr-fs.js +6 -2
- package/lib/instrumentation-security/hooks/http/nr-http.js +123 -5
- package/lib/nr-security-agent/lib/core/Auth-headers.js +1 -0
- package/lib/nr-security-agent/lib/core/ExceptionReporting.js +7 -3
- package/lib/nr-security-agent/lib/core/Policy.js +0 -12
- package/lib/nr-security-agent/lib/core/agent.js +2 -1
- package/lib/nr-security-agent/lib/core/applicationinfo.js +2 -2
- package/lib/nr-security-agent/lib/core/commonUtils.js +11 -43
- package/lib/nr-security-agent/lib/core/connections/websocket/proxy-util.js +61 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/IASTUtils.js +6 -4
- package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +0 -2
- package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +15 -12
- package/lib/nr-security-agent/lib/core/event.js +2 -2
- package/lib/nr-security-agent/lib/core/health-check.js +2 -1
- package/lib/nr-security-agent/lib/core/sec-agent-constants.js +2 -3
- package/lib/nr-security-agent/lib/core/sec-util.js +35 -3
- package/lib/nr-security-agent/lib/core/websocket-client.js +4 -4
- package/package.json +10 -5
- package/lib/nr-security-agent/lib/core/statusUtils.js +0 -165
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
### v2.0.0 (2024-09-20)
|
|
2
|
+
### ⚠ BREAKING CHANGES
|
|
3
|
+
* Dropped support for Node.js v16
|
|
4
|
+
* Dropped functionality to generate snapshot file
|
|
5
|
+
#### Features
|
|
6
|
+
* Support to honour proxy settings via config
|
|
7
|
+
* Support for secure cookie security event generation
|
|
8
|
+
* Report error to Error Inbox upon connection failure to Security Engine
|
|
9
|
+
* Support to detect application and server path
|
|
10
|
+
* Functionality to truncate Incoming HTTP request upto default limit
|
|
11
|
+
* Dropped support for Node.js v16
|
|
12
|
+
* Dropped functionality to generate snapshot file
|
|
13
|
+
#### Bug fixes
|
|
14
|
+
* Handling for empty data in IAST fuzzing header
|
|
15
|
+
* Added identifiers in events
|
|
16
|
+
* Fix for file integrity security event generation
|
|
17
|
+
* Fix for missing identifiers in iast-data-request JSON
|
|
18
|
+
|
|
19
|
+
### v1.5.0 (2024-08-14)
|
|
20
|
+
#### Features
|
|
21
|
+
* Support for Node.js v22.x
|
|
22
|
+
#### Bug fixes
|
|
23
|
+
* Fix for traceId in error reporting
|
|
24
|
+
#### Miscellaneous chores
|
|
25
|
+
* (deps): bumped axios from 1.6.8 to 1.7.4
|
|
26
|
+
* (deps-dev): bumped ws from 7.5.9 to 8.18.0
|
|
27
|
+
#### Continuous integration
|
|
28
|
+
* Added Node.js v22.x to unit tests
|
|
29
|
+
|
|
1
30
|
### v1.4.0 (2024-06-24)
|
|
2
31
|
#### Features
|
|
3
32
|
* Added support to report application's errors while IAST scanning
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -21,6 +21,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
|
|
|
21
21
|
* [find-package-json](#find-package-json)
|
|
22
22
|
* [hash.js](#hashjs)
|
|
23
23
|
* [html-entities](#html-entities)
|
|
24
|
+
* [https-proxy-agent](#https-proxy-agent)
|
|
24
25
|
* [is-invalid-path](#is-invalid-path)
|
|
25
26
|
* [js-yaml](#js-yaml)
|
|
26
27
|
* [jsonschema](#jsonschema)
|
|
@@ -77,7 +78,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
|
|
|
77
78
|
|
|
78
79
|
### axios
|
|
79
80
|
|
|
80
|
-
This product includes source derived from [axios](https://github.com/axios/axios) ([v1.7.
|
|
81
|
+
This product includes source derived from [axios](https://github.com/axios/axios) ([v1.7.4](https://github.com/axios/axios/tree/v1.7.4)), distributed under the [MIT License](https://github.com/axios/axios/blob/v1.7.4/LICENSE):
|
|
81
82
|
|
|
82
83
|
```
|
|
83
84
|
# Copyright (c) 2014-present Matt Zabriskie & Collaborators
|
|
@@ -239,6 +240,22 @@ THE SOFTWARE.
|
|
|
239
240
|
|
|
240
241
|
```
|
|
241
242
|
|
|
243
|
+
### https-proxy-agent
|
|
244
|
+
|
|
245
|
+
This product includes source derived from [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) ([v5.0.1](https://github.com/TooTallNate/node-https-proxy-agent/tree/v5.0.1)), distributed under the [MIT License](https://github.com/TooTallNate/node-https-proxy-agent/blob/v5.0.1/README.md):
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
MIT License
|
|
249
|
+
|
|
250
|
+
Copyright (c) <year> <copyright holders>
|
|
251
|
+
|
|
252
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
253
|
+
|
|
254
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
255
|
+
|
|
256
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
257
|
+
```
|
|
258
|
+
|
|
242
259
|
### is-invalid-path
|
|
243
260
|
|
|
244
261
|
This product includes source derived from [is-invalid-path](https://github.com/jonschlinkert/is-invalid-path) ([v1.0.2](https://github.com/jonschlinkert/is-invalid-path/tree/v1.0.2)), distributed under the [MIT License](https://github.com/jonschlinkert/is-invalid-path/blob/v1.0.2/LICENSE):
|
|
@@ -588,7 +605,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
588
605
|
|
|
589
606
|
### ws
|
|
590
607
|
|
|
591
|
-
This product includes source derived from [ws](https://github.com/websockets/ws) ([v8.
|
|
608
|
+
This product includes source derived from [ws](https://github.com/websockets/ws) ([v8.18.0](https://github.com/websockets/ws/tree/v8.18.0)), distributed under the [MIT License](https://github.com/websockets/ws/blob/v8.18.0/LICENSE):
|
|
592
609
|
|
|
593
610
|
```
|
|
594
611
|
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
@@ -62,6 +62,13 @@ module.exports = {
|
|
|
62
62
|
UNDEFINED: 'undefined',
|
|
63
63
|
SELF_FD_PATH: '/proc/self/fd/',
|
|
64
64
|
NR_CSEC_FUZZ_REQUEST_ID: 'nr-csec-fuzz-request-id',
|
|
65
|
-
ASTERISK: "*"
|
|
65
|
+
ASTERISK: "*",
|
|
66
|
+
COMMA_SPACE: ", ",
|
|
67
|
+
SEMI_COLON: ';',
|
|
68
|
+
EQUAL: '=',
|
|
69
|
+
SECURE: 'secure',
|
|
70
|
+
HTTPONLY: 'httponly',
|
|
71
|
+
SAMESITE: 'samesite',
|
|
72
|
+
STRICT: 'strict'
|
|
66
73
|
|
|
67
74
|
};
|
|
@@ -20,9 +20,10 @@ const EVENT_TYPE = {
|
|
|
20
20
|
UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
|
|
21
21
|
REFLECTED_XSS: 'REFLECTED_XSS',
|
|
22
22
|
XPATH: 'XPATH',
|
|
23
|
-
LDAP: 'LDAP'
|
|
23
|
+
LDAP: 'LDAP',
|
|
24
|
+
SECURE_COOKIE: 'SECURE_COOKIE'
|
|
24
25
|
}
|
|
25
|
-
const
|
|
26
|
+
const EVENT_CATEGORY = {
|
|
26
27
|
MYSQL: 'MYSQL',
|
|
27
28
|
POSTGRES: 'POSTGRES',
|
|
28
29
|
ORACLE: 'ORACLE',
|
|
@@ -40,7 +41,8 @@ const EVENT_CATEGORY = {
|
|
|
40
41
|
UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
|
|
41
42
|
REFLECTED_XSS: 'REFLECTED_XSS',
|
|
42
43
|
XPATH: 'XPATH',
|
|
43
|
-
LDAP: 'LDAP'
|
|
44
|
+
LDAP: 'LDAP',
|
|
45
|
+
SECURE_COOKIE: 'SECURE_COOKIE'
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
module.exports = {
|
|
@@ -48,13 +48,14 @@ function getRequest(shim) {
|
|
|
48
48
|
* @param {*} shim
|
|
49
49
|
* @param {*} data
|
|
50
50
|
*/
|
|
51
|
-
function updateRequestBody(shim, data) {
|
|
51
|
+
function updateRequestBody(shim, data, isTruncated) {
|
|
52
52
|
const segment = shim.getActiveSegment();
|
|
53
53
|
if (segment) {
|
|
54
54
|
const transactionId = segment.transaction.id;
|
|
55
55
|
const requestData = getRequestFromId(transactionId);
|
|
56
56
|
if (requestData) {
|
|
57
57
|
requestData.body = data;
|
|
58
|
+
requestData.dataTruncated = isTruncated;
|
|
58
59
|
setRequest(transactionId, requestData);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -65,6 +66,7 @@ function updateRequestBody(shim, data) {
|
|
|
65
66
|
const requestData = getRequestFromId(traceId);
|
|
66
67
|
if (requestData) {
|
|
67
68
|
requestData.body = data;
|
|
69
|
+
requestData.dataTruncated = isTruncated;
|
|
68
70
|
setRequest(traceId, requestData)
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -194,6 +194,7 @@ function addRequestData(shim, request) {
|
|
|
194
194
|
});
|
|
195
195
|
}
|
|
196
196
|
data.clientIP = requestIp.getClientIp(request);
|
|
197
|
+
data.dataTruncated = false;
|
|
197
198
|
const transactionId = segment.transaction.id;
|
|
198
199
|
const storedRequest = requestManager.getRequestFromId(transactionId);
|
|
199
200
|
if (storedRequest && storedRequest.uri) {
|
|
@@ -25,6 +25,7 @@ const RENAME = 'rename';
|
|
|
25
25
|
const FSReqCallback = 'FSReqCallback';
|
|
26
26
|
const FSReqWrap = 'FSReqWrap';
|
|
27
27
|
const OBJECT = 'object';
|
|
28
|
+
const semver = require('semver');
|
|
28
29
|
|
|
29
30
|
const functionsProbableToFA = [
|
|
30
31
|
"fstat",
|
|
@@ -37,7 +38,7 @@ const functionsProbableToFA = [
|
|
|
37
38
|
"realpath",
|
|
38
39
|
];
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
let functionProbableToFI = [
|
|
41
42
|
"rename",
|
|
42
43
|
"ftruncate",
|
|
43
44
|
"rmdir",
|
|
@@ -214,6 +215,9 @@ function probableToFAHooks(shim, mod, moduleName) {
|
|
|
214
215
|
* @param {*} moduleName
|
|
215
216
|
*/
|
|
216
217
|
function probableToFIHooks(shim, mod, moduleName) {
|
|
218
|
+
if (semver.satisfies(process.version, '>19.0.0')) {
|
|
219
|
+
functionProbableToFI.push('writeFileSync');
|
|
220
|
+
}
|
|
217
221
|
functionProbableToFI.forEach(function (fun) {
|
|
218
222
|
shim.wrap(mod, fun, function makeFIWrapper(shim, fn) {
|
|
219
223
|
logger.debug(`Instrumenting ${moduleName}.${fun}`);
|
|
@@ -228,7 +232,7 @@ function probableToFIHooks(shim, mod, moduleName) {
|
|
|
228
232
|
parameters[0] = path.resolve(parameters[0]);
|
|
229
233
|
parameters[1] = path.resolve(parameters[1]);
|
|
230
234
|
} catch (error) {
|
|
231
|
-
|
|
235
|
+
|
|
232
236
|
}
|
|
233
237
|
let absoluteParameters = [parameters[0]];
|
|
234
238
|
if (fun === COPY_FILE || fun === RENAME) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
module.exports = initialize
|
|
7
7
|
const requestManager = require('../../core/request-manager');
|
|
8
|
-
const { NR_CSEC_FUZZ_REQUEST_ID, QUESTION_MARK, EMPTY_STRING, UTF8, CONTENT_TYPE, TEXT_HTML, APPLICATION_JSON, APPLICATION_XML, APPLICATION_XHTML, TEXT_PLAIN, APPLICATION_X_FORM_URLENCODED, MULTIPART_FORM_DATA, COMMA } = require('../../core/constants');
|
|
8
|
+
const { NR_CSEC_FUZZ_REQUEST_ID, QUESTION_MARK, EMPTY_STRING, UTF8, CONTENT_TYPE, TEXT_HTML, APPLICATION_JSON, APPLICATION_XML, APPLICATION_XHTML, TEXT_PLAIN, APPLICATION_X_FORM_URLENCODED, MULTIPART_FORM_DATA, COMMA, SEMI_COLON, EQUAL, SECURE, HTTPONLY, SAMESITE, STRICT } = require('../../core/constants');
|
|
9
9
|
const ARRAY_TYPE = 'Array';
|
|
10
10
|
const STRING_TYPE = 'string';
|
|
11
11
|
const BUFFER_TYPE = 'Buffer';
|
|
@@ -40,6 +40,7 @@ let CSEC_HOME = NRAgent && NRAgent.config.newrelic_home ? NRAgent.config.newreli
|
|
|
40
40
|
const CSEC_HOME_TMP = `${CSEC_HOME}/nr-security-home/tmp/language-agent/${process.env.applicationUUID}`
|
|
41
41
|
let lastTransactionId = EMPTY_STRING;
|
|
42
42
|
let uncaughtExceptionReportFlag = false;
|
|
43
|
+
const requestBodyLimit = 500;
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -172,8 +173,12 @@ function parseFuzzheaders(requestData, transactionId) {
|
|
|
172
173
|
const additionalData = fuzzheaders[NR_CSEC_FUZZ_REQUEST_ID].split(CSEC_SEP);
|
|
173
174
|
logger.debug('AdditionalData:', additionalData);
|
|
174
175
|
if (additionalData.length >= 8) {
|
|
175
|
-
let encryptedData = additionalData[6];
|
|
176
|
-
let hashVerifier = additionalData[7];
|
|
176
|
+
let encryptedData = additionalData[6].trim();
|
|
177
|
+
let hashVerifier = additionalData[7].trim();
|
|
178
|
+
|
|
179
|
+
if (lodash.isEmpty(encryptedData) || lodash.isEmpty(hashVerifier)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
177
182
|
let decryptedData = secUtils.decryptData(encryptedData);
|
|
178
183
|
let verifiedHash = secUtils.hashVerifier(decryptedData, hashVerifier);
|
|
179
184
|
logger.debug("verifiedHash:", verifiedHash);
|
|
@@ -244,6 +249,7 @@ function addRequestData(shim, request) {
|
|
|
244
249
|
});
|
|
245
250
|
}
|
|
246
251
|
data.clientIP = requestIp.getClientIp(request);
|
|
252
|
+
data.dataTruncated = false;
|
|
247
253
|
const transactionId = segment.transaction.id;
|
|
248
254
|
const storedRequest = requestManager.getRequestFromId(transactionId);
|
|
249
255
|
lastTransactionId = transactionId;
|
|
@@ -363,7 +369,21 @@ function onDataHook(shim, mod) {
|
|
|
363
369
|
const requestData = requestManager.getRequestFromId(transactionId);
|
|
364
370
|
if (requestData) {
|
|
365
371
|
data = requestData.body ? requestData.body.concat(chunk.toString()) : chunk.toString();
|
|
366
|
-
requestManager.updateRequestBody(shim, data);
|
|
372
|
+
requestManager.updateRequestBody(shim, data, false);
|
|
373
|
+
try {
|
|
374
|
+
const contentLength = Buffer.byteLength(data, 'utf8');
|
|
375
|
+
let bodyLimit = requestBodyLimit;
|
|
376
|
+
if (!isNaN(bodyLimit)) {
|
|
377
|
+
bodyLimit = bodyLimit * 1000;
|
|
378
|
+
if (contentLength && contentLength > bodyLimit) {
|
|
379
|
+
data = truncateStringToBytes(data, bodyLimit);
|
|
380
|
+
requestManager.updateRequestBody(shim, data, true);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
} catch (error) {
|
|
385
|
+
logger.error("Error while truncating request body", error);
|
|
386
|
+
}
|
|
367
387
|
}
|
|
368
388
|
|
|
369
389
|
}
|
|
@@ -418,6 +438,11 @@ function responseHook(resp, req, shim) {
|
|
|
418
438
|
} catch (error) {
|
|
419
439
|
logger.debug("Error while reporting error", error);
|
|
420
440
|
}
|
|
441
|
+
try {
|
|
442
|
+
secureCookieCheck(response, shim);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
logger.debug("Error while generating secure cookie event");
|
|
445
|
+
}
|
|
421
446
|
|
|
422
447
|
responseBodyCompute(response, arguments);
|
|
423
448
|
|
|
@@ -594,7 +619,7 @@ function deleteTempFileForIAST(file) {
|
|
|
594
619
|
*/
|
|
595
620
|
process.on('uncaughtException', (err, origin) => {
|
|
596
621
|
console.error(err);
|
|
597
|
-
if(uncaughtExceptionReportFlag){
|
|
622
|
+
if (uncaughtExceptionReportFlag) {
|
|
598
623
|
process.exit(1);
|
|
599
624
|
}
|
|
600
625
|
const request = requestManager.getRequestFromId(lastTransactionId);
|
|
@@ -606,3 +631,96 @@ process.on('uncaughtException', (err, origin) => {
|
|
|
606
631
|
uncaughtExceptionReportFlag = true;
|
|
607
632
|
});
|
|
608
633
|
|
|
634
|
+
/**
|
|
635
|
+
* utility to parse response headers for cookie check
|
|
636
|
+
* @param {*} headersString
|
|
637
|
+
* @returns
|
|
638
|
+
*/
|
|
639
|
+
function responseRawHeaderParsing(headersString) {
|
|
640
|
+
let setCookieValue;
|
|
641
|
+
try {
|
|
642
|
+
const headersArray = headersString.split('\r\n');
|
|
643
|
+
for (const header of headersArray) {
|
|
644
|
+
if (header.toLowerCase().startsWith('set-cookie:')) {
|
|
645
|
+
setCookieValue = header.trim().replace('set-cookie:', '');
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
} catch (error) {
|
|
650
|
+
|
|
651
|
+
}
|
|
652
|
+
return setCookieValue;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Utility to check insecure cookie settings
|
|
657
|
+
* @param {*} response
|
|
658
|
+
* @param {*} shim
|
|
659
|
+
*/
|
|
660
|
+
function secureCookieCheck(response, shim) {
|
|
661
|
+
let cookieHeader = response.getHeader('set-cookie') ? response.getHeader('set-cookie') : responseRawHeaderParsing(response._header);
|
|
662
|
+
let cookieHeaderList = [];
|
|
663
|
+
if (!cookieHeader) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (typeof cookieHeader === 'string') {
|
|
667
|
+
cookieHeaderList.push(cookieHeader)
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
cookieHeaderList = cookieHeaderList.concat(cookieHeader);
|
|
671
|
+
}
|
|
672
|
+
let csec_request = requestManager.getRequest(shim);
|
|
673
|
+
try {
|
|
674
|
+
let parameters = [];
|
|
675
|
+
for (const cookieString of cookieHeaderList) {
|
|
676
|
+
if (cookieString) {
|
|
677
|
+
const keyValuePairs = cookieString.split(SEMI_COLON);
|
|
678
|
+
let params = {
|
|
679
|
+
"isHttpOnly": false,
|
|
680
|
+
"isSecure": false,
|
|
681
|
+
"isSameSiteStrict": false,
|
|
682
|
+
};
|
|
683
|
+
for (let i = 0; i < keyValuePairs.length; i++) {
|
|
684
|
+
const pair = keyValuePairs[i];
|
|
685
|
+
const [key, value] = pair.trim().split(EQUAL);
|
|
686
|
+
if (i == 0) {
|
|
687
|
+
params.name = key;
|
|
688
|
+
params.value = value;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (key.toLowerCase() === SECURE) {
|
|
692
|
+
params.isSecure = true;
|
|
693
|
+
}
|
|
694
|
+
else if (key.toLowerCase() === HTTPONLY) {
|
|
695
|
+
params.isHttpOnly = true;
|
|
696
|
+
}
|
|
697
|
+
else if (key.toLowerCase() === SAMESITE && value.toLowerCase() === STRICT) {
|
|
698
|
+
params.isSameSiteStrict = true;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
parameters.push(params);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const traceObject = secUtils.getTraceObject(shim);
|
|
705
|
+
const secMetadata = securityMetaData.getSecurityMetaData(csec_request, parameters, traceObject, secUtils.getExecutionId(), EVENT_TYPE.SECURE_COOKIE, EVENT_CATEGORY.SECURE_COOKIE)
|
|
706
|
+
const secEvent = API.generateSecEvent(secMetadata);
|
|
707
|
+
API.sendEvent(secEvent);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
logger.debug("Error while generating secure cookie event:", error);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function truncateStringToBytes(inputString, maxBytes) {
|
|
715
|
+
if (!inputString || maxBytes <= 0) {
|
|
716
|
+
return EMPTY_STRING; // Return an empty string if input is invalid
|
|
717
|
+
}
|
|
718
|
+
const buffer = Buffer.from(inputString, UTF8);
|
|
719
|
+
if (buffer.length <= maxBytes) {
|
|
720
|
+
return inputString;
|
|
721
|
+
}
|
|
722
|
+
const truncatedBuffer = buffer.slice(0, maxBytes);
|
|
723
|
+
const truncatedString = truncatedBuffer.toString(UTF8);
|
|
724
|
+
return truncatedString;
|
|
725
|
+
}
|
|
726
|
+
|
|
@@ -98,9 +98,7 @@ function exceptionReporting(exception, request, responseCode) {
|
|
|
98
98
|
this.category = exception.name;
|
|
99
99
|
this.traceId = shaUtil.getSHA256ForData(this.exception.stackTrace.join('|') + this.category + this.httpRequest.route + this.httpRequest.method);
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
this.traceId = shaUtil.getSHA256ForData(this.category + this.httpRequest.route + this.httpRequest.method);
|
|
103
|
-
}
|
|
101
|
+
|
|
104
102
|
if (responseCode) {
|
|
105
103
|
if (responseCode >= 500) {
|
|
106
104
|
this.category = http.STATUS_CODES[responseCode];
|
|
@@ -124,6 +122,9 @@ function exceptionReporting(exception, request, responseCode) {
|
|
|
124
122
|
function generateExceptionReportingEvent(error, request) {
|
|
125
123
|
try {
|
|
126
124
|
const exception = new exceptionReporting(error, request);
|
|
125
|
+
if(!exception.traceId){
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
127
128
|
let key = exception.exception.type + exception.exception.stackTrace[0];
|
|
128
129
|
if (!exceptionsMap.has(key)) {
|
|
129
130
|
exceptionsMap.set(key, exception);
|
|
@@ -141,6 +142,9 @@ function generateExceptionReportingEvent(error, request) {
|
|
|
141
142
|
function generate5xxReportingEvent(error, request, responseCode) {
|
|
142
143
|
try {
|
|
143
144
|
const exception = new exceptionReporting(error, request, responseCode);
|
|
145
|
+
if(!exception.traceId){
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
144
148
|
let key = (request.uri ? request.uri : request.url) + responseCode;
|
|
145
149
|
if (!exceptionsMap.has(key)) {
|
|
146
150
|
exceptionsMap.set(key, exception);
|
|
@@ -17,7 +17,6 @@ const logger = logs.getLogger();
|
|
|
17
17
|
const initLogger = logs.getInitLogger();
|
|
18
18
|
const jsonValidator = require('./jsonValidator');
|
|
19
19
|
const LOG_MESSAGES = require('./sec-agent-constants').LOG_MESSAGES;
|
|
20
|
-
const statusUtils = require('./statusUtils');
|
|
21
20
|
|
|
22
21
|
function Policy() {
|
|
23
22
|
this.data = {};
|
|
@@ -72,7 +71,6 @@ function setDefaultPolicy() {
|
|
|
72
71
|
logger.info(LOG_MESSAGES.DEFAULT_POL_SET, JSON.stringify(policy.data));
|
|
73
72
|
} catch (e) {
|
|
74
73
|
logger.error(LOG_MESSAGES.UNABLE_SET_DEFAULT_POL, e);
|
|
75
|
-
statusUtils.addErrortoBuffer(e);
|
|
76
74
|
};
|
|
77
75
|
}
|
|
78
76
|
|
|
@@ -90,16 +88,6 @@ function appInfoVersionUpdate(policy, data) {
|
|
|
90
88
|
}
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
/**
|
|
94
|
-
* Function to send updated policy to validator
|
|
95
|
-
* @param {*} data
|
|
96
|
-
*/
|
|
97
|
-
function sendUpdatedPolicy(data) {
|
|
98
|
-
const wsClient = Agent.getAgent().client;
|
|
99
|
-
let updatedPolicy = Object.assign({}, data);
|
|
100
|
-
updatedPolicy.jsonName = 'lc-policy';
|
|
101
|
-
wsClient.dispatcher(updatedPolicy);
|
|
102
|
-
}
|
|
103
91
|
module.exports = {
|
|
104
92
|
getInstance,
|
|
105
93
|
setPolicyData,
|
|
@@ -130,7 +130,8 @@ Agent.setNRAgent = function setNRAgent(agent) {
|
|
|
130
130
|
}
|
|
131
131
|
const CSECAgent = Agent.getAgent();
|
|
132
132
|
if (CSECAgent) {
|
|
133
|
-
CSECAgent.applicationInfo.
|
|
133
|
+
CSECAgent.applicationInfo.appEntityGuid = NRAgent.config.entity_guid;
|
|
134
|
+
CSECAgent.applicationInfo.appAccountId = NRAgent.config.account_id;
|
|
134
135
|
CSECAgent.applicationInfo.linkingMetadata = LinkingMetaData.getLinkingMetadata();
|
|
135
136
|
CSECAgent.applicationInfo.linkingMetadata.agentRunId = NRAgent.config.run_id;
|
|
136
137
|
CSECAgent.setApplicationInfo(CSECAgent.applicationInfo);
|
|
@@ -90,10 +90,10 @@ function ApplicationInfo () {
|
|
|
90
90
|
const k8sHost = isK8S();
|
|
91
91
|
const ECSEnv = isECSEnv();
|
|
92
92
|
const isLambda = IS_LAMBDA_ENV;
|
|
93
|
-
|
|
94
93
|
this.jsonName = APPLICATION_INFO_KEYWORD;
|
|
95
94
|
if (NRAgent && NRAgent.config) {
|
|
96
|
-
this.
|
|
95
|
+
this.appEntityGuid = NRAgent.config.entity_guid;
|
|
96
|
+
this.appAccountId = NRAgent.config.account_id;
|
|
97
97
|
}
|
|
98
98
|
this.eventType = 'sec_appinfo';
|
|
99
99
|
this.startTime = Date.now();
|
|
@@ -133,7 +133,6 @@ function getOS() {
|
|
|
133
133
|
function createDirectories() {
|
|
134
134
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home`);
|
|
135
135
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs`);
|
|
136
|
-
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs${SLASH}snapshots`);
|
|
137
136
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp`);
|
|
138
137
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent`);
|
|
139
138
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent${SLASH}${getUUID()}`);
|
|
@@ -159,14 +158,11 @@ async function initialSetup() {
|
|
|
159
158
|
*/
|
|
160
159
|
function isLogAccessible(logFileName) {
|
|
161
160
|
return new Promise((resolve) => {
|
|
162
|
-
if (logFileName === njsAgentConstants.STATUS_LOG_FILE && require('./statusUtils').getBufferedHC().length === 0) {
|
|
163
|
-
resolve('OK');
|
|
164
|
-
}
|
|
165
161
|
fs.access(logFileName, fs.constants.F_OK | fs.constants.W_OK, (err) => {
|
|
166
162
|
if (err) {
|
|
167
163
|
resolve('Error');
|
|
168
164
|
}
|
|
169
|
-
else if ((!logs.isLoggerOk || logger.level.levelStr == 'OFF')
|
|
165
|
+
else if ((!logs.isLoggerOk || logger.level.levelStr == 'OFF')) {
|
|
170
166
|
resolve('Error');
|
|
171
167
|
}
|
|
172
168
|
else {
|
|
@@ -256,42 +252,6 @@ function isAgentActiveState() {
|
|
|
256
252
|
function iastRestClientStatus() {
|
|
257
253
|
return 'OK';
|
|
258
254
|
}
|
|
259
|
-
/**
|
|
260
|
-
* Utiltity to remove older snapshots
|
|
261
|
-
*/
|
|
262
|
-
function removeOlderSnapshots() {
|
|
263
|
-
try {
|
|
264
|
-
const basePath = njsAgentConstants.LOG_DIR + 'snapshots' + SLASH;
|
|
265
|
-
const directoryContent = fs.readdirSync(basePath);
|
|
266
|
-
const files = directoryContent.filter((filename) => {
|
|
267
|
-
return fs.statSync(`${basePath}${SLASH}${filename}`).isFile();
|
|
268
|
-
});
|
|
269
|
-
const sorted = files.sort((a, b) => {
|
|
270
|
-
const firstStat = fs.statSync(`${basePath}${SLASH}${a}`);
|
|
271
|
-
const secondStat = fs.statSync(`${basePath}${SLASH}${b}`);
|
|
272
|
-
return new Date(secondStat.mtime).getTime() - new Date(firstStat.mtime).getTime();
|
|
273
|
-
});
|
|
274
|
-
for (let i = 99; i < sorted.length; i++) {
|
|
275
|
-
const fileToDelete = basePath + sorted[i];
|
|
276
|
-
try {
|
|
277
|
-
fs.unlink(fileToDelete, (err) => {
|
|
278
|
-
if (err) {
|
|
279
|
-
logger.error(err);
|
|
280
|
-
} else {
|
|
281
|
-
logger.debug('Snapshot deleted:', fileToDelete);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
} catch (error) {
|
|
285
|
-
logger.error(error);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
} catch (error) {
|
|
289
|
-
logger.debug(error);
|
|
290
|
-
const LogMessage = require('./LogMessage');
|
|
291
|
-
const logMessage = new LogMessage.logMessage("DEBUG", 'Error in processing snapshot files', __filename, error);
|
|
292
|
-
addLogEventtoBuffer(logMessage);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
255
|
/**
|
|
296
256
|
* Utilty to remove older log files.
|
|
297
257
|
*/
|
|
@@ -459,6 +419,14 @@ function getFramework(){
|
|
|
459
419
|
return framework;
|
|
460
420
|
}
|
|
461
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Utility to send error to error inbox of APM
|
|
424
|
+
* @param {*} error
|
|
425
|
+
*/
|
|
426
|
+
function sendErrorToErrorInbox(error){
|
|
427
|
+
API.newrelic.noticeError(error);
|
|
428
|
+
}
|
|
429
|
+
|
|
462
430
|
|
|
463
431
|
module.exports = {
|
|
464
432
|
getUUID,
|
|
@@ -475,7 +443,6 @@ module.exports = {
|
|
|
475
443
|
getHCStats,
|
|
476
444
|
isAgentActiveState,
|
|
477
445
|
iastRestClientStatus,
|
|
478
|
-
removeOlderSnapshots,
|
|
479
446
|
logRollOver,
|
|
480
447
|
getValidatorServiceEndpointURL,
|
|
481
448
|
getCSECmode,
|
|
@@ -484,5 +451,6 @@ module.exports = {
|
|
|
484
451
|
setWSHealthStatus,
|
|
485
452
|
addLogEventtoBuffer,
|
|
486
453
|
getLogEvents,
|
|
487
|
-
getFramework
|
|
454
|
+
getFramework,
|
|
455
|
+
sendErrorToErrorInbox
|
|
488
456
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: New Relic Software License v1.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const { HttpsProxyAgent } = require('https-proxy-agent')
|
|
9
|
+
const logs = require('../../logging');
|
|
10
|
+
let logger = logs.getLogger();
|
|
11
|
+
const commonUtils = require('../../commonUtils');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
let agentProxyWithKeepAlive = null
|
|
15
|
+
|
|
16
|
+
exports.proxyAgent = function proxyAgent(config) {
|
|
17
|
+
if (null !== agentProxyWithKeepAlive) {
|
|
18
|
+
return agentProxyWithKeepAlive
|
|
19
|
+
}
|
|
20
|
+
const proxyUrl = proxyOptions(config)
|
|
21
|
+
let proxyOpts = {
|
|
22
|
+
secureEndpoint: config.ssl,
|
|
23
|
+
auth: proxyUrl.auth,
|
|
24
|
+
ca: config?.certificates?.length ? config.certificates : [],
|
|
25
|
+
keepAlive: true
|
|
26
|
+
}
|
|
27
|
+
let cert = '';
|
|
28
|
+
try {
|
|
29
|
+
const certPath = commonUtils.getPathOfCACert();
|
|
30
|
+
cert = fs.readFileSync(certPath, 'utf8');
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logger.error("Error in reading certificate:", error);
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
proxyOpts.ca.push(cert);
|
|
36
|
+
logger.info(`Using proxy: ${proxyUrl}`)
|
|
37
|
+
agentProxyWithKeepAlive = new HttpsProxyAgent(proxyUrl, proxyOpts)
|
|
38
|
+
return agentProxyWithKeepAlive
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Utility to create proxy URL
|
|
43
|
+
* @param config
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
function proxyOptions(config) {
|
|
47
|
+
let proxyUrl
|
|
48
|
+
if (config.proxy) {
|
|
49
|
+
proxyUrl = config.proxy
|
|
50
|
+
} else {
|
|
51
|
+
proxyUrl = 'https://'
|
|
52
|
+
let proxyAuth = config.proxy_user
|
|
53
|
+
if (config.proxy_pass !== '') {
|
|
54
|
+
proxyAuth += ':' + config.proxy_pass
|
|
55
|
+
proxyUrl += `${proxyAuth}@`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
proxyUrl += `${config.proxy_host || 'localhost'}:${config.proxy_port || 80}`
|
|
59
|
+
}
|
|
60
|
+
return proxyUrl
|
|
61
|
+
}
|
|
@@ -31,7 +31,7 @@ function completedRequestsMapInit(id) {
|
|
|
31
31
|
|
|
32
32
|
function addCompletedRequests(id, eventId) {
|
|
33
33
|
let data = completedRequestsMap.get(id);
|
|
34
|
-
if(!data){
|
|
34
|
+
if (!data) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
data.push(eventId);
|
|
@@ -42,15 +42,15 @@ function getCompletedRequestsMap() {
|
|
|
42
42
|
return completedRequestsMap;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function clearCompletedRequestMap(){
|
|
45
|
+
function clearCompletedRequestMap() {
|
|
46
46
|
completedRequestsMap.clear();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function clearPendingRequestIdSet(){
|
|
49
|
+
function clearPendingRequestIdSet() {
|
|
50
50
|
pendingRequestIds.clear();
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function IASTCleanup(){
|
|
53
|
+
function IASTCleanup() {
|
|
54
54
|
clearCompletedRequestMap();
|
|
55
55
|
clearPendingRequestIdSet();
|
|
56
56
|
}
|
|
@@ -69,6 +69,8 @@ function generateIASTDataRequest() {
|
|
|
69
69
|
let object = {};
|
|
70
70
|
object['jsonName'] = 'iast-data-request';
|
|
71
71
|
object['applicationUUID'] = Agent.getAgent().applicationInfo.applicationUUID;
|
|
72
|
+
object['appEntityGuid'] = Agent.getAgent().applicationInfo.appEntityGuid;
|
|
73
|
+
object['appAccountId'] = Agent.getAgent().applicationInfo.appAccountId;
|
|
72
74
|
object["batchSize"] = batchSize ? batchSize : 300;
|
|
73
75
|
object['pendingRequestIds'] = getPendingRequestIds();
|
|
74
76
|
object['completedRequests'] = Object.fromEntries(getCompletedRequestsMap());
|
package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js
CHANGED
|
@@ -21,7 +21,6 @@ const LogMessage = require('../../../LogMessage');
|
|
|
21
21
|
require('dns').setDefaultResultOrder('ipv4first')
|
|
22
22
|
const PolicyManager = require('../../../Policy');
|
|
23
23
|
|
|
24
|
-
const statusUtils = require('../../../statusUtils');
|
|
25
24
|
|
|
26
25
|
const find = `${SLASH}{{NR_CSEC_VALIDATOR_HOME_TMP}}`;
|
|
27
26
|
const CSEC_HOME_TMP_CONST = new RegExp(find, 'g');
|
|
@@ -158,7 +157,6 @@ function handleFuzzRequest(fuzzDetails) {
|
|
|
158
157
|
handleFuzzResponse(response, fuzzDetails);
|
|
159
158
|
}
|
|
160
159
|
} catch (err) {
|
|
161
|
-
statusUtils.addErrortoBuffer(err);
|
|
162
160
|
const fuzzFailEvent = new FuzzFailEvent(fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]);
|
|
163
161
|
logger.error(stringify(fuzzFailEvent));
|
|
164
162
|
try {
|
|
@@ -17,11 +17,11 @@ const hc = require('../../health-check');
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const { promisify } = require('../../sec-util');
|
|
19
19
|
const ResponseHandler = require('./response');
|
|
20
|
-
const { EMPTY_APPLICATION_UUID, LOG_MESSAGES, CSEC_SEP, NR_CSEC_FUZZ_REQUEST_ID, EXITEVENT, JSON_NAME,
|
|
21
|
-
const statusUtils = require('../../statusUtils');
|
|
20
|
+
const { EMPTY_APPLICATION_UUID, LOG_MESSAGES, CSEC_SEP, NR_CSEC_FUZZ_REQUEST_ID, EXITEVENT, JSON_NAME, SEVERE } = require('../../sec-agent-constants');
|
|
22
21
|
const commonUtils = require('../../commonUtils');
|
|
23
22
|
const IASTUtil = require('../websocket/response/IASTUtils');
|
|
24
23
|
const LogMessage = require('../../LogMessage');
|
|
24
|
+
const proxyUtil = require('./proxy-util');
|
|
25
25
|
|
|
26
26
|
const BUSY = 'busy';
|
|
27
27
|
const IDLE = 'idle';
|
|
@@ -105,18 +105,25 @@ SecWebSocket.prototype.init = function init() {
|
|
|
105
105
|
const authHeaders = require('../../Auth-headers').getInstance();
|
|
106
106
|
initLogger.info(`Connecting to Validator at ${validatorService}`);
|
|
107
107
|
IASTUtil.IASTCleanup();
|
|
108
|
-
let cert = ''
|
|
108
|
+
let cert = '';
|
|
109
109
|
try {
|
|
110
110
|
const certPath = commonUtils.getPathOfCACert();
|
|
111
111
|
cert = fs.readFileSync(certPath, 'utf8');
|
|
112
112
|
} catch (error) {
|
|
113
113
|
logger.error("Error in reading certificate:", error);
|
|
114
|
+
commonUtils.sendErrorToErrorInbox(error)
|
|
114
115
|
const logMessage = new LogMessage.logMessage(SEVERE, 'Error in reading certificate for websocket', __filename, error);
|
|
115
116
|
commonUtils.addLogEventtoBuffer(logMessage);
|
|
116
117
|
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
const isProxy = !!(NRAgent.config.proxy || NRAgent.config.proxy_port || NRAgent.config.proxy_host)
|
|
120
|
+
if(isProxy){
|
|
121
|
+
const agentWithProxy = proxyUtil.proxyAgent(NRAgent.config);
|
|
122
|
+
webSocket = new WebSocket(validatorService, { headers: authHeaders, cert: cert, handshakeTimeout: 10000, agent:agentWithProxy });
|
|
123
|
+
}
|
|
124
|
+
else{
|
|
125
|
+
webSocket = new WebSocket(validatorService, { headers: authHeaders, cert: cert, handshakeTimeout: 10000 });
|
|
126
|
+
}
|
|
120
127
|
|
|
121
128
|
webSocket.on(WS_ON_OPEN, this.openCB);
|
|
122
129
|
webSocket.on(WS_ON_MSG, this.msgCB);
|
|
@@ -162,6 +169,7 @@ SecWebSocket.prototype.reconnect = function reconnect() {
|
|
|
162
169
|
return;
|
|
163
170
|
}
|
|
164
171
|
commonUtils.setWSHealthStatus('Error');
|
|
172
|
+
commonUtils.sendErrorToErrorInbox(new Error("Unable to connect to Security Engine"))
|
|
165
173
|
logger.debug(LOG_MESSAGES.DETECTED_BROKEN_CONN);
|
|
166
174
|
this.obeyReconnect();
|
|
167
175
|
|
|
@@ -204,13 +212,11 @@ SecWebSocket.prototype.pingWS = async function pingWS() {
|
|
|
204
212
|
if (err) {
|
|
205
213
|
logger.debug("Error while pinging:", err);
|
|
206
214
|
commonUtils.setWSHealthStatus('Error');
|
|
207
|
-
statusUtils.addErrortoBuffer(err);
|
|
208
215
|
|
|
209
216
|
}
|
|
210
217
|
});
|
|
211
218
|
} catch (err) {
|
|
212
219
|
logger.debug("error in ping:", err);
|
|
213
|
-
statusUtils.addErrortoBuffer(err);
|
|
214
220
|
commonUtils.setWSHealthStatus('Error');
|
|
215
221
|
return;
|
|
216
222
|
}
|
|
@@ -266,7 +272,6 @@ SecWebSocket.prototype.dispatch = async function dispatch(event) {
|
|
|
266
272
|
})
|
|
267
273
|
} catch (error) {
|
|
268
274
|
logger.debug(LOG_MESSAGES.ERROR_WHILE_SEND_EVENT, eventStr, error);
|
|
269
|
-
statusUtils.addErrortoBuffer(error);
|
|
270
275
|
handleDispatchFailure(this, event, eventStr);
|
|
271
276
|
}
|
|
272
277
|
};
|
|
@@ -286,7 +291,6 @@ SecWebSocket.prototype.dispatchApplicationInfo = async function dispatchApplicat
|
|
|
286
291
|
try {
|
|
287
292
|
await promisify(this.instance, this.instance.send)(eventStr, { mask: true });
|
|
288
293
|
} catch (error) {
|
|
289
|
-
statusUtils.addErrortoBuffer(error);
|
|
290
294
|
logger.warn('Error while sending applicationInfo:', error);
|
|
291
295
|
this.obeyReconnect();
|
|
292
296
|
}
|
|
@@ -325,7 +329,6 @@ SecWebSocket.prototype.flushEventQueue = async function flushEventQueue() {
|
|
|
325
329
|
const results = await Promise.all(sentEvents);
|
|
326
330
|
hc.getInstance().registerEventsSent(results.length);
|
|
327
331
|
} catch (err) {
|
|
328
|
-
statusUtils.addErrortoBuffer(err);
|
|
329
332
|
logger.debug('Flush failed, reception of all events on IC couldn\'t be ensured.');
|
|
330
333
|
}
|
|
331
334
|
};
|
|
@@ -398,7 +401,7 @@ const defaultOnOpenCB = () => async function onOpen() {
|
|
|
398
401
|
initLogger.info(LOG_MESSAGES.SENDING_APPINFO_COMPLETE, JSON.stringify(applicationInfo));
|
|
399
402
|
} catch (err) {
|
|
400
403
|
logger.error(`Connection broken: ${err.message}`);
|
|
401
|
-
|
|
404
|
+
commonUtils.sendErrorToErrorInbox(err);
|
|
402
405
|
commonUtils.setWSHealthStatus('Error')
|
|
403
406
|
setWebSocketConn();
|
|
404
407
|
self.setValidatorNotReadyForEvents();
|
|
@@ -431,10 +434,10 @@ const defaultOnMsgCB = () => function onMessage(data) {
|
|
|
431
434
|
*/
|
|
432
435
|
const defaultOnErrCB = () => function onClose(error) {
|
|
433
436
|
commonUtils.setWSHealthStatus('Error');
|
|
434
|
-
statusUtils.addErrortoBuffer(error);
|
|
435
437
|
const self = this;
|
|
436
438
|
logger.error("Error while connecting to validator:", error.message)
|
|
437
439
|
const logMessage = new LogMessage.logMessage(SEVERE, 'Error while connecting to validator', __filename, error);
|
|
440
|
+
commonUtils.sendErrorToErrorInbox(error);
|
|
438
441
|
commonUtils.addLogEventtoBuffer(logMessage);
|
|
439
442
|
logger.warn("Security Agent INACTIVE!!!")
|
|
440
443
|
if (error && error.code && (error.code === 'ECONNREFUSED' || error.code === 'EPIPE' || error.code === 'ECONNRESET')) {
|
|
@@ -34,8 +34,8 @@ function BasicInfo() {
|
|
|
34
34
|
this.policyVersion = (!lodash.isEmpty(policyManager.getInstance().data)) ? policyManager.getInstance().data.version : 'DEFAULT';
|
|
35
35
|
|
|
36
36
|
if (NRAgent && NRAgent.config) {
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
37
|
+
this.appAccountId = NRAgent.config.account_id;
|
|
38
|
+
this.appEntityGuid = NRAgent.config.entity_guid;
|
|
39
39
|
this.linkingMetadata = LinkingMetaData.getLinkingMetadata();
|
|
40
40
|
this.linkingMetadata.agentRunId = NRAgent.config.run_id;
|
|
41
41
|
}
|
|
@@ -46,7 +46,8 @@ function HC(info) {
|
|
|
46
46
|
this.applicationUUID = info.applicationUUID;
|
|
47
47
|
this.eventType = 'sec_health_check_lc';
|
|
48
48
|
if (NRAgent && NRAgent.config) {
|
|
49
|
-
this.
|
|
49
|
+
this.appEntityGuid = NRAgent.config.entity_guid;
|
|
50
|
+
this.appAccountId = NRAgent.config.account_id;
|
|
50
51
|
}
|
|
51
52
|
this.protectedServer = NODE_SERVER;
|
|
52
53
|
this.eventDropCount = 0;
|
|
@@ -24,15 +24,14 @@ module.exports = {
|
|
|
24
24
|
CSEC_HOME_TMP: `${CSEC_HOME}${sep}nr-security-home${sep}tmp${sep}language-agent${sep}${process.env.applicationUUID}`,
|
|
25
25
|
AGENT_START_TIME: agentStartTime,
|
|
26
26
|
FRAMEWORK: '',
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
|
|
29
28
|
DB_LIST: {
|
|
30
29
|
MYSQL: 'MYSQL',
|
|
31
30
|
MYSQL2: 'MYSQL',
|
|
32
31
|
POSTGRES: 'POSTGRES',
|
|
33
32
|
MONGODB: 'MONGODB',
|
|
34
33
|
ORACLEDB: 'ORACLEDB',
|
|
35
|
-
MSSQL: 'MSSQL',
|
|
34
|
+
MSSQL: 'MSSQL',
|
|
36
35
|
SQLITE3: 'SQLITE3'
|
|
37
36
|
},
|
|
38
37
|
SERVER_COMMAND: {
|
|
@@ -9,16 +9,17 @@ 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 {
|
|
12
|
+
const { NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR, SEVERE, HYPHEN } = require('./sec-agent-constants');
|
|
13
13
|
const API = require('../../../nr-security-api');
|
|
14
14
|
const NRAgent = API.getNRAgent();
|
|
15
15
|
const HC = require('./health-check');
|
|
16
16
|
const LogMessage = require('./LogMessage');
|
|
17
17
|
const commonUtils = require('./commonUtils');
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const appInfo = require('./applicationinfo').getInstance();
|
|
20
20
|
|
|
21
21
|
let firstEventSent = false;
|
|
22
|
+
let appDir = EMPTY_STR;
|
|
22
23
|
|
|
23
24
|
const events = require('events');
|
|
24
25
|
const eventEmitter = undefined;
|
|
@@ -27,6 +28,20 @@ let logger = logs.getLogger();
|
|
|
27
28
|
const initLogger = logs.getInitLogger();
|
|
28
29
|
|
|
29
30
|
const NODE_SER = 'node-serialize/lib/serialize.js';
|
|
31
|
+
const SECURE_COOKIE = 'SECURE_COOKIE';
|
|
32
|
+
|
|
33
|
+
const insecureSettingApiIdSet = new Set();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Utility to remove insecureSettingAPIIds after 30 mins from the set.
|
|
37
|
+
* @param {*} entry
|
|
38
|
+
*/
|
|
39
|
+
function addInsecureSettingApiId(entry) {
|
|
40
|
+
insecureSettingApiIdSet.add(entry);
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
insecureSettingApiIdSet.delete(entry);
|
|
43
|
+
}, 30 * 60 * 1000);
|
|
44
|
+
}
|
|
30
45
|
|
|
31
46
|
function getSecEventEmitter() {
|
|
32
47
|
if (eventEmitter) {
|
|
@@ -68,6 +83,7 @@ const sleep = (timeout) => new Promise((resolve) => {
|
|
|
68
83
|
function generateSecEvent(securityMetadata) {
|
|
69
84
|
const traceObject = securityMetadata.traceObject;
|
|
70
85
|
const request = securityMetadata.request;
|
|
86
|
+
let appServerInfo = {};
|
|
71
87
|
|
|
72
88
|
if (request && request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
|
|
73
89
|
HC.getInstance().iastEventStats.processed++;
|
|
@@ -75,6 +91,16 @@ function generateSecEvent(securityMetadata) {
|
|
|
75
91
|
else {
|
|
76
92
|
HC.getInstance().raspEventStats.processed++;
|
|
77
93
|
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
appDir = appInfo.serverInfo.deployedApplications[0].deployedPath;
|
|
97
|
+
appServerInfo.applicationDirectory = appDir;
|
|
98
|
+
appServerInfo.serverBaseDirectory = appDir;
|
|
99
|
+
appServerInfo.connectionConfiguration = { [request.serverPort]: request.protocol };
|
|
100
|
+
|
|
101
|
+
} catch (error) {
|
|
102
|
+
}
|
|
103
|
+
|
|
78
104
|
try {
|
|
79
105
|
let stakTrace = traceObject.stacktrace;
|
|
80
106
|
let uri = EMPTY_STR;
|
|
@@ -84,8 +110,15 @@ function generateSecEvent(securityMetadata) {
|
|
|
84
110
|
|
|
85
111
|
let apiId = shaUtil.getSHA256ForData(traceObject.stacktrace.join('|') + uri);
|
|
86
112
|
apiId = securityMetadata.eventType + HYPHEN + apiId;
|
|
113
|
+
if (securityMetadata.eventType == SECURE_COOKIE) {
|
|
114
|
+
if (insecureSettingApiIdSet.has(apiId)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
addInsecureSettingApiId(apiId);
|
|
118
|
+
}
|
|
87
119
|
const agentModule = Agent.getAgent();
|
|
88
120
|
const metaData = {};
|
|
121
|
+
metaData.appServerInfo = appServerInfo;
|
|
89
122
|
if (traceObject.sourceDetails && traceObject.sourceDetails.evalObj) {
|
|
90
123
|
metaData.triggerViaRCI = true;
|
|
91
124
|
metaData.rciMethodsCalls = traceObject.sourceDetails.evalObj.invokedCalls;
|
|
@@ -104,7 +137,6 @@ function generateSecEvent(securityMetadata) {
|
|
|
104
137
|
event.httpRequest.route = uri;
|
|
105
138
|
return event;
|
|
106
139
|
} catch (e) {
|
|
107
|
-
statusUtils.addErrortoBuffer(e);
|
|
108
140
|
logger.error("Error in generating event:", e);
|
|
109
141
|
|
|
110
142
|
const logMessage = new LogMessage.logMessage(SEVERE, 'Error while generating event', __filename, e);
|
|
@@ -25,7 +25,6 @@ const {
|
|
|
25
25
|
} = require('./connections/websocket');
|
|
26
26
|
const { HC_INTERVAL_MS } = require('../../resources/config');
|
|
27
27
|
const { LOG_DIR, EXITEVENT, JSON_NAME } = require('./sec-agent-constants');
|
|
28
|
-
const statusUtils = require('./statusUtils');
|
|
29
28
|
const njsAgentConstants = require('./sec-agent-constants');
|
|
30
29
|
|
|
31
30
|
let wsInstance;
|
|
@@ -120,6 +119,10 @@ const sendHC = async function () {
|
|
|
120
119
|
json = JSON.parse(json);
|
|
121
120
|
json.policyVersion = applicationInfo.policyVersion;
|
|
122
121
|
json.entityGuid = applicationInfo.entityGuid;
|
|
122
|
+
if (NRAgent && NRAgent.config) {
|
|
123
|
+
json.appEntityGuid = NRAgent.config.entity_guid;
|
|
124
|
+
json.appAccountId = NRAgent.config.account_id;
|
|
125
|
+
}
|
|
123
126
|
|
|
124
127
|
try {
|
|
125
128
|
json.stats = await commonUtils.getHCStats();
|
|
@@ -131,7 +134,6 @@ const sendHC = async function () {
|
|
|
131
134
|
json.serviceStatus.websocket = commonUtils.getWSHealthStatus();
|
|
132
135
|
json.serviceStatus.logWriter = await commonUtils.isLogAccessible(`${LOG_DIR}node-security-collector.log`);
|
|
133
136
|
json.serviceStatus.initLogWriter = await commonUtils.isLogAccessible(`${LOG_DIR}node-security-collector-init.log`);
|
|
134
|
-
json.serviceStatus.statusLogWriter = await commonUtils.isLogAccessible(njsAgentConstants.STATUS_LOG_FILE);
|
|
135
137
|
json.serviceStatus.agentActiveStat = commonUtils.isAgentActiveState();
|
|
136
138
|
json.serviceStatus.iastRestClient = commonUtils.iastRestClientStatus();
|
|
137
139
|
|
|
@@ -148,8 +150,6 @@ const sendHC = async function () {
|
|
|
148
150
|
hc.getInstance().resetEventStats();
|
|
149
151
|
|
|
150
152
|
logger.info('Health Check Status:', JSON.stringify(json));
|
|
151
|
-
statusUtils.addHCtoBuffer(json);
|
|
152
|
-
statusUtils.writeSnapshot();
|
|
153
153
|
getDispatcherAndSendEvent(json);
|
|
154
154
|
}
|
|
155
155
|
};
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/security-agent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "New Relic Security Agent for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"jsonVersion": "1.2.
|
|
6
|
+
"jsonVersion": "1.2.5",
|
|
7
7
|
"contributors": [
|
|
8
8
|
{
|
|
9
9
|
"name": "Sumit Suthar",
|
|
10
10
|
"email": "ssuthar@newrelic.com",
|
|
11
|
-
"web": "
|
|
11
|
+
"web": "https://newrelic.com"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"name": "Pratik Gunjetiya",
|
|
15
15
|
"email": "pgunjetiya@newrelic.com",
|
|
16
|
-
"web": "
|
|
16
|
+
"web": "https://newrelic.com"
|
|
17
17
|
}
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"New Relic Security Agent",
|
|
29
29
|
"Node.js Application Security"
|
|
30
30
|
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18",
|
|
33
|
+
"npm": ">=6.0.0"
|
|
34
|
+
},
|
|
31
35
|
"author": "newrelic",
|
|
32
36
|
"license": "New Relic Software License v1.0",
|
|
33
37
|
"repository": {
|
|
@@ -35,13 +39,14 @@
|
|
|
35
39
|
"url": "https://github.com/newrelic/csec-node-agent.git"
|
|
36
40
|
},
|
|
37
41
|
"dependencies": {
|
|
38
|
-
"axios": "^1.
|
|
42
|
+
"axios": "^1.7.4",
|
|
39
43
|
"check-disk-space": "^3.4.0",
|
|
40
44
|
"content-type": "^1.0.5",
|
|
41
45
|
"fast-safe-stringify": "^2.1.1",
|
|
42
46
|
"find-package-json": "^1.2.0",
|
|
43
47
|
"hash.js": "^1.1.7",
|
|
44
48
|
"html-entities": "^2.3.6",
|
|
49
|
+
"https-proxy-agent": "^7.0.4",
|
|
45
50
|
"is-invalid-path": "^1.0.2",
|
|
46
51
|
"js-yaml": "^4.1.0",
|
|
47
52
|
"jsonschema": "^1.4.1",
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
-
* SPDX-License-Identifier: New Relic Software License v1.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/* eslint-disable new-cap */
|
|
7
|
-
|
|
8
|
-
const njsAgentConstants = require('./sec-agent-constants');
|
|
9
|
-
const agentConfig = require('../../resources/config');
|
|
10
|
-
const { AGENT_DIR } = agentConfig;
|
|
11
|
-
const lodash = require('lodash');
|
|
12
|
-
|
|
13
|
-
let errors = [];
|
|
14
|
-
|
|
15
|
-
const ringBuffer = require('ringbufferjs');
|
|
16
|
-
const bufferedHC = new ringBuffer(5);
|
|
17
|
-
const bufferedErrors = new ringBuffer(5);
|
|
18
|
-
const fs = require('fs');
|
|
19
|
-
const util = require('util');
|
|
20
|
-
let HC = [];
|
|
21
|
-
let lastHC = {};
|
|
22
|
-
const logs = require('./logging');
|
|
23
|
-
const commonUtils = require('./commonUtils');
|
|
24
|
-
const logger = logs.getLogger();
|
|
25
|
-
const statusFile = njsAgentConstants.STATUS_LOG_FILE;
|
|
26
|
-
const sep = require('path').sep;
|
|
27
|
-
|
|
28
|
-
const statusTemplate = 'Snapshot timestamp: : %s\n' +
|
|
29
|
-
`CSEC Agent start timestamp: ${njsAgentConstants.AGENT_START_TIME} with application uuid:${commonUtils.getUUID()}\n` +
|
|
30
|
-
`CSEC_HOME: ${njsAgentConstants.CSEC_HOME}${sep}nr-security-home${sep}\n` +
|
|
31
|
-
`Agent location: ${AGENT_DIR}\n` +
|
|
32
|
-
`Using CSEC Agent for Node.js, Node version:${process.version}, PID:${process.pid}\n` +
|
|
33
|
-
`Process title: ${process.title}\n` +
|
|
34
|
-
`Process binary: ${process.execPath}\n` +
|
|
35
|
-
'Application location: %s\n' +
|
|
36
|
-
`Current working directory: ${process.cwd()}\n` +
|
|
37
|
-
`Agent mode: ${commonUtils.getCSECmode()}\n` +
|
|
38
|
-
'Application server: %s\n' +
|
|
39
|
-
`Application Framework: %s\n` +
|
|
40
|
-
`Websocket connection to Prevent Web: ${commonUtils.getValidatorServiceEndpointURL()}, Status: %s\n` +
|
|
41
|
-
'Instrumentation successful\n' +
|
|
42
|
-
'Tracking loaded modules in the application\n' +
|
|
43
|
-
'Policy applied successfully. Policy version is: %s\n' +
|
|
44
|
-
'Started Health Check for Agent\n' +
|
|
45
|
-
'Started Inbound and Outbound monitoring \n' +
|
|
46
|
-
'\nProcess stats:\n%s\n' +
|
|
47
|
-
'\nService stats:\n%s\n' +
|
|
48
|
-
'\nLast 5 errors: \n%s\n' +
|
|
49
|
-
'\nLast 5 Health Checks are:\n %s \n';
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Utility to add hc in hc buffer
|
|
53
|
-
* @param {*} healthCheck
|
|
54
|
-
*/
|
|
55
|
-
function addHCtoBuffer(healthCheck) {
|
|
56
|
-
try {
|
|
57
|
-
bufferedHC.enq(healthCheck);
|
|
58
|
-
lastHC = healthCheck;
|
|
59
|
-
} catch (error) {
|
|
60
|
-
logger.debug(error);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Utility to add error object in error buffer
|
|
66
|
-
* @param {*} error
|
|
67
|
-
*/
|
|
68
|
-
function addErrortoBuffer(error) {
|
|
69
|
-
try {
|
|
70
|
-
let errorObj = {
|
|
71
|
-
'error': error.stack
|
|
72
|
-
}
|
|
73
|
-
bufferedErrors.enq(errorObj);
|
|
74
|
-
} catch (error) {
|
|
75
|
-
logger.debug(error);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* utility to format status template with dynamic values
|
|
81
|
-
*/
|
|
82
|
-
function getFormattedData() {
|
|
83
|
-
const Agent = require('./agent').Agent.getAgent();
|
|
84
|
-
const appInfo = Agent.applicationInfo;
|
|
85
|
-
const deployedApplications = appInfo && appInfo.serverInfo && appInfo.serverInfo.deployedApplications;
|
|
86
|
-
const appLoc = deployedApplications[0].deployedPath;
|
|
87
|
-
const formattedSnapshot = util.format(statusTemplate, new Date().toString(), appLoc, appInfo.serverInfo.name, commonUtils.getFramework(), commonUtils.getWSHealthStatus(), appInfo.policyVersion, getKeyValPairs(lastHC.stats), getKeyValPairs(lastHC.serviceStatus), JSON.stringify(getBufferedErrors()), JSON.stringify(getBufferedHC()));
|
|
88
|
-
return formattedSnapshot;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* utility to write snapshot in snapshot file
|
|
93
|
-
*/
|
|
94
|
-
function writeSnapshot() {
|
|
95
|
-
const snapshot = getFormattedData();
|
|
96
|
-
commonUtils.removeOlderSnapshots();
|
|
97
|
-
fs.writeFile(statusFile, snapshot, { mode: 0o660 }, function (err) {
|
|
98
|
-
if (err) {
|
|
99
|
-
logger.debug(err.message);
|
|
100
|
-
const LogMessage = require('./LogMessage');
|
|
101
|
-
const logMessage = new LogMessage.logMessage("DEBUG", 'Error in creating snapshot file', __filename, err);
|
|
102
|
-
commonUtils.addLogEventtoBuffer(logMessage);
|
|
103
|
-
} else {
|
|
104
|
-
logger.info('Snapshot updated to file: %s', statusFile);
|
|
105
|
-
fs.chmod(statusFile, 0o660, (err) => {
|
|
106
|
-
if (err) {
|
|
107
|
-
addErrortoBuffer(err);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* return buffered error instance.
|
|
115
|
-
*/
|
|
116
|
-
function getRingBufferedErrors() {
|
|
117
|
-
return bufferedErrors;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* return buffered HC list without null, undefined
|
|
122
|
-
*/
|
|
123
|
-
function getBufferedHC() {
|
|
124
|
-
try {
|
|
125
|
-
HC = lodash.compact(bufferedHC._elements);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
logger.debug(error);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return HC;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* returns buffered error list without null, undefined
|
|
134
|
-
*/
|
|
135
|
-
function getBufferedErrors() {
|
|
136
|
-
try {
|
|
137
|
-
errors = lodash.compact(bufferedErrors._elements);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
logger.debug(error);
|
|
140
|
-
}
|
|
141
|
-
return errors;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Utility to get key val pairs from json object
|
|
146
|
-
* @param {*} jsonObject
|
|
147
|
-
*/
|
|
148
|
-
function getKeyValPairs(jsonObject) {
|
|
149
|
-
let statStr = njsAgentConstants.EMPTY_STR;
|
|
150
|
-
for (const key in jsonObject) {
|
|
151
|
-
statStr = statStr + key + ': ' + jsonObject[key] + '\n';
|
|
152
|
-
}
|
|
153
|
-
return statStr;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
module.exports = {
|
|
157
|
-
statusTemplate,
|
|
158
|
-
addHCtoBuffer,
|
|
159
|
-
getFormattedData,
|
|
160
|
-
writeSnapshot,
|
|
161
|
-
addErrortoBuffer,
|
|
162
|
-
getRingBufferedErrors,
|
|
163
|
-
getBufferedHC,
|
|
164
|
-
getBufferedErrors
|
|
165
|
-
};
|