mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +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",
|
||||
"diesel",
|
||||
"error-stack",
|
||||
"external_services",
|
||||
"once_cell",
|
||||
"redis_interface",
|
||||
"router_env",
|
||||
|
||||
@ -19,23 +19,25 @@ request_body_limit = 16_384
|
||||
|
||||
# Main SQL data store credentials
|
||||
[master_database]
|
||||
username = "db_user" # DB Username
|
||||
password = "db_pass" # DB Password
|
||||
host = "localhost" # DB Host
|
||||
port = 5432 # DB Port
|
||||
dbname = "hyperswitch_db" # Name of Database
|
||||
pool_size = 5 # Number of connections to keep open
|
||||
connection_timeout = 10 # Timeout for database connection in seconds
|
||||
username = "db_user" # DB Username
|
||||
password = "db_pass" # DB Password. Only applicable when KMS is disabled.
|
||||
host = "localhost" # DB Host
|
||||
port = 5432 # DB Port
|
||||
dbname = "hyperswitch_db" # Name of Database
|
||||
pool_size = 5 # Number of connections to keep open
|
||||
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_database]
|
||||
username = "replica_user" # DB Username
|
||||
password = "replica_pass" # DB Password
|
||||
host = "localhost" # DB Host
|
||||
port = 5432 # DB Port
|
||||
dbname = "hyperswitch_db" # Name of Database
|
||||
pool_size = 5 # Number of connections to keep open
|
||||
connection_timeout = 10 # Timeout for database connection in seconds
|
||||
username = "replica_user" # DB Username
|
||||
password = "replica_pass" # DB Password. Only applicable when KMS is disabled.
|
||||
host = "localhost" # DB Host
|
||||
port = 5432 # DB Port
|
||||
dbname = "hyperswitch_db" # Name of Database
|
||||
pool_size = 5 # Number of connections to keep open
|
||||
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]
|
||||
|
||||
@ -8,7 +8,8 @@ readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[features]
|
||||
vergen = [ "router_env/vergen" ]
|
||||
kms = ["external_services/kms"]
|
||||
vergen = ["router_env/vergen"]
|
||||
|
||||
[dependencies]
|
||||
async-bb8-diesel = { git = "https://github.com/juspay/async-bb8-diesel", rev = "9a71d142726dbc33f41c1fd935ddaa79841c7be5" }
|
||||
@ -26,6 +27,7 @@ tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
# First Party Crates
|
||||
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" }
|
||||
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"] }
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use bb8::PooledConnection;
|
||||
use diesel::PgConnection;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
|
||||
use crate::settings::Database;
|
||||
|
||||
@ -15,13 +17,29 @@ pub async fn redis_connection(
|
||||
}
|
||||
|
||||
#[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!(
|
||||
"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 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)
|
||||
.await
|
||||
|
||||
@ -18,7 +18,13 @@ pub struct StoreConfig {
|
||||
impl Store {
|
||||
pub async fn new(config: &crate::settings::Settings, test_transaction: bool) -> 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),
|
||||
config: StoreConfig {
|
||||
drainer_stream_name: config.drainer.stream_name.clone(),
|
||||
|
||||
@ -2,6 +2,8 @@ use std::path::PathBuf;
|
||||
|
||||
use common_utils::ext_traits::ConfigExt;
|
||||
use config::{Environment, File};
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use redis_interface as redis;
|
||||
pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry};
|
||||
use router_env::{env, logger};
|
||||
@ -25,17 +27,23 @@ pub struct Settings {
|
||||
pub redis: redis::RedisSettings,
|
||||
pub log: Log,
|
||||
pub drainer: DrainerSettings,
|
||||
#[cfg(feature = "kms")]
|
||||
pub kms: kms::KmsConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct Database {
|
||||
pub username: String,
|
||||
#[cfg(not(feature = "kms"))]
|
||||
pub password: String,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub dbname: String,
|
||||
pub pool_size: u32,
|
||||
pub connection_timeout: u64,
|
||||
#[cfg(feature = "kms")]
|
||||
pub kms_encrypted_password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@ -51,12 +59,16 @@ pub struct DrainerSettings {
|
||||
impl Default for Database {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
username: String::default(),
|
||||
password: String::default(),
|
||||
username: String::new(),
|
||||
#[cfg(not(feature = "kms"))]
|
||||
password: String::new(),
|
||||
host: "localhost".into(),
|
||||
port: 5432,
|
||||
dbname: String::default(),
|
||||
dbname: String::new(),
|
||||
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> {
|
||||
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(), || {
|
||||
Err(errors::DrainerError::ConfigParsingError(
|
||||
"database host must not be empty".into(),
|
||||
@ -99,7 +99,31 @@ impl Database {
|
||||
Err(errors::DrainerError::ConfigParsingError(
|
||||
"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 {
|
||||
Self {
|
||||
username: String::new(),
|
||||
#[cfg(not(feature = "kms"))]
|
||||
password: String::new(),
|
||||
host: "localhost".into(),
|
||||
port: 5432,
|
||||
dbname: String::new(),
|
||||
pool_size: 5,
|
||||
connection_timeout: 10,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_encrypted_password: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,12 +223,15 @@ pub struct Server {
|
||||
#[serde(default)]
|
||||
pub struct Database {
|
||||
pub username: String,
|
||||
#[cfg(not(feature = "kms"))]
|
||||
pub password: String,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub dbname: String,
|
||||
pub pool_size: u32,
|
||||
pub connection_timeout: u64,
|
||||
#[cfg(feature = "kms")]
|
||||
pub kms_encrypted_password: String,
|
||||
}
|
||||
|
||||
#[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(), || {
|
||||
Err(ApplicationError::InvalidConfigurationValueError(
|
||||
"database user username must not be empty".into(),
|
||||
))
|
||||
})?;
|
||||
|
||||
when(self.password.is_default_or_empty(), || {
|
||||
Err(ApplicationError::InvalidConfigurationValueError(
|
||||
"database user password must not be empty".into(),
|
||||
))
|
||||
})?;
|
||||
#[cfg(not(feature = "kms"))]
|
||||
{
|
||||
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(), || {
|
||||
Err(ApplicationError::InvalidConfigurationValueError(
|
||||
"database name 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(),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ use async_bb8_diesel::{AsyncConnection, ConnectionError};
|
||||
use bb8::{CustomizeConnection, PooledConnection};
|
||||
use diesel::PgConnection;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
|
||||
use crate::{configs::settings::Database, errors};
|
||||
|
||||
@ -37,10 +39,24 @@ pub async fn redis_connection(
|
||||
}
|
||||
|
||||
#[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!(
|
||||
"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 mut pool = bb8::Pool::builder()
|
||||
|
||||
@ -109,9 +109,21 @@ impl Store {
|
||||
});
|
||||
|
||||
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")]
|
||||
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,
|
||||
#[cfg(feature = "kv_store")]
|
||||
config: StoreConfig {
|
||||
|
||||
Reference in New Issue
Block a user