@contrast/config 1.0.0 → 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/lib/util.js CHANGED
@@ -179,6 +179,32 @@ function getAutoEnv(name) {
179
179
  : process.env[`CONTRAST__${envName}`] || process.env[envName];
180
180
  }
181
181
 
182
+ /**
183
+ * When you run an application with pm2 on cluster mode, pm2 attaches the
184
+ * process.env to a property pm2_env(only for the agent since we start it with -r flag), so
185
+ * the function checks if there is a pm2_env property and merge contrast
186
+ * related properties to process.env
187
+ */
188
+ function mergePM2Envs() {
189
+ if (!process.env.pm2_env) return;
190
+
191
+ const pm2_env = JSON.parse(process.env.pm2_env);
192
+
193
+ const objectEntries = Object.entries(pm2_env.env).concat(Object.entries(pm2_env));
194
+
195
+ const cfgPath = 'CONTRAST_CONFIG_PATH';
196
+ const pm2CfgPath = pm2_env.env[cfgPath] || pm2_env[cfgPath];
197
+ if (pm2CfgPath) process.env[cfgPath] = pm2CfgPath;
198
+
199
+ objectEntries.forEach(([key, value]) => {
200
+ if (
201
+ !process.env[key] && key.toLocaleLowerCase().includes('contrast')
202
+ ) {
203
+ process.env[key] = value;
204
+ }
205
+ });
206
+ }
207
+
182
208
  /**
183
209
  * @return {Config} merged options
184
210
  */
@@ -229,6 +255,7 @@ function mergeOptions() {
229
255
 
230
256
  util.Config = Config;
231
257
  util.setup = function setup() {
258
+ mergePM2Envs();
232
259
  const mergedOptions = mergeOptions();
233
260
  mergedOptions.validate();
234
261
  return mergedOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/config",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "An API for discovering Contrast agent configuration data",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
package/lib/index.test.js DELETED
@@ -1,331 +0,0 @@
1
- 'use strict';
2
-
3
- const sinon = require('sinon');
4
- const mockfs = require('mock-fs');
5
- const { expect } = require('chai');
6
- const config = require('.');
7
- const configOptions = require('./options');
8
- const mocks = require('../../test/mocks');
9
- const AppInfo = require('../../core/lib/app-info');
10
-
11
- const {
12
- getGoodConfig,
13
- getBadConfig,
14
- getAbsolutePath,
15
- getDefaultConfig,
16
- getProperties,
17
- containsAllProps,
18
- } = require('../test/helpers');
19
-
20
- describe('config', function () {
21
- beforeEach(function () {
22
-
23
- // removing as some agent engineers have this env var set
24
- delete process.env['CONTRAST_CONFIG_PATH'];
25
- sinon.stub(console, 'error');
26
- });
27
-
28
- afterEach(function () {
29
- mockfs.restore();
30
- });
31
-
32
- describe('default config behavior', function () {
33
- it('sets all default properties in the config', function () {
34
- const defaultConfigProps = getProperties(getDefaultConfig());
35
- const loadConfigProps = getProperties(config());
36
- expect(containsAllProps(loadConfigProps, defaultConfigProps)).to.be.true;
37
- });
38
-
39
- it('logs an error a config file path is given and no config is found', function () {
40
- process.env['CONTRAST_CONFIG_PATH'] = 'fake config';
41
- config();
42
- expect(console.error).to.have.been.calledWith(
43
- 'Unable to read config file at %s: %s',
44
- 'fake config',
45
- sinon.match('ENOENT'),
46
- );
47
- delete process.env['CONTRAST_CONFIG_PATH'];
48
- });
49
-
50
- it('logs an error when the config cannot be parsed', function () {
51
- mockfs({
52
- 'contrast_security.yaml': mockfs.load(getBadConfig()),
53
- });
54
- config();
55
- expect(console.error).to.have.been.calledWith(
56
- 'YAML validator found an error in %s: %s',
57
- sinon.match(/contrast_security.yaml$/),
58
- sinon.match(/^Tabs are not allowed as indentation/),
59
- );
60
- });
61
-
62
- it('accepts overrides from env', function () {
63
- process.env['CONTRAST__API__API_KEY'] = 'demo';
64
- const cfg = config();
65
- expect(cfg.api.api_key).to.be.equal('demo');
66
- delete process.env['CONTRAST__API__API_KEY'];
67
- });
68
-
69
- it('loads and sets properties from a valid config', function () {
70
- mockfs({
71
- 'contrast_security.yaml': mockfs.load(getGoodConfig()),
72
- });
73
- const cfg = config();
74
- expect(console.error).to.not.have.been.called;
75
- expect(cfg.api.enable).to.be.true;
76
- expect(cfg.api.url).to.be.equal('http://localhost:19080/Contrast');
77
- expect(cfg.api.api_key).to.be.equal('demo');
78
- expect(cfg.api.service_key).to.be.equal('demo');
79
- expect(cfg.api.user_name).to.be.equal('contrast_admin');
80
- });
81
- });
82
-
83
- describe('config file path precedence', function () {
84
- it('should set config from environment variable even with one in cwd', function () {
85
- mockfs({
86
- 'contrast_security.yaml': mockfs.load(getGoodConfig()),
87
- '/otherDir/config/contrast_security.yaml': mockfs.load(
88
- getGoodConfig('2')
89
- ),
90
- });
91
- process.env['CONTRAST_CONFIG_PATH'] =
92
- '/otherDir/config/contrast_security.yaml';
93
- const cfg = config();
94
- expect(cfg.api.url).to.be.equal('https://localhost:9999/Contrast');
95
- expect(cfg.api.api_key).to.be.equal('QWERTYUIOP');
96
- expect(cfg.api.service_key).to.be.equal('ASDFGHJKL');
97
- expect(cfg.api.user_name).to.be.equal('contrast_user');
98
- delete process.env['CONTRAST_CONFIG_PATH'];
99
- });
100
-
101
- it('should set config from etc/contrast if envVar not set and no config in cwd', function () {
102
- mockfs({
103
- '/etc/contrast/contrast_security.yaml': mockfs.load(getGoodConfig()),
104
- });
105
- const cfg = config();
106
- expect(cfg.api.url).to.be.equal('http://localhost:19080/Contrast');
107
- expect(cfg.api.api_key).to.be.equal('demo');
108
- expect(cfg.api.service_key).to.be.equal('demo');
109
- expect(cfg.api.user_name).to.be.equal('contrast_admin');
110
- });
111
-
112
- it('should set config from cwd even when present in etc/contrast', function () {
113
- mockfs({
114
- '/etc/contrast/contrast_security.yaml': mockfs.load(getGoodConfig()),
115
- 'contrast_security.yaml': mockfs.load(getGoodConfig('2')),
116
- });
117
- const cfg = config();
118
- expect(cfg.api.url).to.be.equal('https://localhost:9999/Contrast');
119
- expect(cfg.api.api_key).to.be.equal('QWERTYUIOP');
120
- expect(cfg.api.service_key).to.be.equal('ASDFGHJKL');
121
- expect(cfg.api.user_name).to.be.equal('contrast_user');
122
- });
123
-
124
- it('should set from env variable even when present in cwd and etc/contrast', function () {
125
- mockfs({
126
- '/etc/contrast/contrast_security.yaml': mockfs.load(getGoodConfig()),
127
- 'contrast_security.yaml': mockfs.load(getGoodConfig('2')),
128
- '/otherDir/config/contrast_security.yaml': mockfs.load(
129
- getGoodConfig('3')
130
- ),
131
- });
132
- process.env['CONTRAST_CONFIG_PATH'] =
133
- '/otherDir/config/contrast_security.yaml';
134
- const cfg = config();
135
- expect(cfg.api.url).to.be.equal('https://localhost:8080/Contrast');
136
- expect(cfg.api.api_key).to.be.equal('QAZWSX');
137
- expect(cfg.api.service_key).to.be.equal('EDCRFV');
138
- expect(cfg.api.user_name).to.be.equal('super_admin');
139
- delete process.env['CONTRAST_CONFIG_PATH'];
140
- });
141
- });
142
-
143
- describe('config value precedence', function () {
144
- let options;
145
-
146
- beforeEach(function () {
147
- options = getDefaultConfig();
148
- delete process.env.CONTRAST__API__API_KEY;
149
- });
150
-
151
- it('should use env var before config file value', function () {
152
- process.env.CONTRAST__API__API_KEY = 'NOPE';
153
- const cfg = config(options);
154
- expect(cfg.api.api_key).to.be.equal('NOPE');
155
- });
156
- });
157
-
158
- describe('environment variables', function () {
159
- it('should support CONTRAST__ options', function () {
160
- const options = getDefaultConfig();
161
- process.env['CONTRAST__AGENT__NODE__ENABLE_REWRITE'] = 'f';
162
- const cfg = config(options);
163
-
164
- expect(cfg.agent.node.enable_rewrite).to.be.equal(false);
165
- delete process.env['CONTRAST__AGENT__NODE__ENABLE_REWRITE'];
166
- });
167
- it('Should support CONTRAST__API__ options', function () {
168
- const options = getDefaultConfig();
169
- process.env['CONTRAST__API__API_KEY'] = 'abcdefg';
170
- const cfg = config(options);
171
-
172
- expect(cfg.api.api_key).to.equal('abcdefg');
173
- delete process.env['CONTRAST__API__API_KEY'];
174
- });
175
- });
176
-
177
- describe('config options functions', function () {
178
- describe('clearBaseCase', function () {
179
- it('should return string if defined', function () {
180
- const string = 'asdf';
181
- expect(configOptions.clearBaseCase(string)).to.equal(string);
182
- });
183
-
184
- it('should return undefined if string is empty', function () {
185
- const string = '';
186
- expect(configOptions.clearBaseCase(string)).to.be.undefined;
187
- });
188
-
189
- it('should return int if and number and defined', function () {
190
- const num = 1;
191
- expect(configOptions.clearBaseCase(num)).to.equal(num);
192
- });
193
-
194
- it('should return undefined if value is NaN', function () {
195
- const num = NaN;
196
- expect(configOptions.clearBaseCase(num)).to.be.undefined;
197
- });
198
- });
199
-
200
- describe('castBoolean', function () {
201
- [true, 'true', 'TRUE', 't', 'T'].forEach(val => {
202
- it(`should return true if string value is ${val}`, function () {
203
- expect(configOptions.castBoolean(val)).to.equal(true);
204
- });
205
- });
206
-
207
- [false, 'false', 'FALSE', 'f', 'F'].forEach(val => {
208
- it(`should return false if string value is ${val}`, function () {
209
- expect(configOptions.castBoolean(val)).to.equal(false);
210
- });
211
- });
212
-
213
- ['rando', [1, 2, 3], {}, 100, null, undefined].forEach(val => {
214
- it(`should return undefined if ${val} not a boolean or string or not true/t/false/f`, function () {
215
- expect(configOptions.castBoolean(val)).to.equal(undefined);
216
- });
217
- });
218
- });
219
- });
220
-
221
- describe('enum', function () {
222
- let logger_level;
223
-
224
- beforeEach(function () {
225
- logger_level = 'CONTRAST__AGENT__LOGGER__LEVEL';
226
- });
227
-
228
- afterEach(function () {
229
- delete process.env[logger_level];
230
- });
231
-
232
- it('should use value from enum if there is a match', function () {
233
- process.env[logger_level] = 'info';
234
- const cfg = config();
235
- expect(cfg.agent.logger.level).to.be.equal('info');
236
- });
237
-
238
- it('should use default when there is no match within enum', function () {
239
- process.env[logger_level] = 'doggo';
240
- const cfg = config();
241
- expect(cfg.agent.logger.level).to.be.equal('error');
242
- });
243
- });
244
-
245
- describe('uppercase, lowercase, parsenum transformations', function () {
246
- it('should uppercase server.environment', function () {
247
- process.env['CONTRAST__SERVER__ENVIRONMENT'] = 'qa';
248
- const cfg = config();
249
- expect(cfg.server.environment).to.equal('QA');
250
- delete process.env['CONTRAST__SERVER__ENVIRONMENT'];
251
- });
252
-
253
- it('should lowercase logger level', function () {
254
- process.env['CONTRAST__AGENT__LOGGER__LEVEL'] = 'DEBUG';
255
- const cfg = config();
256
- expect(cfg.agent.logger.level).to.equal('debug');
257
- delete process.env['CONTRAST__AGENT__LOGGER__LEVEL'];
258
- });
259
-
260
- it('should parse stack_trace_limit into a number', function () {
261
- process.env['CONTRAST__AGENT__STACK_TRACE_LIMIT'] = '25';
262
- const cfg = config();
263
- expect(cfg.agent.stack_trace_limit).to.equal(25);
264
- delete process.env['CONTRAST__AGENT__STACK_TRACE_LIMIT'];
265
- });
266
-
267
- it('agent.stack_trace_limit (Infinity string --> number)', function () {
268
- process.env['CONTRAST__AGENT__STACK_TRACE_LIMIT'] = 'Infinity';
269
- const cfg = config();
270
- expect(cfg.agent.stack_trace_limit).to.be.equal(Infinity);
271
- delete process.env['CONTRAST__AGENT__STACK_TRACE_LIMIT'];
272
- });
273
- });
274
-
275
- describe('application', function () {
276
- describe('validation', function () {
277
- afterEach(function () {
278
- delete process.env['CONTRAST__APPLICATION__SESSION_ID'];
279
- delete process.env['CONTRAST__APPLICATION__SESSION_METADATA'];
280
- });
281
- it('allows one to be set without error', function () {
282
- process.env['CONTRAST__APPLICATION__SESSION_ID'] = 'abcd-1234';
283
- expect(() => {
284
- config();
285
- }).to.not.throw(/Configuration Error:/);
286
- });
287
- it('session options are mutually exclusive', function () {
288
- process.env['CONTRAST__APPLICATION__SESSION_ID'] = 'abcd-1234';
289
- process.env['CONTRAST__APPLICATION__SESSION_METADATA'] = 'a=1,b=2';
290
- expect(() => {
291
- config();
292
- }).to.throw(/Configuration Error:/);
293
- });
294
- });
295
- });
296
-
297
- describe('server', function () {
298
- describe('environment', function () {
299
- it('set from env var', function () {
300
- const env = Date.now();
301
- const options = getDefaultConfig();
302
- process.env.SERVER__ENVIRONMENT = env;
303
-
304
- const cfg = config(options);
305
- const ai = new AppInfo({ appInfo: 'asdf', config: cfg, logger: mocks.logger() });
306
- expect(ai.serverEnvironment).to.be.equal(String(env));
307
- });
308
- });
309
- });
310
-
311
- describe('mapping', function () {
312
- describe('logger', function () {
313
- it('casts logger path to absolute path', function () {
314
- process.env['CONTRAST__AGENT__LOGGER__PATH'] = 'loggy.log';
315
- const cfg = config();
316
- expect(cfg.agent.logger.path).to.be.equal(getAbsolutePath('loggy.log'));
317
- delete process.env['CONTRAST__AGENT__LOGGER__PATH'];
318
- });
319
-
320
- it('--debug does not overwrite explicitly set log level', function () {
321
- process.env['DEBUG'] = 'true';
322
- process.env['CONTRAST__AGENT__LOGGER__LEVEL'] = 'info';
323
-
324
- const cfg = config();
325
- expect(cfg.agent.logger.level).to.be.equal('info');
326
- delete process.env['DEBUG'];
327
- delete process.env['CONTRAST__AGENT__LOGGER__LEVEL'];
328
- });
329
- });
330
- });
331
- });