refactor(config): Add new type for kms encrypted values (#1823)

This commit is contained in:
Sampras Lopes
2023-08-02 14:25:23 +05:30
committed by GitHub
parent de875e6935
commit 73ed7ae7e3
21 changed files with 214 additions and 177 deletions

1
Cargo.lock generated
View File

@ -1811,6 +1811,7 @@ dependencies = [
"diesel_models", "diesel_models",
"error-stack", "error-stack",
"external_services", "external_services",
"masking",
"once_cell", "once_cell",
"redis_interface", "redis_interface",
"router_env", "router_env",

View File

@ -20,24 +20,22 @@ request_body_limit = 16_384
# Main SQL data store credentials # Main SQL data store credentials
[master_database] [master_database]
username = "db_user" # DB Username username = "db_user" # DB Username
password = "db_pass" # DB Password. Only applicable when KMS is disabled. password = "db_pass" # DB Password. Use base-64 encoded kms encrypted value here when kms is enabled
host = "localhost" # DB Host host = "localhost" # DB Host
port = 5432 # DB Port port = 5432 # DB Port
dbname = "hyperswitch_db" # Name of Database dbname = "hyperswitch_db" # Name of Database
pool_size = 5 # Number of connections to keep open pool_size = 5 # Number of connections to keep open
connection_timeout = 10 # Timeout for database connection in seconds connection_timeout = 10 # Timeout for database connection in seconds
kms_encrypted_password = "" # Base64-encoded (KMS encrypted) ciphertext of the database password. Only applicable when KMS is enabled.
# Replica SQL data store credentials # Replica SQL data store credentials
[replica_database] [replica_database]
username = "replica_user" # DB Username username = "replica_user" # DB Username
password = "replica_pass" # DB Password. Only applicable when KMS is disabled. password = "db_pass" # DB Password. Use base-64 encoded kms encrypted value here when kms is enabled
host = "localhost" # DB Host host = "localhost" # DB Host
port = 5432 # DB Port port = 5432 # DB Port
dbname = "hyperswitch_db" # Name of Database dbname = "hyperswitch_db" # Name of Database
pool_size = 5 # Number of connections to keep open pool_size = 5 # Number of connections to keep open
connection_timeout = 10 # Timeout for database connection in seconds connection_timeout = 10 # Timeout for database connection in seconds
kms_encrypted_password = "" # Base64-encoded (KMS encrypted) ciphertext of the database password. Only applicable when KMS is enabled.
# Redis credentials # Redis credentials
[redis] [redis]
@ -101,6 +99,8 @@ admin_api_key = "test_admin" # admin API key for admin authentication. Only
kms_encrypted_admin_api_key = "" # Base64-encoded (KMS encrypted) ciphertext of the admin_api_key. Only applicable when KMS is enabled. kms_encrypted_admin_api_key = "" # Base64-encoded (KMS encrypted) ciphertext of the admin_api_key. Only applicable when KMS is enabled.
jwt_secret = "secret" # JWT secret used for user authentication. Only applicable when KMS is disabled. jwt_secret = "secret" # JWT secret used for user authentication. Only applicable when KMS is disabled.
kms_encrypted_jwt_secret = "" # Base64-encoded (KMS encrypted) ciphertext of the jwt_secret. Only applicable when KMS is enabled. kms_encrypted_jwt_secret = "" # Base64-encoded (KMS encrypted) ciphertext of the jwt_secret. Only applicable when KMS is enabled.
recon_admin_api_key = "recon_test_admin" # recon_admin API key for recon authentication. Only applicable when KMS is disabled.
kms_encrypted_recon_admin_api_key = "" # Base64-encoded (KMS encrypted) ciphertext of the recon_admin_api_key. Only applicable when KMS is enabled
# Locker settings contain details for accessing a card locker, a # Locker settings contain details for accessing a card locker, a
# PCI Compliant storage entity which stores payment method information # PCI Compliant storage entity which stores payment method information

View File

@ -33,6 +33,8 @@ connection_timeout = 10
[secrets] [secrets]
admin_api_key = "test_admin" admin_api_key = "test_admin"
master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a" master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a"
jwt_secret = "secret"
recon_admin_api_key = "recon_test_admin"
[locker] [locker]
host = "" host = ""

View File

@ -40,6 +40,7 @@ pool_size = 5
admin_api_key = "test_admin" admin_api_key = "test_admin"
jwt_secret = "secret" jwt_secret = "secret"
master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a" master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a"
recon_admin_api_key = "recon_test_admin"
[locker] [locker]
host = "" host = ""

View File

@ -4,7 +4,7 @@
//! //!
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use masking::{ExposeInterface, Secret, Strategy}; use masking::{ExposeInterface, PeekInterface, Secret, Strategy};
use quick_xml::de; use quick_xml::de;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -428,6 +428,30 @@ impl ConfigExt for String {
} }
} }
impl<T, U> ConfigExt for Secret<T, U>
where
T: ConfigExt + Default + PartialEq<T>,
U: Strategy<T>,
{
fn is_default(&self) -> bool
where
T: Default + PartialEq<T>,
{
*self.peek() == T::default()
}
fn is_empty_after_trim(&self) -> bool {
self.peek().is_empty_after_trim()
}
fn is_default_or_empty(&self) -> bool
where
T: Default + PartialEq<T>,
{
self.peek().is_default() || self.peek().is_empty_after_trim()
}
}
/// Extension trait for deserializing XML strings using `quick-xml` crate /// Extension trait for deserializing XML strings using `quick-xml` crate
pub trait XmlExt { pub trait XmlExt {
/// ///

View File

@ -28,6 +28,7 @@ tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
# First Party Crates # First Party Crates
common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals"] } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals"] }
external_services = { version = "0.1.0", path = "../external_services" } external_services = { version = "0.1.0", path = "../external_services" }
masking = { version = "0.1.0", path = "../masking" }
redis_interface = { version = "0.1.0", path = "../redis_interface" } redis_interface = { version = "0.1.0", path = "../redis_interface" }
router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] }
diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] }

View File

@ -1,7 +1,9 @@
use bb8::PooledConnection; use bb8::PooledConnection;
use diesel::PgConnection; use diesel::PgConnection;
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
use external_services::kms; use external_services::kms::{self, decrypt::KmsDecrypt};
#[cfg(not(feature = "kms"))]
use masking::PeekInterface;
use crate::settings::Database; use crate::settings::Database;
@ -20,17 +22,17 @@ pub async fn redis_connection(
pub async fn diesel_make_pg_pool( pub async fn diesel_make_pg_pool(
database: &Database, database: &Database,
_test_transaction: bool, _test_transaction: bool,
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig, #[cfg(feature = "kms")] kms_client: &'static kms::KmsClient,
) -> PgPool { ) -> PgPool {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
let password = kms::get_kms_client(kms_config) let password = database
.password
.decrypt_inner(kms_client)
.await .await
.decrypt(&database.kms_encrypted_password) .expect("Failed to decrypt password");
.await
.expect("Failed to KMS decrypt database password");
#[cfg(not(feature = "kms"))] #[cfg(not(feature = "kms"))]
let password = &database.password; let password = &database.password.peek();
let database_url = format!( let database_url = format!(
"postgres://{}:{}@{}:{}/{}", "postgres://{}:{}@{}:{}/{}",

View File

@ -22,7 +22,7 @@ impl Store {
&config.master_database, &config.master_database,
test_transaction, test_transaction,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&config.kms, external_services::kms::get_kms_client(&config.kms).await,
) )
.await, .await,
redis_conn: Arc::new(crate::connection::redis_connection(config).await), redis_conn: Arc::new(crate::connection::redis_connection(config).await),

View File

@ -11,6 +11,11 @@ use serde::Deserialize;
use crate::errors; use crate::errors;
#[cfg(feature = "kms")]
pub type Password = kms::KmsValue;
#[cfg(not(feature = "kms"))]
pub type Password = masking::Secret<String>;
#[derive(clap::Parser, Default)] #[derive(clap::Parser, Default)]
#[cfg_attr(feature = "vergen", command(version = router_env::version!()))] #[cfg_attr(feature = "vergen", command(version = router_env::version!()))]
pub struct CmdLineConf { pub struct CmdLineConf {
@ -35,15 +40,12 @@ pub struct Settings {
#[serde(default)] #[serde(default)]
pub struct Database { pub struct Database {
pub username: String, pub username: String,
#[cfg(not(feature = "kms"))] pub password: Password,
pub password: String,
pub host: String, pub host: String,
pub port: u16, pub port: u16,
pub dbname: String, pub dbname: String,
pub pool_size: u32, pub pool_size: u32,
pub connection_timeout: u64, pub connection_timeout: u64,
#[cfg(feature = "kms")]
pub kms_encrypted_password: String,
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@ -60,15 +62,12 @@ impl Default for Database {
fn default() -> Self { fn default() -> Self {
Self { Self {
username: String::new(), username: String::new(),
#[cfg(not(feature = "kms"))] password: Password::default(),
password: String::new(),
host: "localhost".into(), host: "localhost".into(),
port: 5432, port: 5432,
dbname: String::new(), dbname: String::new(),
pool_size: 5, pool_size: 5,
connection_timeout: 10, connection_timeout: 10,
#[cfg(feature = "kms")]
kms_encrypted_password: String::new(),
} }
} }
} }
@ -107,23 +106,11 @@ impl Database {
)) ))
})?; })?;
#[cfg(not(feature = "kms"))] when(self.password.is_default_or_empty(), || {
{ Err(errors::DrainerError::ConfigParsingError(
when(self.password.is_default_or_empty(), || { "database user password must not be empty".into(),
Err(errors::DrainerError::ConfigParsingError( ))
"database user password must not be empty".into(), })
))
})
}
#[cfg(feature = "kms")]
{
when(self.kms_encrypted_password.is_default_or_empty(), || {
Err(errors::DrainerError::ConfigParsingError(
"database KMS encrypted password must not be empty".into(),
))
})
}
} }
} }

View File

@ -7,7 +7,10 @@ use aws_sdk_kms::{config::Region, primitives::Blob, Client};
use base64::Engine; use base64::Engine;
use common_utils::errors::CustomResult; use common_utils::errors::CustomResult;
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use masking::{PeekInterface, Secret};
use router_env::logger; use router_env::logger;
/// decrypting data using the AWS KMS SDK.
pub mod decrypt;
use crate::{consts, metrics}; use crate::{consts, metrics};
@ -15,7 +18,7 @@ static KMS_CLIENT: tokio::sync::OnceCell<KmsClient> = tokio::sync::OnceCell::con
/// Returns a shared KMS client, or initializes a new one if not previously initialized. /// Returns a shared KMS client, or initializes a new one if not previously initialized.
#[inline] #[inline]
pub async fn get_kms_client(config: &KmsConfig) -> &KmsClient { pub async fn get_kms_client(config: &KmsConfig) -> &'static KmsClient {
KMS_CLIENT.get_or_init(|| KmsClient::new(config)).await KMS_CLIENT.get_or_init(|| KmsClient::new(config)).await
} }
@ -113,6 +116,10 @@ pub enum KmsError {
/// An error occurred UTF-8 decoding KMS decrypted output. /// An error occurred UTF-8 decoding KMS decrypted output.
#[error("Failed to UTF-8 decode decryption output")] #[error("Failed to UTF-8 decode decryption output")]
Utf8DecodingFailed, Utf8DecodingFailed,
/// The KMS client has not been initialized.
#[error("The KMS client has not been initialized")]
KmsClientNotInitialized,
} }
impl KmsConfig { impl KmsConfig {
@ -129,3 +136,14 @@ impl KmsConfig {
}) })
} }
} }
/// A wrapper around a KMS value that can be decrypted.
#[derive(Clone, Debug, Default, serde::Deserialize, Eq, PartialEq)]
#[serde(transparent)]
pub struct KmsValue(Secret<String>);
impl common_utils::ext_traits::ConfigExt for KmsValue {
fn is_empty_after_trim(&self) -> bool {
self.0.peek().is_empty_after_trim()
}
}

View File

@ -0,0 +1,34 @@
use common_utils::errors::CustomResult;
use super::*;
#[async_trait::async_trait]
/// This trait performs in place decryption of the structure on which this is implemented
pub trait KmsDecrypt {
/// The output type of the decryption
type Output;
/// Decrypts the structure given a KMS client
async fn decrypt_inner(self, kms_client: &KmsClient) -> CustomResult<Self::Output, KmsError>
where
Self: Sized;
/// Tries to use the Singleton client to decrypt the structure
async fn try_decrypt_inner(self) -> CustomResult<Self::Output, KmsError>
where
Self: Sized,
{
let client = KMS_CLIENT.get().ok_or(KmsError::KmsClientNotInitialized)?;
self.decrypt_inner(client).await
}
}
#[async_trait::async_trait]
impl KmsDecrypt for &KmsValue {
type Output = String;
async fn decrypt_inner(self, kms_client: &KmsClient) -> CustomResult<Self::Output, KmsError> {
kms_client
.decrypt(self.0.peek())
.await
.attach_printable("Failed to decrypt KMS value")
}
}

View File

@ -1,8 +1,10 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use api_models::{enums, payment_methods::RequiredFieldInfo}; use api_models::{enums, payment_methods::RequiredFieldInfo};
#[cfg(feature = "kms")]
use external_services::kms::KmsValue;
use super::settings::{ConnectorFields, PaymentMethodType, RequiredFieldFinal}; use super::settings::{ConnectorFields, Password, PaymentMethodType, RequiredFieldFinal};
impl Default for super::settings::Server { impl Default for super::settings::Server {
fn default() -> Self { fn default() -> Self {
@ -21,35 +23,12 @@ impl Default for super::settings::Database {
fn default() -> Self { fn default() -> Self {
Self { Self {
username: String::new(), username: String::new(),
#[cfg(not(feature = "kms"))] password: Password::default(),
password: String::new(),
host: "localhost".into(), host: "localhost".into(),
port: 5432, port: 5432,
dbname: String::new(), dbname: String::new(),
pool_size: 5, pool_size: 5,
connection_timeout: 10, connection_timeout: 10,
#[cfg(feature = "kms")]
kms_encrypted_password: String::new(),
}
}
}
impl Default for super::settings::Secrets {
fn default() -> Self {
Self {
#[cfg(not(feature = "kms"))]
jwt_secret: "secret".into(),
#[cfg(not(feature = "kms"))]
admin_api_key: "test_admin".into(),
#[cfg(not(feature = "kms"))]
recon_admin_api_key: "recon_test_admin".into(),
master_enc_key: "".into(),
#[cfg(feature = "kms")]
kms_encrypted_jwt_secret: "".into(),
#[cfg(feature = "kms")]
kms_encrypted_admin_api_key: "".into(),
#[cfg(feature = "kms")]
kms_encrypted_recon_admin_api_key: "".into(),
} }
} }
} }
@ -809,7 +788,7 @@ impl Default for super::settings::ApiKeys {
fn default() -> Self { fn default() -> Self {
Self { Self {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
kms_encrypted_hash_key: String::new(), kms_encrypted_hash_key: KmsValue::default(),
/// Hex-encoded 32-byte long (64 characters long when hex-encoded) key used for calculating /// Hex-encoded 32-byte long (64 characters long when hex-encoded) key used for calculating
/// hashes of API keys /// hashes of API keys

View File

@ -1,60 +1,46 @@
use common_utils::errors::CustomResult; use common_utils::errors::CustomResult;
use external_services::kms; use external_services::kms::{decrypt::KmsDecrypt, KmsClient, KmsError};
use masking::ExposeInterface; use masking::ExposeInterface;
use crate::configs::settings; use crate::configs::settings;
#[async_trait::async_trait]
// This trait performs inplace decryption of the structure on which this is implemented
pub trait KmsDecrypt {
async fn decrypt_inner(self, kms_config: &kms::KmsConfig) -> CustomResult<Self, kms::KmsError>
where
Self: Sized;
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl KmsDecrypt for settings::Jwekey { impl KmsDecrypt for settings::Jwekey {
async fn decrypt_inner(self, kms_config: &kms::KmsConfig) -> CustomResult<Self, kms::KmsError> { type Output = Self;
let client = kms::get_kms_client(kms_config).await;
// If this pattern required repetition, a macro approach needs to be deviced async fn decrypt_inner(
let ( mut self,
locker_encryption_key1, kms_client: &KmsClient,
locker_encryption_key2, ) -> CustomResult<Self::Output, KmsError> {
locker_decryption_key1, (
locker_decryption_key2, self.locker_encryption_key2,
vault_encryption_key, self.locker_decryption_key1,
vault_private_key, self.locker_encryption_key1,
tunnel_private_key, self.locker_decryption_key2,
self.vault_encryption_key,
self.vault_private_key,
self.tunnel_private_key,
) = tokio::try_join!( ) = tokio::try_join!(
client.decrypt(self.locker_encryption_key1), kms_client.decrypt(self.locker_encryption_key1),
client.decrypt(self.locker_encryption_key2), kms_client.decrypt(self.locker_encryption_key2),
client.decrypt(self.locker_decryption_key1), kms_client.decrypt(self.locker_decryption_key1),
client.decrypt(self.locker_decryption_key2), kms_client.decrypt(self.locker_decryption_key2),
client.decrypt(self.vault_encryption_key), kms_client.decrypt(self.vault_encryption_key),
client.decrypt(self.vault_private_key), kms_client.decrypt(self.vault_private_key),
client.decrypt(self.tunnel_private_key), kms_client.decrypt(self.tunnel_private_key),
)?; )?;
Ok(self)
Ok(Self {
locker_key_identifier1: self.locker_key_identifier1,
locker_key_identifier2: self.locker_key_identifier2,
locker_encryption_key1,
locker_encryption_key2,
locker_decryption_key1,
locker_decryption_key2,
vault_encryption_key,
vault_private_key,
tunnel_private_key,
})
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl KmsDecrypt for settings::ActiveKmsSecrets { impl KmsDecrypt for settings::ActiveKmsSecrets {
async fn decrypt_inner(self, kms_config: &kms::KmsConfig) -> CustomResult<Self, kms::KmsError> { type Output = Self;
Ok(Self { async fn decrypt_inner(
jwekey: self.jwekey.expose().decrypt_inner(kms_config).await?.into(), mut self,
}) kms_client: &KmsClient,
) -> CustomResult<Self::Output, KmsError> {
self.jwekey = self.jwekey.expose().decrypt_inner(kms_client).await?.into();
Ok(self)
} }
} }

View File

@ -19,6 +19,10 @@ use crate::{
core::errors::{ApplicationError, ApplicationResult}, core::errors::{ApplicationError, ApplicationResult},
env::{self, logger, Env}, env::{self, logger, Env},
}; };
#[cfg(feature = "kms")]
pub type Password = kms::KmsValue;
#[cfg(not(feature = "kms"))]
pub type Password = masking::Secret<String>;
#[derive(clap::Parser, Default)] #[derive(clap::Parser, Default)]
#[cfg_attr(feature = "vergen", command(version = router_env::version!()))] #[cfg_attr(feature = "vergen", command(version = router_env::version!()))]
@ -338,7 +342,7 @@ where
.collect()) .collect())
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Default, Deserialize, Clone)]
#[serde(default)] #[serde(default)]
pub struct Secrets { pub struct Secrets {
#[cfg(not(feature = "kms"))] #[cfg(not(feature = "kms"))]
@ -347,13 +351,13 @@ pub struct Secrets {
pub admin_api_key: String, pub admin_api_key: String,
#[cfg(not(feature = "kms"))] #[cfg(not(feature = "kms"))]
pub recon_admin_api_key: String, pub recon_admin_api_key: String,
pub master_enc_key: String, pub master_enc_key: Password,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
pub kms_encrypted_jwt_secret: String, pub kms_encrypted_jwt_secret: kms::KmsValue,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
pub kms_encrypted_admin_api_key: String, pub kms_encrypted_admin_api_key: kms::KmsValue,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
pub kms_encrypted_recon_admin_api_key: String, pub kms_encrypted_recon_admin_api_key: kms::KmsValue,
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
@ -414,15 +418,12 @@ pub struct Server {
#[serde(default)] #[serde(default)]
pub struct Database { pub struct Database {
pub username: String, pub username: String,
#[cfg(not(feature = "kms"))] pub password: Password,
pub password: String,
pub host: String, pub host: String,
pub port: u16, pub port: u16,
pub dbname: String, pub dbname: String,
pub pool_size: u32, pub pool_size: u32,
pub connection_timeout: u64, pub connection_timeout: u64,
#[cfg(feature = "kms")]
pub kms_encrypted_password: String,
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
@ -566,7 +567,7 @@ pub struct ApiKeys {
/// Base64-encoded (KMS encrypted) ciphertext of the key used for calculating hashes of API /// Base64-encoded (KMS encrypted) ciphertext of the key used for calculating hashes of API
/// keys /// keys
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
pub kms_encrypted_hash_key: String, pub kms_encrypted_hash_key: kms::KmsValue,
/// Hex-encoded 32-byte long (64 characters long when hex-encoded) key used for calculating /// Hex-encoded 32-byte long (64 characters long when hex-encoded) key used for calculating
/// hashes of API keys /// hashes of API keys

View File

@ -99,23 +99,11 @@ impl super::settings::Database {
)) ))
})?; })?;
#[cfg(not(feature = "kms"))] when(self.password.is_default_or_empty(), || {
{ Err(ApplicationError::InvalidConfigurationValueError(
when(self.password.is_default_or_empty(), || { "database user password must not be empty".into(),
Err(ApplicationError::InvalidConfigurationValueError( ))
"database user password must not be empty".into(), })
))
})
}
#[cfg(feature = "kms")]
{
when(self.kms_encrypted_password.is_default_or_empty(), || {
Err(ApplicationError::InvalidConfigurationValueError(
"database KMS encrypted password must not be empty".into(),
))
})
}
} }
} }

View File

@ -4,6 +4,10 @@ use diesel::PgConnection;
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
use external_services::kms; use external_services::kms;
#[cfg(feature = "kms")]
use external_services::kms::decrypt::KmsDecrypt;
#[cfg(not(feature = "kms"))]
use masking::PeekInterface;
use crate::{configs::settings::Database, errors}; use crate::{configs::settings::Database, errors};
@ -41,17 +45,18 @@ pub async fn redis_connection(
pub async fn diesel_make_pg_pool( pub async fn diesel_make_pg_pool(
database: &Database, database: &Database,
test_transaction: bool, test_transaction: bool,
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig, #[cfg(feature = "kms")] kms_client: &kms::KmsClient,
) -> PgPool { ) -> PgPool {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
let password = kms::get_kms_client(kms_config) let password = database
.await .password
.decrypt(&database.kms_encrypted_password) .clone()
.decrypt_inner(kms_client)
.await .await
.expect("Failed to KMS decrypt database password"); .expect("Failed to KMS decrypt database password");
#[cfg(not(feature = "kms"))] #[cfg(not(feature = "kms"))]
let password = &database.password; let password = &database.password.peek();
let database_url = format!( let database_url = format!(
"postgres://{}:{}@{}:{}/{}", "postgres://{}:{}@{}:{}/{}",

View File

@ -27,19 +27,22 @@ const API_KEY_EXPIRY_NAME: &str = "API_KEY_EXPIRY";
#[cfg(feature = "email")] #[cfg(feature = "email")]
const API_KEY_EXPIRY_RUNNER: &str = "API_KEY_EXPIRY_WORKFLOW"; const API_KEY_EXPIRY_RUNNER: &str = "API_KEY_EXPIRY_WORKFLOW";
#[cfg(feature = "kms")]
use external_services::kms::decrypt::KmsDecrypt;
static HASH_KEY: tokio::sync::OnceCell<StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> = static HASH_KEY: tokio::sync::OnceCell<StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> =
tokio::sync::OnceCell::const_new(); tokio::sync::OnceCell::const_new();
pub async fn get_hash_key( pub async fn get_hash_key(
api_key_config: &settings::ApiKeys, api_key_config: &settings::ApiKeys,
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig, #[cfg(feature = "kms")] kms_client: &kms::KmsClient,
) -> errors::RouterResult<&'static StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> { ) -> errors::RouterResult<&'static StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> {
HASH_KEY HASH_KEY
.get_or_try_init(|| async { .get_or_try_init(|| async {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
let hash_key = kms::get_kms_client(kms_config) let hash_key = api_key_config
.await .kms_encrypted_hash_key
.decrypt(&api_key_config.kms_encrypted_hash_key) .decrypt_inner(kms_client)
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to KMS decrypt API key hashing key")?; .attach_printable("Failed to KMS decrypt API key hashing key")?;
@ -130,7 +133,7 @@ impl PlaintextApiKey {
pub async fn create_api_key( pub async fn create_api_key(
state: &AppState, state: &AppState,
api_key_config: &settings::ApiKeys, api_key_config: &settings::ApiKeys,
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig, #[cfg(feature = "kms")] kms_client: &kms::KmsClient,
api_key: api::CreateApiKeyRequest, api_key: api::CreateApiKeyRequest,
merchant_id: String, merchant_id: String,
) -> RouterResponse<api::CreateApiKeyResponse> { ) -> RouterResponse<api::CreateApiKeyResponse> {
@ -150,7 +153,7 @@ pub async fn create_api_key(
let hash_key = get_hash_key( let hash_key = get_hash_key(
api_key_config, api_key_config,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
kms_config, kms_client,
) )
.await?; .await?;
let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH); let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH);
@ -555,7 +558,7 @@ mod tests {
let hash_key = get_hash_key( let hash_key = get_hash_key(
&settings.api_keys, &settings.api_keys,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&settings.kms, external_services::kms::get_kms_client(&settings.kms).await,
) )
.await .await
.unwrap(); .unwrap();

View File

@ -46,7 +46,7 @@ pub async fn api_key_create(
state, state,
&state.conf.api_keys, &state.conf.api_keys,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&state.conf.kms, external_services::kms::get_kms_client(&state.conf.kms).await,
payload, payload,
merchant_id.clone(), merchant_id.clone(),
) )

View File

@ -1,6 +1,8 @@
use actix_web::{web, Scope}; use actix_web::{web, Scope};
#[cfg(feature = "email")] #[cfg(feature = "email")]
use external_services::email::{AwsSes, EmailClient}; use external_services::email::{AwsSes, EmailClient};
#[cfg(feature = "kms")]
use external_services::kms::{self, decrypt::KmsDecrypt};
use tokio::sync::oneshot; use tokio::sync::oneshot;
#[cfg(feature = "dummy_connector")] #[cfg(feature = "dummy_connector")]
@ -14,8 +16,6 @@ use super::{cache::*, health::*};
use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*};
#[cfg(feature = "oltp")] #[cfg(feature = "oltp")]
use super::{ephemeral_key::*, payment_methods::*, webhooks::*}; use super::{ephemeral_key::*, payment_methods::*, webhooks::*};
#[cfg(feature = "kms")]
use crate::configs::kms;
use crate::{ use crate::{
configs::settings, configs::settings,
db::{MockDb, StorageImpl, StorageInterface}, db::{MockDb, StorageImpl, StorageInterface},
@ -64,6 +64,8 @@ impl AppState {
storage_impl: StorageImpl, storage_impl: StorageImpl,
shut_down_signal: oneshot::Sender<()>, shut_down_signal: oneshot::Sender<()>,
) -> Self { ) -> Self {
#[cfg(feature = "kms")]
let kms_client = kms::get_kms_client(&conf.kms).await;
let testable = storage_impl == StorageImpl::PostgresqlTest; let testable = storage_impl == StorageImpl::PostgresqlTest;
let store: Box<dyn StorageInterface> = match storage_impl { let store: Box<dyn StorageInterface> = match storage_impl {
StorageImpl::Postgresql | StorageImpl::PostgresqlTest => { StorageImpl::Postgresql | StorageImpl::PostgresqlTest => {
@ -74,12 +76,10 @@ impl AppState {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
#[allow(clippy::expect_used)] #[allow(clippy::expect_used)]
let kms_secrets = kms::KmsDecrypt::decrypt_inner( let kms_secrets = settings::ActiveKmsSecrets {
settings::ActiveKmsSecrets { jwekey: conf.jwekey.clone().into(),
jwekey: conf.jwekey.clone().into(), }
}, .decrypt_inner(kms_client)
&conf.kms,
)
.await .await
.expect("Failed while performing KMS decryption"); .expect("Failed while performing KMS decryption");

View File

@ -7,7 +7,9 @@ use std::sync::{atomic, Arc};
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
use external_services::kms; use external_services::kms::{self, decrypt::KmsDecrypt};
#[cfg(not(feature = "kms"))]
use masking::PeekInterface;
use redis_interface::{errors as redis_errors, PubsubInterface, RedisValue}; use redis_interface::{errors as redis_errors, PubsubInterface, RedisValue};
use tokio::sync::oneshot; use tokio::sync::oneshot;
@ -166,11 +168,13 @@ impl Store {
async_spawn!({ async_spawn!({
redis_clone.on_error(shut_down_signal).await; redis_clone.on_error(shut_down_signal).await;
}); });
#[cfg(feature = "kms")]
let kms_client = kms::get_kms_client(&config.kms).await;
let master_enc_key = get_master_enc_key( let master_enc_key = get_master_enc_key(
config, config,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&config.kms, kms_client,
) )
.await; .await;
@ -179,7 +183,7 @@ impl Store {
&config.master_database, &config.master_database,
test_transaction, test_transaction,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&config.kms, kms_client,
) )
.await, .await,
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
@ -187,7 +191,7 @@ impl Store {
&config.replica_database, &config.replica_database,
test_transaction, test_transaction,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&config.kms, kms_client,
) )
.await, .await,
redis_conn, redis_conn,
@ -248,13 +252,14 @@ impl Store {
#[allow(clippy::expect_used)] #[allow(clippy::expect_used)]
async fn get_master_enc_key( async fn get_master_enc_key(
conf: &crate::configs::settings::Settings, conf: &crate::configs::settings::Settings,
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig, #[cfg(feature = "kms")] kms_client: &kms::KmsClient,
) -> Vec<u8> { ) -> Vec<u8> {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
let master_enc_key = hex::decode( let master_enc_key = hex::decode(
kms::get_kms_client(kms_config) conf.secrets
.await .master_enc_key
.decrypt(&conf.secrets.master_enc_key) .clone()
.decrypt_inner(kms_client)
.await .await
.expect("Failed to decrypt master enc key"), .expect("Failed to decrypt master enc key"),
) )
@ -262,7 +267,7 @@ async fn get_master_enc_key(
#[cfg(not(feature = "kms"))] #[cfg(not(feature = "kms"))]
let master_enc_key = let master_enc_key =
hex::decode(&conf.secrets.master_enc_key).expect("Failed to decode from hex"); hex::decode(conf.secrets.master_enc_key.peek()).expect("Failed to decode from hex");
master_enc_key master_enc_key
} }

View File

@ -4,7 +4,7 @@ use async_trait::async_trait;
use common_utils::date_time; use common_utils::date_time;
use error_stack::{report, IntoReport, ResultExt}; use error_stack::{report, IntoReport, ResultExt};
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
use external_services::kms; use external_services::kms::{self, decrypt::KmsDecrypt};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use masking::{PeekInterface, StrongSecret}; use masking::{PeekInterface, StrongSecret};
@ -99,7 +99,7 @@ where
api_keys::get_hash_key( api_keys::get_hash_key(
&config.api_keys, &config.api_keys,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&config.kms, kms::get_kms_client(&config.kms).await,
) )
.await? .await?
}; };
@ -151,14 +151,14 @@ static ADMIN_API_KEY: tokio::sync::OnceCell<StrongSecret<String>> =
pub async fn get_admin_api_key( pub async fn get_admin_api_key(
secrets: &settings::Secrets, secrets: &settings::Secrets,
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig, #[cfg(feature = "kms")] kms_client: &kms::KmsClient,
) -> RouterResult<&'static StrongSecret<String>> { ) -> RouterResult<&'static StrongSecret<String>> {
ADMIN_API_KEY ADMIN_API_KEY
.get_or_try_init(|| async { .get_or_try_init(|| async {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
let admin_api_key = kms::get_kms_client(kms_config) let admin_api_key = secrets
.await .kms_encrypted_admin_api_key
.decrypt(&secrets.kms_encrypted_admin_api_key) .decrypt_inner(kms_client)
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to KMS decrypt admin API key")?; .attach_printable("Failed to KMS decrypt admin API key")?;
@ -191,7 +191,7 @@ where
let admin_api_key = get_admin_api_key( let admin_api_key = get_admin_api_key(
&conf.secrets, &conf.secrets,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&conf.kms, kms::get_kms_client(&conf.kms).await,
) )
.await?; .await?;
@ -456,14 +456,14 @@ static JWT_SECRET: tokio::sync::OnceCell<StrongSecret<String>> = tokio::sync::On
pub async fn get_jwt_secret( pub async fn get_jwt_secret(
secrets: &settings::Secrets, secrets: &settings::Secrets,
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig, #[cfg(feature = "kms")] kms_client: &kms::KmsClient,
) -> RouterResult<&'static StrongSecret<String>> { ) -> RouterResult<&'static StrongSecret<String>> {
JWT_SECRET JWT_SECRET
.get_or_try_init(|| async { .get_or_try_init(|| async {
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
let jwt_secret = kms::get_kms_client(kms_config) let jwt_secret = secrets
.await .kms_encrypted_jwt_secret
.decrypt(&secrets.kms_encrypted_jwt_secret) .decrypt_inner(kms_client)
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to KMS decrypt JWT secret")?; .attach_printable("Failed to KMS decrypt JWT secret")?;
@ -484,7 +484,7 @@ where
let secret = get_jwt_secret( let secret = get_jwt_secret(
&conf.secrets, &conf.secrets,
#[cfg(feature = "kms")] #[cfg(feature = "kms")]
&conf.kms, kms::get_kms_client(&conf.kms).await,
) )
.await? .await?
.peek() .peek()