@usebruno/cli
Version:
With Bruno CLI, you can now run your API collections with ease using simple command line commands.
212 lines (194 loc) • 6.46 kB
JavaScript
const debug = require('debug');
const nodeVaultDebug = debug('node-vault');
const axios = require('axios');
const { getOptions } = require('./bru');
const { createVaultClient, getCACertificates } = require('@usebruno/requests');
const options = getOptions();
const verbose = options?.['verbose'];
if (verbose) {
debug.enable('node-vault');
}
/**
* Build vault requestOptions from CLI options (cacert, insecure, noproxy)
* Similar to buildVaultRequestOptions in bruno-electron
*/
const buildVaultRequestOptions = () => {
const cliOptions = getOptions();
const requestOptions = {};
// TLS verification - if insecure mode is enabled, disable SSL verification
const insecure = cliOptions['insecure'];
requestOptions.strictSSL = !insecure;
// CA certificate handling - only set if TLS verification is enabled
if (!insecure) {
const caCertFilePath = cliOptions['cacert'];
const shouldKeepDefaultCerts = !cliOptions['ignoreTruststore'];
if (caCertFilePath || shouldKeepDefaultCerts) {
try {
const caCertificatesData = getCACertificates({
caCertFilePath,
shouldKeepDefaultCerts
});
if (caCertificatesData.caCertificates) {
requestOptions.ca = caCertificatesData.caCertificates;
}
} catch (err) {
// Log error but continue without custom certs
if (verbose) {
console.warn('Failed to load CA certificates:', err.message);
}
}
}
}
// Proxy configuration - use cached system proxy unless noproxy is set
const noproxy = cliOptions['noproxy'];
if (!noproxy) {
const cachedSystemProxy = cliOptions['cachedSystemProxy'] || {};
const { http_proxy, https_proxy } = cachedSystemProxy;
if (https_proxy?.length) {
requestOptions.proxy = https_proxy;
} else if (http_proxy?.length) {
requestOptions.proxy = http_proxy;
}
}
return requestOptions;
};
const getExternalSecretsData = async ({ type, config, paths, debug }) => {
const requestOptions = buildVaultRequestOptions();
var vault = createVaultClient({ apiVersion: 'v1', debug: debug ? nodeVaultDebug : null, requestOptions });
let secretsData;
if (type == 'vault') {
if (config?.type == 'vault-server') {
if (config?.vaultServerConfig?.auth?.method == 'token') {
vault.endpoint = config?.vaultServerConfig?.url;
if (config?.vaultServerConfig?.namespace?.length) {
vault.namespace = config?.vaultServerConfig?.namespace;
}
vault.token = config?.vaultServerConfig?.auth?.token;
secretsData = await fetchExternalSecrets({
vault,
paths
});
} else if (config?.vaultServerConfig?.auth?.method == 'app_role') {
vault.endpoint = config?.vaultServerConfig?.url;
if (config?.vaultServerConfig?.namespace?.length) {
vault.namespace = config?.vaultServerConfig?.namespace;
}
const accessToken = await getVaultServerApproleToken({
vault,
options: config?.vaultServerConfig?.auth?.appRole
});
vault.token = accessToken;
secretsData = await fetchExternalSecrets({
vault,
paths
});
}
} else if (config?.type == 'vault-cloud') {
if (
config?.vaultCloudConfig?.auth?.method == 'client_credentials'
&& config?.vaultCloudConfig?.auth?.clientCredentials
&& config?.vaultCloudConfig?.project
) {
const accessToken = await getVaultCloudToken({ options: config?.vaultCloudConfig?.auth?.clientCredentials });
secretsData = await Promise.all(
paths.map((path) =>
getVaultCloudSecrets({
options: { ...config?.vaultCloudConfig?.auth?.clientCredentials, ...config?.vaultCloudConfig?.project },
accessToken,
path
})
)
);
}
}
}
return secretsData;
};
const fetchExternalSecrets = async ({ paths, vault }) => {
return Promise.all(
paths.map(
(path) =>
new Promise(async (resolve, reject) => {
let data, error;
await vault
.read(path)
.then((res) => {
let data = res?.data?.data || res?.data || data;
resolve({ path, data });
})
.catch((e) => {
let error = e?.response?.body;
resolve({ path, error });
});
})
)
);
};
const getVaultCloudToken = async ({ options }) => {
const { tokenEndpoint, clientId, clientSecret } = options;
return await axios
.post(
tokenEndpoint,
{
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
audience: 'https://api.hashicorp.cloud'
},
{
headers: { 'check': 'again', 'Content-Type': 'application/x-www-form-urlencoded' }
}
)
.then((response) => {
return response?.data?.access_token;
})
.catch((error) => {
throw new Error(error?.response?.data?.error);
});
};
const getVaultCloudSecrets = async ({ options, accessToken, path }) => {
const { secretsEndpoint, organizationId, projectId } = options;
if (!organizationId || !projectId) {
return {
path,
data: {}
};
}
const appName = path.split('/')?.[0];
const secrets = await axios
.get(`${secretsEndpoint}/organizations/${organizationId}/projects/${projectId}/apps/${appName}/open`, {
headers: { check: 'again', Authorization: `Bearer ${accessToken}` }
})
.then((response) => {
return response.data.secrets;
})
.catch((error) => {
return null;
});
return {
path,
data: secrets?.reduce((acc, s) => {
return { ...acc, [s?.name]: s?.version?.value };
}, {})
};
};
const getVaultServerApproleToken = async ({ vault, options }) => {
const { role, roleId, secretId, mountPath } = options;
return await vault
.approleLogin({ role, role_id: roleId, secret_id: secretId, mount_point: mountPath })
.then((res) => {
return res?.auth?.client_token;
})
.catch((e) => {
throw new Error(
JSON.stringify(e?.response?.body || e?.message || 'Error while getting token from approle login')
);
});
};
module.exports = {
getExternalSecretsData,
fetchExternalSecrets,
getVaultCloudToken,
getVaultCloudSecrets,
getVaultServerApproleToken
};