@sap-cloud-sdk/connectivity
Version:
SAP Cloud SDK for JavaScript connectivity
189 lines • 8.87 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAgentConfig = getAgentConfig;
exports.urlAndAgent = urlAndAgent;
const promises_1 = require("fs/promises");
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const util_1 = require("@sap-cloud-sdk/util");
/* Careful the proxy imports cause circular dependencies if imported from scp directly */
// eslint-disable-next-line import/no-internal-modules
const get_protocol_1 = require("../scp-cf/get-protocol");
const http_proxy_util_1 = require("../scp-cf/destination/http-proxy-util");
// eslint-disable-next-line import/no-internal-modules
const register_destination_cache_1 = require("../scp-cf/destination/register-destination-cache");
const logger = (0, util_1.createLogger)({
package: 'connectivity',
messageContext: 'http-agent'
});
/**
* Returns a promise of the http or https-agent config depending on the destination URL.
* If the destination contains a proxy configuration, the agent will be a proxy-agent.
* If not it will be the default http-agent coming from node.
* @param destination - Determining which kind of configuration is returned.
* @returns A promise of the HTTP or HTTPS agent configuration.
*/
async function getAgentConfig(destination) {
const certificateOptions = {
...getTrustStoreOptions(destination),
...getKeyStoreOptions(destination),
...(await getMtlsOptions(destination))
};
return createAgent(destination, certificateOptions);
}
/**
* @internal
* The http agents (proxy and default) use node tls for trust handling. This method creates the options with the 'ca' or 'rejectUnauthorized' option.
* https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
* @param destination - Destination object
* @returns Options, which can be used later the http client.
*/
function getTrustStoreOptions(destination) {
// http case: no certificate needed
if ((0, get_protocol_1.getProtocolOrDefault)(destination) === 'http') {
if (destination.isTrustingAllCertificates) {
logger.warn('"isTrustingAllCertificates" is not available for HTTP.');
}
if (destination.trustStoreCertificate) {
logger.warn('"trustStore" is not available for HTTP.');
}
return {};
}
// https case
if (destination.isTrustingAllCertificates &&
destination.trustStoreCertificate) {
logger.warn(`Destination ${destination.name} contains the 'trustAll' and 'trustStoreLocation' property which is a redundant setup.`);
}
if (destination.isTrustingAllCertificates) {
logger.warn('"isTrustingAllCertificates" property in the provided destination is set to "true". This is highly discouraged in production.');
return { rejectUnauthorized: !destination.isTrustingAllCertificates };
}
if (destination.trustStoreCertificate) {
const decoded = Buffer.from(destination.trustStoreCertificate.content, 'base64').toString('utf8');
return {
rejectUnauthorized: true,
ca: [decoded]
};
}
return { rejectUnauthorized: true };
}
/**
* @internal
* The http agent uses node tls for the certificate handling. This method creates the options with the pfx and passphrase or key, cert and passphrase, depending on the format of the certificate.
* https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
* @param destination - Destination object.
* @returns Options, which can be used later by tls.createSecureContext() e.g. pfx and passphrase or an empty object, if the protocol is not 'https:' or no client information are in the definition.
*/
function getKeyStoreOptions(destination) {
if (
// Only add certificates, when using ClientCertificateAuthentication (https://github.com/SAP/cloud-sdk-js/issues/3544)
destination.authentication === 'ClientCertificateAuthentication' &&
!mtlsIsEnabled(destination) &&
destination.keyStoreName) {
const certificate = selectCertificate(destination);
validateFormat(certificate);
logger.debug(`Certificate with name "${certificate.name}" selected.`);
if (!destination.keyStorePassword) {
logger.debug(`Destination '${destination.name}' does not have a keystore password.`);
}
const certBuffer = Buffer.from(certificate.content, 'base64');
// if the format is pem, the key and certificate needs to be passed separately
// it could be required to separate the string into two parts, but this seems to work as well
if (getFormat(certificate) === 'pem') {
return {
cert: certBuffer,
key: certBuffer,
passphrase: destination.keyStorePassword
};
}
// pfx is a format that combines key and cert
return {
pfx: certBuffer,
passphrase: destination.keyStorePassword
};
}
return {};
}
/*
Reads mTLS client certificates from known environment variables on CloudFoundry.
*/
async function getMtlsOptions(destination) {
if (destination.mtls &&
!(process.env.CF_INSTANCE_CERT && process.env.CF_INSTANCE_KEY)) {
logger.warn(`Destination ${destination.name ? destination.name : ''} has mTLS enabled, but the required Cloud Foundry environment variables (CF_INSTANCE_CERT and CF_INSTANCE_KEY) are not defined. Note that 'inferMtls' only works on Cloud Foundry.`);
}
if (mtlsIsEnabled(destination)) {
if (register_destination_cache_1.registerDestinationCache.mtls.useMtlsCache) {
return register_destination_cache_1.registerDestinationCache.mtls.getMtlsOptions();
}
const getCert = (0, promises_1.readFile)(process.env.CF_INSTANCE_CERT, 'utf8');
const getKey = (0, promises_1.readFile)(process.env.CF_INSTANCE_KEY, 'utf8');
const [cert, key] = await Promise.all([getCert, getKey]);
return {
cert,
key
};
}
return {};
}
function mtlsIsEnabled(destination) {
return (destination.mtls &&
process.env.CF_INSTANCE_CERT &&
process.env.CF_INSTANCE_KEY);
}
/*
The node client supports only these store formats https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions.
*/
const supportedCertificateFormats = ['p12', 'pfx', 'pem'];
function isSupportedFormat(format) {
return !!format && supportedCertificateFormats.includes(format);
}
function selectCertificate(destination) {
const certificate = destination.certificates.find(c => c.name === destination.keyStoreName);
if (!certificate) {
throw Error(`No certificate with name ${destination.keyStoreName} could be found on the destination!`);
}
return certificate;
}
function getFormat(certificate) {
return (0, util_1.last)(certificate.name.split('.'));
}
function validateFormat(certificate) {
const format = getFormat(certificate);
if (!isSupportedFormat(format)) {
throw Error(`The format of the provided certificate '${certificate.name}' is not supported. Supported formats are: ${supportedCertificateFormats.join(', ')}. ${format && ['jks', 'keystore'].includes(format)
? "You can convert Java Keystores (.jks, .keystore) into PKCS#12 keystores using the JVM's keytool CLI: keytool -importkeystore -srckeystore your-keystore.jks -destkeystore your-keystore.p12 -deststoretype pkcs12"
: ''}`);
}
}
/**
* @internal
* See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for details on the possible options
*/
function createAgent(destination, options) {
return (0, get_protocol_1.getProtocolOrDefault)(destination) === 'https'
? { httpsAgent: new https_1.default.Agent(options) }
: { httpAgent: new http_1.default.Agent(options) };
}
/**
* Builds part of the request config containing the URL and if needed proxy agents or normal http agents.
* Considers the `no_proxy` environment variable together with the `targetUri`.
* @internal
* @param targetUri - Used as baseURL in request config.
* @returns HttpRequestConfig containing baseUrl and http(s) agents.
*/
async function urlAndAgent(targetUri) {
let destination = { url: targetUri, proxyType: 'Internet' };
if ((0, http_proxy_util_1.proxyStrategy)(destination) === 'internet') {
destination = (0, http_proxy_util_1.addProxyConfigurationInternet)(destination);
}
return {
baseURL: destination.url,
...(await getAgentConfig(destination)),
proxy: (0, http_proxy_util_1.getProxyConfig)(destination)
};
}
//# sourceMappingURL=http-agent.js.map