mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
refactor: extract kms module to external_services crate (#793)
This commit is contained in:
@ -6,6 +6,8 @@ use std::{
|
||||
|
||||
use common_utils::ext_traits::ConfigExt;
|
||||
use config::{Environment, File};
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use redis_interface::RedisSettings;
|
||||
pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
@ -59,7 +61,7 @@ pub struct Settings {
|
||||
pub bank_config: BankRedirectConfig,
|
||||
pub api_keys: ApiKeys,
|
||||
#[cfg(feature = "kms")]
|
||||
pub kms: Kms,
|
||||
pub kms: kms::KmsConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
@ -337,14 +339,6 @@ pub struct ApiKeys {
|
||||
pub hash_key: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Kms {
|
||||
pub key_id: String,
|
||||
pub region: String,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new() -> ApplicationResult<Self> {
|
||||
Self::with_config_path(None)
|
||||
@ -420,7 +414,9 @@ impl Settings {
|
||||
self.drainer.validate()?;
|
||||
self.api_keys.validate()?;
|
||||
#[cfg(feature = "kms")]
|
||||
self.kms.validate()?;
|
||||
self.kms
|
||||
.validate()
|
||||
.map_err(|error| ApplicationError::InvalidConfigurationValueError(error.into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -184,22 +184,3 @@ impl super::settings::ApiKeys {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
impl super::settings::Kms {
|
||||
pub fn validate(&self) -> Result<(), ApplicationError> {
|
||||
use common_utils::fp_utils::when;
|
||||
|
||||
when(self.key_id.is_default_or_empty(), || {
|
||||
Err(ApplicationError::InvalidConfigurationValueError(
|
||||
"KMS AWS key ID must not be empty".into(),
|
||||
))
|
||||
})?;
|
||||
|
||||
when(self.region.is_default_or_empty(), || {
|
||||
Err(ApplicationError::InvalidConfigurationValueError(
|
||||
"KMS AWS region must not be empty".into(),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use common_utils::date_time;
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use masking::{PeekInterface, StrongSecret};
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
use crate::services::kms;
|
||||
use crate::{
|
||||
configs::settings,
|
||||
consts,
|
||||
@ -21,7 +21,7 @@ static HASH_KEY: tokio::sync::OnceCell<StrongSecret<[u8; PlaintextApiKey::HASH_K
|
||||
|
||||
pub async fn get_hash_key(
|
||||
api_key_config: &settings::ApiKeys,
|
||||
#[cfg(feature = "kms")] kms_config: &settings::Kms,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
) -> errors::RouterResult<&'static StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> {
|
||||
HASH_KEY
|
||||
.get_or_try_init(|| async {
|
||||
@ -119,7 +119,7 @@ impl PlaintextApiKey {
|
||||
pub async fn create_api_key(
|
||||
store: &dyn StorageInterface,
|
||||
api_key_config: &settings::ApiKeys,
|
||||
#[cfg(feature = "kms")] kms_config: &settings::Kms,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
api_key: api::CreateApiKeyRequest,
|
||||
merchant_id: String,
|
||||
) -> RouterResponse<api::CreateApiKeyResponse> {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use common_utils::ext_traits::StringExt;
|
||||
use error_stack::ResultExt;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use josekit::jwe;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
use crate::services::kms;
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::errors::{self, CustomResult},
|
||||
@ -149,7 +149,7 @@ pub fn get_dotted_jws(jws: encryption::JwsBody) -> String {
|
||||
pub async fn get_decrypted_response_payload(
|
||||
jwekey: &settings::Jwekey,
|
||||
jwe_body: encryption::JweBody,
|
||||
#[cfg(feature = "kms")] kms_config: &settings::Kms,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
) -> CustomResult<String, errors::VaultError> {
|
||||
#[cfg(feature = "kms")]
|
||||
let public_key = kms::get_kms_client(kms_config)
|
||||
@ -192,7 +192,7 @@ pub async fn get_decrypted_response_payload(
|
||||
pub async fn mk_basilisk_req(
|
||||
jwekey: &settings::Jwekey,
|
||||
jws: &str,
|
||||
#[cfg(feature = "kms")] kms_config: &settings::Kms,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
) -> CustomResult<encryption::JweBody, errors::VaultError> {
|
||||
let jws_payload: Vec<&str> = jws.split('.').collect();
|
||||
|
||||
@ -247,7 +247,7 @@ pub async fn mk_add_card_request_hs(
|
||||
card: &api::CardDetail,
|
||||
customer_id: &str,
|
||||
merchant_id: &str,
|
||||
#[cfg(feature = "kms")] kms_config: &settings::Kms,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
let merchant_customer_id = if cfg!(feature = "sandbox") {
|
||||
format!("{customer_id}::{merchant_id}")
|
||||
@ -409,7 +409,7 @@ pub async fn mk_get_card_request_hs(
|
||||
customer_id: &str,
|
||||
merchant_id: &str,
|
||||
card_reference: &str,
|
||||
#[cfg(feature = "kms")] kms_config: &settings::Kms,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
let merchant_customer_id = if cfg!(feature = "sandbox") {
|
||||
format!("{customer_id}::{merchant_id}")
|
||||
@ -501,7 +501,7 @@ pub async fn mk_delete_card_request_hs(
|
||||
customer_id: &str,
|
||||
merchant_id: &str,
|
||||
card_reference: &str,
|
||||
#[cfg(feature = "kms")] kms_config: &settings::Kms,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
) -> CustomResult<services::Request, errors::VaultError> {
|
||||
let merchant_customer_id = if cfg!(feature = "sandbox") {
|
||||
format!("{customer_id}::{merchant_id}")
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use common_utils::generate_id_with_default_len;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
#[cfg(feature = "basilisk")]
|
||||
use external_services::kms;
|
||||
#[cfg(feature = "basilisk")]
|
||||
use josekit::jwe;
|
||||
use masking::PeekInterface;
|
||||
use router_env::{instrument, tracing};
|
||||
@ -18,11 +20,7 @@ use crate::{
|
||||
utils::{self, StringExt},
|
||||
};
|
||||
#[cfg(feature = "basilisk")]
|
||||
use crate::{
|
||||
core::payment_methods::transformers as payment_methods,
|
||||
services::{self, kms},
|
||||
utils::BytesExt,
|
||||
};
|
||||
use crate::{core::payment_methods::transformers as payment_methods, services, utils::BytesExt};
|
||||
#[cfg(feature = "basilisk")]
|
||||
use crate::{
|
||||
db,
|
||||
@ -407,7 +405,7 @@ pub fn get_key_id(keys: &settings::Jwekey) -> &str {
|
||||
#[cfg(feature = "basilisk")]
|
||||
async fn get_locker_jwe_keys(
|
||||
keys: &settings::Jwekey,
|
||||
kms_config: &settings::Kms,
|
||||
kms_config: &kms::KmsConfig,
|
||||
) -> CustomResult<(String, String), errors::EncryptionError> {
|
||||
let key_id = get_key_id(keys);
|
||||
let (encryption_key, decryption_key) = if key_id == keys.locker_key_identifier1 {
|
||||
|
||||
@ -20,6 +20,7 @@ pub mod logger {
|
||||
"actix_server",
|
||||
"api_models",
|
||||
"common_utils",
|
||||
"external_services",
|
||||
"masking",
|
||||
"redis_interface",
|
||||
"router_derive",
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
pub mod api;
|
||||
pub mod authentication;
|
||||
pub mod encryption;
|
||||
#[cfg(feature = "kms")]
|
||||
pub mod kms;
|
||||
pub mod logger;
|
||||
|
||||
use std::sync::{atomic, Arc};
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
use aws_config::meta::region::RegionProviderChain;
|
||||
use aws_sdk_kms::{types::Blob, Client, Region};
|
||||
use base64::Engine;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
|
||||
use crate::{
|
||||
configs::settings,
|
||||
consts,
|
||||
core::errors::{self, CustomResult},
|
||||
logger,
|
||||
routes::metrics,
|
||||
};
|
||||
|
||||
static KMS_CLIENT: tokio::sync::OnceCell<KmsClient> = tokio::sync::OnceCell::const_new();
|
||||
|
||||
#[inline]
|
||||
pub async fn get_kms_client(config: &settings::Kms) -> &KmsClient {
|
||||
KMS_CLIENT.get_or_init(|| KmsClient::new(config)).await
|
||||
}
|
||||
|
||||
pub struct KmsClient {
|
||||
inner_client: Client,
|
||||
key_id: String,
|
||||
}
|
||||
|
||||
impl KmsClient {
|
||||
/// Constructs a new KMS client.
|
||||
pub async fn new(config: &settings::Kms) -> Self {
|
||||
let region_provider = RegionProviderChain::first_try(Region::new(config.region.clone()));
|
||||
let sdk_config = aws_config::from_env().region(region_provider).load().await;
|
||||
|
||||
Self {
|
||||
inner_client: Client::new(&sdk_config),
|
||||
key_id: config.key_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypts the provided base64-encoded encrypted data using the AWS KMS SDK. We assume that
|
||||
/// the SDK has the values required to interact with the AWS KMS APIs (`AWS_ACCESS_KEY_ID` and
|
||||
/// `AWS_SECRET_ACCESS_KEY`) either set in environment variables, or that the SDK is running in
|
||||
/// a machine that is able to assume an IAM role.
|
||||
pub async fn decrypt(&self, data: impl AsRef<[u8]>) -> CustomResult<String, errors::KmsError> {
|
||||
let data = consts::BASE64_ENGINE
|
||||
.decode(data)
|
||||
.into_report()
|
||||
.change_context(errors::KmsError::Base64DecodingFailed)?;
|
||||
let ciphertext_blob = Blob::new(data);
|
||||
|
||||
let decrypt_output = self
|
||||
.inner_client
|
||||
.decrypt()
|
||||
.key_id(&self.key_id)
|
||||
.ciphertext_blob(ciphertext_blob)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
// Logging using `Debug` representation of the error as the `Display`
|
||||
// representation does not hold sufficient information.
|
||||
logger::error!(kms_sdk_error=?error, "Failed to KMS decrypt data");
|
||||
metrics::AWS_KMS_FAILURES.add(&metrics::CONTEXT, 1, &[]);
|
||||
error
|
||||
})
|
||||
.into_report()
|
||||
.change_context(errors::KmsError::DecryptionFailed)?;
|
||||
|
||||
decrypt_output
|
||||
.plaintext
|
||||
.ok_or(errors::KmsError::MissingPlaintextDecryptionOutput)
|
||||
.into_report()
|
||||
.and_then(|blob| {
|
||||
String::from_utf8(blob.into_inner())
|
||||
.into_report()
|
||||
.change_context(errors::KmsError::Utf8DecodingFailed)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user