mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-22 12:15:38 +00:00
ca certs updates and fixes (#5549)
This commit is contained in:
@@ -6,6 +6,7 @@ runs:
|
||||
- name: Install additional OS dependencies for custom CA certs
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get --no-install-recommends install -y \
|
||||
libglib2.0-0 libnss3 libdbus-1-3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libgtk-3-0 libasound2t64 \
|
||||
xvfb libxml2-utils
|
||||
|
||||
3
package-lock.json
generated
3
package-lock.json
generated
@@ -28363,7 +28363,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.11.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"system-ca": "^2.0.1",
|
||||
"xmlbuilder": "^15.1.1",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
@@ -30129,7 +30128,6 @@
|
||||
"nanoid": "3.3.8",
|
||||
"qs": "^6.11.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"system-ca": "^2.0.1",
|
||||
"tough-cookie": "^6.0.0",
|
||||
"uuid": "^9.0.0",
|
||||
"yup": "^0.32.11"
|
||||
@@ -31931,6 +31929,7 @@
|
||||
"axios": "^1.9.0",
|
||||
"grpc-reflection-js": "^0.3.0",
|
||||
"is-ip": "^5.0.1",
|
||||
"system-ca": "^2.0.1",
|
||||
"tough-cookie": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -176,11 +176,11 @@ const General = ({ close }) => {
|
||||
name="keepDefaultCaCertificates.enabled"
|
||||
checked={formik.values.keepDefaultCaCertificates.enabled}
|
||||
onChange={formik.handleChange}
|
||||
className={`mousetrap mr-0 ${formik.values.customCaCertificate.enabled ? '' : 'opacity-25'}`}
|
||||
disabled={formik.values.customCaCertificate.enabled ? false : true}
|
||||
className={`mousetrap mr-0 ${formik.values.customCaCertificate.enabled && formik.values.customCaCertificate.filePath ? '' : 'opacity-25'}`}
|
||||
disabled={formik.values.customCaCertificate.enabled && formik.values.customCaCertificate.filePath ? false : true}
|
||||
/>
|
||||
<label
|
||||
className={`block ml-2 select-none ${formik.values.customCaCertificate.enabled ? '' : 'opacity-25'}`}
|
||||
className={`block ml-2 select-none ${formik.values.customCaCertificate.enabled && formik.values.customCaCertificate.filePath ? '' : 'opacity-25'}`}
|
||||
htmlFor="keepDefaultCaCertificatesEnabled"
|
||||
>
|
||||
Keep Default CA Certificates
|
||||
|
||||
@@ -69,7 +69,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.11.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"system-ca": "^2.0.1",
|
||||
"xmlbuilder": "^15.1.1",
|
||||
"yargs": "^17.6.2"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
const os = require('os');
|
||||
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');
|
||||
@@ -26,7 +24,7 @@ const { getOAuth2Token } = require('./oauth2');
|
||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||
const { NtlmClient } = require('axios-ntlm');
|
||||
const { addDigestInterceptor } = require('@usebruno/requests');
|
||||
const { getCACertificates } = require('../utils/ca-cert');
|
||||
const { getCACertificates } = require('@usebruno/requests');
|
||||
const { encodeUrl } = require('@usebruno/common').utils;
|
||||
|
||||
const onConsoleLog = (type, args) => {
|
||||
@@ -156,13 +154,10 @@ const runSingleRequest = async function (
|
||||
if (insecure) {
|
||||
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
||||
} else {
|
||||
const caCertArray = [options['cacert'], process.env.SSL_CERT_FILE];
|
||||
const caCertFilePath = caCertArray.find((el) => el);
|
||||
const caCertFilePath = options['cacert'];
|
||||
let caCertificatesData = await getCACertificates({ caCertFilePath, shouldKeepDefaultCerts: !options['ignoreTruststore'] });
|
||||
let caCertificates = caCertificatesData.caCertificates;
|
||||
if (caCertificates?.length > 0) {
|
||||
httpsAgentRequestFields['ca'] = caCertificates;
|
||||
}
|
||||
httpsAgentRequestFields['ca'] = caCertificates || [];
|
||||
}
|
||||
|
||||
const interpolationOptions = {
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
"nanoid": "3.3.8",
|
||||
"qs": "^6.11.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"system-ca": "^2.0.1",
|
||||
"tough-cookie": "^6.0.0",
|
||||
"uuid": "^9.0.0",
|
||||
"yup": "^0.32.11"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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 { interpolateString } = require('./interpolate-string');
|
||||
const { getCACertificates } = require('../../utils/ca-cert');
|
||||
|
||||
/**
|
||||
* Gets certificates and proxy configuration for a request
|
||||
@@ -33,13 +33,11 @@ const getCertsAndProxyConfig = async ({
|
||||
});
|
||||
|
||||
let caCertificates = caCertificatesData.caCertificates;
|
||||
let caCertificateDetails = caCertificatesData.caCertificatesCount;
|
||||
let caCertificatesCount = caCertificatesData.caCertificatesCount;
|
||||
|
||||
// configure HTTPS agent with aggregated CA certificates
|
||||
if (caCertificates?.length > 0) {
|
||||
httpsAgentRequestFields['caCertificateDetails'] = caCertificateDetails;
|
||||
httpsAgentRequestFields['ca'] = caCertificates;
|
||||
}
|
||||
httpsAgentRequestFields['caCertificatesCount'] = caCertificatesCount;
|
||||
httpsAgentRequestFields['ca'] = caCertificates || [];
|
||||
|
||||
const brunoConfig = getBrunoConfig(collectionUid);
|
||||
const interpolationOptions = {
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
const { systemCertsAsync } = require('system-ca');
|
||||
const { rootCertificates } = require('node:tls');
|
||||
const fs = require('node:fs');
|
||||
|
||||
let systemCertsCache;
|
||||
|
||||
async function getSystemCerts(systemCAOpts = {}) {
|
||||
if (systemCertsCache) return systemCertsCache;
|
||||
|
||||
try {
|
||||
systemCertsCache = await systemCertsAsync(systemCAOpts);
|
||||
|
||||
return systemCertsCache;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function certToString(cert) {
|
||||
return typeof cert === 'string'
|
||||
? cert
|
||||
: Buffer.from(cert.buffer, cert.byteOffset, cert.byteLength).toString('utf8');
|
||||
}
|
||||
|
||||
function mergeCA(...args) {
|
||||
const ca = new Set();
|
||||
for (const item of args) {
|
||||
if (!item) continue;
|
||||
const caList = Array.isArray(item) ? item : [item];
|
||||
for (const cert of caList) {
|
||||
ca.add(certToString(cert));
|
||||
}
|
||||
}
|
||||
return [...ca].join('\n');
|
||||
}
|
||||
|
||||
function getNodeExtraCACerts() {
|
||||
const extraCACertPath = process.env.NODE_EXTRA_CA_CERTS;
|
||||
if (!extraCACertPath) return [];
|
||||
|
||||
try {
|
||||
if (fs.existsSync(extraCACertPath)) {
|
||||
const extraCACert = fs.readFileSync(extraCACertPath, 'utf8');
|
||||
if (extraCACert && extraCACert.trim()) {
|
||||
return [extraCACert];
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to read NODE_EXTRA_CA_CERTS from ${extraCACertPath}:`, err.message);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
const getCACertificates = async ({ caCertFilePath, shouldKeepDefaultCerts = true }) => {
|
||||
try {
|
||||
let caCertificates = '';
|
||||
let caCertificatesCount = {
|
||||
system: 0,
|
||||
root: 0,
|
||||
custom: 0,
|
||||
extra: 0
|
||||
}
|
||||
|
||||
let systemCerts = [];
|
||||
let rootCerts = [];
|
||||
let customCerts = [];
|
||||
let nodeExtraCerts = [];
|
||||
|
||||
if (shouldKeepDefaultCerts) {
|
||||
// get system certs
|
||||
systemCerts = await getSystemCerts();
|
||||
caCertificatesCount.system = systemCerts.length;
|
||||
|
||||
// get root certs
|
||||
rootCerts = [...rootCertificates];
|
||||
caCertificatesCount.root = rootCerts.length;
|
||||
}
|
||||
|
||||
// handle user-provided custom CA certificate file with optional default certificates
|
||||
if (caCertFilePath) {
|
||||
// validate custom CA certificate file
|
||||
if (fs.existsSync(caCertFilePath)) {
|
||||
try {
|
||||
const customCert = fs.readFileSync(caCertFilePath, 'utf8');
|
||||
if (customCert && customCert.trim()) {
|
||||
customCerts.push(customCert);
|
||||
caCertificatesCount.custom = customCerts.length;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to read custom CA certificate from ${caCertFilePath}:`, (err).message);
|
||||
throw new Error(`Unable to load custom CA certificate: ${(err).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get NODE_EXTRA_CA_CERTS
|
||||
nodeExtraCerts = getNodeExtraCACerts();
|
||||
caCertificatesCount.extra = nodeExtraCerts.length;
|
||||
|
||||
// merge certs
|
||||
const mergedCerts = mergeCA(systemCerts, rootCerts, customCerts, nodeExtraCerts);
|
||||
caCertificates = mergedCerts;
|
||||
|
||||
return {
|
||||
caCertificates,
|
||||
caCertificatesCount
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error configuring CA certificates:', (err).message);
|
||||
throw err; // Re-throw certificate loading errors as they're critical
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCACertificates
|
||||
};
|
||||
@@ -88,8 +88,8 @@ function createTimelineAgentClass(BaseAgentClass) {
|
||||
return class extends BaseAgentClass {
|
||||
constructor(options, timeline) {
|
||||
|
||||
let caCertificateDetails = options.caCertificateDetails || {};
|
||||
delete options.caCertificateDetails;
|
||||
let caCertificatesCount = options.caCertificatesCount || {};
|
||||
delete options.caCertificatesCount;
|
||||
|
||||
// For proxy agents, the first argument is the proxy URI and the second is options
|
||||
if (options?.proxy) {
|
||||
@@ -136,7 +136,7 @@ function createTimelineAgentClass(BaseAgentClass) {
|
||||
});
|
||||
}
|
||||
|
||||
this.caCertificateDetails = caCertificateDetails;
|
||||
this.caCertificatesCount = caCertificatesCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,10 +152,10 @@ function createTimelineAgentClass(BaseAgentClass) {
|
||||
});
|
||||
}
|
||||
|
||||
const rootCerts = this.caCertificateDetails.root || 0;
|
||||
const systemCerts = this.caCertificateDetails.system || 0;
|
||||
const extraCerts = this.caCertificateDetails.extra || 0;
|
||||
const customCerts = this.caCertificateDetails.custom || 0;
|
||||
const rootCerts = this.caCertificatesCount.root || 0;
|
||||
const systemCerts = this.caCertificatesCount.system || 0;
|
||||
const extraCerts = this.caCertificatesCount.extra || 0;
|
||||
const customCerts = this.caCertificatesCount.custom || 0;
|
||||
|
||||
this.timeline.push({
|
||||
timestamp: new Date(),
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"axios": "^1.9.0",
|
||||
"grpc-reflection-js": "^0.3.0",
|
||||
"is-ip": "^5.0.1",
|
||||
"system-ca": "^2.0.1",
|
||||
"tough-cookie": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -39,6 +39,6 @@ module.exports = [
|
||||
typescript({ tsconfig: './tsconfig.json' }),
|
||||
terser(),
|
||||
],
|
||||
external: ['axios', 'qs']
|
||||
external: ['axios', 'qs', 'system-ca']
|
||||
}
|
||||
];
|
||||
|
||||
@@ -2,4 +2,6 @@ export { addDigestInterceptor, getOAuth2Token } from './auth';
|
||||
export { GrpcClient, generateGrpcSampleMessage } from './grpc';
|
||||
export { default as cookies } from './cookies';
|
||||
|
||||
export { getCACertificates } from './utils/ca-cert';
|
||||
|
||||
export * as scripting from './scripting';
|
||||
@@ -1,10 +1,25 @@
|
||||
const { systemCertsAsync } = require('system-ca');
|
||||
const { rootCertificates } = require('node:tls');
|
||||
const fs = require('node:fs');
|
||||
import { systemCertsAsync, Options as SystemCAOptions } from 'system-ca';
|
||||
import { rootCertificates } from 'node:tls';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
let systemCertsCache;
|
||||
type T_CACertificatesOptions = {
|
||||
caCertFilePath?: string;
|
||||
shouldKeepDefaultCerts?: boolean;
|
||||
}
|
||||
|
||||
async function getSystemCerts(systemCAOpts = {}) {
|
||||
type T_CACertificatesResult = {
|
||||
caCertificates: string;
|
||||
caCertificatesCount: {
|
||||
system: number;
|
||||
root: number;
|
||||
custom: number;
|
||||
extra: number;
|
||||
};
|
||||
}
|
||||
|
||||
let systemCertsCache: string[] | undefined;
|
||||
|
||||
async function getSystemCerts(systemCAOpts: SystemCAOptions = {}): Promise<string[]> {
|
||||
if (systemCertsCache) return systemCertsCache;
|
||||
|
||||
try {
|
||||
@@ -17,25 +32,27 @@ async function getSystemCerts(systemCAOpts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function certToString(cert) {
|
||||
function certToString(cert: string | Buffer) {
|
||||
return typeof cert === 'string'
|
||||
? cert
|
||||
: Buffer.from(cert.buffer, cert.byteOffset, cert.byteLength).toString('utf8');
|
||||
}
|
||||
|
||||
function mergeCA(...args) {
|
||||
const ca = new Set();
|
||||
function mergeCA(...args: (string | string[])[]): string {
|
||||
const ca = new Set<string>();
|
||||
for (const item of args) {
|
||||
if (!item) continue;
|
||||
const caList = Array.isArray(item) ? item : [item];
|
||||
for (const cert of caList) {
|
||||
ca.add(certToString(cert));
|
||||
if (cert) {
|
||||
ca.add(certToString(cert));
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...ca].join('\n');
|
||||
}
|
||||
|
||||
function getNodeExtraCACerts() {
|
||||
function getNodeExtraCACerts(): string[] {
|
||||
const extraCACertPath = process.env.NODE_EXTRA_CA_CERTS;
|
||||
if (!extraCACertPath) return [];
|
||||
|
||||
@@ -47,14 +64,34 @@ function getNodeExtraCACerts() {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to read NODE_EXTRA_CA_CERTS from ${extraCACertPath}:`, err.message);
|
||||
console.error(`Failed to read NODE_EXTRA_CA_CERTS from ${extraCACertPath}:`, (err as Error).message);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CA certificates
|
||||
*
|
||||
* Generic function to get CA certificates
|
||||
* - System CA certificates (From OS)
|
||||
* - Root CA certificates (From Node)
|
||||
* - Custom CA certificates (From user-provided file)
|
||||
* - NODE_EXTRA_CA_CERTS (From environment variable)
|
||||
*
|
||||
* If no custom CA certificate file path is provided
|
||||
* → return system CA certificates and root certificates + NODE_EXTRA_CA_CERTS
|
||||
*
|
||||
* If custom CA certificate file path is provided
|
||||
* → use custom CA certificate file + NODE_EXTRA_CA_CERTS
|
||||
* → ignore system + root certificates if shouldKeepDefaultCerts is false
|
||||
*
|
||||
* @param caCertFilePath - path to custom CA certificate file
|
||||
* @param shouldKeepDefaultCerts - whether to keep default CA certificates
|
||||
* @returns {Promise<T_CACertificatesResult>} - CA certificates and their count
|
||||
*/
|
||||
|
||||
const getCACertificates = async ({ caCertFilePath, shouldKeepDefaultCerts = true }) => {
|
||||
const getCACertificates = async ({ caCertFilePath, shouldKeepDefaultCerts = true }: T_CACertificatesOptions): Promise<T_CACertificatesResult> => {
|
||||
try {
|
||||
let caCertificates = '';
|
||||
let caCertificatesCount = {
|
||||
@@ -64,20 +101,11 @@ const getCACertificates = async ({ caCertFilePath, shouldKeepDefaultCerts = true
|
||||
extra: 0
|
||||
}
|
||||
|
||||
let systemCerts = [];
|
||||
let rootCerts = [];
|
||||
let customCerts = [];
|
||||
let nodeExtraCerts = [];
|
||||
let systemCerts: string[] = [];
|
||||
let rootCerts: string[] = [];
|
||||
let customCerts: string[] = [];
|
||||
let nodeExtraCerts: string[] = [];
|
||||
|
||||
if (shouldKeepDefaultCerts) {
|
||||
// get system certs
|
||||
systemCerts = await getSystemCerts();
|
||||
caCertificatesCount.system = systemCerts.length;
|
||||
|
||||
// get root certs
|
||||
rootCerts = [...rootCertificates];
|
||||
caCertificatesCount.root = rootCerts.length;
|
||||
}
|
||||
|
||||
// handle user-provided custom CA certificate file with optional default certificates
|
||||
if (caCertFilePath) {
|
||||
@@ -90,10 +118,30 @@ const getCACertificates = async ({ caCertFilePath, shouldKeepDefaultCerts = true
|
||||
caCertificatesCount.custom = customCerts.length;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to read custom CA certificate from ${caCertFilePath}:`, (err).message);
|
||||
throw new Error(`Unable to load custom CA certificate: ${(err).message}`);
|
||||
console.error(`Failed to read custom CA certificate from ${caCertFilePath}:`, (err as Error).message);
|
||||
throw new Error(`Unable to load custom CA certificate: ${(err as Error).message}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Invalid custom CA certificate path: ${caCertFilePath}`);
|
||||
}
|
||||
|
||||
if (shouldKeepDefaultCerts) {
|
||||
// get system certs
|
||||
systemCerts = await getSystemCerts();
|
||||
caCertificatesCount.system = systemCerts.length;
|
||||
|
||||
// get root certs
|
||||
rootCerts = [...rootCertificates];
|
||||
caCertificatesCount.root = rootCerts.length;
|
||||
}
|
||||
} else {
|
||||
// get system certs
|
||||
systemCerts = await getSystemCerts();
|
||||
caCertificatesCount.system = systemCerts.length;
|
||||
|
||||
// get root certs
|
||||
rootCerts = [...rootCertificates];
|
||||
caCertificatesCount.root = rootCerts.length;
|
||||
}
|
||||
|
||||
// get NODE_EXTRA_CA_CERTS
|
||||
@@ -109,11 +157,11 @@ const getCACertificates = async ({ caCertFilePath, shouldKeepDefaultCerts = true
|
||||
caCertificatesCount
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error configuring CA certificates:', (err).message);
|
||||
console.error('Error configuring CA certificates:', (err as Error).message);
|
||||
throw err; // Re-throw certificate loading errors as they're critical
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
getCACertificates
|
||||
};
|
||||
@@ -35,12 +35,14 @@ export default defineConfig({
|
||||
{
|
||||
command: 'npm run dev:web',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 10 * 60 * 1000
|
||||
},
|
||||
{
|
||||
command: 'npm start --workspace=packages/bruno-tests',
|
||||
url: 'http://localhost:8081/ping',
|
||||
reuseExistingServer: !process.env.CI
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 10 * 60 * 1000
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user