Files
bruno/packages/bruno-electron/src/ipc/network/cert-utils.js

237 lines
8.9 KiB
JavaScript

const fs = require('node:fs');
const path = require('path');
const { get } = require('lodash');
const { getCACertificates } = require('@usebruno/requests');
const { preferencesUtil } = require('../../store/preferences');
const { getBrunoConfig } = require('../../store/bruno-config');
const { getCachedSystemProxy } = require('../../store/system-proxy');
const { interpolateString, interpolateObject } = require('./interpolate-string');
/**
* Gets certificates and proxy configuration for a request
*/
const getCertsAndProxyConfig = async ({
collectionUid,
collection,
request,
envVars,
runtimeVariables,
processEnvVars,
collectionPath,
globalEnvironmentVariables
}) => {
/**
* @see https://github.com/usebruno/bruno/issues/211 set keepAlive to true, this should fix socket hang up errors
* @see https://github.com/nodejs/node/pull/43522 keepAlive was changed to true globally on Node v19+
*/
const httpsAgentRequestFields = { keepAlive: true };
if (!preferencesUtil.shouldVerifyTls()) {
httpsAgentRequestFields['rejectUnauthorized'] = false;
}
let caCertificates = '';
let caCertificatesCount = { system: 0, root: 0, custom: 0, extra: 0 };
// Only load CA certificates if SSL validation is enabled (otherwise they're unused)
if (preferencesUtil.shouldVerifyTls()) {
let caCertFilePath = preferencesUtil.shouldUseCustomCaCertificate() && preferencesUtil.getCustomCaCertificateFilePath();
let caCertificatesData = getCACertificates({
caCertFilePath,
shouldKeepDefaultCerts: preferencesUtil.shouldKeepDefaultCaCertificates()
});
caCertificates = caCertificatesData.caCertificates;
caCertificatesCount = caCertificatesData.caCertificatesCount;
}
// configure HTTPS agent with aggregated CA certificates
httpsAgentRequestFields['caCertificatesCount'] = caCertificatesCount;
httpsAgentRequestFields['ca'] = caCertificates || [];
const { promptVariables } = collection;
const collectionVariables = request.collectionVariables || {};
const folderVariables = request.folderVariables || {};
const requestVariables = request.requestVariables || {};
const brunoConfig = getBrunoConfig(collectionUid, collection);
const interpolationOptions = {
globalEnvironmentVariables,
collectionVariables,
envVars,
folderVariables,
requestVariables,
runtimeVariables,
promptVariables,
processEnvVars
};
// client certificate config
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
for (let clientCert of clientCertConfig) {
const domain = interpolateString(clientCert?.domain, interpolationOptions);
const type = clientCert?.type || 'cert';
if (domain) {
const hostRegex = '^(https:\\/\\/|grpc:\\/\\/|grpcs:\\/\\/|ws:\\/\\/|wss:\\/\\/)?'
+ domain.replaceAll('.', '\\.').replaceAll('*', '.*');
const requestUrl = interpolateString(request.url, interpolationOptions);
if (requestUrl && requestUrl.match(hostRegex)) {
if (type === 'cert') {
try {
let certFilePath = interpolateString(clientCert?.certFilePath, interpolationOptions);
certFilePath = path.isAbsolute(certFilePath) ? certFilePath : path.join(collectionPath, certFilePath);
let keyFilePath = interpolateString(clientCert?.keyFilePath, interpolationOptions);
keyFilePath = path.isAbsolute(keyFilePath) ? keyFilePath : path.join(collectionPath, keyFilePath);
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
} catch (err) {
console.error('Error reading cert/key file', err);
throw new Error('Error reading cert/key file' + err);
}
} else if (type === 'pfx') {
try {
let pfxFilePath = interpolateString(clientCert?.pfxFilePath, interpolationOptions);
pfxFilePath = path.isAbsolute(pfxFilePath) ? pfxFilePath : path.join(collectionPath, pfxFilePath);
httpsAgentRequestFields['pfx'] = fs.readFileSync(pfxFilePath);
} catch (err) {
console.error('Error reading pfx file', err);
throw new Error('Error reading pfx file' + err);
}
}
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
break;
}
}
}
/**
* Proxy configuration
*
* New format:
* - disabled: boolean (optional, defaults to false)
* - inherit: boolean (required)
* - config: { protocol, hostname, port, auth, bypassProxy }
*
* When collection proxy has inherit=false and disabled=false, use collection-specific proxy
* When collection proxy has inherit=true, inherit from global preferences
* When disabled=true, proxy is disabled
*
* Below logic calculates the proxyMode and proxyConfig to be used for the request
*/
let proxyMode = 'off';
let proxyConfig = {};
let proxyModeReason = '';
const collectionProxyConfig = get(brunoConfig, 'proxy', {});
const collectionProxyDisabled = get(collectionProxyConfig, 'disabled', false);
const collectionProxyInherit = get(collectionProxyConfig, 'inherit', true);
const collectionProxyConfigData = get(collectionProxyConfig, 'config', collectionProxyConfig);
if (!collectionProxyDisabled && !collectionProxyInherit) {
// Use collection-specific proxy
proxyConfig = collectionProxyConfigData;
proxyMode = 'on';
} else if (!collectionProxyDisabled && collectionProxyInherit) {
// Inherit from global preferences
const globalProxy = preferencesUtil.getGlobalProxyConfig();
const globalDisabled = get(globalProxy, 'disabled', false);
const globalProxySource = get(globalProxy, 'source', 'manual');
const globalProxyConfigData = get(globalProxy, 'config', {});
if (!globalDisabled) {
if (globalProxySource === 'pac') {
proxyMode = 'pac';
proxyConfig = {
pac: globalProxy.pac ?? {}
};
} else if (globalProxySource === 'inherit') {
proxyMode = 'system';
const systemProxyConfig = await getCachedSystemProxy();
proxyConfig = systemProxyConfig || { http_proxy: null, https_proxy: null, no_proxy: null, pac_url: null, source: 'cache-miss' };
} else {
// source === 'manual'
proxyConfig = globalProxyConfigData;
proxyMode = 'on';
}
} else {
proxyModeReason = 'App-level proxy is disabled';
}
} else {
proxyModeReason = 'Collection-level proxy is disabled';
}
return { proxyMode, proxyModeReason, proxyConfig, httpsAgentRequestFields, interpolationOptions };
};
/**
* Builds the certsAndProxyConfig object for bru.sendRequest
* This allows bru.sendRequest to use the same proxy/certs config as the main request
*/
const buildCertsAndProxyConfig = async ({
collectionUid,
collection,
collectionPath,
envVars,
runtimeVariables,
processEnvVars,
request
}) => {
const brunoConfig = getBrunoConfig(collectionUid, collection);
// Build interpolation options (same pattern as getCertsAndProxyConfig)
const globalEnvironmentVariables = collection.globalEnvironmentVariables || {};
const { promptVariables } = collection;
const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {};
const interpolationOptions = {
globalEnvironmentVariables,
collectionVariables,
envVars,
folderVariables,
requestVariables,
runtimeVariables,
promptVariables,
processEnvVars
};
// Build options for getHttpHttpsAgents
const options = {
noproxy: false,
shouldVerifyTls: preferencesUtil.shouldVerifyTls(),
shouldUseCustomCaCertificate: preferencesUtil.shouldUseCustomCaCertificate(),
customCaCertificateFilePath: preferencesUtil.getCustomCaCertificateFilePath(),
shouldKeepDefaultCaCertificates: preferencesUtil.shouldKeepDefaultCaCertificates(),
cacheSslSession: preferencesUtil.isSslSessionCachingEnabled()
};
// Get client certificates from bruno config and interpolate
const rawClientCertificates = get(brunoConfig, 'clientCertificates');
const clientCertificates = rawClientCertificates
? interpolateObject(rawClientCertificates, interpolationOptions)
: undefined;
// Get proxy config from bruno config and interpolate
const collectionProxyConfig = get(brunoConfig, 'proxy', {});
const collectionLevelProxy = interpolateObject(collectionProxyConfig, interpolationOptions);
// Get app-level proxy config from global preferences
const appLevelProxyConfig = preferencesUtil.getGlobalProxyConfig();
// Get system proxy config
const systemProxyConfig = await getCachedSystemProxy();
return {
collectionPath,
options,
clientCertificates,
collectionLevelProxy,
appLevelProxyConfig,
systemProxyConfig
};
};
module.exports = { getCertsAndProxyConfig, buildCertsAndProxyConfig };