mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(hashicorp): implement hashicorp secrets manager solution (#3297)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -36,6 +36,7 @@ ba = "ba" # ignore minor commit conversions
|
||||
ede = "ede" # ignore minor commit conversions
|
||||
daa = "daa" # Commit id
|
||||
afe = "afe" # Commit id
|
||||
Hashi = "Hashi" # HashiCorp
|
||||
|
||||
[files]
|
||||
extend-exclude = [
|
||||
|
||||
192
Cargo.lock
generated
192
Cargo.lock
generated
@ -114,7 +114,7 @@ version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a0a77f836d869f700e5b47ac7c3c8b9c8bc82e4aec861954c6198abee3ebd4d"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.3",
|
||||
"parse-size",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -189,10 +189,10 @@ dependencies = [
|
||||
"rustls 0.21.7",
|
||||
"rustls-webpki",
|
||||
"tokio 1.35.1",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.23.4",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"webpki-roots",
|
||||
"webpki-roots 0.22.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -954,7 +954,7 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-rustls 0.23.2",
|
||||
"lazy_static",
|
||||
"pin-project-lite",
|
||||
"rustls 0.20.9",
|
||||
@ -1990,14 +1990,38 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
|
||||
dependencies = [
|
||||
"darling_core 0.14.4",
|
||||
"darling_macro 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.20.3",
|
||||
"darling_macro 0.20.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2014,13 +2038,24 @@ dependencies = [
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||
dependencies = [
|
||||
"darling_core 0.14.4",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.20.3",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
@ -2104,6 +2139,37 @@ dependencies = [
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
||||
dependencies = [
|
||||
"darling 0.14.4",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_deref"
|
||||
version = "1.1.1"
|
||||
@ -2421,6 +2487,7 @@ dependencies = [
|
||||
"common_utils",
|
||||
"dyn-clone",
|
||||
"error-stack",
|
||||
"hex",
|
||||
"hyper",
|
||||
"hyper-proxy",
|
||||
"masking",
|
||||
@ -2429,6 +2496,7 @@ dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tokio 1.35.1",
|
||||
"vaultrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2453,7 +2521,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-rustls 0.23.2",
|
||||
"mime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -3098,7 +3166,21 @@ dependencies = [
|
||||
"rustls 0.20.9",
|
||||
"rustls-native-certs",
|
||||
"tokio 1.35.1",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.23.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"rustls 0.21.7",
|
||||
"tokio 1.35.1",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4945,6 +5027,7 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
@ -4955,18 +5038,22 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.7",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"system-configuration",
|
||||
"tokio 1.35.1",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.3",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
@ -5302,6 +5389,40 @@ dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustify"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9c02e25271068de581e03ac3bb44db60165ff1a10d92b9530192ccb898bc706"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes 1.5.0",
|
||||
"http",
|
||||
"reqwest",
|
||||
"rustify_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustify_derive"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58135536c18c04f4634bedad182a3f41baf33ef811cc38a3ec7b7061c57134c8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"serde_urlencoded",
|
||||
"syn 1.0.109",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
@ -5670,7 +5791,7 @@ version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
@ -6561,6 +6682,16 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||
dependencies = [
|
||||
"rustls 0.21.7",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
@ -6794,11 +6925,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.36"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@ -6833,20 +6963,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.22"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@ -7155,6 +7285,26 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vaultrs"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28084ac780b443e7f3514df984a2933bd3ab39e71914d951cdf8e4d298a7c9bc"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes 1.5.0",
|
||||
"derive_builder",
|
||||
"http",
|
||||
"reqwest",
|
||||
"rustify",
|
||||
"rustify_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@ -7346,6 +7496,12 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.7"
|
||||
|
||||
9
Makefile
9
Makefile
@ -34,11 +34,18 @@ ROOT_DIR := $(realpath $(ROOT_DIR_WITH_SLASH))
|
||||
release
|
||||
|
||||
|
||||
# Check a local package and all of its dependencies for errors
|
||||
#
|
||||
# Usage :
|
||||
# make check
|
||||
check:
|
||||
cargo check
|
||||
|
||||
|
||||
# Compile application for running on local machine
|
||||
#
|
||||
# Usage :
|
||||
# make build
|
||||
|
||||
build :
|
||||
cargo build
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ license.workspace = true
|
||||
[features]
|
||||
release = ["kms", "vergen"]
|
||||
kms = ["external_services/kms"]
|
||||
hashicorp-vault = ["external_services/hashicorp-vault"]
|
||||
vergen = ["router_env/vergen"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use bb8::PooledConnection;
|
||||
use diesel::PgConnection;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::{self, decrypt::VaultFetch, Kv2};
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms::{self, decrypt::KmsDecrypt};
|
||||
#[cfg(not(feature = "kms"))]
|
||||
@ -27,16 +29,23 @@ pub async fn diesel_make_pg_pool(
|
||||
database: &Database,
|
||||
_test_transaction: bool,
|
||||
#[cfg(feature = "kms")] kms_client: &'static kms::KmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")] hashicorp_client: &'static hashicorp_vault::HashiCorpVault,
|
||||
) -> PgPool {
|
||||
let password = database.password.clone();
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let password = password
|
||||
.fetch_inner::<Kv2>(hashicorp_client)
|
||||
.await
|
||||
.expect("Failed while fetching db password");
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let password = database
|
||||
.password
|
||||
let password = password
|
||||
.decrypt_inner(kms_client)
|
||||
.await
|
||||
.expect("Failed to decrypt password");
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let password = &database.password.peek();
|
||||
let password = &password.peek();
|
||||
|
||||
let database_url = format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
|
||||
@ -17,6 +17,11 @@ pub struct StoreConfig {
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there is a failure while obtaining the HashiCorp client using the provided configuration.
|
||||
/// This panic indicates a critical failure in setting up external services, and the application cannot proceed without a valid HashiCorp client.
|
||||
///
|
||||
pub async fn new(config: &crate::settings::Settings, test_transaction: bool) -> Self {
|
||||
Self {
|
||||
master_pool: diesel_make_pg_pool(
|
||||
@ -24,6 +29,11 @@ impl Store {
|
||||
test_transaction,
|
||||
#[cfg(feature = "kms")]
|
||||
external_services::kms::get_kms_client(&config.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
#[allow(clippy::expect_used)]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault)
|
||||
.await
|
||||
.expect("Failed while getting hashicorp client"),
|
||||
)
|
||||
.await,
|
||||
redis_conn: Arc::new(crate::connection::redis_connection(config).await),
|
||||
|
||||
@ -2,6 +2,8 @@ use std::path::PathBuf;
|
||||
|
||||
use common_utils::ext_traits::ConfigExt;
|
||||
use config::{Environment, File};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use redis_interface as redis;
|
||||
@ -34,6 +36,8 @@ pub struct Settings {
|
||||
pub drainer: DrainerSettings,
|
||||
#[cfg(feature = "kms")]
|
||||
pub kms: kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
pub hc_vault: hashicorp_vault::HashiCorpVaultConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
||||
@ -10,6 +10,7 @@ license.workspace = true
|
||||
[features]
|
||||
kms = ["dep:aws-config", "dep:aws-sdk-kms"]
|
||||
email = ["dep:aws-config"]
|
||||
hashicorp-vault = [ "dep:vaultrs" ]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.68"
|
||||
@ -27,6 +28,8 @@ thiserror = "1.0.40"
|
||||
tokio = "1.35.1"
|
||||
hyper-proxy = "0.9.1"
|
||||
hyper = "0.14.26"
|
||||
vaultrs = { version = "0.7.0", optional = true }
|
||||
hex = "0.4.3"
|
||||
|
||||
# First party crates
|
||||
common_utils = { version = "0.1.0", path = "../common_utils" }
|
||||
|
||||
215
crates/external_services/src/hashicorp_vault.rs
Normal file
215
crates/external_services/src/hashicorp_vault.rs
Normal file
@ -0,0 +1,215 @@
|
||||
//! Interactions with the HashiCorp Vault
|
||||
|
||||
use std::{collections::HashMap, future::Future, pin::Pin};
|
||||
|
||||
use error_stack::{Report, ResultExt};
|
||||
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};
|
||||
|
||||
/// Utilities for supporting decryption of data
|
||||
pub mod decrypt;
|
||||
|
||||
static HC_CLIENT: tokio::sync::OnceCell<HashiCorpVault> = tokio::sync::OnceCell::const_new();
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
/// A struct representing a connection to HashiCorp Vault.
|
||||
pub struct HashiCorpVault {
|
||||
/// The underlying client used for interacting with HashiCorp Vault.
|
||||
client: VaultClient,
|
||||
}
|
||||
|
||||
/// Configuration for connecting to HashiCorp Vault.
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct HashiCorpVaultConfig {
|
||||
/// The URL of the HashiCorp Vault server.
|
||||
pub url: String,
|
||||
/// The authentication token used to access HashiCorp Vault.
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
/// Asynchronously retrieves a HashiCorp Vault client based on the provided configuration.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `config`: A reference to a `HashiCorpVaultConfig` containing the configuration details.
|
||||
pub async fn get_hashicorp_client(
|
||||
config: &HashiCorpVaultConfig,
|
||||
) -> error_stack::Result<&'static HashiCorpVault, HashiCorpError> {
|
||||
HC_CLIENT
|
||||
.get_or_try_init(|| async { HashiCorpVault::new(config) })
|
||||
.await
|
||||
}
|
||||
|
||||
/// A trait defining an engine for interacting with HashiCorp Vault.
|
||||
pub trait Engine: Sized {
|
||||
/// The associated type representing the return type of the engine's operations.
|
||||
type ReturnType<'b, T>
|
||||
where
|
||||
T: 'b,
|
||||
Self: 'b;
|
||||
/// Reads data from HashiCorp Vault at the specified location.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `client`: A reference to the HashiCorpVault client.
|
||||
/// - `location`: The location in HashiCorp Vault to read data from.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A future representing the result of the read operation.
|
||||
fn read(client: &HashiCorpVault, location: String) -> Self::ReturnType<'_, String>;
|
||||
}
|
||||
|
||||
/// An implementation of the `Engine` trait for the Key-Value version 2 (Kv2) engine.
|
||||
#[derive(Debug)]
|
||||
pub enum Kv2 {}
|
||||
|
||||
impl Engine for Kv2 {
|
||||
type ReturnType<'b, T: 'b> =
|
||||
Pin<Box<dyn Future<Output = error_stack::Result<T, HashiCorpError>> + Send + 'b>>;
|
||||
fn read(client: &HashiCorpVault, location: String) -> Self::ReturnType<'_, String> {
|
||||
Box::pin(async move {
|
||||
let mut split = location.split(':');
|
||||
let mount = split.next().ok_or(HashiCorpError::IncompleteData)?;
|
||||
let path = split.next().ok_or(HashiCorpError::IncompleteData)?;
|
||||
let key = split.next().unwrap_or("value");
|
||||
|
||||
let mut output =
|
||||
vaultrs::kv2::read::<HashMap<String, String>>(&client.client, mount, path)
|
||||
.await
|
||||
.map_err(Into::<Report<_>>::into)
|
||||
.change_context(HashiCorpError::FetchFailed)?;
|
||||
|
||||
Ok(output.remove(key).ok_or(HashiCorpError::ParseError)?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl HashiCorpVault {
|
||||
/// Creates a new instance of HashiCorpVault based on the provided configuration.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `config`: A reference to a `HashiCorpVaultConfig` containing the configuration details.
|
||||
///
|
||||
pub fn new(config: &HashiCorpVaultConfig) -> error_stack::Result<Self, HashiCorpError> {
|
||||
VaultClient::new(
|
||||
VaultClientSettingsBuilder::default()
|
||||
.address(&config.url)
|
||||
.token(&config.token)
|
||||
.build()
|
||||
.map_err(Into::<Report<_>>::into)
|
||||
.change_context(HashiCorpError::ClientCreationFailed)
|
||||
.attach_printable("Failed while building vault settings")?,
|
||||
)
|
||||
.map_err(Into::<Report<_>>::into)
|
||||
.change_context(HashiCorpError::ClientCreationFailed)
|
||||
.map(|client| Self { client })
|
||||
}
|
||||
|
||||
/// Asynchronously fetches data from HashiCorp Vault using the specified engine.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `data`: A String representing the location or identifier of the data in HashiCorp Vault.
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// - `En`: The engine type that implements the `Engine` trait.
|
||||
/// - `I`: The type that can be constructed from the retrieved encoded data.
|
||||
///
|
||||
pub async fn fetch<En, I>(&self, data: String) -> error_stack::Result<I, HashiCorpError>
|
||||
where
|
||||
for<'a> En: Engine<
|
||||
ReturnType<'a, String> = Pin<
|
||||
Box<
|
||||
dyn Future<Output = error_stack::Result<String, HashiCorpError>>
|
||||
+ Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
I: FromEncoded,
|
||||
{
|
||||
let output = En::read(self, data).await?;
|
||||
I::from_encoded(output).ok_or(error_stack::report!(HashiCorpError::HexDecodingFailed))
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types that can be constructed from encoded data in the form of a String.
|
||||
pub trait FromEncoded: Sized {
|
||||
/// Constructs an instance of the type from the provided encoded input.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `input`: A String containing the encoded data.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `Option<Self>` representing the constructed instance if successful, or `None` otherwise.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use your_module::{FromEncoded, masking::Secret, Vec};
|
||||
/// let secret_instance = Secret::<String>::from_encoded("encoded_secret_string".to_string());
|
||||
/// let vec_instance = Vec::<u8>::from_encoded("68656c6c6f".to_string());
|
||||
/// ```
|
||||
fn from_encoded(input: String) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl FromEncoded for masking::Secret<String> {
|
||||
fn from_encoded(input: String) -> Option<Self> {
|
||||
Some(input.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromEncoded for Vec<u8> {
|
||||
fn from_encoded(input: String) -> Option<Self> {
|
||||
hex::decode(input).ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration representing various errors that can occur in interactions with HashiCorp Vault.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum HashiCorpError {
|
||||
/// Failed while creating hashicorp client
|
||||
#[error("Failed while creating a new client")]
|
||||
ClientCreationFailed,
|
||||
|
||||
/// Failed while building configurations for hashicorp client
|
||||
#[error("Failed while building configuration")]
|
||||
ConfigurationBuildFailed,
|
||||
|
||||
/// Failed while decoding data to hex format
|
||||
#[error("Failed while decoding hex data")]
|
||||
HexDecodingFailed,
|
||||
|
||||
/// An error occurred when base64 decoding input data.
|
||||
#[error("Failed to base64 decode input data")]
|
||||
Base64DecodingFailed,
|
||||
|
||||
/// An error occurred when KMS decrypting input data.
|
||||
#[error("Failed to KMS decrypt input data")]
|
||||
DecryptionFailed,
|
||||
|
||||
/// The KMS decrypted output does not include a plaintext output.
|
||||
#[error("Missing plaintext KMS decryption output")]
|
||||
MissingPlaintextDecryptionOutput,
|
||||
|
||||
/// An error occurred UTF-8 decoding KMS decrypted output.
|
||||
#[error("Failed to UTF-8 decode decryption output")]
|
||||
Utf8DecodingFailed,
|
||||
|
||||
/// Incomplete data provided to fetch data from hasicorp
|
||||
#[error("Provided information about the value is incomplete")]
|
||||
IncompleteData,
|
||||
|
||||
/// Failed while fetching data from vault
|
||||
#[error("Failed while fetching data from the server")]
|
||||
FetchFailed,
|
||||
|
||||
/// Failed while parsing received data
|
||||
#[error("Failed while parsing the response")]
|
||||
ParseError,
|
||||
}
|
||||
50
crates/external_services/src/hashicorp_vault/decrypt.rs
Normal file
50
crates/external_services/src/hashicorp_vault/decrypt.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
use masking::ExposeInterface;
|
||||
|
||||
/// A trait for types that can be asynchronously fetched and decrypted from HashiCorp Vault.
|
||||
#[async_trait::async_trait]
|
||||
pub trait VaultFetch: Sized {
|
||||
/// Asynchronously decrypts the inner content of the type.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `Result<Self, super::HashiCorpError>` representing the decrypted instance if successful,
|
||||
/// or an `super::HashiCorpError` with details about the encountered error.
|
||||
///
|
||||
async fn fetch_inner<En>(
|
||||
self,
|
||||
client: &super::HashiCorpVault,
|
||||
) -> error_stack::Result<Self, super::HashiCorpError>
|
||||
where
|
||||
for<'a> En: super::Engine<
|
||||
ReturnType<'a, String> = Pin<
|
||||
Box<
|
||||
dyn Future<Output = error_stack::Result<String, super::HashiCorpError>>
|
||||
+ Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl VaultFetch for masking::Secret<String> {
|
||||
async fn fetch_inner<En>(
|
||||
self,
|
||||
client: &super::HashiCorpVault,
|
||||
) -> error_stack::Result<Self, super::HashiCorpError>
|
||||
where
|
||||
for<'a> En: super::Engine<
|
||||
ReturnType<'a, String> = Pin<
|
||||
Box<
|
||||
dyn Future<Output = error_stack::Result<String, super::HashiCorpError>>
|
||||
+ Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
{
|
||||
client.fetch::<En, Self>(self.expose()).await
|
||||
}
|
||||
}
|
||||
@ -190,6 +190,44 @@ impl KmsConfig {
|
||||
#[serde(transparent)]
|
||||
pub struct KmsValue(Secret<String>);
|
||||
|
||||
impl From<String> for KmsValue {
|
||||
fn from(value: String) -> Self {
|
||||
Self(Secret::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Secret<String>> for KmsValue {
|
||||
fn from(value: Secret<String>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
#[async_trait::async_trait]
|
||||
impl super::hashicorp_vault::decrypt::VaultFetch for KmsValue {
|
||||
async fn fetch_inner<En>(
|
||||
self,
|
||||
client: &super::hashicorp_vault::HashiCorpVault,
|
||||
) -> error_stack::Result<Self, super::hashicorp_vault::HashiCorpError>
|
||||
where
|
||||
for<'a> En: super::hashicorp_vault::Engine<
|
||||
ReturnType<'a, String> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = error_stack::Result<
|
||||
String,
|
||||
super::hashicorp_vault::HashiCorpError,
|
||||
>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
{
|
||||
self.0.fetch_inner::<En>(client).await.map(KmsValue)
|
||||
}
|
||||
}
|
||||
|
||||
impl common_utils::ext_traits::ConfigExt for KmsValue {
|
||||
fn is_empty_after_trim(&self) -> bool {
|
||||
self.0.peek().is_empty_after_trim()
|
||||
|
||||
@ -9,6 +9,9 @@ pub mod email;
|
||||
#[cfg(feature = "kms")]
|
||||
pub mod kms;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
pub mod hashicorp_vault;
|
||||
|
||||
/// Crate specific constants
|
||||
#[cfg(feature = "kms")]
|
||||
pub mod consts {
|
||||
|
||||
@ -12,6 +12,7 @@ license.workspace = true
|
||||
default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm"]
|
||||
aws_s3 = ["dep:aws-sdk-s3", "dep:aws-config"]
|
||||
kms = ["external_services/kms", "dep:aws-config"]
|
||||
hashicorp-vault = ["external_services/hashicorp-vault"]
|
||||
email = ["external_services/email", "dep:aws-config", "olap"]
|
||||
frm = []
|
||||
stripe = ["dep:serde_qs"]
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
mod defaults;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
pub mod hc_vault;
|
||||
#[cfg(feature = "kms")]
|
||||
pub mod kms;
|
||||
pub mod settings;
|
||||
|
||||
134
crates/router/src/configs/hc_vault.rs
Normal file
134
crates/router/src/configs/hc_vault.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use external_services::hashicorp_vault::{
|
||||
decrypt::VaultFetch, Engine, HashiCorpError, HashiCorpVault,
|
||||
};
|
||||
use masking::ExposeInterface;
|
||||
|
||||
use crate::configs::settings;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl VaultFetch for settings::Jwekey {
|
||||
async fn fetch_inner<En>(
|
||||
mut self,
|
||||
client: &HashiCorpVault,
|
||||
) -> error_stack::Result<Self, HashiCorpError>
|
||||
where
|
||||
for<'a> En: Engine<
|
||||
ReturnType<'a, String> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = error_stack::Result<String, HashiCorpError>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
{
|
||||
(
|
||||
self.vault_encryption_key,
|
||||
self.rust_locker_encryption_key,
|
||||
self.vault_private_key,
|
||||
self.tunnel_private_key,
|
||||
) = (
|
||||
masking::Secret::new(self.vault_encryption_key)
|
||||
.fetch_inner::<En>(client)
|
||||
.await?
|
||||
.expose(),
|
||||
masking::Secret::new(self.rust_locker_encryption_key)
|
||||
.fetch_inner::<En>(client)
|
||||
.await?
|
||||
.expose(),
|
||||
masking::Secret::new(self.vault_private_key)
|
||||
.fetch_inner::<En>(client)
|
||||
.await?
|
||||
.expose(),
|
||||
masking::Secret::new(self.tunnel_private_key)
|
||||
.fetch_inner::<En>(client)
|
||||
.await?
|
||||
.expose(),
|
||||
);
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl VaultFetch for settings::Database {
|
||||
async fn fetch_inner<En>(
|
||||
mut self,
|
||||
client: &HashiCorpVault,
|
||||
) -> error_stack::Result<Self, HashiCorpError>
|
||||
where
|
||||
for<'a> En: Engine<
|
||||
ReturnType<'a, String> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = error_stack::Result<String, HashiCorpError>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
{
|
||||
Ok(Self {
|
||||
host: self.host,
|
||||
port: self.port,
|
||||
dbname: self.dbname,
|
||||
username: self.username,
|
||||
password: self.password.fetch_inner::<En>(client).await?,
|
||||
pool_size: self.pool_size,
|
||||
connection_timeout: self.connection_timeout,
|
||||
queue_strategy: self.queue_strategy,
|
||||
min_idle: self.min_idle,
|
||||
max_lifetime: self.max_lifetime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
#[async_trait::async_trait]
|
||||
impl VaultFetch for settings::PayPalOnboarding {
|
||||
async fn fetch_inner<En>(
|
||||
mut self,
|
||||
client: &HashiCorpVault,
|
||||
) -> error_stack::Result<Self, HashiCorpError>
|
||||
where
|
||||
for<'a> En: Engine<
|
||||
ReturnType<'a, String> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = error_stack::Result<String, HashiCorpError>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
{
|
||||
self.client_id = self.client_id.fetch_inner::<En>(client).await?;
|
||||
self.client_secret = self.client_secret.fetch_inner::<En>(client).await?;
|
||||
self.partner_id = self.partner_id.fetch_inner::<En>(client).await?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
#[async_trait::async_trait]
|
||||
impl VaultFetch for settings::ConnectorOnboarding {
|
||||
async fn fetch_inner<En>(
|
||||
mut self,
|
||||
client: &HashiCorpVault,
|
||||
) -> error_stack::Result<Self, HashiCorpError>
|
||||
where
|
||||
for<'a> En: Engine<
|
||||
ReturnType<'a, String> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = error_stack::Result<String, HashiCorpError>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
{
|
||||
self.paypal = self.paypal.fetch_inner::<En>(client).await?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,8 @@ use common_utils::ext_traits::ConfigExt;
|
||||
use config::{Environment, File};
|
||||
#[cfg(feature = "email")]
|
||||
use external_services::email::EmailSettings;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use redis_interface::RedisSettings;
|
||||
@ -88,6 +90,8 @@ pub struct Settings {
|
||||
pub api_keys: ApiKeys,
|
||||
#[cfg(feature = "kms")]
|
||||
pub kms: kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
pub hc_vault: hashicorp_vault::HashiCorpVaultConfig,
|
||||
#[cfg(feature = "aws_s3")]
|
||||
pub file_upload_config: FileUploadConfig,
|
||||
pub tokenization: TokenizationConfig,
|
||||
|
||||
@ -2,8 +2,12 @@ use common_utils::date_time;
|
||||
#[cfg(feature = "email")]
|
||||
use diesel_models::{api_keys::ApiKey, enums as storage_enums};
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::decrypt::VaultFetch;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
#[cfg(not(feature = "kms"))]
|
||||
use masking::ExposeInterface;
|
||||
use masking::{PeekInterface, StrongSecret};
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
@ -35,19 +39,37 @@ 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_client: &kms::KmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
) -> errors::RouterResult<&'static StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> {
|
||||
HASH_KEY
|
||||
.get_or_try_init(|| async {
|
||||
let hash_key = {
|
||||
#[cfg(feature = "kms")]
|
||||
let hash_key = api_key_config
|
||||
.kms_encrypted_hash_key
|
||||
{
|
||||
api_key_config.kms_encrypted_hash_key.clone()
|
||||
}
|
||||
#[cfg(not(feature = "kms"))]
|
||||
{
|
||||
masking::Secret::<_, masking::WithType>::new(api_key_config.hash_key.clone())
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let hash_key = hash_key
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let hash_key = hash_key
|
||||
.decrypt_inner(kms_client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to KMS decrypt API key hashing key")?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let hash_key = &api_key_config.hash_key;
|
||||
let hash_key = hash_key.expose();
|
||||
|
||||
<[u8; PlaintextApiKey::HASH_KEY_LEN]>::try_from(
|
||||
hex::decode(hash_key)
|
||||
@ -132,6 +154,8 @@ impl PlaintextApiKey {
|
||||
pub async fn create_api_key(
|
||||
state: AppState,
|
||||
#[cfg(feature = "kms")] kms_client: &kms::KmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
api_key: api::CreateApiKeyRequest,
|
||||
merchant_id: String,
|
||||
) -> RouterResponse<api::CreateApiKeyResponse> {
|
||||
@ -153,6 +177,8 @@ pub async fn create_api_key(
|
||||
api_key_config,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_client,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client,
|
||||
)
|
||||
.await?;
|
||||
let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH);
|
||||
@ -565,6 +591,10 @@ mod tests {
|
||||
&settings.api_keys,
|
||||
#[cfg(feature = "kms")]
|
||||
external_services::kms::get_kms_client(&settings.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&settings.hc_vault)
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -19,6 +19,8 @@ pub async fn retrieve_forex(
|
||||
state.conf.forex_api.local_fetch_retry_count,
|
||||
#[cfg(feature = "kms")]
|
||||
&state.conf.kms,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
&state.conf.hc_vault,
|
||||
)
|
||||
.await
|
||||
.change_context(ApiErrorResponse::GenericNotFoundError {
|
||||
@ -44,6 +46,8 @@ pub async fn convert_forex(
|
||||
from_currency,
|
||||
#[cfg(feature = "kms")]
|
||||
&state.conf.kms,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
&state.conf.hc_vault,
|
||||
))
|
||||
.await
|
||||
.change_context(ApiErrorResponse::InternalServerError)?,
|
||||
|
||||
@ -68,7 +68,7 @@ use crate::{
|
||||
workflows::payment_sync,
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
#[instrument(skip_all, fields(payment_id, merchant_id))]
|
||||
pub async fn payments_operation_core<F, Req, Op, FData, Ctx>(
|
||||
state: &AppState,
|
||||
|
||||
@ -2,8 +2,14 @@ use api_models::payments as payment_types;
|
||||
use async_trait::async_trait;
|
||||
use common_utils::{ext_traits::ByteSliceExt, request::RequestContent};
|
||||
use error_stack::{IntoReport, Report, ResultExt};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::decrypt::VaultFetch;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use masking::ExposeInterface;
|
||||
|
||||
use super::{ConstructFlowSpecificData, Feature};
|
||||
use crate::{
|
||||
@ -177,10 +183,85 @@ async fn create_applepay_session_token(
|
||||
payment_request_data,
|
||||
session_token_data,
|
||||
} => {
|
||||
let (
|
||||
apple_pay_merchant_cert,
|
||||
apple_pay_merchant_cert_key,
|
||||
common_merchant_identifier,
|
||||
) = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client = external_services::hashicorp_vault::get_hashicorp_client(
|
||||
&state.conf.hc_vault,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed while building hashicorp client")?;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
{
|
||||
Ok::<_, Report<errors::ApiErrorResponse>>((
|
||||
masking::Secret::new(
|
||||
state
|
||||
.conf
|
||||
.applepay_decrypt_keys
|
||||
.apple_pay_merchant_cert
|
||||
.clone(),
|
||||
)
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.expose(),
|
||||
masking::Secret::new(
|
||||
state
|
||||
.conf
|
||||
.applepay_decrypt_keys
|
||||
.apple_pay_merchant_cert_key
|
||||
.clone(),
|
||||
)
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.expose(),
|
||||
masking::Secret::new(
|
||||
state
|
||||
.conf
|
||||
.applepay_merchant_configs
|
||||
.common_merchant_identifier
|
||||
.clone(),
|
||||
)
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.expose(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "hashicorp-vault"))]
|
||||
{
|
||||
Ok::<_, Report<errors::ApiErrorResponse>>((
|
||||
state
|
||||
.conf
|
||||
.applepay_decrypt_keys
|
||||
.apple_pay_merchant_cert
|
||||
.clone(),
|
||||
state
|
||||
.conf
|
||||
.applepay_decrypt_keys
|
||||
.apple_pay_merchant_cert_key
|
||||
.clone(),
|
||||
state
|
||||
.conf
|
||||
.applepay_merchant_configs
|
||||
.common_merchant_identifier
|
||||
.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let decrypted_apple_pay_merchant_cert = kms::get_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(&state.conf.applepay_decrypt_keys.apple_pay_merchant_cert)
|
||||
.decrypt(apple_pay_merchant_cert)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Apple pay merchant certificate decryption failed")?;
|
||||
@ -189,7 +270,7 @@ async fn create_applepay_session_token(
|
||||
let decrypted_apple_pay_merchant_cert_key =
|
||||
kms::get_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(&state.conf.applepay_decrypt_keys.apple_pay_merchant_cert_key)
|
||||
.decrypt(apple_pay_merchant_cert_key)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable(
|
||||
@ -199,21 +280,13 @@ async fn create_applepay_session_token(
|
||||
#[cfg(feature = "kms")]
|
||||
let decrypted_merchant_identifier = kms::get_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(
|
||||
&state
|
||||
.conf
|
||||
.applepay_merchant_configs
|
||||
.common_merchant_identifier,
|
||||
)
|
||||
.decrypt(common_merchant_identifier)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Apple pay merchant identifier decryption failed")?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let decrypted_merchant_identifier = &state
|
||||
.conf
|
||||
.applepay_merchant_configs
|
||||
.common_merchant_identifier;
|
||||
let decrypted_merchant_identifier = common_merchant_identifier;
|
||||
|
||||
let apple_pay_session_request = get_session_request_for_simplified_apple_pay(
|
||||
decrypted_merchant_identifier.to_string(),
|
||||
@ -221,12 +294,10 @@ async fn create_applepay_session_token(
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let decrypted_apple_pay_merchant_cert =
|
||||
&state.conf.applepay_decrypt_keys.apple_pay_merchant_cert;
|
||||
let decrypted_apple_pay_merchant_cert = apple_pay_merchant_cert;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let decrypted_apple_pay_merchant_cert_key =
|
||||
&state.conf.applepay_decrypt_keys.apple_pay_merchant_cert_key;
|
||||
let decrypted_apple_pay_merchant_cert_key = apple_pay_merchant_cert_key;
|
||||
|
||||
(
|
||||
payment_request_data,
|
||||
|
||||
@ -13,6 +13,10 @@ use data_models::{
|
||||
use diesel_models::enums;
|
||||
// TODO : Evaluate all the helper functions ()
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::decrypt::VaultFetch;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use josekit::jwe;
|
||||
@ -1250,6 +1254,7 @@ pub async fn get_connector_default(
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub async fn create_customer_if_not_exist<'a, F: Clone, R, Ctx>(
|
||||
operation: BoxedOperation<'a, F, R, Ctx>,
|
||||
db: &dyn StorageInterface,
|
||||
@ -3501,15 +3506,38 @@ impl ApplePayData {
|
||||
&self,
|
||||
state: &AppState,
|
||||
) -> CustomResult<String, errors::ApplePayDecryptionError> {
|
||||
let apple_pay_ppc = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client =
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&state.conf.hc_vault)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)
|
||||
.attach_printable("Failed while creating client")?;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let output =
|
||||
masking::Secret::new(state.conf.applepay_decrypt_keys.apple_pay_ppc.clone())
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)?
|
||||
.expose();
|
||||
|
||||
#[cfg(not(feature = "hashicorp-vault"))]
|
||||
let output = state.conf.applepay_decrypt_keys.apple_pay_ppc.clone();
|
||||
|
||||
Ok::<_, error_stack::Report<errors::ApplePayDecryptionError>>(output)
|
||||
}
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let cert_data = kms::get_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc)
|
||||
.decrypt(&apple_pay_ppc)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let cert_data = &state.conf.applepay_decrypt_keys.apple_pay_ppc;
|
||||
let cert_data = &apple_pay_ppc;
|
||||
|
||||
let base64_decode_cert_data = BASE64_ENGINE
|
||||
.decode(cert_data)
|
||||
@ -3561,15 +3589,39 @@ impl ApplePayData {
|
||||
.change_context(errors::ApplePayDecryptionError::KeyDeserializationFailed)
|
||||
.attach_printable("Failed to deserialize the public key")?;
|
||||
|
||||
let apple_pay_ppc_key = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client =
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&state.conf.hc_vault)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)
|
||||
.attach_printable("Failed while creating client")?;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let output =
|
||||
masking::Secret::new(state.conf.applepay_decrypt_keys.apple_pay_ppc_key.clone())
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)
|
||||
.attach_printable("Failed while creating client")?
|
||||
.expose();
|
||||
|
||||
#[cfg(not(feature = "hashicorp-vault"))]
|
||||
let output = state.conf.applepay_decrypt_keys.apple_pay_ppc_key.clone();
|
||||
|
||||
Ok::<_, error_stack::Report<errors::ApplePayDecryptionError>>(output)
|
||||
}
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let decrypted_apple_pay_ppc_key = kms::get_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc_key)
|
||||
.decrypt(&apple_pay_ppc_key)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let decrypted_apple_pay_ppc_key = &state.conf.applepay_decrypt_keys.apple_pay_ppc_key;
|
||||
let decrypted_apple_pay_ppc_key = &apple_pay_ppc_key;
|
||||
// Create PKey objects from EcKey
|
||||
let private_key = PKey::private_key_from_pem(decrypted_apple_pay_ppc_key.as_bytes())
|
||||
.into_report()
|
||||
|
||||
@ -5,6 +5,8 @@ use api_models::{
|
||||
payment_methods::{self, BankAccountAccessCreds},
|
||||
payments::{AddressDetails, BankDebitBilling, BankDebitData, PaymentMethodData},
|
||||
};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::{self, decrypt::VaultFetch};
|
||||
use hex;
|
||||
pub mod helpers;
|
||||
pub mod transformers;
|
||||
@ -345,15 +347,36 @@ async fn store_bank_details_in_payment_methods(
|
||||
}
|
||||
}
|
||||
|
||||
let pm_auth_key = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client = external_services::hashicorp_vault::get_hashicorp_client(&state.conf.hc_vault)
|
||||
.await
|
||||
.change_context(ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed while creating client")?;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let output = masking::Secret::new(state.conf.payment_method_auth.pm_auth_key.clone())
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(ApiErrorResponse::InternalServerError)?
|
||||
.expose();
|
||||
|
||||
#[cfg(not(feature = "hashicorp-vault"))]
|
||||
let output = state.conf.payment_method_auth.pm_auth_key.clone();
|
||||
|
||||
Ok::<_, error_stack::Report<ApiErrorResponse>>(output)
|
||||
}
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let pm_auth_key = kms::get_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(state.conf.payment_method_auth.pm_auth_key.clone())
|
||||
.decrypt(pm_auth_key)
|
||||
.await
|
||||
.change_context(ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let pm_auth_key = state.conf.payment_method_auth.pm_auth_key.clone();
|
||||
let pm_auth_key = pm_auth_key;
|
||||
|
||||
let mut update_entries: Vec<(storage::PaymentMethod, storage::PaymentMethodUpdate)> =
|
||||
Vec::new();
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use actix_web::{web, HttpRequest, Responder};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use error_stack::ResultExt;
|
||||
use router_env::{instrument, tracing, Flow};
|
||||
|
||||
use super::app::AppState;
|
||||
@ -44,10 +46,20 @@ pub async fn api_key_create(
|
||||
|state, _, payload| async {
|
||||
#[cfg(feature = "kms")]
|
||||
let kms_client = external_services::kms::get_kms_client(&state.clone().conf.kms).await;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let hc_client = external_services::hashicorp_vault::get_hashicorp_client(
|
||||
&state.clone().conf.hc_vault,
|
||||
)
|
||||
.await
|
||||
.change_context(crate::core::errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
api_keys::create_api_key(
|
||||
state,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_client,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client,
|
||||
payload,
|
||||
merchant_id.clone(),
|
||||
)
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::{web, Scope};
|
||||
#[cfg(all(feature = "kms", feature = "olap"))]
|
||||
#[cfg(all(feature = "olap", any(feature = "hashicorp-vault", feature = "kms")))]
|
||||
use analytics::AnalyticsConfig;
|
||||
#[cfg(feature = "email")]
|
||||
use external_services::email::{ses::AwsSes, EmailService};
|
||||
#[cfg(all(feature = "olap", feature = "hashicorp-vault"))]
|
||||
use external_services::hashicorp_vault::decrypt::VaultFetch;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms::{self, decrypt::KmsDecrypt};
|
||||
#[cfg(all(feature = "olap", feature = "kms"))]
|
||||
@ -146,6 +148,12 @@ impl AppState {
|
||||
Box::pin(async move {
|
||||
#[cfg(feature = "kms")]
|
||||
let kms_client = kms::get_kms_client(&conf.kms).await;
|
||||
#[cfg(all(feature = "hashicorp-vault", feature = "olap"))]
|
||||
#[allow(clippy::expect_used)]
|
||||
let hc_client =
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&conf.hc_vault)
|
||||
.await
|
||||
.expect("Failed while creating hashicorp_client");
|
||||
let testable = storage_impl == StorageImpl::PostgresqlTest;
|
||||
#[allow(clippy::expect_used)]
|
||||
let event_handler = conf
|
||||
@ -153,6 +161,7 @@ impl AppState {
|
||||
.get_event_handler()
|
||||
.await
|
||||
.expect("Failed to create event handler");
|
||||
|
||||
let store: Box<dyn StorageInterface> = match storage_impl {
|
||||
StorageImpl::Postgresql | StorageImpl::PostgresqlTest => match &event_handler {
|
||||
EventsHandler::Kafka(kafka_client) => Box::new(
|
||||
@ -180,6 +189,22 @@ impl AppState {
|
||||
),
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "hashicorp-vault", feature = "olap"))]
|
||||
#[allow(clippy::expect_used)]
|
||||
match conf.analytics {
|
||||
AnalyticsConfig::Clickhouse { .. } => {}
|
||||
AnalyticsConfig::Sqlx { ref mut sqlx }
|
||||
| AnalyticsConfig::CombinedCkh { ref mut sqlx, .. }
|
||||
| AnalyticsConfig::CombinedSqlx { ref mut sqlx, .. } => {
|
||||
sqlx.password = sqlx
|
||||
.password
|
||||
.clone()
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed while fetching from hashicorp vault");
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "kms", feature = "olap"))]
|
||||
#[allow(clippy::expect_used)]
|
||||
match conf.analytics {
|
||||
@ -195,6 +220,16 @@ impl AppState {
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "hashicorp-vault", feature = "olap"))]
|
||||
#[allow(clippy::expect_used)]
|
||||
{
|
||||
conf.connector_onboarding = conf
|
||||
.connector_onboarding
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed to decrypt connector onboarding credentials");
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "kms", feature = "olap"))]
|
||||
#[allow(clippy::expect_used)]
|
||||
{
|
||||
@ -208,6 +243,17 @@ impl AppState {
|
||||
#[cfg(feature = "olap")]
|
||||
let pool = crate::analytics::AnalyticsProvider::from_conf(&conf.analytics).await;
|
||||
|
||||
#[cfg(all(feature = "hashicorp-vault", feature = "olap"))]
|
||||
#[allow(clippy::expect_used)]
|
||||
{
|
||||
conf.jwekey = conf
|
||||
.jwekey
|
||||
.clone()
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed to decrypt connector onboarding credentials");
|
||||
}
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
#[allow(clippy::expect_used)]
|
||||
let kms_secrets = settings::ActiveKmsSecrets {
|
||||
|
||||
@ -13,15 +13,15 @@ pub mod recon;
|
||||
#[cfg(feature = "email")]
|
||||
pub mod email;
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
#[cfg(any(feature = "kms", feature = "hashicorp-vault"))]
|
||||
use data_models::errors::StorageError;
|
||||
use data_models::errors::StorageResult;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::decrypt::VaultFetch;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms::{self, decrypt::KmsDecrypt};
|
||||
#[cfg(not(feature = "kms"))]
|
||||
use masking::PeekInterface;
|
||||
use masking::StrongSecret;
|
||||
use masking::{PeekInterface, StrongSecret};
|
||||
#[cfg(feature = "kv_store")]
|
||||
use storage_impl::KVRouterStore;
|
||||
use storage_impl::RouterStore;
|
||||
@ -48,39 +48,58 @@ pub async fn get_store(
|
||||
#[cfg(feature = "kms")]
|
||||
let kms_client = kms::get_kms_client(&config.kms).await;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let hc_client = external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)?;
|
||||
|
||||
let master_config = config.master_database.clone();
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let master_config = master_config
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to fetch data from hashicorp vault")?;
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let master_config = config
|
||||
.master_database
|
||||
.clone()
|
||||
let master_config = master_config
|
||||
.decrypt_inner(kms_client)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to decrypt master database config")?;
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let master_config = config.master_database.clone().into();
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
let replica_config = config.replica_database.clone();
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "hashicorp-vault"))]
|
||||
let replica_config = replica_config
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to fetch data from hashicorp vault")?;
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "kms"))]
|
||||
let replica_config = config
|
||||
.replica_database
|
||||
.clone()
|
||||
let replica_config = replica_config
|
||||
.decrypt_inner(kms_client)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to decrypt replica database config")?;
|
||||
|
||||
#[cfg(all(feature = "olap", not(feature = "kms")))]
|
||||
let replica_config = config.replica_database.clone().into();
|
||||
|
||||
let master_enc_key = get_master_enc_key(
|
||||
config,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_client,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client,
|
||||
)
|
||||
.await;
|
||||
#[cfg(not(feature = "olap"))]
|
||||
let conf = master_config;
|
||||
let conf = master_config.into();
|
||||
#[cfg(feature = "olap")]
|
||||
let conf = (master_config, replica_config);
|
||||
// this would get abstracted, for all cases
|
||||
#[allow(clippy::useless_conversion)]
|
||||
let conf = (master_config.into(), replica_config.into());
|
||||
|
||||
let store: RouterStore<StoreType> = if test_transaction {
|
||||
RouterStore::test_store(conf, &config.redis, master_enc_key).await?
|
||||
@ -110,21 +129,26 @@ pub async fn get_store(
|
||||
async fn get_master_enc_key(
|
||||
conf: &crate::configs::settings::Settings,
|
||||
#[cfg(feature = "kms")] kms_client: &kms::KmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
) -> StrongSecret<Vec<u8>> {
|
||||
let master_enc_key = conf.secrets.master_enc_key.clone();
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let master_enc_key = master_enc_key
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed to fetch master enc key");
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let master_enc_key = hex::decode(
|
||||
conf.secrets
|
||||
.master_enc_key
|
||||
.clone()
|
||||
let master_enc_key = masking::Secret::<_, masking::WithType>::new(
|
||||
master_enc_key
|
||||
.decrypt_inner(kms_client)
|
||||
.await
|
||||
.expect("Failed to decrypt master enc key"),
|
||||
)
|
||||
.expect("Failed to decode from hex");
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let master_enc_key =
|
||||
hex::decode(conf.secrets.master_enc_key.peek()).expect("Failed to decode from hex");
|
||||
let master_enc_key = hex::decode(master_enc_key.peek()).expect("Failed to decode from hex");
|
||||
|
||||
StrongSecret::new(master_enc_key)
|
||||
}
|
||||
|
||||
@ -3,9 +3,13 @@ use api_models::{payment_methods::PaymentMethodListRequest, payments};
|
||||
use async_trait::async_trait;
|
||||
use common_utils::date_time;
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::decrypt::VaultFetch;
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms::{self, decrypt::KmsDecrypt};
|
||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use masking::ExposeInterface;
|
||||
use masking::{PeekInterface, StrongSecret};
|
||||
use serde::Serialize;
|
||||
|
||||
@ -222,6 +226,10 @@ where
|
||||
&config.api_keys,
|
||||
#[cfg(feature = "kms")]
|
||||
kms::get_kms_client(&config.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
@ -281,9 +289,14 @@ static ADMIN_API_KEY: tokio::sync::OnceCell<StrongSecret<String>> =
|
||||
pub async fn get_admin_api_key(
|
||||
secrets: &settings::Secrets,
|
||||
#[cfg(feature = "kms")] kms_client: &kms::KmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
) -> RouterResult<&'static StrongSecret<String>> {
|
||||
ADMIN_API_KEY
|
||||
.get_or_try_init(|| async {
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let admin_api_key = secrets.admin_api_key.clone();
|
||||
|
||||
#[cfg(feature = "kms")]
|
||||
let admin_api_key = secrets
|
||||
.kms_encrypted_admin_api_key
|
||||
@ -292,8 +305,13 @@ pub async fn get_admin_api_key(
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to KMS decrypt admin API key")?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let admin_api_key = secrets.admin_api_key.clone();
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let admin_api_key = masking::Secret::new(admin_api_key)
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to KMS decrypt admin API key")?
|
||||
.expose();
|
||||
|
||||
Ok(StrongSecret::new(admin_api_key))
|
||||
})
|
||||
@ -348,6 +366,11 @@ where
|
||||
&conf.secrets,
|
||||
#[cfg(feature = "kms")]
|
||||
kms::get_kms_client(&conf.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&conf.hc_vault)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed while getting admin api key")?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ use api_models::enums;
|
||||
use common_utils::{date_time, errors::CustomResult, events::ApiEventMetric, ext_traits::AsyncExt};
|
||||
use currency_conversion::types::{CurrencyFactors, ExchangeRates};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::{self, decrypt::VaultFetch};
|
||||
#[cfg(feature = "kms")]
|
||||
use external_services::kms;
|
||||
use masking::PeekInterface;
|
||||
@ -127,6 +129,8 @@ async fn waited_fetch_and_update_caches(
|
||||
local_fetch_retry_delay: u64,
|
||||
local_fetch_retry_count: u64,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
for _n in 1..local_fetch_retry_count {
|
||||
sleep(Duration::from_millis(local_fetch_retry_delay)).await;
|
||||
@ -149,6 +153,8 @@ async fn waited_fetch_and_update_caches(
|
||||
None,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -187,6 +193,8 @@ pub async fn get_forex_rates(
|
||||
local_fetch_retry_delay: u64,
|
||||
local_fetch_retry_count: u64,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
if let Some(local_rates) = retrieve_forex_from_local().await {
|
||||
if local_rates.is_expired(call_delay) {
|
||||
@ -197,6 +205,8 @@ pub async fn get_forex_rates(
|
||||
local_rates,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@ -212,6 +222,8 @@ pub async fn get_forex_rates(
|
||||
local_fetch_retry_count,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -223,6 +235,8 @@ async fn handler_local_no_data(
|
||||
_local_fetch_retry_delay: u64,
|
||||
_local_fetch_retry_count: u64,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match retrieve_forex_from_redis(state).await {
|
||||
Ok(Some(data)) => {
|
||||
@ -232,6 +246,8 @@ async fn handler_local_no_data(
|
||||
call_delay,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -242,6 +258,8 @@ async fn handler_local_no_data(
|
||||
None,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
@ -252,6 +270,8 @@ async fn handler_local_no_data(
|
||||
None,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
@ -262,6 +282,8 @@ async fn successive_fetch_and_save_forex(
|
||||
state: &AppState,
|
||||
stale_redis_data: Option<FxExchangeRatesCacheEntry>,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match acquire_redis_lock(state).await {
|
||||
Ok(lock_acquired) => {
|
||||
@ -272,6 +294,8 @@ async fn successive_fetch_and_save_forex(
|
||||
state,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await;
|
||||
match api_rates {
|
||||
@ -283,6 +307,8 @@ async fn successive_fetch_and_save_forex(
|
||||
state,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await;
|
||||
match secondary_api_rates {
|
||||
@ -326,6 +352,8 @@ async fn fallback_forex_redis_check(
|
||||
redis_data: FxExchangeRatesCacheEntry,
|
||||
call_delay: i64,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match is_redis_expired(Some(redis_data.clone()).as_ref(), call_delay).await {
|
||||
Some(redis_forex) => {
|
||||
@ -341,6 +369,8 @@ async fn fallback_forex_redis_check(
|
||||
Some(redis_data),
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -352,6 +382,8 @@ async fn handler_local_expired(
|
||||
call_delay: i64,
|
||||
local_rates: FxExchangeRatesCacheEntry,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match retrieve_forex_from_redis(state).await {
|
||||
Ok(redis_data) => {
|
||||
@ -370,6 +402,8 @@ async fn handler_local_expired(
|
||||
Some(local_rates),
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -383,6 +417,8 @@ async fn handler_local_expired(
|
||||
Some(local_rates),
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -392,16 +428,40 @@ async fn handler_local_expired(
|
||||
async fn fetch_forex_rates(
|
||||
state: &AppState,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> Result<FxExchangeRatesCacheEntry, error_stack::Report<ForexCacheError>> {
|
||||
let forex_api_key = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client = hashicorp_vault::get_hashicorp_client(hc_config)
|
||||
.await
|
||||
.change_context(ForexCacheError::KmsDecryptionFailed)?;
|
||||
|
||||
#[cfg(not(feature = "hashicorp-vault"))]
|
||||
let output = state.conf.forex_api.api_key.clone();
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let output = state
|
||||
.conf
|
||||
.forex_api
|
||||
.api_key
|
||||
.clone()
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(ForexCacheError::KmsDecryptionFailed)?;
|
||||
|
||||
Ok::<_, error_stack::Report<ForexCacheError>>(output)
|
||||
}
|
||||
.await?;
|
||||
#[cfg(feature = "kms")]
|
||||
let forex_api_key = kms::get_kms_client(kms_config)
|
||||
.await
|
||||
.decrypt(state.conf.forex_api.api_key.peek())
|
||||
.decrypt(forex_api_key.peek())
|
||||
.await
|
||||
.change_context(ForexCacheError::KmsDecryptionFailed)?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let forex_api_key = state.conf.forex_api.api_key.peek();
|
||||
let forex_api_key = forex_api_key.peek();
|
||||
|
||||
let forex_url: String = format!("{}{}{}", FOREX_BASE_URL, forex_api_key, FOREX_BASE_CURRENCY);
|
||||
let forex_request = services::RequestBuilder::new()
|
||||
@ -457,16 +517,39 @@ async fn fetch_forex_rates(
|
||||
pub async fn fallback_fetch_forex_rates(
|
||||
state: &AppState,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
let fallback_api_key = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client = hashicorp_vault::get_hashicorp_client(hc_config)
|
||||
.await
|
||||
.change_context(ForexCacheError::KmsDecryptionFailed)?;
|
||||
|
||||
#[cfg(not(feature = "hashicorp-vault"))]
|
||||
let output = state.conf.forex_api.fallback_api_key.clone();
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let output = state
|
||||
.conf
|
||||
.forex_api
|
||||
.fallback_api_key
|
||||
.clone()
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.await
|
||||
.change_context(ForexCacheError::KmsDecryptionFailed)?;
|
||||
|
||||
Ok::<_, error_stack::Report<ForexCacheError>>(output)
|
||||
}
|
||||
.await?;
|
||||
#[cfg(feature = "kms")]
|
||||
let fallback_forex_api_key = kms::get_kms_client(kms_config)
|
||||
.await
|
||||
.decrypt(state.conf.forex_api.fallback_api_key.peek())
|
||||
.decrypt(fallback_api_key.peek())
|
||||
.await
|
||||
.change_context(ForexCacheError::KmsDecryptionFailed)?;
|
||||
|
||||
#[cfg(not(feature = "kms"))]
|
||||
let fallback_forex_api_key = state.conf.forex_api.fallback_api_key.peek();
|
||||
let fallback_forex_api_key = fallback_api_key.peek();
|
||||
|
||||
let fallback_forex_url: String =
|
||||
format!("{}{}", FALLBACK_FOREX_BASE_URL, fallback_forex_api_key,);
|
||||
@ -609,6 +692,8 @@ pub async fn convert_currency(
|
||||
to_currency: String,
|
||||
from_currency: String,
|
||||
#[cfg(feature = "kms")] kms_config: &kms::KmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
) -> CustomResult<api_models::currency::CurrencyConversionResponse, ForexCacheError> {
|
||||
let rates = get_forex_rates(
|
||||
&state,
|
||||
@ -617,6 +702,8 @@ pub async fn convert_currency(
|
||||
state.conf.forex_api.local_fetch_retry_count,
|
||||
#[cfg(feature = "kms")]
|
||||
kms_config,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config,
|
||||
)
|
||||
.await
|
||||
.change_context(ForexCacheError::ApiError)?;
|
||||
|
||||
@ -22,10 +22,10 @@ serde_path_to_error = "0.1.14"
|
||||
strum = { version = "0.24.1", features = ["derive"] }
|
||||
time = { version = "0.3.21", default-features = false, features = ["formatting"] }
|
||||
tokio = { version = "1.35.1" }
|
||||
tracing = { version = "=0.1.36" }
|
||||
tracing = { version = "0.1.37" }
|
||||
tracing-actix-web = { version = "0.7.8", features = ["opentelemetry_0_19", "uuid_v7"], optional = true }
|
||||
tracing-appender = { version = "0.2.2" }
|
||||
tracing-attributes = "=0.1.22"
|
||||
tracing-attributes = "0.1.27"
|
||||
tracing-opentelemetry = { version = "0.19.0" }
|
||||
tracing-subscriber = { version = "0.3.17", default-features = true, features = ["env-filter", "json", "registry"] }
|
||||
vergen = { version = "8.2.1", optional = true, features = ["cargo", "git", "git2", "rustc"] }
|
||||
|
||||
Reference in New Issue
Block a user