feat(core): Add mTLS certificates for each request (#5636)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sakil Mostak
2024-08-27 15:30:22 +05:30
committed by GitHub
parent 4585e16245
commit 716d76c53e
10 changed files with 101 additions and 89 deletions

View File

@ -1617,9 +1617,11 @@ merchant_secret="Source verification key"
[itaubank] [itaubank]
[[itaubank.bank_transfer]] [[itaubank.bank_transfer]]
payment_method_type = "pix" payment_method_type = "pix"
[itaubank.connector_auth.BodyKey] [itaubank.connector_auth.MultiAuthKey]
key1="Client Id" key1="Client Id"
api_key="Client Secret" api_key="Client Secret"
api_secret="Certificates"
key2="Certificate Key"
[klarna] [klarna]
[[klarna.pay_later]] [[klarna.pay_later]]

View File

@ -1373,9 +1373,11 @@ merchant_secret="Source verification key"
[itaubank] [itaubank]
[[itaubank.bank_transfer]] [[itaubank.bank_transfer]]
payment_method_type = "pix" payment_method_type = "pix"
[itaubank.connector_auth.BodyKey] [itaubank.connector_auth.MultiAuthKey]
key1="Client Id" key1="Client Id"
api_key="Client Secret" api_key="Client Secret"
api_secret="Certificates"
key2="Certificate Key"
[klarna] [klarna]
[[klarna.pay_later]] [[klarna.pay_later]]

View File

@ -1615,9 +1615,11 @@ merchant_secret="Source verification key"
[itaubank] [itaubank]
[[itaubank.bank_transfer]] [[itaubank.bank_transfer]]
payment_method_type = "pix" payment_method_type = "pix"
[itaubank.connector_auth.BodyKey] [itaubank.connector_auth.MultiAuthKey]
key1="Client Id" key1="Client Id"
api_key="Client Secret" api_key="Client Secret"
api_secret="Certificates"
key2="Certificate Key"
[klarna] [klarna]
[[klarna.pay_later]] [[klarna.pay_later]]

View File

@ -93,7 +93,7 @@ rand = "0.8.5"
rand_chacha = "0.3.1" rand_chacha = "0.3.1"
rdkafka = "0.36.2" rdkafka = "0.36.2"
regex = "1.10.4" regex = "1.10.4"
reqwest = { version = "0.11.27", features = ["json", "native-tls", "__rustls", "gzip", "multipart"] } reqwest = { version = "0.11.27", features = ["json", "rustls-tls", "gzip", "multipart"] }
ring = "0.17.8" ring = "0.17.8"
roxmltree = "0.19.0" roxmltree = "0.19.0"
rust_decimal = { version = "1.35.0", features = ["serde-with-float", "serde-with-str"] } rust_decimal = { version = "1.35.0", features = ["serde-with-float", "serde-with-str"] }

View File

@ -197,12 +197,15 @@ impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, t
req: &types::RefreshTokenRouterData, req: &types::RefreshTokenRouterData,
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?;
let req = Some( let req = Some(
services::RequestBuilder::new() services::RequestBuilder::new()
.method(services::Method::Post) .method(services::Method::Post)
.attach_default_headers() .attach_default_headers()
.headers(types::RefreshTokenType::get_headers(self, req, connectors)?) .headers(types::RefreshTokenType::get_headers(self, req, connectors)?)
.url(&types::RefreshTokenType::get_url(self, req, connectors)?) .url(&types::RefreshTokenType::get_url(self, req, connectors)?)
.add_certificate(auth_details.certificate)
.add_certificate_key(auth_details.certificate_key)
.set_body(types::RefreshTokenType::get_request_body( .set_body(types::RefreshTokenType::get_request_body(
self, req, connectors, self, req, connectors,
)?) )?)
@ -326,6 +329,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req: &types::PaymentsAuthorizeRouterData, req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?;
Ok(Some( Ok(Some(
services::RequestBuilder::new() services::RequestBuilder::new()
.method(services::Method::Post) .method(services::Method::Post)
@ -336,6 +340,8 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
.headers(types::PaymentsAuthorizeType::get_headers( .headers(types::PaymentsAuthorizeType::get_headers(
self, req, connectors, self, req, connectors,
)?) )?)
.add_certificate(auth_details.certificate)
.add_certificate_key(auth_details.certificate_key)
.set_body(types::PaymentsAuthorizeType::get_request_body( .set_body(types::PaymentsAuthorizeType::get_request_body(
self, req, connectors, self, req, connectors,
)?) )?)
@ -406,12 +412,15 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
req: &types::PaymentsSyncRouterData, req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?;
Ok(Some( Ok(Some(
services::RequestBuilder::new() services::RequestBuilder::new()
.method(services::Method::Get) .method(services::Method::Get)
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
.attach_default_headers() .attach_default_headers()
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
.add_certificate(auth_details.certificate)
.add_certificate_key(auth_details.certificate_key)
.build(), .build(),
)) ))
} }
@ -480,6 +489,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
req: &types::PaymentsCaptureRouterData, req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?;
Ok(Some( Ok(Some(
services::RequestBuilder::new() services::RequestBuilder::new()
.method(services::Method::Post) .method(services::Method::Post)
@ -488,6 +498,8 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
.headers(types::PaymentsCaptureType::get_headers( .headers(types::PaymentsCaptureType::get_headers(
self, req, connectors, self, req, connectors,
)?) )?)
.add_certificate(auth_details.certificate)
.add_certificate_key(auth_details.certificate_key)
.set_body(types::PaymentsCaptureType::get_request_body( .set_body(types::PaymentsCaptureType::get_request_body(
self, req, connectors, self, req, connectors,
)?) )?)
@ -597,6 +609,7 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
req: &types::RefundsRouterData<api::Execute>, req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?;
let request = services::RequestBuilder::new() let request = services::RequestBuilder::new()
.method(services::Method::Put) .method(services::Method::Put)
.url(&types::RefundExecuteType::get_url(self, req, connectors)?) .url(&types::RefundExecuteType::get_url(self, req, connectors)?)
@ -604,6 +617,8 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
.headers(types::RefundExecuteType::get_headers( .headers(types::RefundExecuteType::get_headers(
self, req, connectors, self, req, connectors,
)?) )?)
.add_certificate(auth_details.certificate)
.add_certificate_key(auth_details.certificate_key)
.set_body(types::RefundExecuteType::get_request_body( .set_body(types::RefundExecuteType::get_request_body(
self, req, connectors, self, req, connectors,
)?) )?)
@ -679,12 +694,15 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
req: &types::RefundSyncRouterData, req: &types::RefundSyncRouterData,
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?;
Ok(Some( Ok(Some(
services::RequestBuilder::new() services::RequestBuilder::new()
.method(services::Method::Get) .method(services::Method::Get)
.url(&types::RefundSyncType::get_url(self, req, connectors)?) .url(&types::RefundSyncType::get_url(self, req, connectors)?)
.attach_default_headers() .attach_default_headers()
.headers(types::RefundSyncType::get_headers(self, req, connectors)?) .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.add_certificate(auth_details.certificate)
.add_certificate_key(auth_details.certificate_key)
.set_body(types::RefundSyncType::get_request_body( .set_body(types::RefundSyncType::get_request_body(
self, req, connectors, self, req, connectors,
)?) )?)

View File

@ -133,15 +133,30 @@ impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for Itaub
pub struct ItaubankAuthType { pub struct ItaubankAuthType {
pub(super) client_id: Secret<String>, pub(super) client_id: Secret<String>,
pub(super) client_secret: Secret<String>, pub(super) client_secret: Secret<String>,
pub(super) certificate: Option<Secret<String>>,
pub(super) certificate_key: Option<Secret<String>>,
} }
impl TryFrom<&types::ConnectorAuthType> for ItaubankAuthType { impl TryFrom<&types::ConnectorAuthType> for ItaubankAuthType {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type { match auth_type {
types::ConnectorAuthType::MultiAuthKey {
api_key,
key1,
api_secret,
key2,
} => Ok(Self {
client_secret: api_key.to_owned(),
client_id: key1.to_owned(),
certificate: Some(api_secret.to_owned()),
certificate_key: Some(key2.to_owned()),
}),
types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
client_secret: api_key.to_owned(), client_secret: api_key.to_owned(),
client_id: key1.to_owned(), client_id: key1.to_owned(),
certificate: None,
certificate_key: None,
}), }),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
} }

View File

@ -4,7 +4,6 @@ use api_models::{
admin::{self as admin_types}, admin::{self as admin_types},
enums as api_enums, routing as routing_types, enums as api_enums, routing as routing_types,
}; };
use base64::Engine;
use common_utils::{ use common_utils::{
date_time, date_time,
ext_traits::{AsyncExt, Encode, OptionExt, ValueExt}, ext_traits::{AsyncExt, Encode, OptionExt, ValueExt},
@ -24,7 +23,7 @@ use uuid::Uuid;
#[cfg(any(feature = "v1", feature = "v2"))] #[cfg(any(feature = "v1", feature = "v2"))]
use crate::types::transformers::ForeignFrom; use crate::types::transformers::ForeignFrom;
use crate::{ use crate::{
consts::{self, BASE64_ENGINE}, consts,
core::{ core::{
encryption::transfer_encryption_key, encryption::transfer_encryption_key,
errors::{self, RouterResponse, RouterResult, StorageErrorExt}, errors::{self, RouterResponse, RouterResult, StorageErrorExt},
@ -35,7 +34,11 @@ use crate::{
}, },
db::StorageInterface, db::StorageInterface,
routes::{metrics, SessionState}, routes::{metrics, SessionState},
services::{self, api as service_api, authentication, pm_auth as payment_initiation_service}, services::{
self,
api::{self as service_api, client},
authentication, pm_auth as payment_initiation_service,
},
types::{ types::{
self, self,
api::{self, admin}, api::{self, admin},
@ -198,6 +201,10 @@ pub async fn create_merchant_account(
let identifier = km_types::Identifier::Merchant(merchant_id.clone()); let identifier = km_types::Identifier::Merchant(merchant_id.clone());
#[cfg(feature = "keymanager_create")] #[cfg(feature = "keymanager_create")]
{ {
use base64::Engine;
use crate::consts::BASE64_ENGINE;
keymanager::transfer_key_to_key_manager( keymanager::transfer_key_to_key_manager(
key_manager_state, key_manager_state,
EncryptionTransferRequest { EncryptionTransferRequest {
@ -1604,7 +1611,7 @@ impl<'a> ConnectorAuthTypeValidation<'a> {
certificate, certificate,
private_key, private_key,
} => { } => {
helpers::create_identity_from_certificate_and_key( client::create_identity_from_certificate_and_key(
certificate.to_owned(), certificate.to_owned(),
private_key.to_owned(), private_key.to_owned(),
) )
@ -1695,34 +1702,6 @@ impl<'a> PaymentMethodsEnabled<'a> {
} }
} }
struct CertificateAndCertificateKey<'a> {
certificate: &'a Secret<String>,
certificate_key: &'a Secret<String>,
}
impl<'a> CertificateAndCertificateKey<'a> {
pub fn create_identity_from_certificate_and_key(
&self,
) -> Result<reqwest::Identity, error_stack::Report<errors::ApiClientError>> {
let decoded_certificate = BASE64_ENGINE
.decode(self.certificate.clone().expose())
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
let decoded_certificate_key = BASE64_ENGINE
.decode(self.certificate_key.clone().expose())
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
let certificate = String::from_utf8(decoded_certificate)
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
let certificate_key = String::from_utf8(decoded_certificate_key)
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
reqwest::Identity::from_pkcs8_pem(certificate.as_bytes(), certificate_key.as_bytes())
.change_context(errors::ApiClientError::CertificateDecodeFailed)
}
}
struct ConnectorMetadata<'a> { struct ConnectorMetadata<'a> {
connector_metadata: &'a Option<pii::SecretSerdeValue>, connector_metadata: &'a Option<pii::SecretSerdeValue>,
} }
@ -1739,11 +1718,7 @@ impl<'a> ConnectorMetadata<'a> {
})? })?
.and_then(|metadata| metadata.get_apple_pay_certificates()) .and_then(|metadata| metadata.get_apple_pay_certificates())
.map(|(certificate, certificate_key)| { .map(|(certificate, certificate_key)| {
let certificate_and_certificate_key = CertificateAndCertificateKey { client::create_identity_from_certificate_and_key(certificate, certificate_key)
certificate: &certificate,
certificate_key: &certificate_key,
};
certificate_and_certificate_key.create_identity_from_certificate_and_key()
}) })
.transpose() .transpose()
.change_context(errors::ApiErrorResponse::InvalidDataValue { .change_context(errors::ApiErrorResponse::InvalidDataValue {

View File

@ -99,41 +99,6 @@ use crate::{
core::payment_methods::cards::create_encrypted_data, types::storage::CustomerUpdate::Update, core::payment_methods::cards::create_encrypted_data, types::storage::CustomerUpdate::Update,
}; };
pub fn create_identity_from_certificate_and_key(
encoded_certificate: masking::Secret<String>,
encoded_certificate_key: masking::Secret<String>,
) -> Result<reqwest::Identity, error_stack::Report<errors::ApiClientError>> {
let decoded_certificate = BASE64_ENGINE
.decode(encoded_certificate.expose())
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
let decoded_certificate_key = BASE64_ENGINE
.decode(encoded_certificate_key.expose())
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
let certificate = String::from_utf8(decoded_certificate)
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
let certificate_key = String::from_utf8(decoded_certificate_key)
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
reqwest::Identity::from_pkcs8_pem(certificate.as_bytes(), certificate_key.as_bytes())
.change_context(errors::ApiClientError::CertificateDecodeFailed)
}
pub fn create_certificate(
encoded_certificate: masking::Secret<String>,
) -> Result<Vec<reqwest::Certificate>, error_stack::Report<errors::ApiClientError>> {
let decoded_certificate = BASE64_ENGINE
.decode(encoded_certificate.expose())
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
let certificate = String::from_utf8(decoded_certificate)
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
reqwest::Certificate::from_pem_bundle(certificate.as_bytes())
.change_context(errors::ApiClientError::CertificateDecodeFailed)
}
pub fn filter_mca_based_on_profile_and_connector_type( pub fn filter_mca_based_on_profile_and_connector_type(
merchant_connector_accounts: Vec<domain::MerchantConnectorAccount>, merchant_connector_accounts: Vec<domain::MerchantConnectorAccount>,
profile_id: &id_type::ProfileId, profile_id: &id_type::ProfileId,

View File

@ -1,8 +1,9 @@
use std::time::Duration; use std::time::Duration;
use base64::Engine;
use error_stack::ResultExt; use error_stack::ResultExt;
use http::{HeaderValue, Method}; use http::{HeaderValue, Method};
use masking::PeekInterface; use masking::{ExposeInterface, PeekInterface};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use reqwest::multipart::Form; use reqwest::multipart::Form;
use router_env::tracing_actix_web::RequestId; use router_env::tracing_actix_web::RequestId;
@ -10,11 +11,8 @@ use router_env::tracing_actix_web::RequestId;
use super::{request::Maskable, Request}; use super::{request::Maskable, Request};
use crate::{ use crate::{
configs::settings::{Locker, Proxy}, configs::settings::{Locker, Proxy},
consts::LOCKER_HEALTH_CALL_PATH, consts::{BASE64_ENGINE, LOCKER_HEALTH_CALL_PATH},
core::{ core::errors::{ApiClientError, CustomResult},
errors::{ApiClientError, CustomResult},
payments,
},
routes::{app::settings::KeyManagerConfig, SessionState}, routes::{app::settings::KeyManagerConfig, SessionState},
}; };
@ -90,11 +88,11 @@ pub fn create_client(
(Some(encoded_certificate), Some(encoded_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, should_bypass_proxy)?;
let identity = payments::helpers::create_identity_from_certificate_and_key( let identity = create_identity_from_certificate_and_key(
encoded_certificate.clone(), encoded_certificate.clone(),
encoded_certificate_key, encoded_certificate_key,
)?; )?;
let certificate_list = payments::helpers::create_certificate(encoded_certificate)?; let certificate_list = create_certificate(encoded_certificate)?;
let client_builder = certificate_list let client_builder = certificate_list
.into_iter() .into_iter()
.fold(client_builder, |client_builder, certificate| { .fold(client_builder, |client_builder, certificate| {
@ -102,6 +100,7 @@ pub fn create_client(
}); });
client_builder client_builder
.identity(identity) .identity(identity)
.use_rustls_tls()
.build() .build()
.change_context(ApiClientError::ClientConstructionFailed) .change_context(ApiClientError::ClientConstructionFailed)
.attach_printable("Failed to construct client with certificate and certificate key") .attach_printable("Failed to construct client with certificate and certificate key")
@ -110,6 +109,42 @@ pub fn create_client(
} }
} }
pub fn create_identity_from_certificate_and_key(
encoded_certificate: masking::Secret<String>,
encoded_certificate_key: masking::Secret<String>,
) -> Result<reqwest::Identity, error_stack::Report<ApiClientError>> {
let decoded_certificate = BASE64_ENGINE
.decode(encoded_certificate.expose())
.change_context(ApiClientError::CertificateDecodeFailed)?;
let decoded_certificate_key = BASE64_ENGINE
.decode(encoded_certificate_key.expose())
.change_context(ApiClientError::CertificateDecodeFailed)?;
let certificate = String::from_utf8(decoded_certificate)
.change_context(ApiClientError::CertificateDecodeFailed)?;
let certificate_key = String::from_utf8(decoded_certificate_key)
.change_context(ApiClientError::CertificateDecodeFailed)?;
let key_chain = format!("{}{}", certificate_key, certificate);
reqwest::Identity::from_pem(key_chain.as_bytes())
.change_context(ApiClientError::CertificateDecodeFailed)
}
pub fn create_certificate(
encoded_certificate: masking::Secret<String>,
) -> Result<Vec<reqwest::Certificate>, error_stack::Report<ApiClientError>> {
let decoded_certificate = BASE64_ENGINE
.decode(encoded_certificate.expose())
.change_context(ApiClientError::CertificateDecodeFailed)?;
let certificate = String::from_utf8(decoded_certificate)
.change_context(ApiClientError::CertificateDecodeFailed)?;
reqwest::Certificate::from_pem_bundle(certificate.as_bytes())
.change_context(ApiClientError::CertificateDecodeFailed)
}
pub fn proxy_bypass_urls( pub fn proxy_bypass_urls(
key_manager: &KeyManagerConfig, key_manager: &KeyManagerConfig,
locker: &Locker, locker: &Locker,
@ -245,10 +280,8 @@ impl ProxyClient {
(Some(certificate), Some(certificate_key)) => { (Some(certificate), Some(certificate_key)) => {
let client_builder = let client_builder =
reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()); reqwest::Client::builder().redirect(reqwest::redirect::Policy::none());
let identity = payments::helpers::create_identity_from_certificate_and_key( let identity =
certificate, create_identity_from_certificate_and_key(certificate, certificate_key)?;
certificate_key,
)?;
Ok(client_builder Ok(client_builder
.identity(identity) .identity(identity)
.build() .build()

View File

@ -44,7 +44,7 @@ pub struct ConnectorAuthentication {
pub gpayments: Option<HeaderKey>, pub gpayments: Option<HeaderKey>,
pub helcim: Option<HeaderKey>, pub helcim: Option<HeaderKey>,
pub iatapay: Option<SignatureKey>, pub iatapay: Option<SignatureKey>,
pub itaubank: Option<HeaderKey>, pub itaubank: Option<MultiAuthKey>,
pub mifinity: Option<HeaderKey>, pub mifinity: Option<HeaderKey>,
pub mollie: Option<BodyKey>, pub mollie: Option<BodyKey>,
pub multisafepay: Option<HeaderKey>, pub multisafepay: Option<HeaderKey>,