mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	refactor(proxy): specify hosts for proxy exclusion instead of complete URLs (#6957)
This commit is contained in:
		| @ -35,15 +35,8 @@ async fn main() -> CustomResult<(), ProcessTrackerError> { | ||||
|     let conf = Settings::with_config_path(cmd_line.config_path) | ||||
|         .expect("Unable to construct application configuration"); | ||||
|     let api_client = Box::new( | ||||
|         services::ProxyClient::new( | ||||
|             conf.proxy.clone(), | ||||
|             services::proxy_bypass_urls( | ||||
|                 conf.key_manager.get_inner(), | ||||
|                 &conf.locker, | ||||
|                 &conf.proxy.bypass_proxy_urls, | ||||
|             ), | ||||
|         ) | ||||
|         .change_context(ProcessTrackerError::ConfigurationError)?, | ||||
|         services::ProxyClient::new(&conf.proxy) | ||||
|             .change_context(ProcessTrackerError::ConfigurationError)?, | ||||
|     ); | ||||
|     // channel for listening to redis disconnect events | ||||
|     let (redis_shutdown_signal_tx, redis_shutdown_signal_rx) = oneshot::channel(); | ||||
|  | ||||
| @ -59,7 +59,7 @@ impl Default for super::settings::Proxy { | ||||
|             http_url: Default::default(), | ||||
|             https_url: Default::default(), | ||||
|             idle_pool_connection_timeout: Some(90), | ||||
|             bypass_proxy_urls: Vec::new(), | ||||
|             bypass_proxy_hosts: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -638,7 +638,7 @@ pub struct Proxy { | ||||
|     pub http_url: Option<String>, | ||||
|     pub https_url: Option<String>, | ||||
|     pub idle_pool_connection_timeout: Option<u64>, | ||||
|     pub bypass_proxy_urls: Vec<String>, | ||||
|     pub bypass_proxy_hosts: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
| @ -828,7 +828,6 @@ impl Settings<SecuredSecret> { | ||||
|                     .with_list_parse_key("log.telemetry.route_to_trace") | ||||
|                     .with_list_parse_key("redis.cluster_urls") | ||||
|                     .with_list_parse_key("events.kafka.brokers") | ||||
|                     .with_list_parse_key("proxy.bypass_proxy_urls") | ||||
|                     .with_list_parse_key("connectors.supported.wallets") | ||||
|                     .with_list_parse_key("connector_request_reference_id_config.merchant_ids_send_payment_id_as_connector_request_id"), | ||||
|  | ||||
|  | ||||
| @ -237,19 +237,9 @@ pub async fn start_server(conf: settings::Settings<SecuredSecret>) -> Applicatio | ||||
|     logger::debug!(startup_config=?conf); | ||||
|     let server = conf.server.clone(); | ||||
|     let (tx, rx) = oneshot::channel(); | ||||
|     let api_client = Box::new( | ||||
|         services::ProxyClient::new( | ||||
|             conf.proxy.clone(), | ||||
|             services::proxy_bypass_urls( | ||||
|                 conf.key_manager.get_inner(), | ||||
|                 &conf.locker, | ||||
|                 &conf.proxy.bypass_proxy_urls, | ||||
|             ), | ||||
|         ) | ||||
|         .map_err(|error| { | ||||
|             errors::ApplicationError::ApiClientError(error.current_context().clone()) | ||||
|         })?, | ||||
|     ); | ||||
|     let api_client = Box::new(services::ProxyClient::new(&conf.proxy).map_err(|error| { | ||||
|         errors::ApplicationError::ApiClientError(error.current_context().clone()) | ||||
|     })?); | ||||
|     let state = Box::pin(AppState::new(conf, tx, api_client)).await; | ||||
|     let request_body_limit = server.request_body_limit; | ||||
|  | ||||
|  | ||||
| @ -17,7 +17,7 @@ use actix_web::{ | ||||
|     http::header::{HeaderName, HeaderValue}, | ||||
|     web, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError, | ||||
| }; | ||||
| pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient}; | ||||
| pub use client::{ApiClient, MockApiClient, ProxyClient}; | ||||
| pub use common_enums::enums::PaymentAction; | ||||
| pub use common_utils::request::{ContentType, Method, Request, RequestBuilder}; | ||||
| use common_utils::{ | ||||
| @ -416,29 +416,11 @@ pub async fn send_request( | ||||
| ) -> CustomResult<reqwest::Response, errors::ApiClientError> { | ||||
|     logger::info!(method=?request.method, headers=?request.headers, payload=?request.body, ?request); | ||||
|  | ||||
|     let url = reqwest::Url::parse(&request.url) | ||||
|         .change_context(errors::ApiClientError::UrlEncodingFailed)?; | ||||
|     let url = | ||||
|         url::Url::parse(&request.url).change_context(errors::ApiClientError::UrlParsingFailed)?; | ||||
|  | ||||
|     #[cfg(feature = "dummy_connector")] | ||||
|     let should_bypass_proxy = url | ||||
|         .as_str() | ||||
|         .starts_with(&state.conf.connectors.dummyconnector.base_url) | ||||
|         || proxy_bypass_urls( | ||||
|             state.conf.key_manager.get_inner(), | ||||
|             &state.conf.locker, | ||||
|             &state.conf.proxy.bypass_proxy_urls, | ||||
|         ) | ||||
|         .contains(&url.to_string()); | ||||
|     #[cfg(not(feature = "dummy_connector"))] | ||||
|     let should_bypass_proxy = proxy_bypass_urls( | ||||
|         &state.conf.key_manager.get_inner(), | ||||
|         &state.conf.locker, | ||||
|         &state.conf.proxy.bypass_proxy_urls, | ||||
|     ) | ||||
|     .contains(&url.to_string()); | ||||
|     let client = client::create_client( | ||||
|         &state.conf.proxy, | ||||
|         should_bypass_proxy, | ||||
|         request.certificate, | ||||
|         request.certificate_key, | ||||
|     )?; | ||||
|  | ||||
| @ -10,18 +10,16 @@ use router_env::tracing_actix_web::RequestId; | ||||
|  | ||||
| use super::{request::Maskable, Request}; | ||||
| use crate::{ | ||||
|     configs::settings::{Locker, Proxy}, | ||||
|     consts::{BASE64_ENGINE, LOCKER_HEALTH_CALL_PATH}, | ||||
|     configs::settings::Proxy, | ||||
|     consts::BASE64_ENGINE, | ||||
|     core::errors::{ApiClientError, CustomResult}, | ||||
|     routes::{app::settings::KeyManagerConfig, SessionState}, | ||||
|     routes::SessionState, | ||||
| }; | ||||
|  | ||||
| static NON_PROXIED_CLIENT: OnceCell<reqwest::Client> = OnceCell::new(); | ||||
| static PROXIED_CLIENT: OnceCell<reqwest::Client> = OnceCell::new(); | ||||
| static DEFAULT_CLIENT: OnceCell<reqwest::Client> = OnceCell::new(); | ||||
|  | ||||
| fn get_client_builder( | ||||
|     proxy_config: &Proxy, | ||||
|     should_bypass_proxy: bool, | ||||
| ) -> CustomResult<reqwest::ClientBuilder, ApiClientError> { | ||||
|     let mut client_builder = reqwest::Client::builder() | ||||
|         .redirect(reqwest::redirect::Policy::none()) | ||||
| @ -31,16 +29,16 @@ fn get_client_builder( | ||||
|                 .unwrap_or_default(), | ||||
|         )); | ||||
|  | ||||
|     if should_bypass_proxy { | ||||
|         return Ok(client_builder); | ||||
|     } | ||||
|     let proxy_exclusion_config = | ||||
|         reqwest::NoProxy::from_string(&proxy_config.bypass_proxy_hosts.clone().unwrap_or_default()); | ||||
|  | ||||
|     // Proxy all HTTPS traffic through the configured HTTPS proxy | ||||
|     if let Some(url) = proxy_config.https_url.as_ref() { | ||||
|         client_builder = client_builder.proxy( | ||||
|             reqwest::Proxy::https(url) | ||||
|                 .change_context(ApiClientError::InvalidProxyConfiguration) | ||||
|                 .attach_printable("HTTPS proxy configuration error")?, | ||||
|                 .attach_printable("HTTPS proxy configuration error")? | ||||
|                 .no_proxy(proxy_exclusion_config.clone()), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -49,44 +47,35 @@ fn get_client_builder( | ||||
|         client_builder = client_builder.proxy( | ||||
|             reqwest::Proxy::http(url) | ||||
|                 .change_context(ApiClientError::InvalidProxyConfiguration) | ||||
|                 .attach_printable("HTTP proxy configuration error")?, | ||||
|                 .attach_printable("HTTP proxy configuration error")? | ||||
|                 .no_proxy(proxy_exclusion_config), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     Ok(client_builder) | ||||
| } | ||||
|  | ||||
| fn get_base_client( | ||||
|     proxy_config: &Proxy, | ||||
|     should_bypass_proxy: bool, | ||||
| ) -> CustomResult<reqwest::Client, ApiClientError> { | ||||
|     Ok(if should_bypass_proxy | ||||
|         || (proxy_config.http_url.is_none() && proxy_config.https_url.is_none()) | ||||
|     { | ||||
|         &NON_PROXIED_CLIENT | ||||
|     } else { | ||||
|         &PROXIED_CLIENT | ||||
|     } | ||||
|     .get_or_try_init(|| { | ||||
|         get_client_builder(proxy_config, should_bypass_proxy)? | ||||
|             .build() | ||||
|             .change_context(ApiClientError::ClientConstructionFailed) | ||||
|             .attach_printable("Failed to construct base client") | ||||
|     })? | ||||
|     .clone()) | ||||
| fn get_base_client(proxy_config: &Proxy) -> CustomResult<reqwest::Client, ApiClientError> { | ||||
|     Ok(DEFAULT_CLIENT | ||||
|         .get_or_try_init(|| { | ||||
|             get_client_builder(proxy_config)? | ||||
|                 .build() | ||||
|                 .change_context(ApiClientError::ClientConstructionFailed) | ||||
|                 .attach_printable("Failed to construct base client") | ||||
|         })? | ||||
|         .clone()) | ||||
| } | ||||
|  | ||||
| // We may need to use outbound proxy to connect to external world. | ||||
| // Precedence will be the environment variables, followed by the config. | ||||
| pub fn create_client( | ||||
|     proxy_config: &Proxy, | ||||
|     should_bypass_proxy: bool, | ||||
|     client_certificate: Option<masking::Secret<String>>, | ||||
|     client_certificate_key: Option<masking::Secret<String>>, | ||||
| ) -> CustomResult<reqwest::Client, ApiClientError> { | ||||
|     match (client_certificate, client_certificate_key) { | ||||
|         (Some(encoded_certificate), Some(encoded_certificate_key)) => { | ||||
|             let client_builder = get_client_builder(proxy_config, should_bypass_proxy)?; | ||||
|             let client_builder = get_client_builder(proxy_config)?; | ||||
|  | ||||
|             let identity = create_identity_from_certificate_and_key( | ||||
|                 encoded_certificate.clone(), | ||||
| @ -105,7 +94,7 @@ pub fn create_client( | ||||
|                 .change_context(ApiClientError::ClientConstructionFailed) | ||||
|                 .attach_printable("Failed to construct client with certificate and certificate key") | ||||
|         } | ||||
|         _ => get_base_client(proxy_config, should_bypass_proxy), | ||||
|         _ => get_base_client(proxy_config), | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -145,35 +134,6 @@ pub fn create_certificate( | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed) | ||||
| } | ||||
|  | ||||
| pub fn proxy_bypass_urls( | ||||
|     key_manager: &KeyManagerConfig, | ||||
|     locker: &Locker, | ||||
|     config_whitelist: &[String], | ||||
| ) -> Vec<String> { | ||||
|     let key_manager_host = key_manager.url.to_owned(); | ||||
|     let locker_host = locker.host.to_owned(); | ||||
|     let locker_host_rs = locker.host_rs.to_owned(); | ||||
|  | ||||
|     let proxy_list = [ | ||||
|         format!("{locker_host}/cards/add"), | ||||
|         format!("{locker_host}/cards/fingerprint"), | ||||
|         format!("{locker_host}/cards/retrieve"), | ||||
|         format!("{locker_host}/cards/delete"), | ||||
|         format!("{locker_host_rs}/cards/add"), | ||||
|         format!("{locker_host_rs}/cards/retrieve"), | ||||
|         format!("{locker_host_rs}/cards/delete"), | ||||
|         format!("{locker_host_rs}{}", LOCKER_HEALTH_CALL_PATH), | ||||
|         format!("{locker_host}/card/addCard"), | ||||
|         format!("{locker_host}/card/getCard"), | ||||
|         format!("{locker_host}/card/deleteCard"), | ||||
|         format!("{key_manager_host}/data/encrypt"), | ||||
|         format!("{key_manager_host}/data/decrypt"), | ||||
|         format!("{key_manager_host}/key/create"), | ||||
|         format!("{key_manager_host}/key/rotate"), | ||||
|     ]; | ||||
|     [&proxy_list, config_whitelist].concat().to_vec() | ||||
| } | ||||
|  | ||||
| pub trait RequestBuilder: Send + Sync { | ||||
|     fn json(&mut self, body: serde_json::Value); | ||||
|     fn url_encoded_form(&mut self, body: serde_json::Value); | ||||
| @ -201,6 +161,7 @@ where | ||||
|         method: Method, | ||||
|         url: String, | ||||
|     ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError>; | ||||
|  | ||||
|     fn request_with_certificate( | ||||
|         &self, | ||||
|         method: Method, | ||||
| @ -218,7 +179,9 @@ where | ||||
|     ) -> CustomResult<reqwest::Response, ApiClientError>; | ||||
|  | ||||
|     fn add_request_id(&mut self, request_id: RequestId); | ||||
|  | ||||
|     fn get_request_id(&self) -> Option<String>; | ||||
|  | ||||
|     fn add_flow_name(&mut self, flow_name: String); | ||||
| } | ||||
|  | ||||
| @ -226,60 +189,31 @@ dyn_clone::clone_trait_object!(ApiClient); | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct ProxyClient { | ||||
|     proxy_client: reqwest::Client, | ||||
|     non_proxy_client: reqwest::Client, | ||||
|     whitelisted_urls: Vec<String>, | ||||
|     proxy_config: Proxy, | ||||
|     client: reqwest::Client, | ||||
|     request_id: Option<String>, | ||||
| } | ||||
|  | ||||
| impl ProxyClient { | ||||
|     pub fn new( | ||||
|         proxy_config: Proxy, | ||||
|         whitelisted_urls: Vec<String>, | ||||
|     ) -> CustomResult<Self, ApiClientError> { | ||||
|         let non_proxy_client = reqwest::Client::builder() | ||||
|             .redirect(reqwest::redirect::Policy::none()) | ||||
|             .build() | ||||
|             .change_context(ApiClientError::ClientConstructionFailed)?; | ||||
|  | ||||
|         let mut proxy_builder = | ||||
|             reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()); | ||||
|  | ||||
|         if let Some(url) = proxy_config.https_url.as_ref() { | ||||
|             proxy_builder = proxy_builder.proxy( | ||||
|                 reqwest::Proxy::https(url) | ||||
|                     .change_context(ApiClientError::InvalidProxyConfiguration)?, | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         if let Some(url) = proxy_config.http_url.as_ref() { | ||||
|             proxy_builder = proxy_builder.proxy( | ||||
|                 reqwest::Proxy::http(url) | ||||
|                     .change_context(ApiClientError::InvalidProxyConfiguration)?, | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         let proxy_client = proxy_builder | ||||
|     pub fn new(proxy_config: &Proxy) -> CustomResult<Self, ApiClientError> { | ||||
|         let client = get_client_builder(proxy_config)? | ||||
|             .build() | ||||
|             .change_context(ApiClientError::InvalidProxyConfiguration)?; | ||||
|         Ok(Self { | ||||
|             proxy_client, | ||||
|             non_proxy_client, | ||||
|             whitelisted_urls, | ||||
|             proxy_config: proxy_config.clone(), | ||||
|             client, | ||||
|             request_id: None, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn get_reqwest_client( | ||||
|         &self, | ||||
|         base_url: String, | ||||
|         client_certificate: Option<masking::Secret<String>>, | ||||
|         client_certificate_key: Option<masking::Secret<String>>, | ||||
|     ) -> CustomResult<reqwest::Client, ApiClientError> { | ||||
|         match (client_certificate, client_certificate_key) { | ||||
|             (Some(certificate), Some(certificate_key)) => { | ||||
|                 let client_builder = | ||||
|                     reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()); | ||||
|                 let client_builder = get_client_builder(&self.proxy_config)?; | ||||
|                 let identity = | ||||
|                     create_identity_from_certificate_and_key(certificate, certificate_key)?; | ||||
|                 Ok(client_builder | ||||
| @ -290,13 +224,7 @@ impl ProxyClient { | ||||
|                         "Failed to construct client with certificate and certificate key", | ||||
|                     )?) | ||||
|             } | ||||
|             (_, _) => { | ||||
|                 if self.whitelisted_urls.contains(&base_url) { | ||||
|                     Ok(self.non_proxy_client.clone()) | ||||
|                 } else { | ||||
|                     Ok(self.proxy_client.clone()) | ||||
|                 } | ||||
|             } | ||||
|             (_, _) => Ok(self.client.clone()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -355,8 +283,6 @@ impl RequestBuilder for RouterRequestBuilder { | ||||
|     } | ||||
| } | ||||
|  | ||||
| // TODO: remove this when integrating this trait | ||||
| #[allow(dead_code)] | ||||
| #[async_trait::async_trait] | ||||
| impl ApiClient for ProxyClient { | ||||
|     fn request( | ||||
| @ -375,7 +301,7 @@ impl ApiClient for ProxyClient { | ||||
|         certificate_key: Option<masking::Secret<String>>, | ||||
|     ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> { | ||||
|         let client_builder = self | ||||
|             .get_reqwest_client(url.clone(), certificate, certificate_key) | ||||
|             .get_reqwest_client(certificate, certificate_key) | ||||
|             .change_context(ApiClientError::ClientConstructionFailed)?; | ||||
|         Ok(Box::new(RouterRequestBuilder { | ||||
|             inner: Some(client_builder.request(method, url)), | ||||
|  | ||||
| @ -155,7 +155,7 @@ async fn get_oidc_reqwest_client( | ||||
|     state: &SessionState, | ||||
|     request: oidc::HttpRequest, | ||||
| ) -> Result<oidc::HttpResponse, ApiClientError> { | ||||
|     let client = client::create_client(&state.conf.proxy, false, None, None) | ||||
|     let client = client::create_client(&state.conf.proxy, None, None) | ||||
|         .map_err(|e| e.current_context().to_owned())?; | ||||
|  | ||||
|     let mut request_builder = client | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Sanchith Hegde
					Sanchith Hegde