@newrelic/security-agent 1.0.1 → 1.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 CHANGED
@@ -1,3 +1,17 @@
1
+ ### v1.1.0 (2024-03-19)
2
+ #### Features
3
+ * IAST support for grpc
4
+ * Functionality to report API endpoints of the application
5
+ * IAST support for undici
6
+ #### Bug fixes
7
+ * Updated permissions for file/directory created by security agent
8
+ #### Miscellaneous chores
9
+ * Bumped follow-redirects from v1.15.2 to v1.15.4
10
+ * Updated axios to v1.6.8
11
+ * Bumped ip from v2.0.0 to v2.0.1
12
+ * Bumped undici from 5.28.2 to v5.28.3
13
+ * Readme update
14
+
1
15
  ### v1.0.1 (2024-02-07)
2
16
  #### Bug fixes
3
17
  * Reporting of framework in security event json
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  **Note**: The IAST capability should only be used in pre-production environments as the application is tested for real exploitable vulnerabilities.
5
5
 
6
- The New Relic security agent is in preview and is not generally available. This module enables instrumentation of a Node.js application for interactive application security testing(IAST) and exposes exploitable vulnerabilities.
6
+ The New Relic security agent enables instrumentation of a Node.js application for Interactive Application Security Testing(IAST) and exposes exploitable vulnerabilities.
7
7
 
8
8
  ## Installation
9
9
 
@@ -67,6 +67,8 @@ The [Developer docs](http://newrelic.github.io/node-newrelic/) for writing instr
67
67
  - [ldapts](https://www.npmjs.com/package/ldapts)(2.x and above)
68
68
  - [xpath](https://www.npmjs.com/package/xpath)(0.0.20 and above)
69
69
  - [xpath.js](https://www.npmjs.com/package/xpath.js)(0.0.1 and above)
70
+ - [undici](https://www.npmjs.com/package/undici)(4.7.0 and above)
71
+ - [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js)(1.4.x and above)
70
72
 
71
73
  For more information, please see New Relic Node.js agent [compatibility and requirements](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/getting-started/compatibility-requirements-nodejs-agent/).
72
74
 
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: New Relic Pre-Release
4
+ */
5
+
6
+
7
+ const grpcServiceMap = new Map();
8
+ const grpcMethodMap = new Map();
9
+ /**
10
+ * Utility to set service based on key value pair
11
+ * @param {*} key
12
+ * @param {*} value
13
+ */
14
+ function setService(key, value){
15
+ grpcServiceMap.set(key,value);
16
+ }
17
+
18
+ /**
19
+ * Utility to get service corresponding to key
20
+ * @param {*} key
21
+ * @returns
22
+ */
23
+ function getService(key){
24
+ return grpcServiceMap.get(key);
25
+ }
26
+
27
+ /**
28
+ * Utility to set method object
29
+ * @param {*} key
30
+ * @param {*} value
31
+ */
32
+ function setMethod(key, value){
33
+ grpcMethodMap.set(key,value);
34
+ }
35
+
36
+ /**
37
+ * Utility to get method object
38
+ * @param {*} key
39
+ * @returns
40
+ */
41
+ function getMethod(key){
42
+ return grpcMethodMap.get(key);
43
+ }
44
+
45
+ module.exports={
46
+ setService,
47
+ getService,
48
+ grpcServiceMap,
49
+ setMethod,
50
+ getMethod,
51
+ grpcMethodMap
52
+
53
+ }
@@ -10,8 +10,8 @@ const routeMap = new Map();
10
10
  * @param {*} key
11
11
  * @param {*} value
12
12
  */
13
- function setRoute(key, value){
14
- routeMap.set(key,value);
13
+ function setRoute(key, value) {
14
+ routeMap.set(key, value);
15
15
  }
16
16
 
17
17
  /**
@@ -19,12 +19,35 @@ function setRoute(key, value){
19
19
  * @param {*} key
20
20
  * @returns
21
21
  */
22
- function getRoute(key){
22
+ function getRoute(key) {
23
23
  return routeMap.get(key);
24
24
  }
25
25
 
26
- module.exports={
26
+ /**
27
+ * Utility to get all api end points
28
+ * @returns all keys from routeMap
29
+ */
30
+ function getAllAPIEndPoints() {
31
+ let apiEndpoints = [];
32
+ try {
33
+ routeMap.forEach((value, key) => {
34
+ let obj = {};
35
+ let splitted = key.split('@');
36
+ obj.method = splitted[0];
37
+ obj.path = splitted[1];
38
+ obj.handler = routeMap.get(key);
39
+ apiEndpoints.push(obj);
40
+ })
41
+ } catch (error) {
42
+
43
+ }
44
+
45
+ return apiEndpoints;
46
+ }
47
+
48
+ module.exports = {
27
49
  getRoute,
28
50
  setRoute,
29
- routeMap
51
+ routeMap,
52
+ getAllAPIEndPoints
30
53
  }
@@ -150,11 +150,11 @@ function getExecutionId(){
150
150
  function createPathIfNotExist (dir) {
151
151
  try {
152
152
  if (!fs.existsSync(dir)) {
153
- fs.mkdirSync(dir, 777, { recursive: true });
153
+ fs.mkdirSync(dir, 770, { recursive: true });
154
154
  logger.info(dir + ' Created');
155
- fs.chmodSync(dir, 0o777);
155
+ fs.chmodSync(dir, 0o770);
156
156
  } else {
157
- fs.chmodSync(dir, 0o777);
157
+ fs.chmodSync(dir, 0o770);
158
158
  logger.debug(dir + ' Already Exists');
159
159
  }
160
160
  } catch (error) {
@@ -27,9 +27,8 @@ module.exports = function initialize(shim, express, moduleName) {
27
27
 
28
28
  if (express.Router.use) {
29
29
  wrapExpress4(shim, express)
30
- } else {
31
- // wrapExpress3(shim, express)
32
- }
30
+ }
31
+
33
32
  }
34
33
 
35
34
  /**
@@ -57,7 +56,6 @@ function wrapExpress4(shim, express) {
57
56
  }
58
57
  })
59
58
 
60
-
61
59
  //Wrapper to hook process_params method to extract out path param and query params.
62
60
  shim.wrap(express.Router, 'process_params', function wrapParam(shim, fn) {
63
61
  if (!shim.isFunction(fn)) {
@@ -0,0 +1,284 @@
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 } = require('../../core/constants');
13
+ const logger = API.getLogger();
14
+ const semver = require('semver');
15
+ const grpcutils = require('../../core/grpc-utils');
16
+ let serverAddress;
17
+
18
+
19
+ module.exports.initialize = function initialize(shim, grpc, moduleName) {
20
+ logger.info('Instrumenting grpc', moduleName);
21
+ const grpcVersion = shim.require('./package.json').version;
22
+ logger.debug("grpc version:", grpcVersion);
23
+
24
+ if (semver.lt(grpcVersion, '1.4.0')) {
25
+ logger.warn('gRPC server-side instrumentation only supported on grpc-js >=1.4.0')
26
+ return
27
+ }
28
+
29
+ if (semver.gte(grpcVersion, '1.8.0')) {
30
+ const resolvingCall = shim.require('./build/src/resolving-call')
31
+ shim.wrap(resolvingCall.ResolvingCall.prototype, 'start', wrapStart)
32
+ } else {
33
+ const callStream = shim.require('./build/src/call-stream')
34
+ shim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
35
+ }
36
+
37
+
38
+ const server = shim.require('./build/src/server')
39
+ shim.wrap(server.Server.prototype, 'bindAsync', bindAsyncWrapper)
40
+ shim.wrap(server.Server.prototype, 'register', wrapRegister)
41
+
42
+ //get all service
43
+ shim.wrap(server.Server.prototype, 'addService', wrapAddService);
44
+
45
+ //laod package def
46
+ const makeClient = shim.require('./build/src/make-client');
47
+ shim.wrap(makeClient, 'loadPackageDefinition', wrapLoadPackageDefinition);
48
+
49
+ }
50
+
51
+
52
+ module.exports.wrapStartResolve = function wrappedStartResolve(shim, resolvingCall) {
53
+ shim.wrap(resolvingCall.ResolvingCall.prototype, 'start', wrapStart)
54
+ }
55
+
56
+ module.exports.wrapStartCall = function wrappedClient(shim, callStream) {
57
+ shim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
58
+ }
59
+
60
+ module.exports.wrapServer = function wrappedServer(shim, server) {
61
+ const grpcVersion = shim.require('./package.json').version
62
+ logger.info("Instrumenting @grpc/grpc-js", grpcVersion)
63
+ if (semver.lt(grpcVersion, '1.4.0')) {
64
+ logger.warn('gRPC server-side instrumentation only supported on grpc-js >=1.4.0')
65
+ return
66
+ }
67
+
68
+ shim.wrap(server.Server.prototype, 'register', wrapRegister)
69
+ shim.wrap(server.Server.prototype, 'bindAsync', bindAsyncWrapper)
70
+ //get all service
71
+ shim.wrap(server.Server.prototype, 'addService', wrapAddService);
72
+ }
73
+
74
+ module.exports.wrapMakeClient = function wrapMakeClient(shim, makeClient) {
75
+ shim.wrap(makeClient, 'loadPackageDefinition', wrapLoadPackageDefinition);
76
+ }
77
+
78
+
79
+ /**
80
+ * Instruments grpc-js client by intercepting the function that
81
+ * initiates client requests. This handles all four types of client
82
+ * invocations: unary, client-streaming, server-streaming, and
83
+ * bidirectional streaming.
84
+ *
85
+ * @param {object} shim the generic shim to instrument with
86
+ * @param {Function} original the original function
87
+ * @returns {Function} the instrumented function
88
+ */
89
+ function wrapStart(shim, original) {
90
+ return function wrappedStart() {
91
+ const channel = this.channel;
92
+ const authorityName = (channel.target && channel.target.path) || channel.getDefaultAuthority;
93
+ // in 1.8.0 this changed from methodName to method
94
+ const method = this.methodName || this.method;
95
+ const protocol = 'grpc:';
96
+ const url = `${protocol}//${authorityName}${method}`;
97
+ logger.debug("grpc url is:", url)
98
+ const request = requestManager.getRequest(shim);
99
+ let urlObj = {
100
+ "headers":
101
+ {
102
+ "host": authorityName
103
+ },
104
+ "method": method,
105
+ "path": method,
106
+ "protocol": protocol,
107
+ }
108
+ if (request) {
109
+ const traceObject = secUtils.getTraceObject(shim);
110
+ const secMetadata = securityMetaData.getSecurityMetaData(request, urlObj, traceObject, secUtils.getExecutionId(), EVENT_TYPE.HTTP_REQUEST, EVENT_CATEGORY.HTTP)
111
+ const secEvent = API.generateSecEvent(secMetadata);
112
+ API.sendEvent(secEvent);
113
+ }
114
+
115
+ const res = original.apply(this, arguments);
116
+ return res;
117
+ }
118
+ }
119
+
120
+
121
+
122
+ function wrapAddService(shim, original) {
123
+ return function wrappedbindAsync() {
124
+ const result = original.apply(this, arguments);
125
+ return result;
126
+ }
127
+ }
128
+
129
+ function wrapLoadPackageDefinition(shim, original) {
130
+ return function wrappedbindAsync() {
131
+ let serviceObject = arguments[0];
132
+ const result = original.apply(this, arguments);
133
+ iterateNestedObjectForServiceLookup(result, serviceObject);
134
+ return result;
135
+ }
136
+ }
137
+
138
+
139
+
140
+
141
+ function bindAsyncWrapper(shim, original) {
142
+ return function wrappedbindAsync() {
143
+ serverAddress = arguments[0];
144
+ shim.grpcServerAddress = serverAddress;
145
+ try {
146
+ const splittedData = serverAddress.split(":");
147
+ serverPort = splittedData[1];
148
+ shim.serverPort = parseInt(serverPort);
149
+ } catch (error) {
150
+
151
+ }
152
+ const result = original.apply(this, arguments);
153
+ return result;
154
+ }
155
+ }
156
+
157
+
158
+
159
+ /**
160
+ * Instruments the grpc-js server by intercepting the moment when
161
+ * server methods are registered from the method implementations
162
+ * provided to grpc-js. This handles all four types of server
163
+ * invocations: unary, client-streaming, server-streaming, and
164
+ * bidirectional streaming.
165
+ *
166
+ * @param {object} shim the web shim to instrument with
167
+ * @param {Function} original the original function
168
+ * @returns {Function} the instrumented function
169
+ */
170
+ function wrapRegister(shim, original) {
171
+ return function wrappedRegister() {
172
+ wrapHandler(shim, arguments, '1');
173
+ const result = original.apply(this, arguments);
174
+ return result;
175
+ }
176
+ }
177
+
178
+
179
+ /**
180
+ * Wrapper to hook handler function
181
+ * @param {*} shim
182
+ * @param {*} mod
183
+ * @param {*} method
184
+ */
185
+ function wrapHandler(shim, mod, method) {
186
+ shim.wrap(mod, method, function wrapHandler(shim, fn) {
187
+ return function wrappedHandler() {
188
+ const type = this.type;
189
+ const path = this.path;
190
+ wrapCallback(shim, arguments, '1');
191
+ addRequestData(shim, type, path, arguments[0]);
192
+ const result = fn.apply(this, arguments);
193
+ return result;
194
+
195
+ }
196
+ })
197
+ }
198
+
199
+ /**
200
+ * Wrapper to hook callback response
201
+ * @param {*} shim
202
+ * @param {*} mod
203
+ * @param {*} method
204
+ */
205
+ function wrapCallback(shim, mod, method) {
206
+ shim.wrap(mod, method, function wrapCallback(shim, fn) {
207
+ return function wrappedCallback() {
208
+ const response = arguments[1];
209
+ const result = fn.apply(this, arguments);
210
+ return result;
211
+
212
+ }
213
+ })
214
+ }
215
+
216
+ /**
217
+ * Utility function to add grpc request data in request map
218
+ * @param {*} shim
219
+ * @param {*} type
220
+ * @param {*} path
221
+ * @param {*} args
222
+ */
223
+ function addRequestData(shim, type, path, args) {
224
+ const data = Object.assign({});
225
+ try {
226
+ const segment = shim.getActiveSegment();
227
+ if (segment && segment.transaction) {
228
+ data.method = path;
229
+ data.url = path;
230
+ data.uri = path;
231
+ data.headers = args.metadata.getMap();
232
+ if (data.headers && data.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
233
+ let bufferObj = Buffer.from(data.headers[NR_CSEC_FUZZ_REQUEST_ID], "base64");
234
+ let decodedString = bufferObj.toString("utf8");
235
+ data.headers[NR_CSEC_FUZZ_REQUEST_ID] = decodedString;
236
+ }
237
+ data.type = type;
238
+ data.protocol = 'grpc';
239
+ data.isGRPC = true;
240
+ data.body = JSON.stringify(args.request);
241
+ data.serverPort = shim.serverPort;
242
+ const transactionId = segment.transaction.id;
243
+ requestManager.setRequest(transactionId, data);
244
+ }
245
+ } catch (error) {
246
+ logger.error("Error occured while extracting grpc request", error);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Utilty to set grpc util Maps
252
+ * @param {*} serviceObject
253
+ */
254
+ function serviceUtil(serviceObject) {
255
+ let serviceValues = Object.values(serviceObject)[0];
256
+ for (const [key, value] of Object.entries(serviceValues)) {
257
+ let servObj = {
258
+ 'serviceName': serviceObject.serviceName,
259
+ 'originalName': value.originalName,
260
+ }
261
+ grpcutils.setMethod(value.path, servObj);
262
+ }
263
+ }
264
+
265
+ function iterateNestedObjectForServiceLookup(result, serviceObject) {
266
+ try {
267
+ for (const key in result) {
268
+ if (typeof result[key] === 'object' && result[key] !== 'null') {
269
+ iterateNestedObjectForServiceLookup(result[key], serviceObject); // Recurse into nested object
270
+ } else {
271
+ let value = result[key];
272
+ if (value && value.serviceName) {
273
+ if (!value.type && value.serviceName) {
274
+ grpcutils.setService(value.serviceName, serviceObject);
275
+ serviceUtil(value);
276
+ }
277
+ }
278
+ }
279
+ }
280
+ } catch (error) {
281
+ logger.error("Error while getting service definition", error);
282
+ }
283
+
284
+ }
@@ -0,0 +1,139 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: New Relic Pre-Release
4
+ */
5
+
6
+ module.exports = initialize
7
+ const requestManager = require('../../core/request-manager');
8
+ const secUtils = require("../../core/sec-utils");
9
+ const API = require("../../../nr-security-api");
10
+ const lodash = require('lodash');
11
+ const securityMetaData = require('../../core/security-metadata');
12
+ const { EVENT_TYPE, EVENT_CATEGORY } = require('../../core/event-constants');
13
+ const { NR_CSEC_FUZZ_REQUEST_ID, QUESTION_MARK, EMPTY_STRING, COLON, SLASH } = require('../../core/constants');
14
+ const NRCSECTRACINGDATA = 'NR-CSEC-TRACING-DATA';
15
+ const DOUBLE_SLASH = '//';
16
+ const logger = API.getLogger();
17
+ const url = require('url');
18
+
19
+
20
+ /**
21
+ * Entry point undici module instrumentation.
22
+ * @param {*} shim
23
+ * @param {*} mod
24
+ * @param {*} moduleName
25
+ */
26
+ function initialize(shim, mod, moduleName) {
27
+ logger.info('Instrumenting ' + moduleName);
28
+ fetchHook(shim, mod, "fetch");
29
+ let util = shim.require('./lib/core/util');
30
+ parseOriginHook(shim, util, "parseOrigin")
31
+
32
+ }
33
+ /**
34
+ * Wrapper for util.parseOrigin
35
+ * @param {*} shim
36
+ * @param {*} mod
37
+ * @param {*} methodName
38
+ */
39
+ function parseOriginHook(shim, mod, methodName) {
40
+ shim.wrap(mod, methodName, function ClientWrapper(shim, fn) {
41
+ logger.debug(`Instrumenting ${methodName}`);
42
+ if (!shim.isFunction(fn)) {
43
+ return fn
44
+ }
45
+ return function wrapper() {
46
+ const request = requestManager.getRequest(shim);
47
+ try {
48
+ if (typeof arguments[0] === 'string') {
49
+ let URLObject = new URL(arguments[0]);
50
+ const data = {};
51
+ data.headers = {};
52
+ data.path = URLObject.pathname ? URLObject.pathname : EMPTY_STRING;
53
+ data.protocol = URLObject.protocol ? URLObject.protocol : 'http:';
54
+ data.headers.host = URLObject.host ? URLObject.host : URLObject.hostname;
55
+ shim.interceptedArgs = data;
56
+ if (request) {
57
+ const traceObject = secUtils.getTraceObject(shim);
58
+ const secMetadata = securityMetaData.getSecurityMetaData(request, data, traceObject, secUtils.getExecutionId(), EVENT_TYPE.HTTP_REQUEST, EVENT_CATEGORY.HTTP)
59
+ this.secEvent = API.generateSecEvent(secMetadata);
60
+ API.sendEvent(this.secEvent);
61
+ }
62
+ }
63
+ } catch (error) {
64
+
65
+ }
66
+ const result = fn.apply(this, arguments);
67
+ if (result && request && request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
68
+ API.generateExitEvent(this.secEvent);
69
+ delete this.secEvent
70
+ }
71
+ return result;
72
+ }
73
+ })
74
+ }
75
+
76
+ /**
77
+ * Wrapper to hook undici.fetch()
78
+ * @param {*} shim
79
+ * @param {*} mod
80
+ * @param {*} methodName
81
+ */
82
+ function fetchHook(shim, mod, methodName) {
83
+ shim.wrap(mod, methodName, function makeWrapper(shim, fn) {
84
+ logger.debug(`Instrumenting ${methodName}`);
85
+ if (!shim.isFunction(fn)) {
86
+ return fn
87
+ }
88
+ return function wrapper() {
89
+ const request = requestManager.getRequest(shim);
90
+ try {
91
+ const completeURL = arguments[0];
92
+ const parsedUrl = url.parse(completeURL);
93
+ const data = {};
94
+ data.headers = {};
95
+ data.method = arguments[0].method ? arguments[0].method : EMPTY_STRING;
96
+ data.path = parsedUrl.pathname ? parsedUrl.pathname : parsedUrl.path;
97
+ data.protocol = parsedUrl.protocol;
98
+ data.headers.host = parsedUrl.host ? parsedUrl.host : parsedUrl.hostname;
99
+ data.port = parsedUrl.port ? parsedUrl.port : parsedUrl.port;
100
+
101
+ shim.interceptedArgs = data;
102
+ if (request && !lodash.isEmpty(data.headers.host) && !lodash.isEmpty(data.path)) {
103
+ const appUUID = API.getSecAgent().applicationInfo.applicationUUID;
104
+ const traceObject = secUtils.getTraceObject(shim);
105
+ const secMetadata = securityMetaData.getSecurityMetaData(request, data, traceObject, secUtils.getExecutionId(), EVENT_TYPE.HTTP_REQUEST, EVENT_CATEGORY.HTTP)
106
+ this.secEvent = API.generateSecEvent(secMetadata);
107
+ API.sendEvent(this.secEvent);
108
+
109
+ let traceHeader = EMPTY_STRING;
110
+ if (request.headers['nr-csec-tracing-data']) {
111
+ traceHeader = request.headers['nr-csec-tracing-data'];
112
+ }
113
+
114
+ if (arguments[1] instanceof Object) {
115
+ if (arguments[1] && arguments[1].headers && request.headers) {
116
+ arguments[1].headers[NRCSECTRACINGDATA] = traceHeader + appUUID + '/' + this.secEvent.apiId + '/' + this.secEvent.id + ';';
117
+ } else {
118
+ arguments[1].headers = {};
119
+ arguments[1].headers[NRCSECTRACINGDATA] = traceHeader + appUUID + '/' + this.secEvent.apiId + '/' + this.secEvent.id + ';';
120
+ }
121
+ if (request && request.headers[NR_CSEC_FUZZ_REQUEST_ID] && arguments[1].headers) {
122
+ arguments[1].headers[NR_CSEC_FUZZ_REQUEST_ID] = request.headers[NR_CSEC_FUZZ_REQUEST_ID];
123
+ }
124
+ }
125
+ }
126
+ } catch (error) {
127
+ logger.debug("Error while extracting undici fetch args:", error);
128
+ }
129
+ const result = fn.apply(this, arguments);
130
+
131
+ if (result && request && request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
132
+ API.generateExitEvent(this.secEvent);
133
+ delete this.secEvent
134
+ }
135
+ return result;
136
+ }
137
+ })
138
+ }
139
+
@@ -190,9 +190,65 @@ newrelic.instrumentWebframework({
190
190
  }
191
191
  })
192
192
 
193
+ newrelic.instrumentWebframework({
194
+ moduleName: 'undici',
195
+ isEsm: true,
196
+ onRequire: require('./hooks/undici/nr-undici'),
197
+ onError: function intrumentErrorHandler(err) {
198
+ logger.error(err.message, err.stack)
199
+ }
200
+ })
201
+
193
202
 
203
+ newrelic.instrument({
204
+ type: 'conglomerate',
205
+ moduleName: '@grpc/grpc-js/build/src/server',
206
+ isEsm: true,
207
+ onRequire: require('./hooks/grpc-js/nr-grpc').wrapServer,
208
+ onError: function intrumentErrorHandler(err) {
209
+ logger.error(err.message, err.stack)
210
+ }
211
+ })
194
212
 
213
+ newrelic.instrument({
214
+ type: 'conglomerate',
215
+ moduleName: '@grpc/grpc-js/build/src/make-client',
216
+ isEsm: true,
217
+ onRequire: require('./hooks/grpc-js/nr-grpc').wrapMakeClient,
218
+ onError: function intrumentErrorHandler(err) {
219
+ logger.error(err.message, err.stack)
220
+ }
221
+ })
195
222
 
223
+ newrelic.instrument({
224
+ type: 'conglomerate',
225
+ moduleName: '@grpc/grpc-js/build/src/resolving-call',
226
+ isEsm: true,
227
+ onRequire: require('./hooks/grpc-js/nr-grpc').wrapStartResolve,
228
+ onError: function intrumentErrorHandler(err) {
229
+ logger.error(err.message, err.stack)
230
+ }
231
+ })
232
+
233
+ newrelic.instrument({
234
+ type: 'conglomerate',
235
+ moduleName: '@grpc/grpc-js/build/src/call-stream',
236
+ isEsm: true,
237
+ onRequire: require('./hooks/grpc-js/nr-grpc').wrapStartCall,
238
+ onError: function intrumentErrorHandler(err) {
239
+ logger.error(err.message, err.stack)
240
+ }
241
+ })
242
+
243
+ newrelic.instrument({
244
+ type: 'conglomerate',
245
+ moduleName: '@grpc/grpc-js',
246
+ isEsm: true,
247
+ onRequire: require('./hooks/grpc-js/nr-grpc').initialize,
248
+ onError: function intrumentErrorHandler(err) {
249
+ logger.error(err.message, err.stack)
250
+ }
251
+ })
196
252
 
197
253
 
198
254
 
@@ -0,0 +1,29 @@
1
+
2
+ /*
3
+ * Copyright 2023 New Relic Corporation. All rights reserved.
4
+ * SPDX-License-Identifier: New Relic Software License v1.0
5
+ */
6
+
7
+ const { BasicInfo } = require('./event');
8
+ const applicationInfo = require('./applicationinfo').getInstance();
9
+ /**
10
+ * Function Constructor to create object of FuzzFail event
11
+ * @param {*} fuzzHeader
12
+ */
13
+ function APIEndPoint (apiEndpoints) {
14
+ BasicInfo.call(this);
15
+ this.jsonVersion = applicationInfo.jsonVersion;
16
+ this.jsonName = 'sec-application-url-mapping';
17
+ this.eventType = "sec-application-url-mapping"
18
+ this.applicationUUID = applicationInfo.applicationUUID;
19
+ this.groupName = applicationInfo.groupName;
20
+ this.mappings = apiEndpoints;
21
+
22
+ return this;
23
+ }
24
+
25
+ APIEndPoint.prototype.constructor = APIEndPoint;
26
+
27
+ module.exports = {
28
+ APIEndPoint
29
+ };
@@ -97,11 +97,11 @@ function runtimeSupportsAsyncFunctionGenerators() {
97
97
  function createPathIfNotExist(dir) {
98
98
  try {
99
99
  if (!fs.existsSync(dir)) {
100
- fs.mkdirSync(dir, 777, { recursive: true });
100
+ fs.mkdirSync(dir, 770, { recursive: true });
101
101
  logger.info(dir + ' Created');
102
- fs.chmodSync(dir, 0o777);
102
+ fs.chmodSync(dir, 0o770);
103
103
  } else {
104
- fs.chmodSync(dir, 0o777);
104
+ fs.chmodSync(dir, 0o770);
105
105
  logger.debug(dir + ' Already Exists');
106
106
  }
107
107
  } catch (error) {
@@ -139,7 +139,7 @@ function createDirectories() {
139
139
  createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent${SLASH}${getUUID()}`);
140
140
  createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent${SLASH}${getUUID()}${SLASH}ds-tmp`);
141
141
  try {
142
- fs.chmodSync(`${CSEC_HOME}${SLASH}nr-security-home`, 0o777);
142
+ fs.chmodSync(`${CSEC_HOME}${SLASH}nr-security-home`, 0o770);
143
143
  } catch (error) {
144
144
  }
145
145
  }
@@ -0,0 +1,111 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: New Relic Pre-Release
4
+ */
5
+ const logger = require('../logging').getLogger();
6
+ const grpcutils = require('../../../../instrumentation-security/core/grpc-utils');
7
+ const clientMap = new Map();
8
+ let grpc;
9
+ const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../core/sec-agent-constants');
10
+ try {
11
+ grpc = require("@grpc/grpc-js");
12
+ } catch (error) {
13
+
14
+ }
15
+
16
+ /**
17
+ * Utility to get client object
18
+ * @param {*} config
19
+ * @returns
20
+ */
21
+ function getGPRCClient(config) {
22
+ try {
23
+ const serviceName = grpcutils.getMethod(config.method).serviceName;
24
+
25
+ if (clientMap.has(serviceName)) {
26
+ return clientMap.get(serviceName);
27
+ }
28
+ const serviceObject = grpcutils.getService(serviceName);
29
+ const definition = grpc.loadPackageDefinition(serviceObject);
30
+ let service = getNestedValue(definition, serviceName)
31
+ if (service) {
32
+ logger.info("GRPC client service extracted successfully");
33
+ }
34
+ const client = new service(
35
+ "localhost:" + config.serverPort,
36
+ grpc.credentials.createInsecure()
37
+ );
38
+ if (!clientMap.has(serviceName)) {
39
+ clientMap.set(serviceName, client);
40
+ }
41
+ if(client){
42
+ logger.info("GRPC IAST client created successfully");
43
+ }
44
+ return client;
45
+ } catch (error) {
46
+ logger.error("error while creating client:", error);
47
+ }
48
+
49
+ }
50
+
51
+ /**
52
+ * Utility to fire unary requests
53
+ * @param {*} client
54
+ * @param {*} config
55
+ */
56
+ function fireUnaryRequest(client, config) {
57
+ try {
58
+ const methodObj = grpcutils.getMethod(config.requestURI);
59
+ if (methodObj && methodObj.originalName) {
60
+ let method = methodObj.originalName;
61
+ let metadata = new grpc.Metadata();
62
+ let bufferObj = Buffer.from(config.headers[NR_CSEC_FUZZ_REQUEST_ID], "utf8");
63
+ let base64String = bufferObj.toString("base64");
64
+ metadata.add(NR_CSEC_FUZZ_REQUEST_ID, base64String);
65
+ client[method](JSON.parse(config.data), metadata, (err, res) => {
66
+ if (err) {
67
+ logger.debug("Error occured:", err);
68
+ return
69
+ }
70
+
71
+ logger.info("Fuzz success: ", JSON.stringify(config));
72
+ return res;
73
+ })
74
+ }
75
+ else {
76
+ logger.debug("Not Found:", config.url);
77
+ }
78
+ } catch (error) {
79
+ logger.debug("Error while firing Unary request:", error);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * fire gRPC request based on provided config
85
+ * @param {*} config
86
+ * @returns
87
+ */
88
+ function fireRequest(config) {
89
+ let client = getGPRCClient(config);
90
+ fireUnaryRequest(client, config)
91
+ }
92
+
93
+
94
+ function getNestedValue(obj, targetKey) {
95
+ for (const key in obj) {
96
+ if (key === targetKey) {
97
+ return obj[key];
98
+ } else if (typeof obj[key] === 'object' && obj[key] !== null) {
99
+ const result = getNestedValue(obj[key], targetKey);
100
+ if (result !== undefined) {
101
+ return result;
102
+ }
103
+ }
104
+ }
105
+ return undefined; // Key not found
106
+ }
107
+
108
+
109
+ module.exports = {
110
+ fireRequest
111
+ };
@@ -7,6 +7,7 @@
7
7
  const stringify = require('fast-safe-stringify');
8
8
 
9
9
  const restClient = require('../../restclient');
10
+ const grpcClient = require('../../grpcClient');
10
11
  const { Agent } = require('../../../agent');
11
12
  const { FuzzFailEvent } = require('../../../FuzzFailEvent');
12
13
  const { CSEC_HOME_TMP, COLON, NR_CSEC_FUZZ_REQUEST_ID, SLASH, CSEC_SEP, SEVERE } = require('../../../sec-agent-constants');
@@ -20,9 +21,6 @@ const LogMessage = require('../../../LogMessage');
20
21
  require('dns').setDefaultResultOrder('ipv4first')
21
22
  const PolicyManager = require('../../../Policy');
22
23
 
23
- const {
24
- IS_LAMBDA_ENV,
25
- AWS_LAMBDA_FUNCTION_VERSION_ENV_IDENTIFIER } = require('../../../sec-agent-constants');
26
24
  const statusUtils = require('../../../statusUtils');
27
25
 
28
26
  const find = `${SLASH}{{NR_CSEC_VALIDATOR_HOME_TMP}}`;
@@ -125,13 +123,16 @@ function handler(json) {
125
123
  function handleFuzzRequest(fuzzDetails) {
126
124
  const { fuzzRequest } = fuzzDetails;
127
125
  try {
128
- if (IS_LAMBDA_ENV) {
129
- const qualifiedARN = Agent.getAgent().applicationInfo.applicationUUID;
130
- const splitArn = String.prototype.split.call(qualifiedARN, ':');
131
- const arn = splitArn.slice(0, splitArn.length - 1).join(':');
132
- const ver = process.env[AWS_LAMBDA_FUNCTION_VERSION_ENV_IDENTIFIER];
133
- logger.info('Invoking Lambda:: ARN: ' + arn);
134
- logger.info('Invoking Lambda:: Version: ' + ver);
126
+ if (fuzzRequest.protocol == 'grpc') {
127
+ const config = parseGRPCRequestToFuzz(fuzzRequest);
128
+ config.headers['nr-csec-parent-id'] = fuzzRequest.id;
129
+ IASTUtil.completedRequestsMapInit(fuzzRequest.id);
130
+ grpcClient.fireRequest(config);
131
+ if (fuzzRequest.headers && fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
132
+ logScannedApiId(fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID], fuzzRequest.requestURI)
133
+ }
134
+
135
+ IASTUtil.removePendingRequestId(fuzzRequest.id);
135
136
 
136
137
  } else {
137
138
  const config = parseAxiosHttpRequestToFuzz(fuzzRequest);
@@ -153,7 +154,6 @@ function handleFuzzRequest(fuzzDetails) {
153
154
 
154
155
 
155
156
  logger.info('Firing http request:: URL: ' + config.url);
156
-
157
157
  const response = restClient.fireRequest(config);
158
158
  handleFuzzResponse(response, fuzzDetails);
159
159
  }
@@ -210,6 +210,26 @@ function parseAxiosHttpRequestToFuzz(requestObject) {
210
210
  };
211
211
  }
212
212
 
213
+ /**
214
+ * Parses the passed fuzz request object to
215
+ * Axios config.
216
+ *
217
+ * @param {JSON} requestObject
218
+ */
219
+ function parseGRPCRequestToFuzz(requestObject) {
220
+ let serverName = requestObject.serverName ? requestObject.serverName : LOCALHOST;
221
+ let host = serverName + COLON + requestObject.serverPort
222
+ return {
223
+ url: requestObject.protocol + COLON_SLASH_SLASH + host + requestObject.url,
224
+ requestURI: requestObject.requestURI,
225
+ protocol: requestObject.protocol,
226
+ serverPort: requestObject.serverPort,
227
+ method: requestObject.method,
228
+ data: requestObject.body,
229
+ headers: requestObject.headers,
230
+ timeout: 5000,
231
+ };
232
+ }
213
233
 
214
234
 
215
235
  /**
@@ -31,11 +31,11 @@ const CSEC_ROOT = `${CSEC_HOME}${sep}nr-security-home${sep}`;
31
31
  function createLogPathIfNotExist(dir) {
32
32
  try {
33
33
  if (!fs.existsSync(dir)) {
34
- fs.mkdirSync(dir, 777, { recursive: true });
35
- fs.chmodSync(dir, 0o777);
34
+ fs.mkdirSync(dir, 770, { recursive: true });
35
+ fs.chmodSync(dir, 0o770);
36
36
  }
37
37
  else {
38
- fs.chmodSync(dir, 0o777);
38
+ fs.chmodSync(dir, 0o770);
39
39
  }
40
40
  } catch (error) {
41
41
  }
@@ -168,8 +168,8 @@ function getLogger(applicationUUID = process.env.applicationUUID) {
168
168
  logger = log4js.getLogger('NR_CSEC');
169
169
  const initLogger = getInitLogger();
170
170
  try {
171
- fs.chmodSync(`${logFileName}`, 0o777);
172
- fs.chmodSync(`${initLogFileName}`, 0o777);
171
+ fs.chmodSync(`${logFileName}`, 0o660);
172
+ fs.chmodSync(`${initLogFileName}`, 0o660);
173
173
  } catch (error) {
174
174
  }
175
175
  initLogger.info("[STEP-2] => Generating unique identifier:", applicationUUID)
@@ -93,7 +93,7 @@ function getFormattedData() {
93
93
  function writeSnapshot() {
94
94
  const snapshot = getFormattedData();
95
95
  commonUtils.removeOlderSnapshots();
96
- fs.writeFile(statusFile, snapshot, { mode: 0o777 }, function (err) {
96
+ fs.writeFile(statusFile, snapshot, { mode: 0o660 }, function (err) {
97
97
  if (err) {
98
98
  logger.debug(err.message);
99
99
  const LogMessage = require('./LogMessage');
@@ -101,7 +101,7 @@ function writeSnapshot() {
101
101
  commonUtils.addLogEventtoBuffer(logMessage);
102
102
  } else {
103
103
  logger.info('Snapshot updated to file: %s', statusFile);
104
- fs.chmod(statusFile, 0o777, (err) => {
104
+ fs.chmod(statusFile, 0o660, (err) => {
105
105
  if (err) {
106
106
  addErrortoBuffer(err);
107
107
  }
@@ -70,6 +70,15 @@ const initialize = function () {
70
70
  setTimeout(() => {
71
71
  setInterval(sendHC, HC_INTERVAL_MS);
72
72
  sendHC();
73
+ try {
74
+ let apiEndpoints = require('../../../instrumentation-security/core/route-manager').getAllAPIEndPoints();
75
+ let data = require('./apiEndpoints').APIEndPoint(apiEndpoints);
76
+ logger.debug("All API end points of the application is:",JSON.stringify(data));
77
+ getDispatcherAndSendEvent(data);
78
+ } catch (error) {
79
+ logger.debug("Error while processing API endpoints",error);
80
+ }
81
+
73
82
  try {
74
83
  const framework = NRAgent.environment.get('Framework')[0];
75
84
  const dispatcher = NRAgent.environment.get('Dispatcher')[0];
@@ -78,8 +87,8 @@ const initialize = function () {
78
87
  } catch (error) {
79
88
  logger.debug("Error while extracting data from APM environment",error);
80
89
  }
90
+ }, 20000);
81
91
 
82
- }, 30000);
83
92
 
84
93
  setInterval(commonUtils.logRollOver, 30000);
85
94
  initLogger.info("[STEP-5] => Security Agent components started");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/security-agent",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "New Relic Security Agent for Node.js",
5
5
  "main": "index.js",
6
6
  "jsonVersion": "1.1.1",
@@ -35,7 +35,7 @@
35
35
  "url": "https://github.com/newrelic/csec-node-agent.git"
36
36
  },
37
37
  "dependencies": {
38
- "axios": "^1.6.5",
38
+ "axios": "^1.6.8",
39
39
  "check-disk-space": "^3.4.0",
40
40
  "content-type": "^1.0.5",
41
41
  "fast-safe-stringify": "^2.1.1",
@@ -58,6 +58,8 @@
58
58
  "ws": "^8.14.2"
59
59
  },
60
60
  "devDependencies": {
61
+ "@grpc/grpc-js": "^1.9.5",
62
+ "@grpc/proto-loader": "^0.7.10",
61
63
  "@hapi/hapi": "^21.3.0",
62
64
  "@koa/router": "^12.0.0",
63
65
  "@newrelic/newrelic-oss-cli": "^0.1.2",
@@ -84,7 +86,9 @@
84
86
  "request": "^2.88.2",
85
87
  "restify": "^11.1.0",
86
88
  "sinon": "^15.0.0",
89
+ "stream": "^0.0.2",
87
90
  "tap": "^16.3.2",
91
+ "undici": "^5.28.0",
88
92
  "xmldom": "^0.6.0",
89
93
  "xpath": "^0.0.32",
90
94
  "xpath.js": "^1.1.0"