@newrelic/security-agent 1.5.0 → 2.1.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 +31 -0
- package/THIRD_PARTY_NOTICES.md +48 -1
- 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 +40 -8
- package/lib/instrumentation-security/core/sec-utils.js +6 -5
- package/lib/instrumentation-security/hooks/director/nr-director.js +6 -6
- package/lib/instrumentation-security/hooks/express/nr-express.js +11 -8
- package/lib/instrumentation-security/hooks/fastify/nr-fastify.js +6 -6
- package/lib/instrumentation-security/hooks/fs/nr-fs.js +11 -28
- package/lib/instrumentation-security/hooks/grpc-js/nr-grpc.js +4 -4
- package/lib/instrumentation-security/hooks/hapi/nr-hapi.js +7 -7
- package/lib/instrumentation-security/hooks/http/nr-http.js +137 -13
- package/lib/instrumentation-security/hooks/koa/@koa/nr-@koa-router.js +5 -5
- package/lib/instrumentation-security/hooks/ldap/nr-ldap.js +4 -0
- package/lib/instrumentation-security/hooks/mongodb/nr-mongodb.js +4 -0
- package/lib/instrumentation-security/hooks/mysql/nr-mysql.js +4 -0
- package/lib/instrumentation-security/hooks/native/nr-childProcess.js +4 -0
- package/lib/instrumentation-security/hooks/postgres/nr-postgres.js +4 -0
- package/lib/instrumentation-security/hooks/restify/nr-restify.js +7 -7
- package/lib/instrumentation-security/hooks/undici/nr-undici.js +4 -0
- package/lib/instrumentation-security/hooks/xpath/nr-xpath.js +4 -0
- package/lib/nr-security-agent/index.js +38 -5
- package/lib/nr-security-agent/lib/core/Auth-headers.js +20 -0
- package/lib/nr-security-agent/lib/core/ExceptionReporting.js +1 -0
- package/lib/nr-security-agent/lib/core/Policy.js +0 -12
- package/lib/nr-security-agent/lib/core/agent.js +5 -1
- package/lib/nr-security-agent/lib/core/applicationinfo.js +2 -2
- package/lib/nr-security-agent/lib/core/commonUtils.js +141 -45
- 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 +34 -6
- package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +36 -6
- package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js +5 -1
- package/lib/nr-security-agent/lib/core/connections/websocket/response/ws-reconnect.js +7 -4
- package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +32 -14
- package/lib/nr-security-agent/lib/core/event.js +2 -2
- package/lib/nr-security-agent/lib/core/health-check.js +8 -2
- package/lib/nr-security-agent/lib/core/sec-agent-constants.js +2 -3
- package/lib/nr-security-agent/lib/core/sec-util.js +40 -4
- package/lib/nr-security-agent/lib/core/websocket-client.js +35 -12
- package/lib/nr-security-api/index.js +47 -15
- package/package.json +10 -4
- package/lib/nr-security-agent/lib/core/statusUtils.js +0 -165
|
@@ -23,6 +23,23 @@ let websocketHealth;
|
|
|
23
23
|
|
|
24
24
|
const ringBuffer = require('ringbufferjs');
|
|
25
25
|
const bufferedLogEvents = new ringBuffer(10);
|
|
26
|
+
const { CronJob } = require('cron');
|
|
27
|
+
let scanStartTime = 0;
|
|
28
|
+
let trafficStartedTime = 0;
|
|
29
|
+
const cron = require('cron');
|
|
30
|
+
|
|
31
|
+
const skip_detection_category_map = {
|
|
32
|
+
insecure_settings: ['CRYPTO', 'HASH', 'RANDOM', 'SECURE_COOKIE', 'TRUSTBOUNDARY'],
|
|
33
|
+
invalid_file_access: ['FILE_OPERATION', 'FILE_INTEGRITY'],
|
|
34
|
+
sql_injection: ['SQL_DB_COMMAND'],
|
|
35
|
+
nosql_injection: ['NOSQL_DB_COMMAND'],
|
|
36
|
+
ldap_injection: ['LDAP'],
|
|
37
|
+
javascript_injection: ['JS_INJECTION'],
|
|
38
|
+
command_injection: ['SYSTEM_COMMAND'],
|
|
39
|
+
xpath_injection: ['XPATH'],
|
|
40
|
+
ssrf: ['HTTP_REQUEST'],
|
|
41
|
+
rxss: ['REFLECTED_XSS']
|
|
42
|
+
}
|
|
26
43
|
|
|
27
44
|
|
|
28
45
|
/**
|
|
@@ -133,7 +150,6 @@ function getOS() {
|
|
|
133
150
|
function createDirectories() {
|
|
134
151
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home`);
|
|
135
152
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs`);
|
|
136
|
-
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs${SLASH}snapshots`);
|
|
137
153
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp`);
|
|
138
154
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent`);
|
|
139
155
|
createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent${SLASH}${getUUID()}`);
|
|
@@ -159,14 +175,11 @@ async function initialSetup() {
|
|
|
159
175
|
*/
|
|
160
176
|
function isLogAccessible(logFileName) {
|
|
161
177
|
return new Promise((resolve) => {
|
|
162
|
-
if (logFileName === njsAgentConstants.STATUS_LOG_FILE && require('./statusUtils').getBufferedHC().length === 0) {
|
|
163
|
-
resolve('OK');
|
|
164
|
-
}
|
|
165
178
|
fs.access(logFileName, fs.constants.F_OK | fs.constants.W_OK, (err) => {
|
|
166
179
|
if (err) {
|
|
167
180
|
resolve('Error');
|
|
168
181
|
}
|
|
169
|
-
else if ((!logs.isLoggerOk || logger.level.levelStr == 'OFF')
|
|
182
|
+
else if ((!logs.isLoggerOk || logger.level.levelStr == 'OFF')) {
|
|
170
183
|
resolve('Error');
|
|
171
184
|
}
|
|
172
185
|
else {
|
|
@@ -256,42 +269,6 @@ function isAgentActiveState() {
|
|
|
256
269
|
function iastRestClientStatus() {
|
|
257
270
|
return 'OK';
|
|
258
271
|
}
|
|
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
272
|
/**
|
|
296
273
|
* Utilty to remove older log files.
|
|
297
274
|
*/
|
|
@@ -311,7 +288,7 @@ function removeOlderlogfiles() {
|
|
|
311
288
|
return new Date(secondStat.mtime).getTime() - new Date(firstStat.mtime).getTime();
|
|
312
289
|
});
|
|
313
290
|
let maxLogfile = process.env.NR_CSEC_DEBUG_LOGFILE_MAX_COUNT;
|
|
314
|
-
if(isNaN(maxLogfile)){
|
|
291
|
+
if (isNaN(maxLogfile)) {
|
|
315
292
|
maxLogfile = 2;
|
|
316
293
|
}
|
|
317
294
|
for (let i = maxLogfile; i < sorted.length; i++) {
|
|
@@ -449,7 +426,7 @@ function addLogEventtoBuffer(logMessage) {
|
|
|
449
426
|
* Utility to get framework from APM
|
|
450
427
|
* @returns
|
|
451
428
|
*/
|
|
452
|
-
function getFramework(){
|
|
429
|
+
function getFramework() {
|
|
453
430
|
let framework = EMPTY_STR;
|
|
454
431
|
try {
|
|
455
432
|
framework = NRAgent.environment.get('Framework')[0];
|
|
@@ -459,6 +436,119 @@ function getFramework(){
|
|
|
459
436
|
return framework;
|
|
460
437
|
}
|
|
461
438
|
|
|
439
|
+
function listSkipDetectionCategory() {
|
|
440
|
+
let skipList = [];
|
|
441
|
+
try {
|
|
442
|
+
let skipCataegories = NRAgent.config.security.exclude_from_iast_scan.iast_detection_category;
|
|
443
|
+
for (const key in skipCataegories) {
|
|
444
|
+
if (skipCataegories[key]) {
|
|
445
|
+
skipList = skipList.concat(skip_detection_category_map[key]);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} catch (error) {
|
|
449
|
+
logger.error("Error while processing skip detection categories:", error);
|
|
450
|
+
}
|
|
451
|
+
return skipList;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function refreshAgent() {
|
|
455
|
+
const { Agent } = require('./agent');
|
|
456
|
+
if (Agent.getAgent().status.getStatus() == 'disabled' || Agent.getAgent().delayed) {
|
|
457
|
+
Agent.getAgent().delayed = false;
|
|
458
|
+
const wsClient = Agent.getAgent().client;
|
|
459
|
+
Agent.getAgent().status.setStatus('connecting');
|
|
460
|
+
wsClient.obeyReconnect();
|
|
461
|
+
honourDurationConfiguration();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function executeCronSchedule() {
|
|
466
|
+
try {
|
|
467
|
+
let schedule = NRAgent.config.security.scan_schedule.schedule;
|
|
468
|
+
if (lodash.isEmpty(schedule)) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
logger.debug("Schedule config is set to:", schedule);
|
|
472
|
+
schedule = schedule.replace(/\?/g, '*');
|
|
473
|
+
logger.info("Security Agent scheduled time is set to:", getScheduledScanTime(Math.ceil(cron.timeout(schedule) / 60000)))
|
|
474
|
+
const job = new cron.CronJob(
|
|
475
|
+
schedule, // cronTime
|
|
476
|
+
function () {
|
|
477
|
+
logger.debug('Cron schedule invoked');
|
|
478
|
+
logger.info("Security Agent scheduled time is set to:", getScheduledScanTime(Math.ceil(cron.timeout(schedule) / 60000)))
|
|
479
|
+
refreshAgent()
|
|
480
|
+
}, // onTick
|
|
481
|
+
null, // onComplete
|
|
482
|
+
true, // start
|
|
483
|
+
);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
logger.error("Error while processing schedule. Please check schedule cron expression", error);
|
|
486
|
+
shutDownAgent();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function honourDurationConfiguration() {
|
|
492
|
+
let durationFromConfig = parseInt(NRAgent.config.security.scan_schedule.duration);
|
|
493
|
+
let duration = durationFromConfig;
|
|
494
|
+
if (isNaN(duration) || duration < 0) {
|
|
495
|
+
duration = 0;
|
|
496
|
+
}
|
|
497
|
+
logger.debug("IAST duration is set to:", duration);
|
|
498
|
+
NRAgent.config.security.scan_schedule.duration = duration;
|
|
499
|
+
if (duration < 1) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
logger.info("Security Agent shutdown is set to:", getScheduledScanTime(duration))
|
|
503
|
+
setTimeout(() => {
|
|
504
|
+
shutDownAgent();
|
|
505
|
+
}, duration * 60000);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Utility to send error to error inbox of APM
|
|
509
|
+
* @param {*} error
|
|
510
|
+
*/
|
|
511
|
+
function sendErrorToErrorInbox(error) {
|
|
512
|
+
API.newrelic.noticeError(error);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const getScheduledScanTime = (delayInMinutes) => {
|
|
516
|
+
// Get the current time
|
|
517
|
+
const currentTime = new Date();
|
|
518
|
+
|
|
519
|
+
// Add the given minutes to the current time
|
|
520
|
+
currentTime.setMinutes(currentTime.getMinutes() + delayInMinutes);
|
|
521
|
+
|
|
522
|
+
const year = currentTime.getFullYear();
|
|
523
|
+
const month = currentTime.toLocaleString("default", { month: "long" });
|
|
524
|
+
const day = currentTime.toLocaleString("default", { weekday: "long" });
|
|
525
|
+
const date = currentTime.getDate();
|
|
526
|
+
const hours = currentTime.getHours();
|
|
527
|
+
const minutes = currentTime.getMinutes();
|
|
528
|
+
const seconds = currentTime.getSeconds();
|
|
529
|
+
const futureUpdatedTime = `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
530
|
+
const futureDateTime = `${day}, ${month} ${date} ${year} ${futureUpdatedTime}`;
|
|
531
|
+
return futureDateTime
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
function shutDownAgent() {
|
|
535
|
+
const { Agent } = require('./agent');
|
|
536
|
+
if (NRAgent.config.security.scan_schedule.always_sample_traces) {
|
|
537
|
+
Agent.getAgent().delayed = true;
|
|
538
|
+
logger.warn('Scan duration completed, IAST going under hibernate mode')
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (Agent.getAgent().status.getStatus() === 'disabled') {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
Agent.getAgent().status.setStatus('disabled');
|
|
545
|
+
const wsClient = Agent.getAgent().client;
|
|
546
|
+
wsClient.closeWS();
|
|
547
|
+
logger.warn("Security Agent status is disabled");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
|
|
462
552
|
|
|
463
553
|
module.exports = {
|
|
464
554
|
getUUID,
|
|
@@ -475,7 +565,6 @@ module.exports = {
|
|
|
475
565
|
getHCStats,
|
|
476
566
|
isAgentActiveState,
|
|
477
567
|
iastRestClientStatus,
|
|
478
|
-
removeOlderSnapshots,
|
|
479
568
|
logRollOver,
|
|
480
569
|
getValidatorServiceEndpointURL,
|
|
481
570
|
getCSECmode,
|
|
@@ -484,5 +573,12 @@ module.exports = {
|
|
|
484
573
|
setWSHealthStatus,
|
|
485
574
|
addLogEventtoBuffer,
|
|
486
575
|
getLogEvents,
|
|
487
|
-
getFramework
|
|
576
|
+
getFramework,
|
|
577
|
+
listSkipDetectionCategory,
|
|
578
|
+
executeCronSchedule,
|
|
579
|
+
honourDurationConfiguration,
|
|
580
|
+
scanStartTime,
|
|
581
|
+
trafficStartedTime,
|
|
582
|
+
sendErrorToErrorInbox,
|
|
583
|
+
getScheduledScanTime,
|
|
488
584
|
};
|
|
@@ -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
|
+
}
|
|
@@ -6,8 +6,13 @@ const { Agent } = require('../../../agent');
|
|
|
6
6
|
let pendingRequestIds = new Set();
|
|
7
7
|
let completedRequestsMap = new Map();
|
|
8
8
|
const PolicyManager = require('../../../Policy');
|
|
9
|
+
const API = require('../../../../../../nr-security-api');
|
|
9
10
|
let batchSize;
|
|
10
|
-
const batchthreshold =
|
|
11
|
+
const batchthreshold = 200;
|
|
12
|
+
const logs = require('../../../logging');
|
|
13
|
+
let logger = logs.getLogger();
|
|
14
|
+
const commonUtils = require('../../../commonUtils');
|
|
15
|
+
|
|
11
16
|
function getPendingRequestIds() {
|
|
12
17
|
return [...pendingRequestIds];
|
|
13
18
|
}
|
|
@@ -31,7 +36,7 @@ function completedRequestsMapInit(id) {
|
|
|
31
36
|
|
|
32
37
|
function addCompletedRequests(id, eventId) {
|
|
33
38
|
let data = completedRequestsMap.get(id);
|
|
34
|
-
if(!data){
|
|
39
|
+
if (!data) {
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
37
42
|
data.push(eventId);
|
|
@@ -42,20 +47,21 @@ function getCompletedRequestsMap() {
|
|
|
42
47
|
return completedRequestsMap;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
function clearCompletedRequestMap(){
|
|
50
|
+
function clearCompletedRequestMap() {
|
|
46
51
|
completedRequestsMap.clear();
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
function clearPendingRequestIdSet(){
|
|
54
|
+
function clearPendingRequestIdSet() {
|
|
50
55
|
pendingRequestIds.clear();
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
function IASTCleanup(){
|
|
58
|
+
function IASTCleanup() {
|
|
54
59
|
clearCompletedRequestMap();
|
|
55
60
|
clearPendingRequestIdSet();
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
function generateIASTDataRequest() {
|
|
64
|
+
|
|
59
65
|
const policyInstance = PolicyManager.getInstance();
|
|
60
66
|
if (policyInstance.data && policyInstance.data.vulnerabilityScan && policyInstance.data.vulnerabilityScan.iastScan) {
|
|
61
67
|
batchSize = batchSize ? batchSize : policyInstance.data.vulnerabilityScan.iastScan.probing.batchSize;
|
|
@@ -64,11 +70,33 @@ function generateIASTDataRequest() {
|
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
if (getCompletedRequestsMap().size >= batchSize || batchSize < batchthreshold || getPendingRequestIds().length < batchSize) {
|
|
67
|
-
batchSize = (2 * batchSize);
|
|
73
|
+
batchSize = (2 * batchSize) - getPendingRequestIds().length;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const NRAgent = API.getNRAgent();
|
|
77
|
+
let scanRateLimit = 3600;
|
|
78
|
+
let scanRateLimitFromConfig = parseInt(NRAgent.config.security.scan_controllers.iast_scan_request_rate_limit);
|
|
79
|
+
scanRateLimit = scanRateLimitFromConfig
|
|
80
|
+
if (isNaN(scanRateLimitFromConfig)) {
|
|
81
|
+
scanRateLimit = 3600;
|
|
68
82
|
}
|
|
83
|
+
if (scanRateLimit >= 0 || scanRateLimit < 0) {
|
|
84
|
+
batchSize = parseInt(scanRateLimit / 12);
|
|
85
|
+
if (scanRateLimit < 12) {
|
|
86
|
+
batchSize = 1;
|
|
87
|
+
} else if (scanRateLimit > 3600) {
|
|
88
|
+
batchSize = 300;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
69
92
|
let object = {};
|
|
70
93
|
object['jsonName'] = 'iast-data-request';
|
|
71
94
|
object['applicationUUID'] = Agent.getAgent().applicationInfo.applicationUUID;
|
|
95
|
+
object['appEntityGuid'] = Agent.getAgent().applicationInfo.appEntityGuid;
|
|
96
|
+
object['appAccountId'] = Agent.getAgent().applicationInfo.appAccountId;
|
|
97
|
+
if (batchSize && batchSize > 100) {
|
|
98
|
+
batchSize = 100;
|
|
99
|
+
}
|
|
72
100
|
object["batchSize"] = batchSize ? batchSize : 300;
|
|
73
101
|
object['pendingRequestIds'] = getPendingRequestIds();
|
|
74
102
|
object['completedRequests'] = Object.fromEntries(getCompletedRequestsMap());
|
package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js
CHANGED
|
@@ -21,10 +21,12 @@ 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');
|
|
27
|
+
const commonUtils = require('../../../commonUtils');
|
|
28
|
+
|
|
29
|
+
const CSEC_HOME_TMP_CONST_ENCODED = new RegExp('%7B%7BNR_CSEC_VALIDATOR_HOME_TMP%7D%7D', 'g');
|
|
28
30
|
|
|
29
31
|
let logger;
|
|
30
32
|
let lastFuzzEventTime = 0;
|
|
@@ -32,13 +34,26 @@ let iastIntervalConst;
|
|
|
32
34
|
let coolDownIntervalConst;
|
|
33
35
|
let additionalCoolDownTime = 0;
|
|
34
36
|
let fuzzedApiIDSet = new Set();
|
|
37
|
+
const API = require('../../../../../../nr-security-api');
|
|
38
|
+
const NRAgent = API.getNRAgent();
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* utility to start IAST Schedular
|
|
39
43
|
*/
|
|
40
44
|
function startIASTSchedular() {
|
|
41
|
-
Agent.getAgent().
|
|
45
|
+
if (Agent.getAgent().delayed) {
|
|
46
|
+
let delayFromConfig = parseInt(NRAgent.config.security.scan_schedule.delay);
|
|
47
|
+
let delay = delayFromConfig;
|
|
48
|
+
if (isNaN(delay) || delay < 0) {
|
|
49
|
+
delay = 0;
|
|
50
|
+
}
|
|
51
|
+
// logger.debug("IAST data pull request is scheduled at %s", require('../../../commonUtils').getScheduledScanTime(delay))
|
|
52
|
+
}
|
|
53
|
+
if (!Agent.getAgent().delayed) {
|
|
54
|
+
Agent.getAgent().client.dispatcher(IASTUtil.generateIASTDataRequest());
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
if (iastIntervalConst) {
|
|
43
58
|
clearInterval(iastIntervalConst);
|
|
44
59
|
}
|
|
@@ -51,8 +66,11 @@ function startIASTSchedular() {
|
|
|
51
66
|
if (isNaN(probingInterval)) {
|
|
52
67
|
probingInterval = 5;
|
|
53
68
|
}
|
|
54
|
-
|
|
69
|
+
|
|
55
70
|
iastIntervalConst = setInterval(() => {
|
|
71
|
+
if (Agent.getAgent().status.getStatus() == 'disabled' || Agent.getAgent().delayed) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
56
74
|
let data = IASTUtil.generateIASTDataRequest();
|
|
57
75
|
let currentTime = Date.now();
|
|
58
76
|
let completedListSize = IASTUtil.getCompletedRequestsMap().size;
|
|
@@ -87,9 +105,19 @@ function logScannedApiId(fuzzHeader, requestURL) {
|
|
|
87
105
|
|
|
88
106
|
// handler function to parse fuzz event and prepare fuzz request
|
|
89
107
|
function handler(json) {
|
|
108
|
+
if (Agent.getAgent().status.getStatus() == 'disabled') {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
90
111
|
setLastFuzzEventTime();
|
|
91
112
|
let rawFuzzRequest = json.arguments[0];
|
|
92
|
-
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
rawFuzzRequest = rawFuzzRequest.replace(CSEC_HOME_TMP_CONST, CSEC_HOME_TMP);
|
|
116
|
+
rawFuzzRequest = rawFuzzRequest.replace(CSEC_HOME_TMP_CONST_ENCODED, encodeURI(CSEC_HOME_TMP));
|
|
117
|
+
} catch (error) {
|
|
118
|
+
logger.debug("Error while replacing place holder", error);
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
let fuzzRequest;
|
|
94
122
|
try {
|
|
95
123
|
fuzzRequest = JSON.parse(rawFuzzRequest);
|
|
@@ -97,7 +125,7 @@ function handler(json) {
|
|
|
97
125
|
IASTUtil.addPendingRequestId(json.id);
|
|
98
126
|
} catch (error) {
|
|
99
127
|
logger.error('Parsing exeception in fuzz request: ', error);
|
|
100
|
-
|
|
128
|
+
|
|
101
129
|
const logMessage = new LogMessage.logMessage(SEVERE, 'Parsing exeception in fuzz request', __filename, error);
|
|
102
130
|
require('../../../commonUtils').addLogEventtoBuffer(logMessage);
|
|
103
131
|
}
|
|
@@ -158,7 +186,6 @@ function handleFuzzRequest(fuzzDetails) {
|
|
|
158
186
|
handleFuzzResponse(response, fuzzDetails);
|
|
159
187
|
}
|
|
160
188
|
} catch (err) {
|
|
161
|
-
statusUtils.addErrortoBuffer(err);
|
|
162
189
|
const fuzzFailEvent = new FuzzFailEvent(fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]);
|
|
163
190
|
logger.error(stringify(fuzzFailEvent));
|
|
164
191
|
try {
|
|
@@ -179,6 +206,9 @@ function handleFuzzResponse(response, fuzzDetails) {
|
|
|
179
206
|
const { rawFuzzRequest, fuzzRequest } = fuzzDetails;
|
|
180
207
|
if (response) {
|
|
181
208
|
IASTUtil.removePendingRequestId(fuzzRequest.id);
|
|
209
|
+
if (commonUtils.scanStartTime == 0) {
|
|
210
|
+
commonUtils.scanStartTime = Date.now();
|
|
211
|
+
}
|
|
182
212
|
response.then(() => {
|
|
183
213
|
logger.info('Fuzz success: ' + rawFuzzRequest);
|
|
184
214
|
}).catch(() => {
|
package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js
CHANGED
|
@@ -10,6 +10,10 @@ const initLogger = logs.getInitLogger();
|
|
|
10
10
|
const LOG_MESSAGES = require('../../../sec-agent-constants').LOG_MESSAGES;
|
|
11
11
|
const applicationInfoModule = require('../../../applicationinfo');
|
|
12
12
|
const LogMessage = require('../../../LogMessage');
|
|
13
|
+
const API = require('../../../../../../nr-security-api');
|
|
14
|
+
const NRAgent = API.getNRAgent();
|
|
15
|
+
const { Agent } = require('../../../agent');
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
module.exports.handler = function handler(json) {
|
|
15
19
|
try {
|
|
@@ -20,7 +24,7 @@ module.exports.handler = function handler(json) {
|
|
|
20
24
|
initLogger.info('Security Agent is now ACTIVE for', applicationInfoModule.getInstance().applicationUUID);
|
|
21
25
|
const logMessage = new LogMessage.logMessage("INFO", `Security Agent is ACTIVE for ${ applicationInfoModule.getInstance().applicationUUID}`, __filename, null);
|
|
22
26
|
require('../../../commonUtils').addLogEventtoBuffer(logMessage);
|
|
23
|
-
|
|
27
|
+
|
|
24
28
|
} catch (error) {
|
|
25
29
|
logger.error(LOG_MESSAGES.PARSING_EXP_WELCOME_MSG, error);
|
|
26
30
|
}
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
let logger;
|
|
7
|
+
const { Agent } = require('../../../agent');
|
|
7
8
|
module.exports.handler = function handler (json) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (Agent.getAgent().status.getStatus() != 'disabled'){
|
|
10
|
+
logger.info('WS reconnect received', json);
|
|
11
|
+
const wsClient = Agent.getAgent().client;
|
|
12
|
+
wsClient.obeyReconnect();
|
|
13
|
+
}
|
|
14
|
+
|
|
12
15
|
};
|
|
13
16
|
|
|
14
17
|
module.exports.setLogger = l => { logger = l };
|
|
@@ -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,12 +169,16 @@ 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
|
|
|
168
176
|
};
|
|
169
177
|
|
|
170
178
|
SecWebSocket.prototype.obeyReconnect = function obeyReconnect() {
|
|
179
|
+
if (Agent.getAgent().status.getStatus() == 'disabled') {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
171
182
|
const { instance } = this;
|
|
172
183
|
if (initTimeOut) {
|
|
173
184
|
clearTimeout(initTimeOut);
|
|
@@ -186,13 +197,25 @@ SecWebSocket.prototype.obeyReconnect = function obeyReconnect() {
|
|
|
186
197
|
|
|
187
198
|
}
|
|
188
199
|
|
|
200
|
+
SecWebSocket.prototype.closeWS = function closeWS() {
|
|
201
|
+
const { instance } = this;
|
|
202
|
+
try {
|
|
203
|
+
instance.close();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
logger.error("Error while closing websocket", error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
189
209
|
SecWebSocket.prototype.pingWS = async function pingWS() {
|
|
210
|
+
if (Agent.getAgent().status.getStatus() != 'active') {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
190
213
|
const self = this;
|
|
191
214
|
let currentTime = Date.now();
|
|
192
215
|
|
|
193
216
|
let timeDiffInPing = (currentTime - lastPongtime) / 1000;
|
|
194
|
-
logger.debug("Websocket ready state is:",self.instance.readyState)
|
|
195
|
-
|
|
217
|
+
logger.debug("Websocket ready state is:", self.instance.readyState)
|
|
218
|
+
|
|
196
219
|
if (timeDiffInPing > 40 && lastPongtime != 0 && self.instance.readyState > 1) {
|
|
197
220
|
self.obeyReconnect();
|
|
198
221
|
lastPongtime = 0;
|
|
@@ -204,13 +227,11 @@ SecWebSocket.prototype.pingWS = async function pingWS() {
|
|
|
204
227
|
if (err) {
|
|
205
228
|
logger.debug("Error while pinging:", err);
|
|
206
229
|
commonUtils.setWSHealthStatus('Error');
|
|
207
|
-
statusUtils.addErrortoBuffer(err);
|
|
208
230
|
|
|
209
231
|
}
|
|
210
232
|
});
|
|
211
233
|
} catch (err) {
|
|
212
234
|
logger.debug("error in ping:", err);
|
|
213
|
-
statusUtils.addErrortoBuffer(err);
|
|
214
235
|
commonUtils.setWSHealthStatus('Error');
|
|
215
236
|
return;
|
|
216
237
|
}
|
|
@@ -266,7 +287,6 @@ SecWebSocket.prototype.dispatch = async function dispatch(event) {
|
|
|
266
287
|
})
|
|
267
288
|
} catch (error) {
|
|
268
289
|
logger.debug(LOG_MESSAGES.ERROR_WHILE_SEND_EVENT, eventStr, error);
|
|
269
|
-
statusUtils.addErrortoBuffer(error);
|
|
270
290
|
handleDispatchFailure(this, event, eventStr);
|
|
271
291
|
}
|
|
272
292
|
};
|
|
@@ -286,7 +306,6 @@ SecWebSocket.prototype.dispatchApplicationInfo = async function dispatchApplicat
|
|
|
286
306
|
try {
|
|
287
307
|
await promisify(this.instance, this.instance.send)(eventStr, { mask: true });
|
|
288
308
|
} catch (error) {
|
|
289
|
-
statusUtils.addErrortoBuffer(error);
|
|
290
309
|
logger.warn('Error while sending applicationInfo:', error);
|
|
291
310
|
this.obeyReconnect();
|
|
292
311
|
}
|
|
@@ -325,7 +344,6 @@ SecWebSocket.prototype.flushEventQueue = async function flushEventQueue() {
|
|
|
325
344
|
const results = await Promise.all(sentEvents);
|
|
326
345
|
hc.getInstance().registerEventsSent(results.length);
|
|
327
346
|
} catch (err) {
|
|
328
|
-
statusUtils.addErrortoBuffer(err);
|
|
329
347
|
logger.debug('Flush failed, reception of all events on IC couldn\'t be ensured.');
|
|
330
348
|
}
|
|
331
349
|
};
|
|
@@ -398,7 +416,7 @@ const defaultOnOpenCB = () => async function onOpen() {
|
|
|
398
416
|
initLogger.info(LOG_MESSAGES.SENDING_APPINFO_COMPLETE, JSON.stringify(applicationInfo));
|
|
399
417
|
} catch (err) {
|
|
400
418
|
logger.error(`Connection broken: ${err.message}`);
|
|
401
|
-
|
|
419
|
+
commonUtils.sendErrorToErrorInbox(err);
|
|
402
420
|
commonUtils.setWSHealthStatus('Error')
|
|
403
421
|
setWebSocketConn();
|
|
404
422
|
self.setValidatorNotReadyForEvents();
|
|
@@ -431,10 +449,10 @@ const defaultOnMsgCB = () => function onMessage(data) {
|
|
|
431
449
|
*/
|
|
432
450
|
const defaultOnErrCB = () => function onClose(error) {
|
|
433
451
|
commonUtils.setWSHealthStatus('Error');
|
|
434
|
-
statusUtils.addErrortoBuffer(error);
|
|
435
452
|
const self = this;
|
|
436
453
|
logger.error("Error while connecting to validator:", error.message)
|
|
437
454
|
const logMessage = new LogMessage.logMessage(SEVERE, 'Error while connecting to validator', __filename, error);
|
|
455
|
+
commonUtils.sendErrorToErrorInbox(error);
|
|
438
456
|
commonUtils.addLogEventtoBuffer(logMessage);
|
|
439
457
|
logger.warn("Security Agent INACTIVE!!!")
|
|
440
458
|
if (error && error.code && (error.code === 'ECONNREFUSED' || error.code === 'EPIPE' || error.code === 'ECONNRESET')) {
|