@contrast/config 1.33.0 → 1.34.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/common.js +34 -12
- package/lib/common.test.js +183 -0
- package/lib/config.js +5 -5
- package/lib/index.test.js +71 -7
- package/lib/options.js +36 -12
- package/package.json +2 -2
package/lib/common.js
CHANGED
|
@@ -39,8 +39,18 @@ const {
|
|
|
39
39
|
UNTRUSTED_DESERIALIZATION,
|
|
40
40
|
XXE,
|
|
41
41
|
},
|
|
42
|
+
primordials: { StringPrototypeToLowerCase },
|
|
43
|
+
get,
|
|
44
|
+
isString,
|
|
42
45
|
} = require('@contrast/common');
|
|
43
46
|
|
|
47
|
+
function coerceLowerCase(path) {
|
|
48
|
+
return function(remoteData) {
|
|
49
|
+
const value = get(remoteData, path);
|
|
50
|
+
if (value && isString(value)) return StringPrototypeToLowerCase.call(value);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
function protectModeReader(ruleId) {
|
|
45
55
|
return function (msg) {
|
|
46
56
|
const remoteSetting = msg?.protect?.rules?.[ruleId];
|
|
@@ -62,20 +72,11 @@ const ConfigSource = {
|
|
|
62
72
|
USER_CONFIGURATION_FILE: 'USER_CONFIGURATION_FILE',
|
|
63
73
|
};
|
|
64
74
|
|
|
75
|
+
// these should return `undefined` if there is no remote value corresponding to the effective config name.
|
|
65
76
|
const mappings = {
|
|
66
|
-
//
|
|
67
|
-
'
|
|
68
|
-
'agent.logger.path': (remoteData) => remoteData.logger?.path,
|
|
69
|
-
'application.session_id': (remoteData) => remoteData?.settings?.assessment?.session_id,
|
|
70
|
-
'agent.security_logger.syslog.enable': (remoteData) => remoteData.security_logger?.syslog?.enable,
|
|
71
|
-
'agent.security_logger.syslog.ip': (remoteData) => remoteData.security_logger?.syslog?.ip,
|
|
72
|
-
'agent.security_logger.syslog.port': (remoteData) => remoteData.security_logger?.syslog?.port,
|
|
73
|
-
'agent.security_logger.syslog.facility': (remoteData) => remoteData.security_logger?.syslog?.facility,
|
|
74
|
-
'agent.security_logger.syslog.severity_exploited': (remoteData) => remoteData.security_logger?.syslog?.severity_exploited?.toLowerCase(),
|
|
75
|
-
'agent.security_logger.syslog.severity_blocked': (remoteData) => remoteData.security_logger?.syslog?.severity_blocked?.toLowerCase(),
|
|
76
|
-
'agent.security_logger.syslog.severity_probed': (remoteData) => remoteData.security_logger?.syslog?.severity_probed?.toLowerCase(),
|
|
77
|
+
// application-create
|
|
78
|
+
'application.session_id': (remoteData) => remoteData.settings?.assessment?.session_id,
|
|
77
79
|
// application settings
|
|
78
|
-
'assess.enable': (remoteData) => remoteData.assess?.enable,
|
|
79
80
|
'protect.enable': (remoteData) => remoteData.protect?.enable,
|
|
80
81
|
'protect.rules.cmd-injection.mode': protectModeReader(CMD_INJECTION),
|
|
81
82
|
'protect.rules.cmd-injection-command-backdoors.mode': protectModeReader(CMD_INJECTION_COMMAND_BACKDOORS),
|
|
@@ -92,6 +93,27 @@ const mappings = {
|
|
|
92
93
|
'protect.rules.unsafe-file-upload.mode': protectModeReader(UNSAFE_FILE_UPLOAD),
|
|
93
94
|
'protect.rules.untrusted-deserialization.mode': protectModeReader(UNTRUSTED_DESERIALIZATION),
|
|
94
95
|
'protect.rules.xxe.mode': protectModeReader(XXE),
|
|
96
|
+
// server features
|
|
97
|
+
'assess.enable': (remoteData) => remoteData.assess?.enable,
|
|
98
|
+
'assess.probabilistic_sampling.enable': (remoteData) => remoteData.assess?.sampling?.enable,
|
|
99
|
+
'assess.probabilistic_sampling.base_probability': (remoteData) => {
|
|
100
|
+
const request_frequency = remoteData.assess?.sampling?.request_frequency;
|
|
101
|
+
if (request_frequency > 0) {
|
|
102
|
+
const baseProbability = 1 / request_frequency;
|
|
103
|
+
if (!isNaN(baseProbability)) return baseProbability;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
'agent.logger.level': coerceLowerCase('logger.level'),
|
|
107
|
+
'agent.logger.path': (remoteData) => remoteData.logger?.path,
|
|
108
|
+
'agent.security_logger.syslog.enable': (remoteData) => remoteData.security_logger?.syslog?.enable,
|
|
109
|
+
'agent.security_logger.syslog.ip': (remoteData) => remoteData.security_logger?.syslog?.ip,
|
|
110
|
+
'agent.security_logger.syslog.port': (remoteData) => remoteData.security_logger?.syslog?.port,
|
|
111
|
+
'agent.security_logger.syslog.facility': (remoteData) => remoteData.security_logger?.syslog?.facility,
|
|
112
|
+
'agent.security_logger.syslog.severity_exploited': coerceLowerCase('security_logger.syslog.severity_exploited'),
|
|
113
|
+
'agent.security_logger.syslog.severity_blocked': coerceLowerCase('security_logger.syslog.severity_blocked'),
|
|
114
|
+
'agent.security_logger.syslog.severity_probed': coerceLowerCase('security_logger.syslog.severity_probed'),
|
|
115
|
+
'server.environment': (remoteData) => remoteData.environment,
|
|
116
|
+
|
|
95
117
|
};
|
|
96
118
|
|
|
97
119
|
/*
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const { ProtectRuleMode } = require('@contrast/common');
|
|
5
|
+
const { mappings } = require('./common');
|
|
6
|
+
|
|
7
|
+
describe('config effective value mappings', function () {
|
|
8
|
+
describe('"simple" mappings', function() {
|
|
9
|
+
const simplyMappedNames = [
|
|
10
|
+
'assess.enable',
|
|
11
|
+
'agent.logger.path',
|
|
12
|
+
'agent.security_logger.syslog.enable',
|
|
13
|
+
'agent.security_logger.syslog.ip',
|
|
14
|
+
'agent.security_logger.syslog.port',
|
|
15
|
+
'agent.security_logger.syslog.facility',
|
|
16
|
+
'server.environment',
|
|
17
|
+
'protect.enable',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
it('"simple" mappings will return any value for corresponding remote setting (todo: validation for each)', function () {
|
|
21
|
+
['', 'foo', null, 0.3, 3, {}, [], true, false].forEach((val) => {
|
|
22
|
+
const remoteData = {
|
|
23
|
+
// server
|
|
24
|
+
assess: {
|
|
25
|
+
enable: val,
|
|
26
|
+
sampling: { enable: val }
|
|
27
|
+
},
|
|
28
|
+
logger: {
|
|
29
|
+
path: val,
|
|
30
|
+
},
|
|
31
|
+
security_logger: {
|
|
32
|
+
syslog: {
|
|
33
|
+
enable: val,
|
|
34
|
+
ip: val,
|
|
35
|
+
port: val,
|
|
36
|
+
facility: val,
|
|
37
|
+
serverity_exploited: val,
|
|
38
|
+
serverity_blocked: val,
|
|
39
|
+
exploited_probed: val,
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
environment: val,
|
|
43
|
+
// app
|
|
44
|
+
protect: {
|
|
45
|
+
enable: val,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
for (const name of simplyMappedNames) {
|
|
50
|
+
const result = mappings[name](remoteData);
|
|
51
|
+
expect(result).to.equal(val);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('"simple" mappings will return `undefined` if there is no corresponding remote setting', function () {
|
|
57
|
+
for (const name of simplyMappedNames) {
|
|
58
|
+
expect(mappings[name]({})).to.equal(undefined);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('maps \'assess.probabilistic_sampling.base_probability\'', function() {
|
|
64
|
+
[
|
|
65
|
+
{
|
|
66
|
+
desc: 'sets value by inverting request_frequency',
|
|
67
|
+
remoteData: {
|
|
68
|
+
assess: { sampling: { request_frequency: 20 } }
|
|
69
|
+
},
|
|
70
|
+
expected: 1 / 20,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
desc: 'ignores and returns undefined if request_frequency is 0',
|
|
74
|
+
remoteData: {
|
|
75
|
+
assess: { sampling: { request_frequency: 0 } }
|
|
76
|
+
},
|
|
77
|
+
expected: undefined,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
desc: 'ignores and returns undefined if calculating probability gives NaN',
|
|
81
|
+
remoteData: {
|
|
82
|
+
assess: { sampling: { request_frequency: 'forty five' } }
|
|
83
|
+
},
|
|
84
|
+
expected: undefined,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
desc: 'ignores and returns undefined if value is negative',
|
|
88
|
+
remoteData: {
|
|
89
|
+
assess: { sampling: { request_frequency: -12 } }
|
|
90
|
+
},
|
|
91
|
+
expected: undefined,
|
|
92
|
+
},
|
|
93
|
+
].forEach(({ desc, remoteData, expected }) => {
|
|
94
|
+
it(desc, function() {
|
|
95
|
+
expect(mappings['assess.probabilistic_sampling.base_probability'](remoteData)).to.equal(expected);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('maps Protect rule modes', function() {
|
|
101
|
+
[
|
|
102
|
+
{
|
|
103
|
+
tsValue: 'OFF',
|
|
104
|
+
expected: ProtectRuleMode.OFF,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
tsValue: 'MONITORING',
|
|
108
|
+
expected: ProtectRuleMode.MONITOR,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
tsValue: 'BLOCKING',
|
|
112
|
+
expected: ProtectRuleMode.BLOCK,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
tsValue: 'BLOCK_AT_PERIMETER',
|
|
116
|
+
expected: ProtectRuleMode.BLOCK_AT_PERIMETER,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
tsValue: 'FOO',
|
|
120
|
+
expected: undefined
|
|
121
|
+
},
|
|
122
|
+
].forEach(({ desc, tsValue, expected }) => {
|
|
123
|
+
[
|
|
124
|
+
'protect.rules.cmd-injection.mode',
|
|
125
|
+
'protect.rules.cmd-injection-command-backdoors.mode',
|
|
126
|
+
'protect.rules.cmd-injection-semantic-chained-commands.mode',
|
|
127
|
+
'protect.rules.cmd-injection-semantic-dangerous-paths.mode',
|
|
128
|
+
'protect.rules.method-tampering.mode',
|
|
129
|
+
'protect.rules.nosql-injection.mode',
|
|
130
|
+
'protect.rules.nosql-injection-mongo.mode',
|
|
131
|
+
'protect.rules.path-traversal.mode',
|
|
132
|
+
'protect.rules.path-traversal-semantic-file-security-bypass.mode',
|
|
133
|
+
'protect.rules.reflected-xss.mode',
|
|
134
|
+
'protect.rules.sql-injection.mode',
|
|
135
|
+
'protect.rules.ssjs-injection.mode',
|
|
136
|
+
'protect.rules.unsafe-file-upload.mode',
|
|
137
|
+
'protect.rules.untrusted-deserialization.mode',
|
|
138
|
+
'protect.rules.xxe.mode',
|
|
139
|
+
].forEach((fullName) => {
|
|
140
|
+
const ruleName = /rules\.(.*)\.mode/.exec(fullName)[1];
|
|
141
|
+
it(`sets ${ruleName} to ${expected} when value is ${tsValue}`, function() {
|
|
142
|
+
expect(mappings[fullName]({
|
|
143
|
+
protect: {
|
|
144
|
+
rules: {
|
|
145
|
+
[ruleName]: { mode: tsValue },
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
})).to.equal(expected);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('maps security logger severity levels (lowercase)', function() {
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
tsValue: 'FOO',
|
|
158
|
+
expected: 'foo',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
tsValue: '',
|
|
162
|
+
expected: undefined,
|
|
163
|
+
}
|
|
164
|
+
].forEach(({ tsValue, expected }) => {
|
|
165
|
+
[
|
|
166
|
+
'agent.security_logger.syslog.severity_exploited',
|
|
167
|
+
'agent.security_logger.syslog.severity_blocked',
|
|
168
|
+
'agent.security_logger.syslog.severity_probed',
|
|
169
|
+
].forEach((fullName) => {
|
|
170
|
+
const severity = fullName.substr(fullName.lastIndexOf('.') + 1);
|
|
171
|
+
it(`maps to ${expected} when tsValue is ${tsValue}`, function() {
|
|
172
|
+
expect(mappings[fullName]({
|
|
173
|
+
security_logger: {
|
|
174
|
+
syslog: {
|
|
175
|
+
[severity]: tsValue,
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
})).to.equal(expected);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
package/lib/config.js
CHANGED
|
@@ -20,7 +20,7 @@ const path = require('path');
|
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const os = require('os');
|
|
22
22
|
const yaml = require('yaml');
|
|
23
|
-
const { Event, get, set } = require('@contrast/common');
|
|
23
|
+
const { Event, get, set, primordials: { ArrayPrototypeJoin, BufferPrototypeToString, StringPrototypeToUpperCase, JSONParse } } = require('@contrast/common');
|
|
24
24
|
const options = require('./options');
|
|
25
25
|
const {
|
|
26
26
|
ConfigSource: {
|
|
@@ -74,7 +74,7 @@ module.exports = class Config {
|
|
|
74
74
|
|
|
75
75
|
// report all the errors found during initialization.
|
|
76
76
|
if (this._errors.length) {
|
|
77
|
-
const errors = this._errors.map((e, ix) => `${ix + 1}) ${e.message}`)
|
|
77
|
+
const errors = ArrayPrototypeJoin.call(this._errors.map((e, ix) => `${ix + 1}) ${e.message}`), '; ');
|
|
78
78
|
throw new Error(`Errors found in configuration ${errors}`);
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -104,7 +104,7 @@ module.exports = class Config {
|
|
|
104
104
|
let parsedEnv;
|
|
105
105
|
|
|
106
106
|
try {
|
|
107
|
-
parsedEnv =
|
|
107
|
+
parsedEnv = JSONParse(env.pm2_env);
|
|
108
108
|
} catch (_err) {
|
|
109
109
|
const err = new Error(`Unable to parse pm2 environment variable: '${env.pm2_env}'`);
|
|
110
110
|
err.cause = _err;
|
|
@@ -122,7 +122,7 @@ module.exports = class Config {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
return Object.entries(env).reduce((acc, [key, val]) => {
|
|
125
|
-
const name =
|
|
125
|
+
const name = StringPrototypeToUpperCase.call(key);
|
|
126
126
|
if (name.startsWith('CONTRAST_')) {
|
|
127
127
|
acc[name] = val;
|
|
128
128
|
}
|
|
@@ -165,7 +165,7 @@ module.exports = class Config {
|
|
|
165
165
|
let fileContents;
|
|
166
166
|
|
|
167
167
|
try {
|
|
168
|
-
fileContents = fs.readFileSync(_filepath)
|
|
168
|
+
fileContents = BufferPrototypeToString.call(fs.readFileSync(_filepath), 'utf-8');
|
|
169
169
|
} catch (e) {
|
|
170
170
|
const err = new Error(`Unable to read Contrast configuration file: '${_filepath}'`);
|
|
171
171
|
err.cause = e;
|
package/lib/index.test.js
CHANGED
|
@@ -371,11 +371,12 @@ describe('config', function () {
|
|
|
371
371
|
// there needs to be a valid config file or env var set so config doesn't throw
|
|
372
372
|
// an error.
|
|
373
373
|
process.env.CONTRAST_CONFIG_PATH = path.resolve(__dirname, '../../test/fixtures/protect_contrast_security.yaml');
|
|
374
|
+
//process.env.CONTRAST__PROTECT__RULES__DISABLED_RULES = 'cmd-injection,sql-injection';
|
|
374
375
|
config = require('.')(core);
|
|
375
376
|
|
|
376
377
|
config.setValue('contrast_config_path', path.resolve(__dirname, '../../test/fixtures/protect_contrast_security.yaml'), ENVIRONMENT_VARIABLE);
|
|
377
378
|
config.setValue('contrast__agent__stack_trace_limit', 20, ENVIRONMENT_VARIABLE);
|
|
378
|
-
config.setValue('
|
|
379
|
+
config.setValue('protect.rules.disabled_rules', ['cmd-injection', 'sql-injection'], ENVIRONMENT_VARIABLE);
|
|
379
380
|
config.setValue('api.enable', true, ENVIRONMENT_VARIABLE);
|
|
380
381
|
config.setValue('api.api_key', 'ABCDEFGHIJ', ENVIRONMENT_VARIABLE);
|
|
381
382
|
config.setValue('api.service_key', 'KLMNOPQRST', ENVIRONMENT_VARIABLE);
|
|
@@ -407,11 +408,12 @@ describe('config', function () {
|
|
|
407
408
|
const {
|
|
408
409
|
config: {
|
|
409
410
|
effective_config,
|
|
411
|
+
environment_variable,
|
|
410
412
|
}
|
|
411
413
|
} = result;
|
|
412
414
|
|
|
413
415
|
expect(result.config).to.have.property('status', 'Success');
|
|
414
|
-
|
|
416
|
+
const expectedResults = [
|
|
415
417
|
{
|
|
416
418
|
canonical_name: 'api.enable',
|
|
417
419
|
name: 'api.enable',
|
|
@@ -422,13 +424,13 @@ describe('config', function () {
|
|
|
422
424
|
canonical_name: 'api.api_key',
|
|
423
425
|
name: 'api.api_key',
|
|
424
426
|
source: 'ENVIRONMENT_VARIABLE',
|
|
425
|
-
value: '
|
|
427
|
+
value: 'ABCDEFGHIJ',
|
|
426
428
|
},
|
|
427
429
|
{
|
|
428
430
|
canonical_name: 'api.service_key',
|
|
429
431
|
name: 'api.service_key',
|
|
430
432
|
source: 'ENVIRONMENT_VARIABLE',
|
|
431
|
-
value: '
|
|
433
|
+
value: 'KLMNOPQRST',
|
|
432
434
|
},
|
|
433
435
|
{
|
|
434
436
|
canonical_name: 'api.proxy.enable',
|
|
@@ -496,7 +498,37 @@ describe('config', function () {
|
|
|
496
498
|
value: '150',
|
|
497
499
|
source: 'DEFAULT_VALUE'
|
|
498
500
|
}
|
|
499
|
-
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
const failures = [];
|
|
504
|
+
let effective;
|
|
505
|
+
let envs;
|
|
506
|
+
const envFailures = [];
|
|
507
|
+
|
|
508
|
+
for (const expected of expectedResults) {
|
|
509
|
+
effective = effective_config.filter(i => i.canonical_name === expected.canonical_name);
|
|
510
|
+
expect(effective).an('array').lengthOf(1); // eslint-disable-line
|
|
511
|
+
effective = effective[0];
|
|
512
|
+
try {
|
|
513
|
+
expect(expected).to.deep.equal(effective);
|
|
514
|
+
} catch (e) {
|
|
515
|
+
failures.push({ expected, actual: effective });
|
|
516
|
+
}
|
|
517
|
+
if (expected.source !== 'ENVIRONMENT_VARIABLE') continue;
|
|
518
|
+
|
|
519
|
+
envs = environment_variable.filter(i => i.canonical_name === expected.canonical_name);
|
|
520
|
+
expect(envs).an('array')
|
|
521
|
+
.lengthOf(1, `Expected to find ${expected.name} in environment_variable`);
|
|
522
|
+
envs = envs[0];
|
|
523
|
+
try {
|
|
524
|
+
expect(expected).to.deep.equal(envs);
|
|
525
|
+
} catch (e) {
|
|
526
|
+
envFailures.push({ expected, actual: envs });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// this provides a useful error message
|
|
530
|
+
expect(failures).eql([]);
|
|
531
|
+
expect(envFailures).eql([]);
|
|
500
532
|
});
|
|
501
533
|
|
|
502
534
|
it('stringifies values and redacts api keys when `redact` is set to true', function () {
|
|
@@ -504,11 +536,13 @@ describe('config', function () {
|
|
|
504
536
|
const {
|
|
505
537
|
config: {
|
|
506
538
|
effective_config,
|
|
539
|
+
environment_variable,
|
|
507
540
|
}
|
|
508
541
|
} = result;
|
|
509
542
|
|
|
510
543
|
expect(result.config).to.have.property('status', 'Success');
|
|
511
|
-
|
|
544
|
+
|
|
545
|
+
const expectedResults = [
|
|
512
546
|
{
|
|
513
547
|
canonical_name: 'api.enable',
|
|
514
548
|
name: 'api.enable',
|
|
@@ -593,7 +627,37 @@ describe('config', function () {
|
|
|
593
627
|
value: '150',
|
|
594
628
|
source: 'DEFAULT_VALUE'
|
|
595
629
|
}
|
|
596
|
-
|
|
630
|
+
];
|
|
631
|
+
|
|
632
|
+
const failures = [];
|
|
633
|
+
let effective;
|
|
634
|
+
let envs;
|
|
635
|
+
const envFailures = [];
|
|
636
|
+
|
|
637
|
+
for (const expected of expectedResults) {
|
|
638
|
+
effective = effective_config.filter(i => i.canonical_name === expected.canonical_name);
|
|
639
|
+
expect(effective).an('array').lengthOf(1); // eslint-disable-line
|
|
640
|
+
effective = effective[0];
|
|
641
|
+
try {
|
|
642
|
+
expect(expected).to.deep.equal(effective);
|
|
643
|
+
} catch (e) {
|
|
644
|
+
failures.push({ expected, actual: effective });
|
|
645
|
+
}
|
|
646
|
+
if (expected.source !== 'ENVIRONMENT_VARIABLE') continue;
|
|
647
|
+
|
|
648
|
+
envs = environment_variable.filter(i => i.canonical_name === expected.canonical_name);
|
|
649
|
+
expect(envs).an('array')
|
|
650
|
+
.lengthOf(1, `Expected to find ${expected.name} in environment_variable`);
|
|
651
|
+
envs = envs[0];
|
|
652
|
+
try {
|
|
653
|
+
expect(expected).to.deep.equal(envs);
|
|
654
|
+
} catch (e) {
|
|
655
|
+
envFailures.push({ expected, actual: envs });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// this provides a useful error message
|
|
659
|
+
expect(failures).eql([]);
|
|
660
|
+
expect(envFailures).eql([]);
|
|
597
661
|
});
|
|
598
662
|
});
|
|
599
663
|
});
|
package/lib/options.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
const os = require('os');
|
|
21
21
|
const url = require('url');
|
|
22
22
|
const path = require('path');
|
|
23
|
-
const { Rule } = require('@contrast/common');
|
|
23
|
+
const { Rule, primordials: { BufferFrom, BufferPrototypeToString, StringPrototypeReplace, StringPrototypeReplaceAll, StringPrototypeSplit, StringPrototypeToLowerCase, StringPrototypeToUpperCase, JSONParse } } = require('@contrast/common');
|
|
24
24
|
const { ConfigSource: { DEFAULT_VALUE } } = require('./common');
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -33,7 +33,7 @@ function castBoolean(value) {
|
|
|
33
33
|
if (type !== 'string' && type !== 'boolean') {
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
value = value.toString()
|
|
36
|
+
value = StringPrototypeToLowerCase.call(value.toString());
|
|
37
37
|
return value === 'true' || value === 't'
|
|
38
38
|
? true
|
|
39
39
|
: value === 'false' || value === 'f'
|
|
@@ -61,11 +61,11 @@ function clearBaseCase(val) {
|
|
|
61
61
|
const split = (val) => {
|
|
62
62
|
if (val === '') return [];
|
|
63
63
|
if (val === undefined) return val;
|
|
64
|
-
return
|
|
64
|
+
return StringPrototypeSplit.call(val, ',');
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
const uppercase = (val = '') => clearBaseCase(`${val}
|
|
68
|
-
const lowercase = (val = '') => clearBaseCase(`${val}
|
|
67
|
+
const uppercase = (val = '') => clearBaseCase(`${StringPrototypeToUpperCase.call(val)}`);
|
|
68
|
+
const lowercase = (val = '') => clearBaseCase(`${StringPrototypeToLowerCase.call(val)}`);
|
|
69
69
|
const parseNum = (val) => {
|
|
70
70
|
const float = parseFloat(val);
|
|
71
71
|
return clearBaseCase(Math.ceil(float));
|
|
@@ -129,7 +129,7 @@ const options = [
|
|
|
129
129
|
|
|
130
130
|
if (uri.pathname) {
|
|
131
131
|
// kill trailling /
|
|
132
|
-
uri.pathname = uri.pathname
|
|
132
|
+
uri.pathname = StringPrototypeReplace.call(uri.pathname, /\/+$/, '');
|
|
133
133
|
|
|
134
134
|
if (!uri.pathname.endsWith('Contrast')) {
|
|
135
135
|
uri.pathname += 'Contrast';
|
|
@@ -163,7 +163,7 @@ const options = [
|
|
|
163
163
|
fn(value, cfg, source) {
|
|
164
164
|
try {
|
|
165
165
|
// parse the base64 encoded value
|
|
166
|
-
const parsed =
|
|
166
|
+
const parsed = JSONParse(BufferPrototypeToString.call(BufferFrom(value, 'base64'), 'utf8'));
|
|
167
167
|
// set the top level `api` keys only if they aren't already present.
|
|
168
168
|
// since this value comes after the others, they should be set first if present in the config file or environment.
|
|
169
169
|
['url', 'api_key', 'service_key', 'user_name'].forEach(key => {
|
|
@@ -439,7 +439,7 @@ Example - \`/opt/Contrast/contrast.log\` creates a log in the \`/opt/Contrast\`
|
|
|
439
439
|
name: 'agent.node.cmd_ignore_list',
|
|
440
440
|
arg: '<commands>',
|
|
441
441
|
default: '',
|
|
442
|
-
fn: (arg) =>
|
|
442
|
+
fn: (arg) => StringPrototypeSplit.call(arg, ',').filter((v) => v),
|
|
443
443
|
desc: 'comma-separated list of commands that will not startup the agent if agent is required; npm* will ignore all npm executables but not your application\'s scripts'
|
|
444
444
|
},
|
|
445
445
|
{
|
|
@@ -533,6 +533,29 @@ Example - \`/opt/Contrast/contrast.log\` creates a log in the \`/opt/Contrast\`
|
|
|
533
533
|
fn: castBoolean,
|
|
534
534
|
desc: 'Include this property to determine if the Assess feature should be enabled. If this property is not present, the decision is delegated to the Contrast UI.',
|
|
535
535
|
},
|
|
536
|
+
{
|
|
537
|
+
name: 'assess.probabilistic_sampling.enable',
|
|
538
|
+
arg: '[true]',
|
|
539
|
+
default: false,
|
|
540
|
+
fn: castBoolean,
|
|
541
|
+
desc: 'Set to true to enable sampling of requests for dataflow and other Assess features',
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: 'assess.probabilistic_sampling.base_probability',
|
|
545
|
+
arg: '<probability>',
|
|
546
|
+
fn: (val) => {
|
|
547
|
+
const p = parseFloat(val);
|
|
548
|
+
if (p >= 0 && p <= 1) return p;
|
|
549
|
+
|
|
550
|
+
if (val && val != 'undefined') {
|
|
551
|
+
throw new Error('Invalid option: assess.probabilistic_sampling.base_probability', {
|
|
552
|
+
cause: `${val} is not not in interval 0 <= p <= 1. value as float: ${p}`
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
default: 0.01,
|
|
557
|
+
desc: 'A value p within the interval [0, 1]. Each request will share same probability p of being sampled.',
|
|
558
|
+
},
|
|
536
559
|
{
|
|
537
560
|
name: 'assess.tags',
|
|
538
561
|
arg: '<tags>',
|
|
@@ -686,10 +709,11 @@ Example - \`label1, label2, label3\``,
|
|
|
686
709
|
fn: castBoolean,
|
|
687
710
|
desc: 'Set to `false` to disable detection of cloud provider metadata such as resource identifiers.'
|
|
688
711
|
},
|
|
689
|
-
].map((opt) =>
|
|
690
|
-
env
|
|
691
|
-
|
|
692
|
-
})
|
|
712
|
+
].map((opt) => {
|
|
713
|
+
let env = StringPrototypeReplaceAll.call(StringPrototypeToUpperCase.call(opt.name), '.', '__');
|
|
714
|
+
env = StringPrototypeReplaceAll.call(env, '-', '_');
|
|
715
|
+
return Object.assign(opt, { env: `CONTRAST__${env}` });
|
|
716
|
+
});
|
|
693
717
|
|
|
694
718
|
module.exports = options;
|
|
695
719
|
module.exports.clearBaseCase = clearBaseCase;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.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)",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.
|
|
20
|
+
"@contrast/common": "1.26.0",
|
|
21
21
|
"yaml": "^2.2.2"
|
|
22
22
|
}
|
|
23
23
|
}
|