From b01f2e0c5fe7cf6c0d4b3d111033f71f9d8be56b Mon Sep 17 00:00:00 2001 From: slowjoe007 Date: Mon, 17 Jun 2024 12:58:39 +0200 Subject: [PATCH] feature: Augment default truststore by default, optionally limit to custom CA certs (#2057) * feat: Allow default truststore extension on bru CLI * feat: augment default truststore by default, optionally limit to custom CA certs * feat: augment default truststore by default, optionally limit to custom CA certs * feat: Allow default truststore extension on bru CLI * feat: augment default truststore by default, optionally limit to custom CA certs --- .../components/Preferences/General/index.js | 2 +- .../src/providers/ReduxStore/slices/app.js | 2 +- packages/bruno-cli/readme.md | 16 ++++++++-- packages/bruno-cli/src/commands/run.js | 32 +++++++++++++++++-- .../src/runner/run-single-request.js | 7 +++- .../bruno-electron/src/store/preferences.js | 4 +-- 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index addb72a4d..9855c2747 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -47,7 +47,7 @@ const General = ({ close }) => { filePath: get(preferences, 'request.customCaCertificate.filePath', null) }, keepDefaultCaCertificates: { - enabled: get(preferences, 'request.keepDefaultCaCertificates.enabled', false) + enabled: get(preferences, 'request.keepDefaultCaCertificates.enabled', true) }, timeout: preferences.request.timeout, storeCookies: get(preferences, 'request.storeCookies', true), diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index beb3d1fcd..3cd276880 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -18,7 +18,7 @@ const initialState = { filePath: null }, keepDefaultCaCertificates: { - enabled: false + enabled: true }, timeout: 0 }, diff --git a/packages/bruno-cli/readme.md b/packages/bruno-cli/readme.md index 1eedb839d..5cc98f3c0 100644 --- a/packages/bruno-cli/readme.md +++ b/packages/bruno-cli/readme.md @@ -32,18 +32,30 @@ Or run all requests in a collection's subfolder: bru run folder ``` -If you need to use an environment, you can specify it with the --env option: +If you need to use an environment, you can specify it with the `--env` option: ```bash bru run folder --env Local ``` -If you need to collect the results of your API tests, you can specify the --output option: +If you need to collect the results of your API tests, you can specify the `--output` option: ```bash bru run folder --output results.json ``` +If you need to run a set of requests that connect to peers with both publicly and privately signed certificates respectively, you can add private CA certificates via the `--cacert` option. By default, these certificates will be used in addition to the default truststore: + +```bash +bru run folder --cacert myCustomCA.pem +``` + +If you need to limit the trusted CA to a specified set when validating the request peer, provide them via `--cacert` and in addition use `--ignore-truststore` to disable the default truststore: + +```bash +bru run request.bru --cacert myCustomCA.pem --ignore-truststore +``` + ## Scripting Bruno cli returns the following exit status codes: diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 00f592bd2..35192b128 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -190,6 +190,12 @@ const builder = async (yargs) => { type: 'string', description: 'CA certificate to verify peer against' }) + .option('ignore-truststore', { + type: 'boolean', + default: false, + description: + 'The specified custom CA certificate (--cacert) will be used exclusively and the default truststore is ignored, if this option is specified. Evaluated in combination with "--cacert" only.' + }) .option('env', { describe: 'Environment variables', type: 'string' @@ -241,12 +247,33 @@ const builder = async (yargs) => { '$0 run request.bru --output results.html --format html', 'Run a request and write the results to results.html in html format in the current directory' ) - .example('$0 run request.bru --tests-only', 'Run all requests that have a test'); + + .example('$0 run request.bru --tests-only', 'Run all requests that have a test') + .example( + '$0 run request.bru --cacert myCustomCA.pem', + 'Use a custom CA certificate in combination with the default truststore when validating the peer of this request.' + ) + .example( + '$0 run folder --cacert myCustomCA.pem --ignore-truststore', + 'Use a custom CA certificate exclusively when validating the peers of the requests in the specified folder.' + ); }; const handler = async function (argv) { try { - let { filename, cacert, env, envVar, insecure, r: recursive, output: outputPath, format, testsOnly, bail } = argv; + let { + filename, + cacert, + ignoreTruststore, + env, + envVar, + insecure, + r: recursive, + output: outputPath, + format, + testsOnly, + bail + } = argv; const collectionPath = process.cwd(); // todo @@ -337,6 +364,7 @@ const handler = async function (argv) { } } } + options['ignoreTruststore'] = ignoreTruststore; if (['json', 'junit', 'html'].indexOf(format) === -1) { console.error(chalk.red(`Format must be one of "json", "junit or "html"`)); diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index ec4767efb..187f371cf 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -3,6 +3,7 @@ const qs = require('qs'); const chalk = require('chalk'); const decomment = require('decomment'); const fs = require('fs'); +const tls = require('tls'); const { forOwn, isUndefined, isNull, each, extend, get, compact } = require('lodash'); const FormData = require('form-data'); const prepareRequest = require('./prepare-request'); @@ -106,7 +107,11 @@ const runSingleRequest = async function ( const caCert = caCertArray.find((el) => el); if (caCert && caCert.length > 1) { try { - httpsAgentRequestFields['ca'] = fs.readFileSync(caCert); + let caCertBuffer = fs.readFileSync(caCert); + if (!options['ignoreTruststore']) { + caCertBuffer += '\n' + tls.rootCertificates.join('\n'); // Augment default truststore with custom CA certificates + } + httpsAgentRequestFields['ca'] = caCertBuffer; } catch (err) { console.log('Error reading CA cert file:' + caCert, err); } diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 844e96b06..f9497abee 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -16,7 +16,7 @@ const defaultPreferences = { filePath: null }, keepDefaultCaCertificates: { - enabled: false + enabled: true }, storeCookies: true, sendCookies: true, @@ -118,7 +118,7 @@ const preferencesUtil = { return get(getPreferences(), 'request.customCaCertificate.enabled', false); }, shouldKeepDefaultCaCertificates: () => { - return get(getPreferences(), 'request.keepDefaultCaCertificates.enabled', false); + return get(getPreferences(), 'request.keepDefaultCaCertificates.enabled', true); }, getCustomCaCertificateFilePath: () => { return get(getPreferences(), 'request.customCaCertificate.filePath', null);