@contrast/config 1.32.0 → 1.33.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/config.js +10 -7
- package/lib/index.d.ts +13 -6
- package/lib/index.test.js +43 -0
- package/lib/options.js +32 -2
- package/lib/validators.js +23 -0
- package/lib/validators.test.js +42 -0
- package/package.json +1 -1
package/lib/config.js
CHANGED
|
@@ -38,7 +38,7 @@ const HOME_CONFIG_DIR = path.resolve(os.homedir(), '.config', 'contrast');
|
|
|
38
38
|
const OS_CONFIG_DIR = os.platform() === 'win32'
|
|
39
39
|
? path.resolve(process.env.ProgramData || '', 'contrast')
|
|
40
40
|
: '/etc/contrast';
|
|
41
|
-
const REDACTED_KEYS = ['api.api_key', 'api.service_key'];
|
|
41
|
+
const REDACTED_KEYS = ['api.api_key', 'api.service_key', 'api.token'];
|
|
42
42
|
const OVERRIDABLE_SOURCES = [DEFAULT_VALUE, CONTRAST_UI];
|
|
43
43
|
|
|
44
44
|
module.exports = class Config {
|
|
@@ -48,6 +48,7 @@ module.exports = class Config {
|
|
|
48
48
|
this._errors = [];
|
|
49
49
|
this._effectiveMap = new Map();
|
|
50
50
|
this._status = '';
|
|
51
|
+
this._logs = [];
|
|
51
52
|
|
|
52
53
|
// config object
|
|
53
54
|
this.api = {};
|
|
@@ -207,7 +208,7 @@ module.exports = class Config {
|
|
|
207
208
|
source = DEFAULT_VALUE;
|
|
208
209
|
}
|
|
209
210
|
|
|
210
|
-
if (opt.fn) value = opt.fn(value);
|
|
211
|
+
if (opt.fn) value = opt.fn(value, this, source);
|
|
211
212
|
if (opt.enum && !opt.enum.includes(value)) value = opt.default;
|
|
212
213
|
|
|
213
214
|
set(this, opt.name, value);
|
|
@@ -221,7 +222,7 @@ module.exports = class Config {
|
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
_redact(name, value) {
|
|
224
|
-
if (value
|
|
225
|
+
if (value === null) return value;
|
|
225
226
|
return REDACTED_KEYS.includes(name) ? `contrast-redacted-${name}` : value;
|
|
226
227
|
}
|
|
227
228
|
|
|
@@ -239,12 +240,14 @@ module.exports = class Config {
|
|
|
239
240
|
const effective_config = [], environment_variable = [], contrast_ui = [];
|
|
240
241
|
|
|
241
242
|
Array.from(this._effectiveMap.values()).forEach((v) => {
|
|
242
|
-
let value =
|
|
243
|
+
let { value } = v;
|
|
244
|
+
if (redact) value = this._redact(v.name, v.value);
|
|
243
245
|
if (value === undefined) value = null;
|
|
244
246
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (v.source ===
|
|
247
|
+
const redacted = { ...v, value: String(value) };
|
|
248
|
+
effective_config.push(redacted);
|
|
249
|
+
if (v.source === ENVIRONMENT_VARIABLE) environment_variable.push(redacted);
|
|
250
|
+
if (v.source === CONTRAST_UI) contrast_ui.push(redacted);
|
|
248
251
|
});
|
|
249
252
|
|
|
250
253
|
return {
|
package/lib/index.d.ts
CHANGED
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
import { ProtectRuleMode, Rule } from '@contrast/common';
|
|
17
17
|
import { LevelWithSilent } from 'pino';
|
|
18
|
-
export { ConfigSource } from './common
|
|
18
|
+
export { ConfigSource } from './common';
|
|
19
19
|
|
|
20
20
|
export interface EffectiveEntry<T> {
|
|
21
21
|
canonical_name: string;
|
|
22
22
|
name: string;
|
|
23
23
|
value: T;
|
|
24
|
-
source:
|
|
24
|
+
source: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export type Level =
|
|
@@ -47,7 +47,7 @@ export interface ConfigOption<T> {
|
|
|
47
47
|
arg: string;
|
|
48
48
|
enum?: T[];
|
|
49
49
|
default?: T;
|
|
50
|
-
fn?: (arg: any) => T;
|
|
50
|
+
fn?: (arg: any, cfg: Config, source: string) => T;
|
|
51
51
|
desc: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -56,6 +56,13 @@ export interface Config {
|
|
|
56
56
|
_effectiveMap: Map<string, EffectiveEntry<any>>;
|
|
57
57
|
_errors: Error[];
|
|
58
58
|
_status: string,
|
|
59
|
+
_logs: {
|
|
60
|
+
level: import('pino').LevelWithSilentOrString;
|
|
61
|
+
obj?: any;
|
|
62
|
+
msg: string;
|
|
63
|
+
args?: any[];
|
|
64
|
+
}[];
|
|
65
|
+
|
|
59
66
|
|
|
60
67
|
api: {
|
|
61
68
|
/** Default: `true` */
|
|
@@ -296,10 +303,10 @@ export interface Config {
|
|
|
296
303
|
/** Default: `true` */
|
|
297
304
|
discover_cloud_resource: boolean;
|
|
298
305
|
};
|
|
299
|
-
getEffectiveSource(cannonicalName: string):
|
|
300
|
-
getEffectiveValue(cannonicalName: string):
|
|
306
|
+
getEffectiveSource(cannonicalName: string): string;
|
|
307
|
+
getEffectiveValue<T = any>(cannonicalName: string): T;
|
|
301
308
|
getReport({ redact: boolean }): any;
|
|
302
|
-
setValue(name: string, value:
|
|
309
|
+
setValue<T = any>(name: string, value: T, source: string): void;
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
declare function init(core: { config?: Config }): Config;
|
package/lib/index.test.js
CHANGED
|
@@ -186,6 +186,49 @@ describe('config', function () {
|
|
|
186
186
|
});
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
describe('api.token handling', function () {
|
|
190
|
+
it('sets api config vars when they are not present', function () {
|
|
191
|
+
env['CONTRAST_CONFIG_PATH'] = getGoodConfig('token');
|
|
192
|
+
const cfg = config();
|
|
193
|
+
|
|
194
|
+
expect(cfg.api).to.include({
|
|
195
|
+
url: 'http://localhost:12345/Contrast',
|
|
196
|
+
api_key: 'secret',
|
|
197
|
+
service_key: 'secret',
|
|
198
|
+
user_name: 'tokenuser'
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('does not override api config vars when they are explicitly set in config', function () {
|
|
203
|
+
// url: 'http://localhost:12345/Contrast',
|
|
204
|
+
// api_key: 'secret',
|
|
205
|
+
// service_key: 'secret',
|
|
206
|
+
// user_name: 'tokenuser'
|
|
207
|
+
env['CONTRAST__API__TOKEN'] = 'eyJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQ1L0NvbnRyYXN0IiwiYXBpX2tleSI6InNlY3JldCIsInNlcnZpY2Vfa2V5Ijoic2VjcmV0IiwidXNlcl9uYW1lIjoidG9rZW51c2VyIn0=';
|
|
208
|
+
const cfg = config();
|
|
209
|
+
|
|
210
|
+
expect(cfg.api).to.include({
|
|
211
|
+
url: 'http://localhost:19080/Contrast',
|
|
212
|
+
api_key: 'demo',
|
|
213
|
+
service_key: 'demo',
|
|
214
|
+
user_name: 'contrast_admin'
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('observes the correct precedence', function () {
|
|
219
|
+
env['CONTRAST_CONFIG_PATH'] = getGoodConfig('token');
|
|
220
|
+
env['CONTRAST__API__API_KEY'] = 'something else';
|
|
221
|
+
const cfg = config();
|
|
222
|
+
|
|
223
|
+
expect(cfg.api).to.include({
|
|
224
|
+
url: 'http://localhost:12345/Contrast',
|
|
225
|
+
api_key: 'something else',
|
|
226
|
+
service_key: 'secret',
|
|
227
|
+
user_name: 'tokenuser'
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
189
232
|
describe('multiple errors can be thrown in an exception', function() {
|
|
190
233
|
it('should report multiple errors', function() {
|
|
191
234
|
const options = getDefaultConfig();
|
package/lib/options.js
CHANGED
|
@@ -21,6 +21,7 @@ const os = require('os');
|
|
|
21
21
|
const url = require('url');
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const { Rule } = require('@contrast/common');
|
|
24
|
+
const { ConfigSource: { DEFAULT_VALUE } } = require('./common');
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Takes strings "true"|"t" or "false"|"f" (case insensitive) and return the appropriate boolean.
|
|
@@ -155,6 +156,35 @@ const options = [
|
|
|
155
156
|
arg: '<name>',
|
|
156
157
|
desc: 'Set the user name used to communicate with the Contrast UI. It is used to calculate the Authorization header.',
|
|
157
158
|
},
|
|
159
|
+
{
|
|
160
|
+
name: 'api.token',
|
|
161
|
+
arg: '<token>',
|
|
162
|
+
desc: 'base64 encoded JSON object containing the `url`, `api_key`, `service_key`, and `user_name` config options, allowing them all to be set in a single variable.',
|
|
163
|
+
fn(value, cfg, source) {
|
|
164
|
+
try {
|
|
165
|
+
// parse the base64 encoded value
|
|
166
|
+
const parsed = JSON.parse(Buffer.from(value, 'base64').toString('utf8'));
|
|
167
|
+
// set the top level `api` keys only if they aren't already present.
|
|
168
|
+
// since this value comes after the others, they should be set first if present in the config file or environment.
|
|
169
|
+
['url', 'api_key', 'service_key', 'user_name'].forEach(key => {
|
|
170
|
+
const canonicalName = `api.${key}`;
|
|
171
|
+
const existingSource = cfg.getEffectiveSource(canonicalName);
|
|
172
|
+
if (existingSource !== DEFAULT_VALUE) {
|
|
173
|
+
cfg._logs.push({
|
|
174
|
+
level: 'info',
|
|
175
|
+
msg: 'Using configured value for `%s` (set by %s) instead of `api.token`.',
|
|
176
|
+
args: [canonicalName, existingSource]
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
cfg.setValue(canonicalName, parsed[key], source);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return value;
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
158
188
|
// api.proxy
|
|
159
189
|
{
|
|
160
190
|
name: 'api.proxy.enable',
|
|
@@ -512,10 +542,10 @@ Example - \`label1, label2, label3\``,
|
|
|
512
542
|
{
|
|
513
543
|
name: 'assess.stacktraces',
|
|
514
544
|
arg: '<level>',
|
|
515
|
-
enum: ['ALL', 'SOME', 'NONE'],
|
|
545
|
+
enum: ['ALL', 'SOME', 'SINK', 'NONE'],
|
|
516
546
|
default: 'ALL',
|
|
517
547
|
fn: uppercase,
|
|
518
|
-
desc: 'Select the level of collected stacktraces. ALL - for all assess events, SOME - for Source and Sink events, NONE - no stacktraces collected',
|
|
548
|
+
desc: 'Select the level of collected stacktraces. ALL - for all assess events, SOME - for Source and Sink events, SINK - for Sink events, NONE - no stacktraces collected',
|
|
519
549
|
},
|
|
520
550
|
{
|
|
521
551
|
name: 'assess.max_context_source_events',
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
// Abusing the `validators` pattern to allow us to log after core has been set up.
|
|
19
|
+
module.exports.config = function config(core) {
|
|
20
|
+
core.config._logs.forEach(({ level, obj, msg, args = [] }) => {
|
|
21
|
+
core.logger[level](obj, msg, ...args);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const mocks = require('@contrast/test/mocks');
|
|
5
|
+
const validators = require('./validators');
|
|
6
|
+
|
|
7
|
+
describe('config validators', function () {
|
|
8
|
+
let core;
|
|
9
|
+
|
|
10
|
+
beforeEach(function () {
|
|
11
|
+
core = { logger: mocks.logger() };
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('config', function () {
|
|
15
|
+
it('calls `logger` for each log message provided', function () {
|
|
16
|
+
core.config = {
|
|
17
|
+
_logs: [
|
|
18
|
+
{ level: 'info', msg: 'some info level message' },
|
|
19
|
+
{
|
|
20
|
+
level: 'debug',
|
|
21
|
+
obj: { foo: 'bar' },
|
|
22
|
+
msg: 'some debug level message with %d %s',
|
|
23
|
+
args: [2, 'args'],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
validators.config(core);
|
|
29
|
+
|
|
30
|
+
expect(core.logger.info).to.have.been.calledWith(
|
|
31
|
+
undefined,
|
|
32
|
+
'some info level message'
|
|
33
|
+
);
|
|
34
|
+
expect(core.logger.debug).to.have.been.calledWith(
|
|
35
|
+
{ foo: 'bar' },
|
|
36
|
+
'some debug level message with %d %s',
|
|
37
|
+
2,
|
|
38
|
+
'args',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.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)",
|