import { AgentOptions } from 'https' ; import { URL } from 'url' ; import { HttpProxyAgent } from 'http-proxy-agent' ; import { HttpsProxyAgent } from 'https-proxy-agent' ; import { createLogger } from '@sap-cloud-sdk/util' ; import { Protocol } from '../protocol' ; import { ProxyConfiguration } from '../connectivity-service-types' ; import { basicHeader } from '../authorization-header' ; import { HttpAgentConfig , HttpsAgentConfig } from '../../http-agent' ; import { getProtocolOrDefault } from '../get-protocol' ; import { Destination } from './destination-service-types' ; const logger = createLogger ( { package : 'connectivity' , messageContext : 'proxy-util' } ) ; param destination returns internal export function proxyStrategy ( destination ) { if ( destination . proxyType === 'OnPremise' ) { logger . info ( 'OnPrem destination proxy settings from connectivity service will be used.' ) ; return ProxyStrategy . ON_PREMISE_PROXY ; } const destinationProtocol = getProtocolOrDefault ( destination ) ; if ( ! getProxyEnvValue ( destinationProtocol ) ) { logger . info ( `No Proxy settings for ${ destinationProtocol } are found in environment variables - no proxy used` ) ; return ProxyStrategy . NO_PROXY ; } if ( getNoProxyEnvValue ( ) . includes ( destination . url ) ) { logger . info ( `Destination URL ${ destination . url } is in no_proxy list: ${ getNoProxyEnvValue ( ) } - no proxy used` ) ; return ProxyStrategy . NO_PROXY ; } if ( getProxyEnvValue ( destinationProtocol ) ) { logger . info ( `Proxy settings for ${ destinationProtocol } are found in environment variables.` ) ; return ProxyStrategy . INTERNET_PROXY ; } return ProxyStrategy . NO_PROXY ; } function getProxyEnvValue ( protocol ) { const proxyEnvKey = protocol + '_proxy' ; const proxyEnvValue = process . env [ proxyEnvKey . toLowerCase ( ) ] || process . env [ proxyEnvKey . toUpperCase ( ) ] ; logger . info ( `Try to fetch ${ proxyEnvKey . toLowerCase ( ) } or ${ proxyEnvKey . toUpperCase ( ) } from the process env. Found value is ${ proxyEnvValue }` ) ; if ( ! proxyEnvValue ) { return undefined ; } return proxyEnvValue ; } function getNoProxyEnvValue ( ) { const noProxyEnv = process . env . no_proxy || process . env . NO_PROXY ; if ( ! noProxyEnv ) { return [ ] ; } const split = noProxyEnv . split ( ',' ) . map ( s => s . trim ( ) ) ; if ( split . find ( s => s . includes ( '*' ) ) ) { logger . warn ( `The no_proxy env contains a wildcard ${ noProxyEnv }, which is currently not supported` ) ; } return split ; } function getPort ( url ) { if ( url . port ) { return parseInt ( url . port ) ; } return url . protocol === 'https:' ? 443 : 80 ; } function getOriginalProtocol ( href ) { const test = href . match ( /^[\w.-]+:\/\// ) ; return test ? test [ 0 ] . slice ( 0 , - 2 ) : undefined ; } function sanitizeUrl ( href ) { const protocol = getOriginalProtocol ( href ) ; if ( ! protocol ) { logger . debug ( 'No protocol specified, using "http:".' ) ; return `http://${ href }` ; } return href ; } function validateUrl ( url ) { if ( url . protocol !== 'http:' && url . protocol !== 'https:' ) { throw new Error ( `Unsupported protocol "${ url . protocol }".` ) ; } if ( url . protocol === 'https:' ) { logger . info ( 'Using protocol "https:" to connect to a proxy. This is unusual but possible.' ) ; } if ( url . username && ! url . password ) { throw new Error ( 'Password missing.' ) ; } } param proxyEnvValue returns export function parseProxyEnv ( proxyEnvValue ) { const href = sanitizeUrl ( proxyEnvValue ) ; try { const url = new URL ( href ) ; validateUrl ( url ) ; const proxyConfig = { host : url . hostname , protocol : Protocol . of ( url . protocol ) ! , port : getPort ( url ) } ; if ( url . username && url . password ) { proxyConfig . headers = { 'Proxy-Authorization' : basicHeader ( decodeURIComponent ( url . username ) , decodeURIComponent ( url . password ) ) } ; } if ( proxyConfig ) { const loggableConfig = { ... proxyConfig , headers : proxyConfig . headers ? 'Authorization header present. Not logged for security reasons.' : 'No authorization header present.' } ; logger . debug ( `Used Proxy Configuration: ${ JSON . stringify ( loggableConfig , null , 2 ) }.` ) ; } return proxyConfig ; } catch ( err ) { logger . warn ( `Could not parse proxy configuration from environment variable. Reason: ${ err . message }` ) ; } } param destination returns internal export function addProxyConfigurationInternet ( destination ) { const proxyEnvValue = getProxyEnvValue ( getProtocolOrDefault ( destination ) ) ; if ( proxyEnvValue ) { const proxyConfiguration = parseProxyEnv ( proxyEnvValue ) ; if ( proxyConfiguration ) { return { ... destination , proxyConfiguration } ; } return { ... destination } ; } logger . warn ( 'Attempt to get proxy config from environment variables failed. At this point this should not happen - no proxy used.' ) ; return { ... destination } ; } param destination param options returns internal export function proxyAgent ( destination , options ? ) { const targetProtocol = getProtocolOrDefault ( destination ) ; const proxyConfig = destination . proxyConfiguration ; if ( ! proxyConfig ) { throw new Error ( 'Proxy config must not be undefined.' ) ; } if ( options ?. host ) { logger . warn ( `The agent options you passed to the proxy agent creation contains the host "${ options . host }" which will overwrite the host from the proxy config.` ) ; } if ( options ?. port ) { logger . warn ( `The agent options you passed to the proxy agent creation contains the port "${ options . port }" which will overwrite the port from the proxy config.` ) ; } const agentConfig = { host : proxyConfig . host , protocol : proxyConfig . protocol , port : proxyConfig . port , ... options } ; switch ( targetProtocol ) { case Protocol . HTTP : return { httpAgent : new HttpProxyAgent ( agentConfig ) } ; case Protocol . HTTPS : return { httpsAgent : new HttpsProxyAgent ( agentConfig ) } ; } } internal export enum ProxyStrategy { NO_PROXY , ON_PREMISE_PROXY , INTERNET_PROXY }