@contrast/agent 4.26.0 → 4.27.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/bootstrap.js CHANGED
@@ -41,6 +41,13 @@ Module.runMain = async function (...args) {
41
41
  await loader.bootstrap(process.argv);
42
42
  }
43
43
  await loader.resetArgs(process.argv[0], process.argv[1]);
44
+
45
+ const diagnostics = process.env['CONTRAST__AGENT__DIAGNOSTICS__ENABLE'];
46
+ if (diagnostics === 'true') {
47
+ // eslint-disable-next-line no-process-exit
48
+ process.exit(0);
49
+ }
50
+
44
51
  loader.logTime(startTime, 'agent');
45
52
  const appStartTime = process.hrtime();
46
53
  orig.apply(this, args);
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ Copyright: 2022 Contrast Security, Inc
4
+ Contact: support@contrastsecurity.com
5
+ License: Commercial
6
+
7
+ NOTICE: This Software and the patented inventions embodied within may only be
8
+ used as part of Contrast Security’s commercial offerings. Even though it is
9
+ made available through public repositories, use of this Software is subject to
10
+ the applicable End User Licensing Agreement found at
11
+ https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
12
+ between Contrast Security and the End User. The Software may not be reverse
13
+ engineered, modified, repackaged, sold, redistributed or otherwise used in a
14
+ way not consistent with the End User License Agreement.
15
+ */
16
+ 'use strict';
17
+
18
+ const { exec } = require('child_process');
19
+
20
+ function handleDependentParameter(args, currParameter, nextParameter) {
21
+ switch (currParameter) {
22
+ case '--output':
23
+ case '-o':
24
+ args.output = nextParameter;
25
+ break;
26
+ default:
27
+ throw new Error(`Invalid parameter ${currParameter}`);
28
+ }
29
+ }
30
+
31
+ function handleIndependentParameter(args, parameter) {
32
+ switch (parameter) {
33
+ case '--help':
34
+ args.help = true;
35
+ break;
36
+ case '--quiet':
37
+ case '-q':
38
+ args.quiet = true;
39
+ break;
40
+ default:
41
+ throw new Error(`Invalid parameter ${parameter}`);
42
+ }
43
+ }
44
+
45
+ function printHelpMessage() {
46
+ console.log(
47
+ '\nDescription:\n' +
48
+ 'The config-diagnostics utility returns the current effective node agent configuration\n' +
49
+ '\nUsage:\n' +
50
+ 'npx -p @contrast/agent config-diagnostics <path/to/entry/script> [options]\n' +
51
+ 'npx -p @contrast/agent config-diagnostics --help\n' +
52
+ '\nOptions:\n' +
53
+ '--quiet -q Prevents output of the report to the console.\n' +
54
+ '--output -o The directory to write the report in. Defaults to the current directory.\n'
55
+ );
56
+ }
57
+
58
+ function parseArgv() {
59
+ const argv = process.argv.slice(2);
60
+ const args = {
61
+ output: 'contrast_effective_config.json',
62
+ quiet: false,
63
+ help: false,
64
+ };
65
+ let currParameter = null;
66
+
67
+ for (let i = 0; i < argv.length; i++) {
68
+ if (!currParameter) {
69
+ currParameter = argv[i];
70
+ }
71
+ if (!(currParameter.startsWith('--') || currParameter.startsWith('-'))) {
72
+ currParameter = null;
73
+ continue;
74
+ }
75
+
76
+ if (i + 1 == argv.length || argv[i + 1].startsWith('--') || argv[i + 1].startsWith('-')) {
77
+ handleIndependentParameter(args, currParameter);
78
+ currParameter = null;
79
+ } else {
80
+ handleDependentParameter(args, currParameter, argv[i + 1]);
81
+ currParameter = null;
82
+ }
83
+ }
84
+
85
+ return args;
86
+ }
87
+
88
+ function executeNodeAgent(args) {
89
+ let agentEnvArgs = 'CONTRAST__SHOW__BANNER=false CONTRAST__AGENT__DIAGNOSTICS__ENABLE=true';
90
+ const entry = process.argv.slice(2).shift();
91
+
92
+ if (!args.quiet) {
93
+ agentEnvArgs = agentEnvArgs.concat(' CONTRAST__AGENT__DIAGNOSTICS__QUIET=false');
94
+ }
95
+ if (args.output) {
96
+ agentEnvArgs = agentEnvArgs.concat(` CONTRAST__AGENT__DIAGNOSTICS__REPORT_PATH=${args.output}`);
97
+ }
98
+
99
+ exec(`${agentEnvArgs} node -r ./node_modules/@contrast/agent/bootstrap.js ${entry}`,
100
+ (error, stdout, stderr) => {
101
+ if (error && !args.quiet) {
102
+ console.log(`error: ${error.message}`);
103
+ return;
104
+ }
105
+
106
+ // by default errors still won't be visible since stdout == sterr
107
+ if (stderr) {
108
+ console.log(`stderr: ${stderr}`);
109
+ return;
110
+ }
111
+
112
+ if (!args.quiet) {
113
+ console.log(stdout);
114
+ }
115
+ }
116
+ );
117
+ }
118
+
119
+ function fetchAgentConfig(args) {
120
+ if (process.argv.slice(2).length == 1 && args.help) {
121
+ printHelpMessage();
122
+ } else {
123
+ executeNodeAgent(args);
124
+ }
125
+ }
126
+
127
+
128
+ fetchAgentConfig(parseArgv());
129
+ // exporting this stuff for testing purposes only
130
+ module.exports = { printHelpMessage, parseArgv, executeNodeAgent, fetchAgentConfig };
package/lib/contrast.js CHANGED
@@ -14,7 +14,7 @@ Copyright: 2022 Contrast Security, Inc
14
14
  */
15
15
  'use strict';
16
16
 
17
- const { program } = require('./core/config/options');
17
+ const { program, options } = require('./core/config/options');
18
18
  const path = require('path');
19
19
  const os = require('os');
20
20
  const semver = require('semver');
@@ -25,6 +25,7 @@ const Module = require('module');
25
25
  const sourceMapUtility = require('./util/source-map');
26
26
  const loggerFactory = require('./core/logger');
27
27
  const logger = loggerFactory('contrast:contrast-init');
28
+ const { outputAgentConfigFile } = require('./util/config-diagnostics-utils');
28
29
 
29
30
  function getAgentSnippet() {
30
31
  // getting reference to name every time for testing purposes
@@ -59,9 +60,12 @@ contrastAgent.showBanner = function showBanner() {
59
60
  console.log(colors.red(cat));
60
61
  }
61
62
 
62
- logger.console();
63
- logger.console('%s %s', NAME, VERSION);
64
- logger.console(colors.green('--------------------------------------'));
63
+ const showAgentBanner = (process.env['CONTRAST__SHOW__BANNER'] === 'false') ? false : true;
64
+ if (showAgentBanner) {
65
+ logger.console();
66
+ logger.console('%s %s', NAME, VERSION);
67
+ logger.console(colors.green('--------------------------------------'));
68
+ }
65
69
  };
66
70
 
67
71
  /**
@@ -284,18 +288,16 @@ contrastAgent.prepare = function(...args) {
284
288
 
285
289
  /**
286
290
  * Sends startup message to TeamServer then instruments user code
287
- *
288
291
  * @param {Array} args process.argv
289
292
  */
290
293
  contrastAgent.bootstrap = function(args) {
291
294
  const reporter = new TSReporter(agent);
295
+
292
296
  // returning promise for testing purposes only
293
297
  return reporter
294
298
  .onboard(args)
295
299
  .catch((err) => {
296
- logger.error(
297
- 'Reporter onboarding failed. Continuing without instrumentation.'
298
- );
300
+ logger.error('Reporter onboarding failed. Continuing without instrumentation.');
299
301
  logger.error(err);
300
302
  agent.clearIntervals();
301
303
  return;
@@ -314,6 +316,25 @@ contrastAgent.bootstrap = function(args) {
314
316
  'Unexpected error while trying to start contrast. Continuing without instrumentation. Error: %o',
315
317
  err
316
318
  );
319
+ })
320
+ .finally(async () => {
321
+ let destination = process.env['CONTRAST__AGENT__DIAGNOSTICS__REPORT_PATH'];
322
+ if (destination == null) {
323
+ destination = agent.config._flat['agent.logger.path'].split('/');
324
+ destination.pop() && destination.push('contrast_effective_config.json');
325
+ destination = destination.join('/');
326
+ }
327
+
328
+ const args = {
329
+ quiet: (process.env['CONTRAST__AGENT__DIAGNOSTICS__QUIET'] === 'false') ? false : true,
330
+ output: destination,
331
+ };
332
+
333
+ try {
334
+ outputAgentConfigFile(agent, options, args, null);
335
+ } catch (err) {
336
+ outputAgentConfigFile(agent, options, args, err);
337
+ }
317
338
  });
318
339
  };
319
340
 
@@ -0,0 +1,221 @@
1
+ /**
2
+ Copyright: 2022 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
+ 'use strict';
16
+
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+
20
+ // eslint-disable-next-line complexity
21
+ function getLoggerValues(option, config, tsData) {
22
+ let tsValue, effectiveValue = config._flat[option.name];
23
+
24
+ switch (option.name) {
25
+ case 'agent.logger.level':
26
+ tsValue = tsData.logLevel;
27
+ break;
28
+ case 'agent.logger.path':
29
+ tsValue = tsData.logFile;
30
+ break;
31
+ case 'agent.security_logger.syslog.enable':
32
+ tsValue = tsData.defend.syslog.enabled;
33
+ break;
34
+ case 'agent.security_logger.syslog.ip':
35
+ tsValue = tsData.defend.syslog.ipAddress;
36
+ break;
37
+ case 'agent.security_logger.syslog.port':
38
+ tsValue = tsData.defend.syslog.port;
39
+ break;
40
+ case 'agent.security_logger.syslog.fascility':
41
+ tsValue = tsData.defend.syslog.fascilityCode;
42
+ break;
43
+ case 'agent.security_logger.syslog.severity_exploited':
44
+ tsValue = tsData.defend.syslog.severityExploited;
45
+ break;
46
+ case 'agent.security_logger.syslog.severity_blocked':
47
+ tsValue = tsData.defend.syslog.severityBlocked;
48
+ break;
49
+ case 'agent.security_logger.syslog.severity_probed':
50
+ tsValue = tsData.defend.syslog.severityProbed;
51
+ break;
52
+ default:
53
+ break;
54
+ }
55
+
56
+ if (config._sources[option.name] == 'DEFAULT' && tsValue != null) {
57
+ effectiveValue = tsValue;
58
+ config._sources[option.name] = 'ContrastUI';
59
+ }
60
+
61
+ return {
62
+ CanonicalName: option.name,
63
+ Name: option.name,
64
+ Value: effectiveValue,
65
+ Source: config._sources[option.name],
66
+ };
67
+ }
68
+
69
+ function getAssessValues(option, config, tsData) {
70
+ let tsValue, effectiveValue = config._flat[option.name];
71
+
72
+ switch (option.name) {
73
+ case 'assess.enable':
74
+ tsValue = tsData.assess.enabled;
75
+ break;
76
+ case 'assess.enable_propagators':
77
+ tsValue = tsData.assess.propagators;
78
+ break;
79
+ case 'assess.sampling.enable':
80
+ tsValue = tsData.assess.sampling;
81
+ break;
82
+ default:
83
+ break;
84
+ }
85
+
86
+ if (config._sources[option.name] == 'DEFAULT' && tsValue != null) {
87
+ effectiveValue = tsValue;
88
+ config._sources[option.name] = 'ContrastUI';
89
+ }
90
+
91
+ return {
92
+ CanonicalName: option.name,
93
+ Name: option.name,
94
+ Value: effectiveValue,
95
+ Source: config._sources[option.name],
96
+ };
97
+ }
98
+
99
+ function getProtectValues(option, config, tsData) {
100
+ let value = {};
101
+
102
+ if (option.name.includes('ip-denylist')) {
103
+ value = {
104
+ CanonicalName: option.name.split('.')[2],
105
+ Name: option.name,
106
+ Value: tsData.ipDenylistsList,
107
+ Source: 'ContrastUI',
108
+ };
109
+ } else if (option.name.includes('virtual-patch')) {
110
+ value = {
111
+ CanonicalName: option.name.split('.')[2],
112
+ Name: option.name,
113
+ Value: tsData.virtualPatchesList,
114
+ Source: 'ContrastUI',
115
+ };
116
+ } else if (option.name.includes('protect.rules') && option.name.includes('mode')) {
117
+ const apiRule = tsData.protectionRulesList.find(r => r.id == option.name.split('.')[2]);
118
+
119
+ const apiMode = apiRule ? apiRule.mode : undefined;
120
+ const ymlMode = config._flat[option.name];
121
+ let effectiveMode;
122
+
123
+ if (!ymlMode && apiMode) {
124
+ config._sources[option.name] = 'ContrastUI';
125
+ effectiveMode = apiMode;
126
+ } else if (ymlMode) {
127
+ effectiveMode = ymlMode;
128
+ }
129
+
130
+ value = {
131
+ CanonicalName: option.name.split('.')[2],
132
+ Name: option.name,
133
+ Value: effectiveMode,
134
+ Source: config._sources[option.name],
135
+ };
136
+ }
137
+
138
+ return value;
139
+ }
140
+
141
+ function getEffectiveConfigValues(agent, options, err) {
142
+ const tsData = { ...agent.tsFeatureSet.serverFeatures, ...agent.tsFeatureSet.applicationSettings };
143
+ const values = [];
144
+
145
+ for (const option of options) {
146
+ if (!err && option.name.includes('protect.rules') && option.name.includes('mode')) {
147
+ // get all protect rules
148
+ values.push(getProtectValues(option, agent.config, tsData));
149
+ } else if (!err && option.name.includes('logger')) {
150
+ // fetch agent/service/sys logger settings
151
+ values.push(getLoggerValues(option, agent.config, tsData));
152
+ } else if (!err && option.name.includes('assess')) {
153
+ // fetch assess settings
154
+ values.push(getAssessValues(option, agent.config, tsData));
155
+ } else if (agent.config._flat[option.name] != null) {
156
+ // fetch everything else
157
+ values.push({
158
+ CanonicalName: option.name,
159
+ Name: option.name,
160
+ Value: agent.config._flat[option.name],
161
+ Source: agent.config._sources[option.name],
162
+ });
163
+ }
164
+ }
165
+ return values;
166
+ }
167
+
168
+ function getEffectiveConfig(agent, options, err) {
169
+ const effectiveConfigValues = getEffectiveConfigValues(agent, options, err);
170
+ const envValues = effectiveConfigValues.filter(v => v.Source == 'ENV');
171
+ const cliValues = effectiveConfigValues.filter(v => v.Source == 'CLI');
172
+ const ymlValues = effectiveConfigValues.filter(v => v.Source == 'YAML');
173
+ const apiValues = effectiveConfigValues.filter(v => v.Source == 'ContrastUI');
174
+
175
+ const effectiveConfig = {
176
+ ReportCreate: new Date(Date.now()),
177
+ Config: {
178
+ EffectiveConfig: {
179
+ Status: undefined,
180
+ Values: effectiveConfigValues
181
+ },
182
+ Environment: envValues,
183
+ CommandLine: cliValues,
184
+ ContrastUI: apiValues,
185
+ File: ymlValues,
186
+ }
187
+ };
188
+
189
+ return effectiveConfig;
190
+ }
191
+
192
+ function outputAgentConfigFile(agent, options, args, err) {
193
+ const effectiveConfig = getEffectiveConfig(agent, options, err);
194
+
195
+ if (err) {
196
+ effectiveConfig.Config.Status = 'Failure! ' +
197
+ `Could not connect to TeamServer, so no TeamServer data will be included. Error: ${err}`;
198
+ } else {
199
+ effectiveConfig.Config.Status = 'Success';
200
+ }
201
+
202
+ try {
203
+ fs.accessSync(path.join(args.output, '..'), fs.constants.RDWD_OK);
204
+ fs.writeFileSync(args.output, JSON.stringify(effectiveConfig, null, 2), 'utf-8');
205
+ } catch (err) {
206
+ console.log(`Couldn't create effective config file: ${err}`);
207
+ }
208
+
209
+ if (!args.quiet) {
210
+ console.log(JSON.stringify(effectiveConfig, null, 2));
211
+ }
212
+ }
213
+
214
+ module.exports = {
215
+ getEffectiveConfigValues,
216
+ getEffectiveConfig,
217
+ outputAgentConfigFile,
218
+ getProtectValues,
219
+ getAssessValues,
220
+ getLoggerValues
221
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.26.0",
3
+ "version": "4.27.0",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",
@@ -54,7 +54,8 @@
54
54
  "bin": {
55
55
  "node-contrast": "cli.js",
56
56
  "perf-logs": "perf-logs.js",
57
- "contrast-transpile": "cli-rewriter.js"
57
+ "contrast-transpile": "cli-rewriter.js",
58
+ "read-contrast-config": "config-diagnostics.js"
58
59
  },
59
60
  "files": [
60
61
  "bin/**",
@@ -64,7 +65,8 @@
64
65
  "cli.js",
65
66
  "esm.mjs",
66
67
  "perf-logs.js",
67
- "cli-rewriter.js"
68
+ "cli-rewriter.js",
69
+ "config-diagnostics.js"
68
70
  ],
69
71
  "repository": {
70
72
  "type": "git"