@newrelic/security-agent 1.2.0 → 1.4.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 +25 -0
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +212 -211
- package/lib/instrumentation-security/core/constants.js +1 -0
- package/lib/instrumentation-security/core/grpc-utils.js +53 -0
- package/lib/instrumentation-security/core/security-metadata.js +12 -2
- package/lib/instrumentation-security/hooks/fs/nr-fs.js +4 -4
- package/lib/instrumentation-security/hooks/grpc-js/nr-grpc.js +342 -0
- package/lib/instrumentation-security/hooks/http/nr-http.js +46 -6
- package/lib/instrumentation-security/index.js +32 -3
- package/lib/nr-security-agent/index.js +7 -1
- package/lib/nr-security-agent/lib/core/ExceptionReporting.js +182 -0
- package/lib/nr-security-agent/lib/core/LogMessage.js +4 -1
- package/lib/nr-security-agent/lib/core/commonUtils.js +2 -2
- package/lib/nr-security-agent/lib/core/connections/grpcClient.js +112 -0
- package/lib/nr-security-agent/lib/core/connections/restclient.js +6 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/IASTUtils.js +3 -0
- package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +32 -12
- package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js +1 -1
- package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +9 -4
- package/lib/nr-security-agent/lib/core/event.js +1 -0
- package/lib/nr-security-agent/lib/core/health-check.js +0 -1
- package/lib/nr-security-agent/lib/core/sec-util.js +1 -0
- package/lib/nr-security-agent/lib/core/statusUtils.js +1 -1
- package/lib/nr-security-agent/lib/core/websocket-client.js +2 -1
- package/lib/nr-security-agent/lib/core/xss-utils.js +11 -5
- package/package.json +4 -3
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: New Relic Pre-Release
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const requestManager = require("../../core/request-manager");
|
|
8
|
+
const secUtils = require("../../core/sec-utils");
|
|
9
|
+
const API = require("../../../nr-security-api");
|
|
10
|
+
const securityMetaData = require('../../core/security-metadata');
|
|
11
|
+
const { EVENT_TYPE, EVENT_CATEGORY } = require('../../core/event-constants');
|
|
12
|
+
const { NR_CSEC_FUZZ_REQUEST_ID, DOUBLE_DOLLAR, ATTHERATE, ASTERISK } = require('../../core/constants');
|
|
13
|
+
const logger = API.getLogger();
|
|
14
|
+
const semver = require('semver');
|
|
15
|
+
const grpcutils = require('../../core/grpc-utils');
|
|
16
|
+
const routeManager = require('../../core/route-manager');
|
|
17
|
+
let serverAddress;
|
|
18
|
+
const ExceptionReporting = require('../../../nr-security-agent/lib/core/ExceptionReporting');
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
module.exports.initialize = function initialize(shim, grpc, moduleName) {
|
|
23
|
+
logger.info('Instrumenting grpc', moduleName);
|
|
24
|
+
const grpcVersion = shim.require('./package.json').version;
|
|
25
|
+
logger.debug("grpc version:", grpcVersion);
|
|
26
|
+
|
|
27
|
+
if (semver.lt(grpcVersion, '1.4.0')) {
|
|
28
|
+
logger.warn('gRPC server-side instrumentation only supported on grpc-js >=1.4.0')
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (semver.gte(grpcVersion, '1.8.0')) {
|
|
33
|
+
const resolvingCall = shim.require('./build/src/resolving-call')
|
|
34
|
+
shim.wrap(resolvingCall.ResolvingCall.prototype, 'start', wrapStart)
|
|
35
|
+
} else {
|
|
36
|
+
const callStream = shim.require('./build/src/call-stream')
|
|
37
|
+
shim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const server = shim.require('./build/src/server')
|
|
41
|
+
shim.wrap(server.Server.prototype, 'bindAsync', bindAsyncWrapper)
|
|
42
|
+
shim.wrap(server.Server.prototype, 'register', wrapRegister)
|
|
43
|
+
|
|
44
|
+
//get all service
|
|
45
|
+
shim.wrap(server.Server.prototype, 'addService', wrapAddService);
|
|
46
|
+
|
|
47
|
+
//load package definition
|
|
48
|
+
const makeClient = shim.require('./build/src/make-client');
|
|
49
|
+
shim.wrap(makeClient, 'loadPackageDefinition', wrapLoadPackageDefinition);
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports.wrapStartResolve = function wrappedStartResolve(shim, resolvingCall) {
|
|
54
|
+
shim.wrap(resolvingCall.ResolvingCall.prototype, 'start', wrapStart)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports.wrapStartCall = function wrappedClient(shim, callStream) {
|
|
58
|
+
shim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports.wrapServer = function wrappedServer(shim, server) {
|
|
62
|
+
const grpcVersion = shim.require('./package.json').version
|
|
63
|
+
logger.info("Instrumenting @grpc/grpc-js", grpcVersion)
|
|
64
|
+
if (semver.lt(grpcVersion, '1.4.0')) {
|
|
65
|
+
logger.warn('gRPC server-side instrumentation only supported on grpc-js >=1.4.0')
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
shim.wrap(server.Server.prototype, 'register', wrapRegister)
|
|
70
|
+
shim.wrap(server.Server.prototype, 'bindAsync', bindAsyncWrapper)
|
|
71
|
+
//get all service
|
|
72
|
+
shim.wrap(server.Server.prototype, 'addService', wrapAddService);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports.wrapMakeClient = function wrapMakeClient(shim, makeClient) {
|
|
76
|
+
shim.wrap(makeClient, 'loadPackageDefinition', wrapLoadPackageDefinition);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Instruments grpc-js client by intercepting the function that
|
|
82
|
+
* initiates client requests. This handles all four types of client
|
|
83
|
+
* invocations: unary, client-streaming, server-streaming, and
|
|
84
|
+
* bidirectional streaming.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} shim the generic shim to instrument with
|
|
87
|
+
* @param {Function} original the original function
|
|
88
|
+
* @returns {Function} the instrumented function
|
|
89
|
+
*/
|
|
90
|
+
function wrapStart(shim, original) {
|
|
91
|
+
if (!shim.isFunction(original)) {
|
|
92
|
+
return original
|
|
93
|
+
}
|
|
94
|
+
return function wrappedStart() {
|
|
95
|
+
const channel = this.channel;
|
|
96
|
+
const authorityName = (channel.target && channel.target.path) || channel.getDefaultAuthority;
|
|
97
|
+
// in 1.8.0 this changed from methodName to method
|
|
98
|
+
const method = this.methodName || this.method;
|
|
99
|
+
const protocol = 'grpc:';
|
|
100
|
+
const url = `${protocol}//${authorityName}${method}`;
|
|
101
|
+
logger.debug("grpc url is:", url)
|
|
102
|
+
const request = requestManager.getRequest(shim);
|
|
103
|
+
let urlObj = {
|
|
104
|
+
"headers":
|
|
105
|
+
{
|
|
106
|
+
"host": authorityName
|
|
107
|
+
},
|
|
108
|
+
"method": method,
|
|
109
|
+
"path": method,
|
|
110
|
+
"protocol": protocol,
|
|
111
|
+
}
|
|
112
|
+
if (request) {
|
|
113
|
+
const traceObject = secUtils.getTraceObject(shim);
|
|
114
|
+
const secMetadata = securityMetaData.getSecurityMetaData(request, urlObj, traceObject, secUtils.getExecutionId(), EVENT_TYPE.HTTP_REQUEST, EVENT_CATEGORY.HTTP)
|
|
115
|
+
const secEvent = API.generateSecEvent(secMetadata);
|
|
116
|
+
API.sendEvent(secEvent);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const res = original.apply(this, arguments);
|
|
120
|
+
return res;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Wrapper for addService method
|
|
127
|
+
* @param {*} shim
|
|
128
|
+
* @param {*} original
|
|
129
|
+
* @returns
|
|
130
|
+
*/
|
|
131
|
+
function wrapAddService(shim, original) {
|
|
132
|
+
if (!shim.isFunction(original)) {
|
|
133
|
+
return original
|
|
134
|
+
}
|
|
135
|
+
return function wrappedbindAsync() {
|
|
136
|
+
try {
|
|
137
|
+
const stakTrace = secUtils.traceElementForRoute();
|
|
138
|
+
const splittedStack = stakTrace[0].split(DOUBLE_DOLLAR);
|
|
139
|
+
let serviceValues = Object.values(arguments[0]);
|
|
140
|
+
for (const [key, value] of Object.entries(serviceValues)) {
|
|
141
|
+
const routeKey = ASTERISK + ATTHERATE + value.path;
|
|
142
|
+
let finalHandler = value.originalName + ATTHERATE + splittedStack[0];
|
|
143
|
+
routeManager.setRoute(routeKey, finalHandler);
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
}
|
|
147
|
+
const result = original.apply(this, arguments);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* wrapper for loadPackageDefinition method
|
|
154
|
+
* @param {*} shim
|
|
155
|
+
* @param {*} original
|
|
156
|
+
* @returns
|
|
157
|
+
*/
|
|
158
|
+
function wrapLoadPackageDefinition(shim, original) {
|
|
159
|
+
if (!shim.isFunction(original)) {
|
|
160
|
+
return original
|
|
161
|
+
}
|
|
162
|
+
return function wrappedbindAsync() {
|
|
163
|
+
let serviceObject = arguments[0];
|
|
164
|
+
const result = original.apply(this, arguments);
|
|
165
|
+
iterateNestedObjectForServiceLookup(result, serviceObject);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
function bindAsyncWrapper(shim, original) {
|
|
174
|
+
if (!shim.isFunction(original)) {
|
|
175
|
+
return original
|
|
176
|
+
}
|
|
177
|
+
return function wrappedbindAsync() {
|
|
178
|
+
serverAddress = arguments[0];
|
|
179
|
+
shim.grpcServerAddress = serverAddress;
|
|
180
|
+
try {
|
|
181
|
+
const splittedData = serverAddress.split(":");
|
|
182
|
+
serverPort = splittedData[1];
|
|
183
|
+
shim.serverPort = parseInt(serverPort);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
const result = original.apply(this, arguments);
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Instruments the grpc-js server by intercepting the moment when
|
|
196
|
+
* server methods are registered from the method implementations
|
|
197
|
+
* provided to grpc-js. This handles all four types of server
|
|
198
|
+
* invocations: unary, client-streaming, server-streaming, and
|
|
199
|
+
* bidirectional streaming.
|
|
200
|
+
*
|
|
201
|
+
* @param {object} shim the web shim to instrument with
|
|
202
|
+
* @param {Function} original the original function
|
|
203
|
+
* @returns {Function} the instrumented function
|
|
204
|
+
*/
|
|
205
|
+
function wrapRegister(shim, original) {
|
|
206
|
+
if (!shim.isFunction(original)) {
|
|
207
|
+
return original
|
|
208
|
+
}
|
|
209
|
+
return function wrappedRegister() {
|
|
210
|
+
wrapHandler(shim, arguments, '1');
|
|
211
|
+
const result = original.apply(this, arguments);
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Wrapper to hook handler function
|
|
219
|
+
* @param {*} shim
|
|
220
|
+
* @param {*} mod
|
|
221
|
+
* @param {*} method
|
|
222
|
+
*/
|
|
223
|
+
function wrapHandler(shim, mod, method) {
|
|
224
|
+
shim.wrap(mod, method, function wrapHandler(shim, fn) {
|
|
225
|
+
if (!shim.isFunction(fn)) {
|
|
226
|
+
return fn
|
|
227
|
+
}
|
|
228
|
+
return function wrappedHandler() {
|
|
229
|
+
const type = this.type;
|
|
230
|
+
const path = this.path;
|
|
231
|
+
wrapCallback(shim, arguments, '1');
|
|
232
|
+
addRequestData(shim, type, path, arguments[0]);
|
|
233
|
+
const result = fn.apply(this, arguments);
|
|
234
|
+
return result;
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Wrapper to hook callback response
|
|
242
|
+
* @param {*} shim
|
|
243
|
+
* @param {*} mod
|
|
244
|
+
* @param {*} method
|
|
245
|
+
*/
|
|
246
|
+
function wrapCallback(shim, mod, method) {
|
|
247
|
+
shim.wrap(mod, method, function wrapCallback(shim, fn) {
|
|
248
|
+
if (!shim.isFunction(fn)) {
|
|
249
|
+
return fn
|
|
250
|
+
}
|
|
251
|
+
return function wrappedCallback() {
|
|
252
|
+
const response = arguments[1];
|
|
253
|
+
try {
|
|
254
|
+
const request = requestManager.getRequest(shim);
|
|
255
|
+
if (arguments[0]) {
|
|
256
|
+
if (arguments[0].details && request) {
|
|
257
|
+
ExceptionReporting.generateExceptionReportingEvent(arguments[0].details, request);
|
|
258
|
+
}
|
|
259
|
+
//TODO GRPC response errors reporting
|
|
260
|
+
// if (arguments[0].code && arguments[0].code > 0 && request) {
|
|
261
|
+
// let responseCode = arguments[0].code
|
|
262
|
+
// ExceptionReporting.generate5xxReportingEvent(null, request, responseCode);
|
|
263
|
+
// }
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
const result = fn.apply(this, arguments);
|
|
269
|
+
return result;
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Utility function to add grpc request data in request map
|
|
277
|
+
* @param {*} shim
|
|
278
|
+
* @param {*} type
|
|
279
|
+
* @param {*} path
|
|
280
|
+
* @param {*} args
|
|
281
|
+
*/
|
|
282
|
+
function addRequestData(shim, type, path, args) {
|
|
283
|
+
const data = Object.assign({});
|
|
284
|
+
try {
|
|
285
|
+
const segment = shim.getActiveSegment();
|
|
286
|
+
if (segment && segment.transaction) {
|
|
287
|
+
data.method = path;
|
|
288
|
+
data.url = path;
|
|
289
|
+
data.uri = path;
|
|
290
|
+
data.headers = args.metadata.getMap();
|
|
291
|
+
if (data.headers && data.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
|
|
292
|
+
let bufferObj = Buffer.from(data.headers[NR_CSEC_FUZZ_REQUEST_ID], "base64");
|
|
293
|
+
let decodedString = bufferObj.toString("utf8");
|
|
294
|
+
data.headers[NR_CSEC_FUZZ_REQUEST_ID] = decodedString;
|
|
295
|
+
}
|
|
296
|
+
data.type = type;
|
|
297
|
+
data.protocol = 'grpc';
|
|
298
|
+
data.isGrpc = true;
|
|
299
|
+
data.body = JSON.stringify(args.request);
|
|
300
|
+
data.serverPort = shim.serverPort;
|
|
301
|
+
const transactionId = segment.transaction.id;
|
|
302
|
+
requestManager.setRequest(transactionId, data);
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
logger.error("Error occured while extracting grpc request", error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Utilty to set grpc util Maps
|
|
311
|
+
* @param {*} serviceObject
|
|
312
|
+
*/
|
|
313
|
+
function serviceUtil(serviceObject) {
|
|
314
|
+
let serviceValues = Object.values(serviceObject)[0];
|
|
315
|
+
for (const [key, value] of Object.entries(serviceValues)) {
|
|
316
|
+
let servObj = {
|
|
317
|
+
'serviceName': serviceObject.serviceName,
|
|
318
|
+
'originalName': value.originalName,
|
|
319
|
+
}
|
|
320
|
+
grpcutils.setMethod(value.path, servObj);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function iterateNestedObjectForServiceLookup(result, serviceObject) {
|
|
325
|
+
try {
|
|
326
|
+
for (const key in result) {
|
|
327
|
+
if (typeof result[key] === 'object' && result[key] !== 'null') {
|
|
328
|
+
iterateNestedObjectForServiceLookup(result[key], serviceObject); // Recurse into nested object
|
|
329
|
+
} else {
|
|
330
|
+
let value = result[key];
|
|
331
|
+
if (value && value.serviceName) {
|
|
332
|
+
if (!value.type && value.serviceName) {
|
|
333
|
+
grpcutils.setService(value.serviceName, serviceObject);
|
|
334
|
+
serviceUtil(value);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
logger.error("Error while getting service definition", error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -31,13 +31,16 @@ const isInvalid = require('is-invalid-path');
|
|
|
31
31
|
const path = require('path');
|
|
32
32
|
const fs = require('fs');
|
|
33
33
|
const semver = require('semver');
|
|
34
|
-
|
|
34
|
+
const ExceptionReporting = require('../../../nr-security-agent/lib/core/ExceptionReporting');
|
|
35
35
|
|
|
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
|
+
let lastTransactionId = EMPTY_STRING;
|
|
42
|
+
let uncaughtExceptionReportFlag = false;
|
|
43
|
+
|
|
41
44
|
|
|
42
45
|
/**
|
|
43
46
|
* Entry point of http module hook
|
|
@@ -178,7 +181,7 @@ function parseFuzzheaders(requestData, transactionId) {
|
|
|
178
181
|
if (verifiedHash) {
|
|
179
182
|
logger.debug("Encrypted Data:", encryptedData);
|
|
180
183
|
logger.debug("Decrypted Data:", decryptedData)
|
|
181
|
-
logger.debug("fliesTocreate:",filesToCreate);
|
|
184
|
+
logger.debug("fliesTocreate:", filesToCreate);
|
|
182
185
|
for (let i = 0; i < filesToCreate.length; i++) {
|
|
183
186
|
let file = filesToCreate[i].trim();
|
|
184
187
|
file = file.replace(CSEC_HOME_TMP_CONST, CSEC_HOME_TMP);
|
|
@@ -192,11 +195,11 @@ function parseFuzzheaders(requestData, transactionId) {
|
|
|
192
195
|
logger.debug(parentDir + ' Already Exists');
|
|
193
196
|
}
|
|
194
197
|
fs.closeSync(fs.openSync(file, 'w'));
|
|
195
|
-
|
|
198
|
+
|
|
196
199
|
requestData.tempFiles = [];
|
|
197
200
|
requestData.tempFiles.push(file);
|
|
198
201
|
requestManager.setRequest(transactionId, requestData);
|
|
199
|
-
|
|
202
|
+
|
|
200
203
|
} catch (error) {
|
|
201
204
|
logger.debug(error);
|
|
202
205
|
}
|
|
@@ -243,6 +246,7 @@ function addRequestData(shim, request) {
|
|
|
243
246
|
data.clientIP = requestIp.getClientIp(request);
|
|
244
247
|
const transactionId = segment.transaction.id;
|
|
245
248
|
const storedRequest = requestManager.getRequestFromId(transactionId);
|
|
249
|
+
lastTransactionId = transactionId;
|
|
246
250
|
if (storedRequest && storedRequest.uri) {
|
|
247
251
|
data.uri = storedRequest.uri;
|
|
248
252
|
data.parameterMap = storedRequest.parameterMap;
|
|
@@ -396,8 +400,27 @@ function responseHook(resp, req, shim) {
|
|
|
396
400
|
}
|
|
397
401
|
return function wrappedEnd() {
|
|
398
402
|
const response = this;
|
|
399
|
-
responseBodyCompute(response, arguments);
|
|
400
403
|
const request = requestManager.getRequest(shim);
|
|
404
|
+
const txInfo = lodash.get(response, Object.getOwnPropertySymbols(response).find(symbol => symbol.toString() === 'Symbol(transactionInfo)'));
|
|
405
|
+
try {
|
|
406
|
+
if (txInfo && txInfo.error && request) {
|
|
407
|
+
ExceptionReporting.generateExceptionReportingEvent(txInfo.error, request);
|
|
408
|
+
}
|
|
409
|
+
const responseCode = response.statusCode;
|
|
410
|
+
if (responseCode && parseInt(responseCode / 100) == 5 && request) {
|
|
411
|
+
if (txInfo && txInfo.error) {
|
|
412
|
+
ExceptionReporting.generate5xxReportingEvent(txInfo.error, request, responseCode);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
ExceptionReporting.generate5xxReportingEvent(null, request, responseCode);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
logger.debug("Error while reporting error", error);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
responseBodyCompute(response, arguments);
|
|
423
|
+
|
|
401
424
|
const construct = API.checkForReflectedXSS(request, response.res.body, response.getHeaders());
|
|
402
425
|
const policy = API.getPolicy();
|
|
403
426
|
const dynamicScanningFlag = policy.data ? (policy.data.vulnerabilityScan?.enabled && policy.data.vulnerabilityScan.iastScan.enabled) : false;
|
|
@@ -565,4 +588,21 @@ function deleteTempFileForIAST(file) {
|
|
|
565
588
|
} catch (error) {
|
|
566
589
|
logger.debug("Unable to delete temporary file", error);
|
|
567
590
|
}
|
|
568
|
-
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Collect and report the uncaught Exception
|
|
594
|
+
*/
|
|
595
|
+
process.on('uncaughtException', (err, origin) => {
|
|
596
|
+
console.error(err);
|
|
597
|
+
if(uncaughtExceptionReportFlag){
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
const request = requestManager.getRequestFromId(lastTransactionId);
|
|
601
|
+
const exception = new ExceptionReporting.exceptionReporting(err, request);
|
|
602
|
+
if (exception) {
|
|
603
|
+
API.sendEvent(exception);
|
|
604
|
+
}
|
|
605
|
+
logger.error("An uncaughtException is detected:", err);
|
|
606
|
+
uncaughtExceptionReportFlag = true;
|
|
607
|
+
});
|
|
608
|
+
|
|
@@ -199,10 +199,39 @@ newrelic.instrumentWebframework({
|
|
|
199
199
|
}
|
|
200
200
|
})
|
|
201
201
|
|
|
202
|
+
newrelic.instrument({
|
|
203
|
+
moduleName: '@grpc/grpc-js/build/src/server',
|
|
204
|
+
isEsm: true,
|
|
205
|
+
onRequire: require('./hooks/grpc-js/nr-grpc').wrapServer,
|
|
206
|
+
onError: function intrumentErrorHandler(err) {
|
|
207
|
+
logger.error(err.message, err.stack)
|
|
208
|
+
}
|
|
209
|
+
})
|
|
202
210
|
|
|
211
|
+
newrelic.instrument({
|
|
212
|
+
moduleName: '@grpc/grpc-js/build/src/make-client',
|
|
213
|
+
isEsm: true,
|
|
214
|
+
onRequire: require('./hooks/grpc-js/nr-grpc').wrapMakeClient,
|
|
215
|
+
onError: function intrumentErrorHandler(err) {
|
|
216
|
+
logger.error(err.message, err.stack)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
203
219
|
|
|
220
|
+
newrelic.instrument({
|
|
221
|
+
moduleName: '@grpc/grpc-js/build/src/resolving-call',
|
|
222
|
+
isEsm: true,
|
|
223
|
+
onRequire: require('./hooks/grpc-js/nr-grpc').wrapStartResolve,
|
|
224
|
+
onError: function intrumentErrorHandler(err) {
|
|
225
|
+
logger.error(err.message, err.stack)
|
|
226
|
+
}
|
|
227
|
+
})
|
|
204
228
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
229
|
+
newrelic.instrument({
|
|
230
|
+
moduleName: '@grpc/grpc-js/build/src/call-stream',
|
|
231
|
+
isEsm: true,
|
|
232
|
+
onRequire: require('./hooks/grpc-js/nr-grpc').wrapStartCall,
|
|
233
|
+
onError: function intrumentErrorHandler(err) {
|
|
234
|
+
logger.error(err.message, err.stack)
|
|
235
|
+
}
|
|
236
|
+
})
|
|
208
237
|
|
|
@@ -16,6 +16,13 @@ const njsAgentConstants = require('./lib/core/sec-agent-constants');
|
|
|
16
16
|
|
|
17
17
|
const NRLogger = API.newrelic.shim.logger.child({ component: 'security-agent' });
|
|
18
18
|
NRLogger.info("Starting New Relic Node.js Security Agent ");
|
|
19
|
+
try {
|
|
20
|
+
if (NRAgent.config.security.enabled){
|
|
21
|
+
require('../instrumentation-security');
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
logger.error("Error while applying security instrumentation")
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
const {
|
|
21
28
|
IS_LAMBDA_ENV,
|
|
@@ -169,7 +176,6 @@ function getProcCMDLineSanitized(applicationInfoModule) {
|
|
|
169
176
|
return;
|
|
170
177
|
}
|
|
171
178
|
if (initialize()) {
|
|
172
|
-
require('../instrumentation-security');
|
|
173
179
|
initLogger.info("[STEP-6] => Application instrumentation applied successfully");
|
|
174
180
|
}
|
|
175
181
|
})();
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: New Relic Software License v1.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const LinkingMetaData = require('./LinkingMetadata');
|
|
7
|
+
const API = require('../../../nr-security-api');
|
|
8
|
+
const NRAgent = API.getNRAgent();
|
|
9
|
+
const { BasicInfo } = require('./event');
|
|
10
|
+
const applicationInfo = require('./applicationinfo').getInstance();
|
|
11
|
+
const exceptionsMap = new Map();
|
|
12
|
+
const logs = require('./logging');
|
|
13
|
+
const logger = logs.getLogger();
|
|
14
|
+
const exceptionReportingInterval = 30000;
|
|
15
|
+
const http = require('http');
|
|
16
|
+
const shaUtil = require('./sha-size-util');
|
|
17
|
+
|
|
18
|
+
const NR_LIB = '/newrelic/lib';
|
|
19
|
+
|
|
20
|
+
const GRPCStatusMap = {
|
|
21
|
+
0: 'OK',
|
|
22
|
+
1: 'CANCELLED',
|
|
23
|
+
2: 'UNKNOWN',
|
|
24
|
+
3: 'INVALID_ARGUMENT',
|
|
25
|
+
4: 'DEADLINE_EXCEEDED',
|
|
26
|
+
5: 'NOT_FOUND',
|
|
27
|
+
6: 'ALREADY_EXISTS',
|
|
28
|
+
7: 'PERMISSION_DENIED',
|
|
29
|
+
8: 'RESOURCE_EXHAUSTED',
|
|
30
|
+
9: 'FAILED_PRECONDITION',
|
|
31
|
+
10: 'ABORTED',
|
|
32
|
+
11: 'OUT_OF_RANGE',
|
|
33
|
+
12: 'UNIMPLEMENTED',
|
|
34
|
+
13: 'INTERNAL',
|
|
35
|
+
14: 'UNAVAILABLE',
|
|
36
|
+
15: 'DATA_LOSS',
|
|
37
|
+
16: 'UNAUTHENTICATED',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* constructor function to create createException object
|
|
42
|
+
* @param {*} message
|
|
43
|
+
* @param {*} cause
|
|
44
|
+
* @param {*} stacktrace
|
|
45
|
+
*/
|
|
46
|
+
function createException(exception) {
|
|
47
|
+
let exceptionObject = {};
|
|
48
|
+
try {
|
|
49
|
+
exceptionObject.message = exception.message ? exception.message : '';
|
|
50
|
+
exceptionObject.cause = null;
|
|
51
|
+
exceptionObject.type = exception.name ? exception.name : 'Error';
|
|
52
|
+
if (exception.stack) {
|
|
53
|
+
let stacktraceInList = exception.stack.split('\n');
|
|
54
|
+
stacktraceInList = stacktraceInList.slice(1);
|
|
55
|
+
let fileteredStackTrace = [];
|
|
56
|
+
for (const str of stacktraceInList) {
|
|
57
|
+
if (!str.includes(NR_LIB) && str.length > 0) {
|
|
58
|
+
fileteredStackTrace.push(str.trim());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exceptionObject.stackTrace = fileteredStackTrace;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} catch (error) {
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return exceptionObject;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* constructor function to create exceptionReporting object;
|
|
72
|
+
* @param {*} timestamp
|
|
73
|
+
* @param {*} level
|
|
74
|
+
* @param {*} message
|
|
75
|
+
* @param {*} caller
|
|
76
|
+
* @param {*} exception
|
|
77
|
+
* @param {*} linkingMetadata
|
|
78
|
+
*/
|
|
79
|
+
function exceptionReporting(exception, request, responseCode) {
|
|
80
|
+
try {
|
|
81
|
+
BasicInfo.call(this);
|
|
82
|
+
this.jsonVersion = applicationInfo.jsonVersion;
|
|
83
|
+
this.pid = applicationInfo.pid ? applicationInfo.pid : null;
|
|
84
|
+
this.jsonName = 'application-runtime-error';
|
|
85
|
+
this.applicationUUID = applicationInfo.applicationUUID;
|
|
86
|
+
this.policyVersion = applicationInfo.policyVersion;
|
|
87
|
+
this.collectorVersion = applicationInfo.collectorVersion ? applicationInfo.collectorVersion : null;
|
|
88
|
+
this.buildNumber = applicationInfo.buildNumber ? applicationInfo.buildNumber : null;
|
|
89
|
+
this.timestamp = Date.now();
|
|
90
|
+
this.httpRequest = request;
|
|
91
|
+
this.httpRequest.route = request.uri ? request.uri : request.url;
|
|
92
|
+
this.exception = null;
|
|
93
|
+
this.counter = 1;
|
|
94
|
+
this.responseCode = responseCode ? responseCode : null
|
|
95
|
+
|
|
96
|
+
if (exception) {
|
|
97
|
+
this.exception = createException(exception);
|
|
98
|
+
this.category = exception.name;
|
|
99
|
+
this.traceId = shaUtil.getSHA256ForData(this.exception.stackTrace.join('|') + this.category + this.httpRequest.route + this.httpRequest.method);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.traceId = shaUtil.getSHA256ForData(this.category + this.httpRequest.route + this.httpRequest.method);
|
|
103
|
+
}
|
|
104
|
+
if (responseCode) {
|
|
105
|
+
if (responseCode >= 500) {
|
|
106
|
+
this.category = http.STATUS_CODES[responseCode];
|
|
107
|
+
this.traceId = shaUtil.getSHA256ForData(this.category + this.httpRequest.route + this.httpRequest.method);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.category = GRPCStatusMap[responseCode];
|
|
111
|
+
this.traceId = shaUtil.getSHA256ForData(this.category + this.httpRequest.route + this.httpRequest.method);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (NRAgent) {
|
|
116
|
+
this.linkingMetadata = LinkingMetaData.getLinkingMetadata();
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.debug("Error while generating exceptionReporing", error);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function generateExceptionReportingEvent(error, request) {
|
|
125
|
+
try {
|
|
126
|
+
const exception = new exceptionReporting(error, request);
|
|
127
|
+
let key = exception.exception.type + exception.exception.stackTrace[0];
|
|
128
|
+
if (!exceptionsMap.has(key)) {
|
|
129
|
+
exceptionsMap.set(key, exception);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
let previousCounter = exceptionsMap.get(key).counter;
|
|
133
|
+
exception.counter = previousCounter ? previousCounter + 1: exception.counter + 1;
|
|
134
|
+
exceptionsMap.set(key, exception);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
logger.debug("Error while generating application-runtime-error:", err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function generate5xxReportingEvent(error, request, responseCode) {
|
|
142
|
+
try {
|
|
143
|
+
const exception = new exceptionReporting(error, request, responseCode);
|
|
144
|
+
let key = (request.uri ? request.uri : request.url) + responseCode;
|
|
145
|
+
if (!exceptionsMap.has(key)) {
|
|
146
|
+
exceptionsMap.set(key, exception);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
let previousCounter = exceptionsMap.get(key).counter;
|
|
150
|
+
exception.counter = previousCounter ? previousCounter + 1: exception.counter + 1;
|
|
151
|
+
exceptionsMap.set(key, exception);
|
|
152
|
+
}
|
|
153
|
+
} catch (err) {
|
|
154
|
+
logger.debug("Error while generating 5xx application-runtime-error:", err);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
function startExceptionSendingSchedular() {
|
|
161
|
+
try {
|
|
162
|
+
setInterval(() => {
|
|
163
|
+
const values = Array.from(exceptionsMap.values());
|
|
164
|
+
for (let i = 0; i < values.length; i++) {
|
|
165
|
+
API.sendEvent(values[i]);
|
|
166
|
+
}
|
|
167
|
+
exceptionsMap.clear();
|
|
168
|
+
}, exceptionReportingInterval);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.info("Error in exception reporting schedular");
|
|
171
|
+
}
|
|
172
|
+
logger.info("Exception reporting schedular started");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
exceptionReporting,
|
|
178
|
+
createException,
|
|
179
|
+
generateExceptionReportingEvent,
|
|
180
|
+
generate5xxReportingEvent,
|
|
181
|
+
startExceptionSendingSchedular
|
|
182
|
+
};
|