newrelic 9.7.0 → 9.7.2

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.
Files changed (37) hide show
  1. package/NEWS.md +42 -18
  2. package/THIRD_PARTY_NOTICES.md +203 -2
  3. package/api.js +155 -103
  4. package/lib/agent.js +29 -82
  5. package/lib/collector/api.js +234 -201
  6. package/lib/config/attribute-filter.js +35 -25
  7. package/lib/config/default.js +45 -19
  8. package/lib/config/index.js +260 -199
  9. package/lib/environment.js +16 -12
  10. package/lib/errors/error-collector.js +124 -55
  11. package/lib/errors/index.js +59 -49
  12. package/lib/instrumentation/@hapi/hapi.js +56 -50
  13. package/lib/instrumentation/@node-redis/client.js +50 -41
  14. package/lib/instrumentation/amqplib.js +116 -151
  15. package/lib/instrumentation/core/{async_hooks.js → async-hooks.js} +26 -11
  16. package/lib/instrumentation/core/globals.js +1 -1
  17. package/lib/instrumentation/core/http-outbound.js +193 -78
  18. package/lib/instrumentation/core/timers.js +106 -59
  19. package/lib/instrumentation/grpc-js/grpc.js +20 -23
  20. package/lib/instrumentation/mongodb/common.js +87 -85
  21. package/lib/instrumentation/redis.js +112 -90
  22. package/lib/instrumentation/undici.js +204 -192
  23. package/lib/instrumentation/{when.js → when/constants.js} +13 -10
  24. package/lib/instrumentation/when/contextualizer.js +168 -0
  25. package/lib/instrumentation/when/index.js +354 -0
  26. package/lib/instrumentation/when/nr-hooks.js +15 -0
  27. package/lib/instrumentations.js +1 -1
  28. package/lib/shim/shim.js +2 -0
  29. package/lib/shim/webframework-shim.js +19 -0
  30. package/lib/symbols.js +1 -0
  31. package/lib/system-info.js +240 -163
  32. package/lib/util/async-each-limit.js +30 -0
  33. package/lib/util/attributes.js +159 -0
  34. package/lib/util/code-level-metrics.js +58 -0
  35. package/lib/util/deep-equal.js +11 -144
  36. package/package.json +5 -4
  37. package/lib/instrumentation/promise.js +0 -572
@@ -5,9 +5,6 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- /* eslint sonarjs/cognitive-complexity: ["error", 29] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
9
- /* eslint sonarjs/max-switch-cases: ["error", 36] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
10
-
11
8
  const AttributeFilter = require('./attribute-filter')
12
9
  const CollectorResponse = require('../collector/response')
13
10
  const copy = require('../util/copy')
@@ -41,6 +38,7 @@ const LASP_MAP = require('./lasp').LASP_MAP
41
38
  const HSM = require('./hsm')
42
39
  const REMOVE_BEFORE_SEND = new Set(['attributeFilter'])
43
40
  const SSL_WARNING = 'SSL config key can no longer be disabled, not updating.'
41
+ const SERVERLESS_DT_KEYS = ['account_id', 'primary_application_id', 'trusted_account_key']
44
42
 
45
43
  const exists = fs.existsSync
46
44
  let logger = null // Lazy-loaded in `initialize`.
@@ -200,7 +198,7 @@ Config.prototype.mergeServerConfig = mergeServerConfig
200
198
  * recognized, unsupported, and unknown parameters.
201
199
  *
202
200
  * @param {object} json The config blob sent by New Relic.
203
- * @param recursion
201
+ * @param {boolean} recursion flag indicating coming from server side config
204
202
  */
205
203
  Config.prototype.onConnect = function onConnect(json, recursion) {
206
204
  json = json || Object.create(null)
@@ -233,6 +231,49 @@ Config.prototype._getMostSecure = function getMostSecure(key, currentVal, newVal
233
231
  return filter(currentVal, newVal)
234
232
  }
235
233
 
234
+ /**
235
+ * Helper that checks if value from server is false
236
+ * then updates the corresponding configuration enabled flag.
237
+ * We never allow server-side config to enable a feature but you can disable
238
+ *
239
+ * @param {*} serverValue value from server
240
+ * @param {string} key within configuration to disable its enabled flag
241
+ */
242
+ Config.prototype._disableOption = function _disableOption(serverValue, key) {
243
+ if (serverValue === false) {
244
+ this[key].enabled = false
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Updates harvest_limits for event_harvest_config if they are valid values
250
+ *
251
+ * @param {object} serverConfig harvest config from server
252
+ * @param {string} key value from server side config that stores the event harvest config
253
+ */
254
+ Config.prototype._updateHarvestConfig = function _updateHarvestConfig(serverConfig, key) {
255
+ const val = serverConfig[key]
256
+ const isValidConfig = harvestConfigValidator.isValidHarvestConfig(val)
257
+ if (!isValidConfig) {
258
+ this.emit(key, null)
259
+ return
260
+ }
261
+
262
+ logger.info('Valid event_harvest_config received. Updating harvest cycles.', val)
263
+ const limits = Object.keys(val.harvest_limits).reduce((acc, k) => {
264
+ const v = val.harvest_limits[k]
265
+ if (harvestConfigValidator.isValidHarvestValue(v)) {
266
+ acc[k] = v
267
+ } else {
268
+ logger.info(`Omitting limit for ${k} due to invalid value ${v}`)
269
+ }
270
+ return acc
271
+ }, {})
272
+ val.harvest_limits = limits
273
+ this[key] = val
274
+ this.emit(key, val)
275
+ }
276
+
236
277
  /**
237
278
  * The guts of the logic about how to deal with server-side configuration.
238
279
  *
@@ -240,6 +281,7 @@ Config.prototype._getMostSecure = function getMostSecure(key, currentVal, newVal
240
281
  * @param {string} key The particular configuration parameter to set.
241
282
  */
242
283
  Config.prototype._fromServer = function _fromServer(params, key) {
284
+ /* eslint-disable-next-line sonarjs/max-switch-cases */
243
285
  switch (key) {
244
286
  // handled by the connection
245
287
  case 'messages':
@@ -318,49 +360,19 @@ Config.prototype._fromServer = function _fromServer(params, key) {
318
360
  this._updateIfChanged(params, key)
319
361
  break
320
362
  case 'event_harvest_config':
321
- const val = params[key]
322
- const isValidConfig = harvestConfigValidator.isValidHarvestConfig(val)
323
- if (!isValidConfig) {
324
- this.emit(key, null)
325
- break
326
- }
327
- logger.info('Valid event_harvest_config received. Updating harvest cycles.', val)
328
- const limits = Object.keys(val.harvest_limits).reduce((acc, k) => {
329
- const v = val.harvest_limits[k]
330
- if (harvestConfigValidator.isValidHarvestValue(v)) {
331
- acc[k] = v
332
- } else {
333
- logger.info(`Omitting limit for ${k} due to invalid value ${v}`)
334
- }
335
- return acc
336
- }, {})
337
- val.harvest_limits = limits
338
- this[key] = val
339
- this.emit(key, val)
363
+ this._updateHarvestConfig(params, key)
340
364
  break
341
365
 
342
366
  case 'collect_analytics_events':
343
- // never enable from server-side
344
- // but we allow the server to disable
345
- if (params.collect_analytics_events === false) {
346
- this.transaction_events.enabled = false
347
- }
367
+ this._disableOption(params.collect_analytics_events, 'transaction_events')
348
368
  break
349
369
 
350
370
  case 'collect_custom_events':
351
- // never enable from server-side
352
- // but we allow the server to disable
353
- if (params.collect_custom_events === false) {
354
- this.custom_insights_events.enabled = false
355
- }
371
+ this._disableOption(params.collect_custom_events, 'custom_insights_events')
356
372
  break
357
373
 
358
374
  case 'collect_span_events':
359
- // never enable from server-side
360
- // but we allow the server to disable
361
- if (params.collect_span_events === false) {
362
- this.span_events.enabled = false
363
- }
375
+ this._disableOption(params.collect_span_events, 'span_events')
364
376
  break
365
377
 
366
378
  case 'allow_all_headers':
@@ -930,6 +942,8 @@ function getHostnameSafe() {
930
942
 
931
943
  /**
932
944
  * Ensure that the apps names are always returned as a list.
945
+ *
946
+ * @returns {Array} list of applications
933
947
  */
934
948
  Config.prototype.applications = function applications() {
935
949
  const apps = this.app_name
@@ -950,7 +964,7 @@ Config.prototype.applications = function applications() {
950
964
  *
951
965
  * @param {object} external The configuration being loaded.
952
966
  * @param {object} internal Whichever chunk of the config being overridden.
953
- * @param arbitrary
967
+ * @param {boolean} arbitrary flag indicating if it is in the HAS_ARBITRARY_KEYS set
954
968
  */
955
969
  Config.prototype._fromPassed = function _fromPassed(external, internal, arbitrary) {
956
970
  if (!external) {
@@ -1035,15 +1049,15 @@ Config.prototype._featureFlagsFromEnv = function _featureFlagsFromEnv() {
1035
1049
  * @returns {string} formatted env var name
1036
1050
  */
1037
1051
  function deriveEnvVar(key, paths) {
1038
- let path = paths.join('_')
1039
- path = path ? `${path}_` : path
1040
- return `NEW_RELIC_${path.toUpperCase()}${key.toUpperCase()}`
1052
+ let configPath = paths.join('_')
1053
+ configPath = configPath ? `${configPath}_` : configPath
1054
+ return `NEW_RELIC_${configPath.toUpperCase()}${key.toUpperCase()}`
1041
1055
  }
1042
1056
 
1043
1057
  /**
1044
1058
  * Assigns the value of an env var to its corresponding configuration path
1045
1059
  *
1046
- * @param {object} params
1060
+ * @param {object} params object passed to fn
1047
1061
  * @param {object} params.config agent config
1048
1062
  * @param {string} params.key key to assign value
1049
1063
  * @param {string} params.envVar name of env var
@@ -1064,7 +1078,7 @@ function setFromEnv({ config, key, envVar, formatter, paths }) {
1064
1078
  * @param {object} [config=this] The current level of the configuration object.
1065
1079
  * @param {object} [data=configDefinition] The current level of the config definition object.
1066
1080
  * @param {Array} [paths=[]] keeps track of the nested path to properly derive the env var
1067
- * @param {int} [objectKeys=1] indicator of how many keys exist in current node to know when to remove current node after all keys are processed
1081
+ * @param {number} [objectKeys=1] indicator of how many keys exist in current node to know when to remove current node after all keys are processed
1068
1082
  */
1069
1083
  Config.prototype._fromEnvironment = function _fromEnvironment(
1070
1084
  config = this,
@@ -1101,144 +1115,131 @@ Config.prototype._fromEnvironment = function _fromEnvironment(
1101
1115
  }
1102
1116
 
1103
1117
  /**
1104
- * Returns true if logging has been manually enabled via configuration file or
1105
- * environment variable.
1118
+ * Disables `logging.enabled` if not set in configuration file or environment variable.
1106
1119
  *
1107
1120
  * @param {*} inputConfig configuration passed to the Config constructor
1108
- * @returns {boolean}
1121
+ * @returns {void}
1109
1122
  */
1110
- Config.prototype._loggingManuallySet = function _loggingManuallySet(inputConfig) {
1111
- const inputEnabled = inputConfig && inputConfig.logging && inputConfig.logging.enabled
1123
+ Config.prototype._serverlessLogging = function _serverlessLogging(inputConfig) {
1124
+ const inputEnabled = inputConfig?.logging?.enabled
1112
1125
  const envEnabled = process.env.NEW_RELIC_LOG_ENABLED
1113
1126
 
1114
- return inputEnabled !== undefined || envEnabled !== undefined
1127
+ if (inputEnabled === undefined && envEnabled === undefined) {
1128
+ this.logging.enabled = false
1129
+
1130
+ logger.info(
1131
+ 'Logging is disabled by default when serverless_mode is enabled. ' +
1132
+ 'If desired, enable logging via config file or environment variable and ' +
1133
+ 'set filepath to a valid path for current environment, stdout or stderr.'
1134
+ )
1135
+ }
1115
1136
  }
1116
1137
 
1117
1138
  /**
1118
1139
  * Returns true if native-metrics has been manually enabled via configuration
1119
- * file or enveironment variable
1140
+ * file or environment variable
1120
1141
  *
1121
1142
  * @param {*} inputConfig configuration pass to the Config constructor
1122
- * @returns {boolean}
1143
+ * @returns {void}
1123
1144
  */
1124
- Config.prototype._nativeMetricsManuallySet = function _nativeMetricsManuallySet(inputConfig) {
1125
- const inputEnabled =
1126
- inputConfig &&
1127
- inputConfig.plugins &&
1128
- inputConfig.plugins.native_metrics &&
1129
- inputConfig.plugins.native_metrics.enabled
1145
+ Config.prototype._serverlessNativeMetrics = function _serverlessNativeMetrics(inputConfig) {
1146
+ const inputEnabled = inputConfig?.plugins?.native_metrics?.enabled
1130
1147
  const envEnabled = process.env.NEW_RELIC_NATIVE_METRICS_ENABLED
1131
1148
 
1132
- return inputEnabled !== undefined || envEnabled !== undefined
1149
+ if (
1150
+ (inputEnabled !== undefined || envEnabled !== undefined) &&
1151
+ this.plugins.native_metrics.enabled
1152
+ ) {
1153
+ logger.info(
1154
+ 'Enabling the native-metrics module when in serverless mode may greatly ' +
1155
+ 'increase cold-start times. Given the limited benefit of the VM metrics' +
1156
+ 'and general lack of control in a serverless environment, we do not ' +
1157
+ 'recommend this trade-off.'
1158
+ )
1159
+ } else {
1160
+ this.plugins.native_metrics.enabled = false
1161
+
1162
+ logger.info(
1163
+ 'The native-metrics module is disabled by default when serverless_mode ' +
1164
+ 'is enabled. If desired, enable the native-metrics module via config file ' +
1165
+ 'or environment variable.'
1166
+ )
1167
+ }
1133
1168
  }
1134
1169
 
1135
1170
  /**
1136
- * Returns true if distributed tracing has been manually enabled via configuration file or
1137
- * environment variable.
1171
+ * Application name is not currently leveraged by our Lambda product (March 2021).
1172
+ * Defaulting the name removes burden on customers to set while avoiding
1173
+ * breaking should it be used in the future.
1138
1174
  *
1139
- * @param {*} inputConfig configuration passed to the Config constructor
1140
- * @returns {boolean}
1175
+ * @returns {void}
1141
1176
  */
1142
- Config.prototype._DTManuallySet = function _DTManuallySet(inputConfig) {
1143
- const inputEnabled =
1144
- inputConfig && inputConfig.distributed_tracing && inputConfig.distributed_tracing.enabled
1177
+ Config.prototype._serverlessAppName = function _serverlessAppName() {
1178
+ if (!this.app_name || this.app_name.length === 0) {
1179
+ const namingSource = process.env.AWS_LAMBDA_FUNCTION_NAME
1180
+ ? 'process.env.AWS_LAMBDA_FUNCTION_NAME'
1181
+ : 'DEFAULT'
1145
1182
 
1146
- const envEnabled = process.env.NEW_RELIC_DISTRIBUTED_TRACING_ENABLED
1183
+ const name = process.env.AWS_LAMBDA_FUNCTION_NAME || 'Serverless Application'
1184
+ this.app_name = [name]
1147
1185
 
1148
- return inputEnabled !== undefined || envEnabled !== undefined
1186
+ logger.info("Auto-naming serverless application to ['%s'] from: %s", name, namingSource)
1187
+ }
1149
1188
  }
1150
1189
 
1151
1190
  /**
1152
- * Enforces config rules specific to running in serverless_mode:
1153
- * - disables cross_application_tracer.enabled if set
1154
- * - defaults logging to disabled
1155
- * - verifies data specific to running DT is defined either in config file of env vars
1156
- *
1157
- * @param {*} inputConfig configuration passed to the Config constructor
1191
+ * Disables CAT in serverless mode
1158
1192
  */
1159
- Config.prototype._enforceServerless = function _enforceServerless(inputConfig) {
1160
- if (this.serverless_mode.enabled) {
1161
- // Application name is not currently leveraged by our Lambda product (March 2021).
1162
- // Defaulting the name removes burden on customers to set while avoiding
1163
- // breaking should it be used in the future.
1164
- if (!this.app_name || this.app_name.length === 0) {
1165
- const namingSource = process.env.AWS_LAMBDA_FUNCTION_NAME
1166
- ? 'process.env.AWS_LAMBDA_FUNCTION_NAME'
1167
- : 'DEFAULT'
1168
-
1169
- const name = process.env.AWS_LAMBDA_FUNCTION_NAME || 'Serverless Application'
1170
- this.app_name = [name]
1171
-
1172
- logger.info("Auto-naming serverless application to ['%s'] from: %s", name, namingSource)
1173
- }
1174
-
1175
- // Explicitly disable old CAT in serverless_mode
1176
- if (this.cross_application_tracer.enabled) {
1177
- this.cross_application_tracer.enabled = false
1178
- logger.info('Cross application tracing is explicitly disabled in serverless_mode.')
1179
- }
1180
-
1181
- if (this.infinite_tracing.trace_observer.host) {
1182
- this.infinite_tracing.trace_observer.host = ''
1183
- this.infinite_tracing.trace_observer.port = ''
1184
- logger.info('Infinite tracing is explicitly disabled in serverless_mode.')
1185
- }
1186
-
1187
- if (!this._loggingManuallySet(inputConfig)) {
1188
- this.logging.enabled = false
1189
-
1190
- logger.info(
1191
- 'Logging is disabled by default when serverless_mode is enabled. ' +
1192
- 'If desired, enable logging via config file or environment variable and ' +
1193
- 'set filepath to a valid path for current environment, stdout or stderr.'
1194
- )
1195
- }
1193
+ Config.prototype._serverlessCAT = function _serverlessCAT() {
1194
+ if (this.cross_application_tracer.enabled) {
1195
+ this.cross_application_tracer.enabled = false
1196
+ logger.info('Cross application tracing is explicitly disabled in serverless_mode.')
1197
+ }
1198
+ }
1196
1199
 
1197
- if (this._nativeMetricsManuallySet(inputConfig) && this.plugins.native_metrics.enabled) {
1198
- logger.info(
1199
- 'Enabling the native-metrics module when in serverless mode may greatly ' +
1200
- 'increase cold-start times. Given the limited benefit of the VM metrics' +
1201
- 'and general lack of control in a serverless environment, we do not ' +
1202
- 'recommend this trade-off.'
1203
- )
1204
- } else {
1205
- this.plugins.native_metrics.enabled = false
1200
+ Config.prototype._serverlessInfiniteTracing = function _serverlessInfiniteTracing() {
1201
+ if (this.infinite_tracing.trace_observer.host) {
1202
+ this.infinite_tracing.trace_observer.host = ''
1203
+ this.infinite_tracing.trace_observer.port = ''
1204
+ logger.info('Infinite tracing is explicitly disabled in serverless_mode.')
1205
+ }
1206
+ }
1206
1207
 
1207
- logger.info(
1208
- 'The native-metrics module is disabled by default when serverless_mode ' +
1209
- 'is enabled. If desired, enable the native-metrics module via config file ' +
1210
- 'or environment variable.'
1208
+ /**
1209
+ * Disables DT if account_id is not set.
1210
+ * Otherwise it will set trusted_account_key and primary_application_id accordingly.
1211
+ *
1212
+ * @returns {void}
1213
+ */
1214
+ Config.prototype._serverlessDT = function _serverlessDT() {
1215
+ if (!this.account_id) {
1216
+ if (this.distributed_tracing.enabled) {
1217
+ logger.warn(
1218
+ 'Using distributed tracing in serverless mode requires account_id be ' +
1219
+ 'defined, either in your newrelic.js file or via environment variables. ' +
1220
+ 'Disabling distributed tracing.'
1211
1221
  )
1222
+ this.distributed_tracing.enabled = false
1212
1223
  }
1213
-
1214
- if (!this._DTManuallySet(inputConfig)) {
1215
- this.distributed_tracing.enabled = true
1216
- }
1217
-
1218
- if (!this.account_id) {
1219
- if (this.distributed_tracing.enabled) {
1220
- logger.warn(
1221
- 'Using distributed tracing in serverless mode requires account_id be ' +
1222
- 'defined, either in your newrelic.js file or via environment variables. ' +
1223
- 'Disabling distributed tracing.'
1224
- )
1225
- this.distributed_tracing.enabled = false
1226
- }
1227
-
1228
- return
1229
- }
1224
+ } else {
1230
1225
  // default trusted_account_key to account_id
1231
1226
  this.trusted_account_key = this.trusted_account_key || this.account_id
1232
1227
 
1233
1228
  // Not required in serverless mode but must default to Unknown to function.
1234
1229
  this.primary_application_id = this.primary_application_id || 'Unknown'
1235
-
1236
- return
1237
1230
  }
1231
+ }
1238
1232
 
1239
- const DT_KEYS = ['account_id', 'primary_application_id', 'trusted_account_key']
1233
+ /**
1234
+ * In serverless mode we allow defer auth to the downstream serverless entities.
1235
+ * This means we set account_id, primary_application_id, and trusted_account_key in configuration.
1236
+ * This function sets all those to null because this.serverles_mode.enabled is falsey.
1237
+ *
1238
+ * @returns {void}
1239
+ */
1240
+ Config.prototype._preventServerlessDT = function _preventServerlessDT() {
1240
1241
  // Don't allow DT config settings to be set if serverless_mode is disabled
1241
- DT_KEYS.forEach((key) => {
1242
+ SERVERLESS_DT_KEYS.forEach((key) => {
1242
1243
  if (this[key]) {
1243
1244
  logger.warn(
1244
1245
  key +
@@ -1250,6 +1251,27 @@ Config.prototype._enforceServerless = function _enforceServerless(inputConfig) {
1250
1251
  })
1251
1252
  }
1252
1253
 
1254
+ /**
1255
+ * Enforces config rules specific to running in serverless_mode:
1256
+ * - disables cross_application_tracer.enabled if set
1257
+ * - defaults logging to disabled
1258
+ * - verifies data specific to running DT is defined either in config file of env vars
1259
+ *
1260
+ * @param {*} inputConfig configuration passed to the Config constructor
1261
+ */
1262
+ Config.prototype._enforceServerless = function _enforceServerless(inputConfig) {
1263
+ if (this.serverless_mode.enabled) {
1264
+ this._serverlessAppName()
1265
+ this._serverlessCAT()
1266
+ this._serverlessInfiniteTracing()
1267
+ this._serverlessLogging(inputConfig)
1268
+ this._serverlessNativeMetrics(inputConfig)
1269
+ this._serverlessDT(inputConfig)
1270
+ } else {
1271
+ this._preventServerlessDT()
1272
+ }
1273
+ }
1274
+
1253
1275
  /**
1254
1276
  * Depending on how the status codes are set, they could be strings, which
1255
1277
  * makes strict equality testing / indexOf fail. To keep things cheap, parse
@@ -1290,43 +1312,57 @@ Config.prototype._canonicalize = function _canonicalize() {
1290
1312
  }
1291
1313
  }
1292
1314
 
1293
- function _parseCodes(codes) {
1294
- // range does not support negative values
1295
- function parseRange(range, parsed) {
1296
- const split = range.split('-')
1297
- if (split.length !== 2) {
1298
- logger.warn('Failed to parse range %s', range)
1299
- return parsed
1300
- }
1301
- if (split[0] === '') {
1302
- // catch negative code. ex. -7
1303
- return parsed.push(parseInt(range, 10))
1304
- }
1305
- const lower = parseInt(split[0], 10)
1306
- const upper = parseInt(split[1], 10)
1307
- if (Number.isNaN(lower) || Number.isNaN(upper)) {
1308
- logger.warn('Range must contain two numbers %s', range)
1309
- return parsed
1310
- }
1311
- if (lower > upper) {
1312
- logger.warn('Range must start with lower bound %s', range)
1313
- } else if (lower < 0 || upper > 1000) {
1314
- logger.warn('Range must be between 0 and 1000 %s', range)
1315
- } else {
1316
- // success
1317
- for (let i = lower; i <= upper; i++) {
1318
- parsed.push(i)
1319
- }
1320
- }
1315
+ /**
1316
+ * Splits a range of status codes. It will not
1317
+ * allow negative values, non-numbers, or numbers above 1000.
1318
+ *
1319
+ * @param {string} range range of status codes 400-421
1320
+ * @param {Array} parsed list of parsed codes
1321
+ * @returns {Array} adds to the cleansed list of status codes
1322
+ * by removing any ranges and adds as elements
1323
+ */
1324
+ function _parseRange(range, parsed) {
1325
+ const split = range.split('-')
1326
+ if (split.length !== 2) {
1327
+ logger.warn('Failed to parse range %s', range)
1321
1328
  return parsed
1322
1329
  }
1330
+ if (split[0] === '') {
1331
+ // catch negative code. ex. -7
1332
+ return parsed.push(parseInt(range, 10))
1333
+ }
1334
+ const lower = parseInt(split[0], 10)
1335
+ const upper = parseInt(split[1], 10)
1336
+ if (Number.isNaN(lower) || Number.isNaN(upper)) {
1337
+ logger.warn('Range must contain two numbers %s', range)
1338
+ return parsed
1339
+ }
1340
+ if (lower > upper) {
1341
+ logger.warn('Range must start with lower bound %s', range)
1342
+ } else if (lower < 0 || upper > 1000) {
1343
+ logger.warn('Range must be between 0 and 1000 %s', range)
1344
+ } else {
1345
+ // success
1346
+ for (let i = lower; i <= upper; i++) {
1347
+ parsed.push(i)
1348
+ }
1349
+ }
1350
+ return parsed
1351
+ }
1323
1352
 
1353
+ /**
1354
+ * Parses a list of status codes. It can also
1355
+ * parse a range of status codes
1356
+ *
1357
+ * @param {Array} codes list of status codes
1358
+ * @returns {Array} cleansed list of status codes
1359
+ */
1360
+ function _parseCodes(codes) {
1324
1361
  const parsedCodes = []
1325
1362
  for (let i = 0; i < codes.length; i++) {
1326
1363
  const code = codes[i]
1327
-
1328
1364
  if (typeof code === 'string' && code.indexOf('-') !== -1) {
1329
- parseRange(code, parsedCodes)
1365
+ _parseRange(code, parsedCodes)
1330
1366
  } else {
1331
1367
  const parsedCode = parseInt(code, 10)
1332
1368
  if (!Number.isNaN(parsedCode)) {
@@ -1376,30 +1412,29 @@ Config.prototype._applyHighSecurity = function _applyHighSecurity() {
1376
1412
  }
1377
1413
 
1378
1414
  /**
1379
- * Checks policies received from preconnect against those expected
1380
- * by the agent, if LASP-enabled. Responds with an error to shut down
1381
- * the agent if necessary.
1415
+ * Sends a response to collector for LASP application
1382
1416
  *
1383
- * @param {Agent} agent
1384
- * @param {object} policies
1385
- * @returns {CollectorResponse} The result of the processing, with the known
1386
- * policies as the response payload.
1417
+ * @param {Array} keys keys from a LASP policy
1418
+ * @returns {CollectorResponse} creates CollectorResponse with either preserve or shutdown
1387
1419
  */
1388
- Config.prototype.applyLasp = function applyLasp(agent, policies) {
1389
- const config = this
1390
- const keys = Object.keys(policies)
1391
-
1392
- if (!config.security_policies_token) {
1393
- if (keys.length) {
1394
- logger.error(
1395
- 'The agent received one or more unexpected security policies and will shut down.'
1396
- )
1397
- return CollectorResponse.fatal(null)
1398
- }
1399
- return CollectorResponse.success(null)
1420
+ function _laspReponse(keys) {
1421
+ if (keys.length) {
1422
+ logger.error('The agent received one or more unexpected security policies and will shut down.')
1423
+ return CollectorResponse.fatal(null)
1400
1424
  }
1425
+ return CollectorResponse.success(null)
1426
+ }
1401
1427
 
1402
- const missingLASP = []
1428
+ /**
1429
+ * Applies the server side LASP policies to a local configuration object
1430
+ *
1431
+ * @param agent
1432
+ * @param {object} policies server side LASP policy
1433
+ * @returns {object} { missingRequired, finalPolicies } list of missing required fields and finalized LASP policy
1434
+ */
1435
+ Config.prototype._buildLaspPolicy = function _buildLaspPolicy(agent, policies) {
1436
+ const config = this
1437
+ const keys = Object.keys(policies)
1403
1438
  const missingRequired = []
1404
1439
 
1405
1440
  const finalPolicies = keys.reduce(function applyPolicy(obj, name) {
@@ -1451,6 +1486,29 @@ Config.prototype.applyLasp = function applyLasp(agent, policies) {
1451
1486
  return obj
1452
1487
  }, Object.create(null))
1453
1488
 
1489
+ return { missingRequired, finalPolicies }
1490
+ }
1491
+
1492
+ /**
1493
+ * Checks policies received from preconnect against those expected
1494
+ * by the agent, if LASP-enabled. Responds with an error to shut down
1495
+ * the agent if necessary.
1496
+ *
1497
+ * @param agent
1498
+ * @param {object} policies lasp policy
1499
+ * @returns {CollectorResponse} The result of the processing, with the known
1500
+ * policies as the response payload.
1501
+ */
1502
+ Config.prototype.applyLasp = function applyLasp(agent, policies) {
1503
+ const keys = Object.keys(policies)
1504
+
1505
+ if (!this.security_policies_token) {
1506
+ return _laspReponse(keys)
1507
+ }
1508
+
1509
+ const { finalPolicies, missingRequired } = this._buildLaspPolicy(agent, policies)
1510
+
1511
+ const missingLASP = []
1454
1512
  Object.keys(LASP_MAP).forEach(function checkPolicy(name) {
1455
1513
  if (!policies[name]) {
1456
1514
  // agent is expecting a policy that was not sent from server -- fail
@@ -1511,7 +1569,7 @@ function redactValue(value) {
1511
1569
  * Get a JSONifiable object containing all settings we want to report to the
1512
1570
  * collector and store in the environment_values table.
1513
1571
  *
1514
- * @returns Object containing simple key-value pairs of settings
1572
+ * @returns {object} containing simple key-value pairs of settings
1515
1573
  */
1516
1574
  Config.prototype.publicSettings = function publicSettings() {
1517
1575
  let settings = Object.create(null)
@@ -1586,8 +1644,8 @@ Config.prototype._warnDeprecations = function _warnDeprecations() {
1586
1644
  * the environment variables will override their corresponding
1587
1645
  * configuration file settings.
1588
1646
  *
1589
- * @param {object} config Optional configuration to be used in place of a
1590
- * config file.
1647
+ * @param {object} config Optional configuration to be used in place of a config file.
1648
+ * @returns {object} instantiated configuration object
1591
1649
  */
1592
1650
  function initialize(config) {
1593
1651
  /**
@@ -1660,7 +1718,8 @@ function initialize(config) {
1660
1718
  /**
1661
1719
  * This helper function creates an empty configuration object
1662
1720
  *
1663
- * @param config
1721
+ * @param {object} config current configuration object to overwrite
1722
+ * @returns {object} config new config object
1664
1723
  */
1665
1724
  function createNewConfigObject(config) {
1666
1725
  config = new Config(Object.create(null))
@@ -1674,6 +1733,8 @@ function createNewConfigObject(config) {
1674
1733
  * This function honors the singleton nature of this module while allowing
1675
1734
  * consumers to just request an instance without having to worry if one was
1676
1735
  * already created.
1736
+ *
1737
+ * @returns {object} initialized configuration object
1677
1738
  */
1678
1739
  function getOrCreateInstance() {
1679
1740
  if (_configInstance === null) {