Skip to content

Commit

Permalink
feat: initial mTLS support
Browse files Browse the repository at this point in the history
  • Loading branch information
phoval committed Oct 10, 2023
1 parent ff3321d commit 6faa6ba
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styled from 'styled-components';

const StyledWrapper = styled.div`
.settings-label {
width: 80px;
}
.textbox {
border: 1px solid #ccc;
padding: 0.15rem 0.45rem;
box-shadow: none;
border-radius: 0px;
outline: none;
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}
`;

export default StyledWrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';

import StyledWrapper from './StyledWrapper';

const ClientCertSettings = ({ clientCertConfig, onUpdate }) => {
const formik = useFormik({
initialValues: {
enabled: clientCertConfig.enabled || false,
domain: clientCertConfig.domain || '',
cert: clientCertConfig.cert || '',
key: clientCertConfig.key || '',
passphrase: clientCertConfig.port || ''
},
validationSchema: Yup.object({
enabled: Yup.boolean(),
domain: Yup.string(),
cert: Yup.string(),
key: Yup.string(),
passphrase: Yup.string()
}),
onSubmit: (values) => {
onUpdate(values);
}
});

useEffect(() => {
formik.setValues({
enabled: clientCertConfig.enabled || false,
domain: clientCertConfig.domain || '',
cert: clientCertConfig.cert || '',
key: clientCertConfig.key || '',
passphrase: clientCertConfig.port || ''
});
}, [clientCertConfig]);

const getFile = (e) => {
formik.values[e.name] = e.files[0].path;
};

return (
<StyledWrapper>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="enabled">
Enabled
</label>
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="domain">
Domain
</label>
<input
id="domain"
type="text"
name="domain"
placeholder="*.example.org"
className="block textbox"
onChange={formik.handleChange}
value={formik.values.domain || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="cert">
Cert file
</label>
<input id="cert" type="file" name="cert" className="block" onChange={(e) => getFile(e.target)} />
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="key">
Key file
</label>
<input id="key" type="file" name="key" className="block" onChange={(e) => getFile(e.target)} />
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="passphrase">
Passphrase
</label>
<input
id="passphrase"
type="text"
name="passphrase"
className="block textbox"
onChange={formik.handleChange}
value={formik.values.passphrase || ''}
/>
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary">
Save
</button>
</div>
</form>
</StyledWrapper>
);
};

export default ClientCertSettings;
20 changes: 20 additions & 0 deletions packages/bruno-app/src/components/CollectionSettings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actio
import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections';
import { useDispatch } from 'react-redux';
import ProxySettings from './ProxySettings';
import ClientCertSettings from './ClientCertSettings';
import Headers from './Headers';
import Auth from './Auth';
import Script from './Script';
Expand All @@ -27,6 +28,8 @@ const CollectionSettings = ({ collection }) => {

const proxyConfig = get(collection, 'brunoConfig.proxy', {});

const clientCertConfig = get(collection, 'brunoConfig.clientCert', {});

const onProxySettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.proxy = config;
Expand All @@ -37,6 +40,17 @@ const CollectionSettings = ({ collection }) => {
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};

const onClientCertSettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
console.log(config);
brunoConfig.clientCert = config;
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
.then(() => {
toast.success('Collection settings updated successfully');
})
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};

const getTabPanel = (tab) => {
switch (tab) {
case 'headers': {
Expand All @@ -54,6 +68,9 @@ const CollectionSettings = ({ collection }) => {
case 'proxy': {
return <ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />;
}
case 'clientCert': {
return <ClientCertSettings clientCertConfig={clientCertConfig} onUpdate={onClientCertSettingsUpdate} />;
}
}
};

Expand Down Expand Up @@ -81,6 +98,9 @@ const CollectionSettings = ({ collection }) => {
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy
</div>
<div className={getTabClassname('clientCert')} role="tab" onClick={() => setTab('clientCert')}>
Client certificate
</div>
</div>
<section className={`flex ${['auth', 'script'].includes(tab) ? '' : 'mt-4'}`}>{getTabPanel(tab)}</section>
</StyledWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import styled from 'styled-components';

const StyledWrapper = styled.div`
color: ${(props) => props.theme.text};
.settings-label {
width: 80px;
}
`;

export default StyledWrapper;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { usePreferences } from 'providers/Preferences';
import StyledWrapper from './StyledWrapper';

Expand Down
42 changes: 32 additions & 10 deletions packages/bruno-electron/src/ipc/network/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const os = require('os');
const qs = require('qs');
const fs = require('fs');
const https = require('https');
const axios = require('axios');
const decomment = require('decomment');
Expand Down Expand Up @@ -212,7 +213,6 @@ const registerNetworkIpc = (mainWindow) => {
cacertFile = cacertArray.find((el) => el);
if (cacertFile && cacertFile.length > 1) {
try {
const fs = require('fs');
caCrt = fs.readFileSync(cacertFile);
httpsAgentRequestFields['ca'] = caCrt;
} catch (err) {
Expand All @@ -221,18 +221,40 @@ const registerNetworkIpc = (mainWindow) => {
}
}

// proxy configuration
const brunoConfig = getBrunoConfig(collectionUid);
const interpolationOptions = {
envVars,
collectionVariables,
processEnvVars
};

// client certificate config
const clientCertEnabled = get(brunoConfig, 'clientCert.enabled', false);
if (clientCertEnabled) {
const clientCertDomain = get(brunoConfig, 'clientCert.domain', interpolationOptions);
const clientCertCert = get(brunoConfig, 'clientCert.cert', interpolationOptions);
const clientCertKey = get(brunoConfig, 'clientCert.key', interpolationOptions);
const clientCertPassphrase = get(brunoConfig, 'clientCert.passphrase', interpolationOptions);
if (clientCertDomain && clientCertCert && clientCertKey) {
const hostRegex = '^https:\\/\\/' + clientCertDomain.replaceAll('.', '\\.').replaceAll('*', '.*');

if (request.url.match(hostRegex)) {
try {
httpsAgentRequestFields['cert'] = fs.readFileSync(clientCertCert);
httpsAgentRequestFields['key'] = fs.readFileSync(clientCertKey);
} catch (err) {
console.log('Error loading file cert/key', err);
}
httpsAgentRequestFields['passphrase'] = clientCertPassphrase;
}
}
}

// proxy configuration
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
if (proxyEnabled) {
let proxyUri;

const interpolationOptions = {
envVars,
collectionVariables,
processEnvVars
};

const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);
Expand Down Expand Up @@ -711,14 +733,14 @@ const registerNetworkIpc = (mainWindow) => {

if (socksEnabled) {
const socksProxyAgent = new SocksProxyAgent(proxyUri);

request.httpsAgent = socksProxyAgent;
request.httpAgent = socksProxyAgent;
} else {
request.httpsAgent = new HttpsProxyAgent(proxyUri, {
rejectUnauthorized: sslVerification
});

request.httpAgent = new HttpProxyAgent(proxyUri);
}
} else if (!sslVerification) {
Expand Down

0 comments on commit 6faa6ba

Please sign in to comment.