mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	refactor(drainer, router): KMS decrypt database password when kms feature is enabled (#733)
				
					
				
			This commit is contained in:
		
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1527,6 +1527,7 @@ dependencies = [ | |||||||
|  "config", |  "config", | ||||||
|  "diesel", |  "diesel", | ||||||
|  "error-stack", |  "error-stack", | ||||||
|  |  "external_services", | ||||||
|  "once_cell", |  "once_cell", | ||||||
|  "redis_interface", |  "redis_interface", | ||||||
|  "router_env", |  "router_env", | ||||||
|  | |||||||
| @ -19,23 +19,25 @@ 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 | password = "db_pass"        # DB Password. Only applicable when KMS is disabled. | ||||||
| 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 | password = "replica_pass"   # DB Password. Only applicable when KMS is disabled. | ||||||
| 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] | ||||||
|  | |||||||
| @ -8,7 +8,8 @@ readme = "README.md" | |||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
|  |  | ||||||
| [features] | [features] | ||||||
| vergen = [ "router_env/vergen" ] | kms = ["external_services/kms"] | ||||||
|  | vergen = ["router_env/vergen"] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| async-bb8-diesel = { git = "https://github.com/juspay/async-bb8-diesel", rev = "9a71d142726dbc33f41c1fd935ddaa79841c7be5" } | async-bb8-diesel = { git = "https://github.com/juspay/async-bb8-diesel", rev = "9a71d142726dbc33f41c1fd935ddaa79841c7be5" } | ||||||
| @ -26,9 +27,10 @@ tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] } | |||||||
|  |  | ||||||
| # First Party Crates | # First Party Crates | ||||||
| common_utils = { version = "0.1.0", path = "../common_utils" } | common_utils = { version = "0.1.0", path = "../common_utils" } | ||||||
|  | external_services = { version = "0.1.0", path = "../external_services" } | ||||||
| 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"] } | ||||||
| storage_models = { version = "0.1.0", path = "../storage_models", features = ["kv_store"] } | storage_models = { version = "0.1.0", path = "../storage_models", features = ["kv_store"] } | ||||||
|  |  | ||||||
| [build-dependencies] | [build-dependencies] | ||||||
| router_env = { version = "0.1.0", path = "../router_env", default-features = false } | router_env = { version = "0.1.0", path = "../router_env", default-features = false } | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| use bb8::PooledConnection; | use bb8::PooledConnection; | ||||||
| use diesel::PgConnection; | use diesel::PgConnection; | ||||||
|  | #[cfg(feature = "kms")] | ||||||
|  | use external_services::kms; | ||||||
|  |  | ||||||
| use crate::settings::Database; | use crate::settings::Database; | ||||||
|  |  | ||||||
| @ -15,13 +17,29 @@ pub async fn redis_connection( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[allow(clippy::expect_used)] | #[allow(clippy::expect_used)] | ||||||
| pub async fn diesel_make_pg_pool(database: &Database, _test_transaction: bool) -> PgPool { | pub async fn diesel_make_pg_pool( | ||||||
|  |     database: &Database, | ||||||
|  |     _test_transaction: bool, | ||||||
|  |     #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, | ||||||
|  | ) -> PgPool { | ||||||
|  |     #[cfg(feature = "kms")] | ||||||
|  |     let password = kms::get_kms_client(kms_config) | ||||||
|  |         .await | ||||||
|  |         .decrypt(&database.kms_encrypted_password) | ||||||
|  |         .await | ||||||
|  |         .expect("Failed to KMS decrypt database password"); | ||||||
|  |  | ||||||
|  |     #[cfg(not(feature = "kms"))] | ||||||
|  |     let password = &database.password; | ||||||
|  |  | ||||||
|     let database_url = format!( |     let database_url = format!( | ||||||
|         "postgres://{}:{}@{}:{}/{}", |         "postgres://{}:{}@{}:{}/{}", | ||||||
|         database.username, database.password, database.host, database.port, database.dbname |         database.username, password, database.host, database.port, database.dbname | ||||||
|     ); |     ); | ||||||
|     let manager = async_bb8_diesel::ConnectionManager::<PgConnection>::new(database_url); |     let manager = async_bb8_diesel::ConnectionManager::<PgConnection>::new(database_url); | ||||||
|     let pool = bb8::Pool::builder().max_size(database.pool_size); |     let pool = bb8::Pool::builder() | ||||||
|  |         .max_size(database.pool_size) | ||||||
|  |         .connection_timeout(std::time::Duration::from_secs(database.connection_timeout)); | ||||||
|  |  | ||||||
|     pool.build(manager) |     pool.build(manager) | ||||||
|         .await |         .await | ||||||
|  | |||||||
| @ -18,7 +18,13 @@ pub struct StoreConfig { | |||||||
| impl Store { | impl Store { | ||||||
|     pub async fn new(config: &crate::settings::Settings, test_transaction: bool) -> Self { |     pub async fn new(config: &crate::settings::Settings, test_transaction: bool) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             master_pool: diesel_make_pg_pool(&config.master_database, test_transaction).await, |             master_pool: diesel_make_pg_pool( | ||||||
|  |                 &config.master_database, | ||||||
|  |                 test_transaction, | ||||||
|  |                 #[cfg(feature = "kms")] | ||||||
|  |                 &config.kms, | ||||||
|  |             ) | ||||||
|  |             .await, | ||||||
|             redis_conn: Arc::new(crate::connection::redis_connection(config).await), |             redis_conn: Arc::new(crate::connection::redis_connection(config).await), | ||||||
|             config: StoreConfig { |             config: StoreConfig { | ||||||
|                 drainer_stream_name: config.drainer.stream_name.clone(), |                 drainer_stream_name: config.drainer.stream_name.clone(), | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ use std::path::PathBuf; | |||||||
|  |  | ||||||
| use common_utils::ext_traits::ConfigExt; | use common_utils::ext_traits::ConfigExt; | ||||||
| use config::{Environment, File}; | use config::{Environment, File}; | ||||||
|  | #[cfg(feature = "kms")] | ||||||
|  | use external_services::kms; | ||||||
| use redis_interface as redis; | use redis_interface as redis; | ||||||
| pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry}; | pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry}; | ||||||
| use router_env::{env, logger}; | use router_env::{env, logger}; | ||||||
| @ -25,17 +27,23 @@ pub struct Settings { | |||||||
|     pub redis: redis::RedisSettings, |     pub redis: redis::RedisSettings, | ||||||
|     pub log: Log, |     pub log: Log, | ||||||
|     pub drainer: DrainerSettings, |     pub drainer: DrainerSettings, | ||||||
|  |     #[cfg(feature = "kms")] | ||||||
|  |     pub kms: kms::KmsConfig, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize, Clone)] | #[derive(Debug, Deserialize, Clone)] | ||||||
| #[serde(default)] | #[serde(default)] | ||||||
| pub struct Database { | pub struct Database { | ||||||
|     pub username: String, |     pub username: String, | ||||||
|  |     #[cfg(not(feature = "kms"))] | ||||||
|     pub password: String, |     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, | ||||||
|  |     #[cfg(feature = "kms")] | ||||||
|  |     pub kms_encrypted_password: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Deserialize)] | #[derive(Debug, Clone, Deserialize)] | ||||||
| @ -51,12 +59,16 @@ pub struct DrainerSettings { | |||||||
| impl Default for Database { | impl Default for Database { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             username: String::default(), |             username: String::new(), | ||||||
|             password: String::default(), |             #[cfg(not(feature = "kms"))] | ||||||
|  |             password: String::new(), | ||||||
|             host: "localhost".into(), |             host: "localhost".into(), | ||||||
|             port: 5432, |             port: 5432, | ||||||
|             dbname: String::default(), |             dbname: String::new(), | ||||||
|             pool_size: 5, |             pool_size: 5, | ||||||
|  |             connection_timeout: 10, | ||||||
|  |             #[cfg(feature = "kms")] | ||||||
|  |             kms_encrypted_password: String::new(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -77,18 +89,6 @@ impl Database { | |||||||
|     fn validate(&self) -> Result<(), errors::DrainerError> { |     fn validate(&self) -> Result<(), errors::DrainerError> { | ||||||
|         use common_utils::fp_utils::when; |         use common_utils::fp_utils::when; | ||||||
|  |  | ||||||
|         when(self.username.is_default_or_empty(), || { |  | ||||||
|             Err(errors::DrainerError::ConfigParsingError( |  | ||||||
|                 "database username must not be empty".into(), |  | ||||||
|             )) |  | ||||||
|         })?; |  | ||||||
|  |  | ||||||
|         when(self.password.is_default_or_empty(), || { |  | ||||||
|             Err(errors::DrainerError::ConfigParsingError( |  | ||||||
|                 "database user password must not be empty".into(), |  | ||||||
|             )) |  | ||||||
|         })?; |  | ||||||
|  |  | ||||||
|         when(self.host.is_default_or_empty(), || { |         when(self.host.is_default_or_empty(), || { | ||||||
|             Err(errors::DrainerError::ConfigParsingError( |             Err(errors::DrainerError::ConfigParsingError( | ||||||
|                 "database host must not be empty".into(), |                 "database host must not be empty".into(), | ||||||
| @ -99,7 +99,31 @@ impl Database { | |||||||
|             Err(errors::DrainerError::ConfigParsingError( |             Err(errors::DrainerError::ConfigParsingError( | ||||||
|                 "database name must not be empty".into(), |                 "database name must not be empty".into(), | ||||||
|             )) |             )) | ||||||
|         }) |         })?; | ||||||
|  |  | ||||||
|  |         when(self.username.is_default_or_empty(), || { | ||||||
|  |             Err(errors::DrainerError::ConfigParsingError( | ||||||
|  |                 "database user username must not be empty".into(), | ||||||
|  |             )) | ||||||
|  |         })?; | ||||||
|  |  | ||||||
|  |         #[cfg(not(feature = "kms"))] | ||||||
|  |         { | ||||||
|  |             when(self.password.is_default_or_empty(), || { | ||||||
|  |                 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(), | ||||||
|  |                 )) | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -15,12 +15,15 @@ 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: String::new(), |             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(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -223,12 +223,15 @@ 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: String, |     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)] | ||||||
|  | |||||||
| @ -61,23 +61,35 @@ impl super::settings::Database { | |||||||
|             )) |             )) | ||||||
|         })?; |         })?; | ||||||
|  |  | ||||||
|  |         when(self.dbname.is_default_or_empty(), || { | ||||||
|  |             Err(ApplicationError::InvalidConfigurationValueError( | ||||||
|  |                 "database name must not be empty".into(), | ||||||
|  |             )) | ||||||
|  |         })?; | ||||||
|  |  | ||||||
|         when(self.username.is_default_or_empty(), || { |         when(self.username.is_default_or_empty(), || { | ||||||
|             Err(ApplicationError::InvalidConfigurationValueError( |             Err(ApplicationError::InvalidConfigurationValueError( | ||||||
|                 "database user username must not be empty".into(), |                 "database user username must not be empty".into(), | ||||||
|             )) |             )) | ||||||
|         })?; |         })?; | ||||||
|  |  | ||||||
|         when(self.password.is_default_or_empty(), || { |         #[cfg(not(feature = "kms"))] | ||||||
|             Err(ApplicationError::InvalidConfigurationValueError( |         { | ||||||
|                 "database user password must not be empty".into(), |             when(self.password.is_default_or_empty(), || { | ||||||
|             )) |                 Err(ApplicationError::InvalidConfigurationValueError( | ||||||
|         })?; |                     "database user password must not be empty".into(), | ||||||
|  |                 )) | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |  | ||||||
|         when(self.dbname.is_default_or_empty(), || { |         #[cfg(feature = "kms")] | ||||||
|             Err(ApplicationError::InvalidConfigurationValueError( |         { | ||||||
|                 "database name must not be empty".into(), |             when(self.kms_encrypted_password.is_default_or_empty(), || { | ||||||
|             )) |                 Err(ApplicationError::InvalidConfigurationValueError( | ||||||
|         }) |                     "database KMS encrypted password must not be empty".into(), | ||||||
|  |                 )) | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ use async_bb8_diesel::{AsyncConnection, ConnectionError}; | |||||||
| use bb8::{CustomizeConnection, PooledConnection}; | use bb8::{CustomizeConnection, PooledConnection}; | ||||||
| use diesel::PgConnection; | use diesel::PgConnection; | ||||||
| use error_stack::{IntoReport, ResultExt}; | use error_stack::{IntoReport, ResultExt}; | ||||||
|  | #[cfg(feature = "kms")] | ||||||
|  | use external_services::kms; | ||||||
|  |  | ||||||
| use crate::{configs::settings::Database, errors}; | use crate::{configs::settings::Database, errors}; | ||||||
|  |  | ||||||
| @ -37,10 +39,24 @@ pub async fn redis_connection( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[allow(clippy::expect_used)] | #[allow(clippy::expect_used)] | ||||||
| pub async fn diesel_make_pg_pool(database: &Database, test_transaction: bool) -> PgPool { | pub async fn diesel_make_pg_pool( | ||||||
|  |     database: &Database, | ||||||
|  |     test_transaction: bool, | ||||||
|  |     #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, | ||||||
|  | ) -> PgPool { | ||||||
|  |     #[cfg(feature = "kms")] | ||||||
|  |     let password = kms::get_kms_client(kms_config) | ||||||
|  |         .await | ||||||
|  |         .decrypt(&database.kms_encrypted_password) | ||||||
|  |         .await | ||||||
|  |         .expect("Failed to KMS decrypt database password"); | ||||||
|  |  | ||||||
|  |     #[cfg(not(feature = "kms"))] | ||||||
|  |     let password = &database.password; | ||||||
|  |  | ||||||
|     let database_url = format!( |     let database_url = format!( | ||||||
|         "postgres://{}:{}@{}:{}/{}", |         "postgres://{}:{}@{}:{}/{}", | ||||||
|         database.username, database.password, database.host, database.port, database.dbname |         database.username, password, database.host, database.port, database.dbname | ||||||
|     ); |     ); | ||||||
|     let manager = async_bb8_diesel::ConnectionManager::<PgConnection>::new(database_url); |     let manager = async_bb8_diesel::ConnectionManager::<PgConnection>::new(database_url); | ||||||
|     let mut pool = bb8::Pool::builder() |     let mut pool = bb8::Pool::builder() | ||||||
|  | |||||||
| @ -109,9 +109,21 @@ impl Store { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         Self { |         Self { | ||||||
|             master_pool: diesel_make_pg_pool(&config.master_database, test_transaction).await, |             master_pool: diesel_make_pg_pool( | ||||||
|  |                 &config.master_database, | ||||||
|  |                 test_transaction, | ||||||
|  |                 #[cfg(feature = "kms")] | ||||||
|  |                 &config.kms, | ||||||
|  |             ) | ||||||
|  |             .await, | ||||||
|             #[cfg(feature = "olap")] |             #[cfg(feature = "olap")] | ||||||
|             replica_pool: diesel_make_pg_pool(&config.replica_database, test_transaction).await, |             replica_pool: diesel_make_pg_pool( | ||||||
|  |                 &config.replica_database, | ||||||
|  |                 test_transaction, | ||||||
|  |                 #[cfg(feature = "kms")] | ||||||
|  |                 &config.kms, | ||||||
|  |             ) | ||||||
|  |             .await, | ||||||
|             redis_conn, |             redis_conn, | ||||||
|             #[cfg(feature = "kv_store")] |             #[cfg(feature = "kv_store")] | ||||||
|             config: StoreConfig { |             config: StoreConfig { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Sanchith Hegde
					Sanchith Hegde