// @ts-check
'use strict';
/** @type {import('assert/strict')} */
const assert = require('assert').strict;
const SocksClient = require('socks').SocksClient;
let btpCfSocks5ProxyUtils = {
// See the implementation of assertAuthenticationResponse() at Using the TCP Protocol for Cloud Applications | https://help.sap.com/viewer/cca91383641e40ffbe03bdc78f00f681/Cloud/en-US/cd1583775afa43f0bb9ec69d9db...
SOCKS5_AUTHENTICATION_SUCCESS_BYTE: 0x00,
SOCKS5_CUSTOM_RESP_SIZE: 2,
SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION: 0x01,
/**
* @typedef {object} ConnectionOptions
* @prop {string} [cc_location=""] - cloud connector location, optional
* @prop {string} conn_svc_token - JWT token obtained via client_credentials grant for bound connectivity service
* @prop {string} remote_host - proxy destination host
* @prop {number} remote_port - proxy destination port
* @prop {string} onpremise_proxy_host - SOCKS5 proxy host
* @prop {number | string} onpremise_socks5_proxy_port - SOCKS5 proxy port
*/
/**
* @typedef {import('socks/typings/common/constants').SocksClientEstablishedEvent} SocksClientEstablishedEvent
*/
/**
* Creates a new SOCKS connection.
* @param {ConnectionOptions} opts
* @returns {Promise<SocksClientEstablishedEvent>}
*/
createConnection: function (opts) {
const ccLocation = opts.cc_location || "";
/**
* @type {import('socks').SocksClientOptions}
*/
const options = {
proxy: {
host: opts.onpremise_proxy_host,
port: typeof opts.onpremise_socks5_proxy_port === 'number' ? opts.onpremise_socks5_proxy_port : parseInt(opts.onpremise_socks5_proxy_port, 10),
type: 5, // Proxy version (4 or 5)
//
// SOCKS5 Custom authentication
custom_auth_method: 0x80,
custom_auth_request_handler: btpCfSocks5ProxyUtils.getCustomAuthRequestHandler(
opts.conn_svc_token, ccLocation),
custom_auth_response_size: btpCfSocks5ProxyUtils.SOCKS5_CUSTOM_RESP_SIZE,
custom_auth_response_handler: btpCfSocks5ProxyUtils.customAuthResponseHandler
},
command: 'connect', // SOCKS command (createConnection factory function only supports the connect command)
destination: {
host: opts.remote_host,
port: opts.remote_port
}
};
return SocksClient.createConnection(options);
},
/**
* @callback CustomAuthRequestHandler
* @param {string} connSvcToken - JWT token obtained via client_credentials grant for bound connectivity service
* @param {string} cloudConnectorLocation - cloud connector location or ""
* @returns {Promise<Buffer>}
*/
/** @type {CustomAuthRequestHandler} */
customAuthRequestHandler: async function (connSvcToken, cloudConnectorLocation) {
// This will be called when it's time to send the custom auth handshake. You must return a Buffer containing the data to send as your authentication.
const _1_authMethodVersion = Buffer.from([btpCfSocks5ProxyUtils.SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION]); // Authentication method version
const _3_jwtBuf = Buffer.from(connSvcToken, 'binary'); // X bytes: The actual value of the JWT in its encoded form
const _2_jwtBufLength = Buffer.allocUnsafe(4); // 4 bytes: Length of the JWT
_2_jwtBufLength.writeInt32BE(_3_jwtBuf.length);
const _5_ccNameB64 = Buffer.from(Buffer.from(cloudConnectorLocation).toString('base64'), 'binary');
// Y - The value of the Cloud Connector location ID in base64-encoded form
const _4_ccNameLength = Buffer.allocUnsafe(1); // 1 byte: Length of the Cloud Connector location ID (0 if no Cloud Connector location ID is used)
_4_ccNameLength.writeUInt8(_5_ccNameB64.length);
let retBuf = Buffer.alloc(
_1_authMethodVersion.length +
_2_jwtBufLength.length +
_3_jwtBuf.length +
_4_ccNameLength.length +
_5_ccNameB64.length
);
/**
* @type {number}
*/
let offset = 0;
_1_authMethodVersion.copy(retBuf, offset); offset += _1_authMethodVersion.length;
_2_jwtBufLength.copy(retBuf, offset); offset += _2_jwtBufLength.length;
_3_jwtBuf.copy(retBuf, offset); offset += _3_jwtBuf.length;
_4_ccNameLength.copy(retBuf, offset); offset += _4_ccNameLength.length;
if (_5_ccNameB64.length > 0) {
_5_ccNameB64.copy(retBuf, offset); offset += _5_ccNameB64.length;
}
assert.equal(offset, retBuf.length);
return retBuf;
},
/**
* @param {Buffer} data SOCKS proxy authentication response
* @returns Promise<boolean>
*/
customAuthResponseHandler: async function (data) {
assert.equal(data.length, btpCfSocks5ProxyUtils.SOCKS5_CUSTOM_RESP_SIZE);
const authenticationMethodVersion = data[0];
const authenticationStatus = data[1];
// console.log(data);
if (btpCfSocks5ProxyUtils.SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION !== authenticationMethodVersion) {
throw new Error(`Unsupported authentication method version - expected ${btpCfSocks5ProxyUtils.SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION}, but received ${authenticationMethodVersion}`);
}
if (btpCfSocks5ProxyUtils.SOCKS5_AUTHENTICATION_SUCCESS_BYTE !== authenticationStatus) {
throw new Error(`Authentication failed (${authenticationStatus})!`);
}
return btpCfSocks5ProxyUtils.SOCKS5_AUTHENTICATION_SUCCESS_BYTE === authenticationStatus;
},
/**
* @param {string} connSvcToken - JWT token obtained via client_credentials grant for bound connectivity service
* @param {string} cloudConnectorLocation - cloud connector location or ""
* @returns {() => Promise<Buffer>}
*/
getCustomAuthRequestHandler: function (connSvcToken, cloudConnectorLocation) {
return btpCfSocks5ProxyUtils.customAuthRequestHandler.bind(null, connSvcToken, cloudConnectorLocation);
}
};
module.exports = btpCfSocks5ProxyUtils;
const btpCfSocks5ProxyUtils = require('btp-cf-socks5-proxy-utils');
const sdkCore = require('@sap-cloud-sdk/core');
const xsenv = require('@sap/xsenv');
// Connectivity service
const connServiceCredentials = xsenv.serviceCredentials({ tag: 'connectivity' });
const connSvcToken = await sdkCore.serviceToken('connectivity', {
isolationStrategy: sdkCore.IsolationStrategy.No_Isolation, // there's just one bound connectivity service, no tenants
useCache: true
});
// SOCKS proxy
const info = await btpCfSocks5ProxyUtils.createConnection({
cc_location: options.ldapsVirtualLocation,
conn_svc_token: connSvcToken,
remote_host: options.ldapsVirtualHost,
remote_port: options.ldapsVirtualPort,
onpremise_proxy_host: connServiceCredentials.onpremise_proxy_host,
onpremise_socks5_proxy_port: connServiceCredentials.onpremise_socks5_proxy_port
});
// Example LDAP client that uses the socket - info.socket - from above
const newLdapClient = new LdapClient({
idleTimeout: options.idleTimeoutMillisec || 0,
tlsOptions: Object.assign({}, tlsOptions, { socket: info.socket }),
url: [ldapUrl]
});
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
19 | |
10 | |
8 | |
5 | |
4 | |
4 | |
3 | |
3 | |
2 | |
2 |