mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
refactor: introducing hyperswitch_interface crates (#3536)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -2495,6 +2495,7 @@ dependencies = [
|
||||
"hex",
|
||||
"hyper",
|
||||
"hyper-proxy",
|
||||
"hyperswitch_interfaces",
|
||||
"masking",
|
||||
"once_cell",
|
||||
"router_env",
|
||||
@ -3214,6 +3215,18 @@ dependencies = [
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperswitch_interfaces"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"common_utils",
|
||||
"dyn-clone",
|
||||
"masking",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.58"
|
||||
@ -5186,6 +5199,7 @@ dependencies = [
|
||||
"hex",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyperswitch_interfaces",
|
||||
"image",
|
||||
"infer 0.13.0",
|
||||
"josekit",
|
||||
|
||||
@ -3,7 +3,10 @@ use diesel::PgConnection;
|
||||
#[cfg(feature = "aws_kms")]
|
||||
use external_services::aws_kms::{self, decrypt::AwsKmsDecrypt};
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault::{self, decrypt::VaultFetch, Kv2};
|
||||
use external_services::hashicorp_vault::{
|
||||
core::{HashiCorpVault, Kv2},
|
||||
decrypt::VaultFetch,
|
||||
};
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
use masking::PeekInterface;
|
||||
|
||||
@ -28,8 +31,8 @@ pub async fn redis_connection(
|
||||
pub async fn diesel_make_pg_pool(
|
||||
database: &Database,
|
||||
_test_transaction: bool,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &'static aws_kms::AwsKmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")] hashicorp_client: &'static hashicorp_vault::HashiCorpVault,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &'static aws_kms::core::AwsKmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")] hashicorp_client: &'static HashiCorpVault,
|
||||
) -> PgPool {
|
||||
let password = database.password.clone();
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
|
||||
@ -34,10 +34,10 @@ impl Store {
|
||||
&config.master_database,
|
||||
test_transaction,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
external_services::aws_kms::get_aws_kms_client(&config.kms).await,
|
||||
external_services::aws_kms::core::get_aws_kms_client(&config.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
#[allow(clippy::expect_used)]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault)
|
||||
external_services::hashicorp_vault::core::get_hashicorp_client(&config.hc_vault)
|
||||
.await
|
||||
.expect("Failed while getting hashicorp client"),
|
||||
)
|
||||
|
||||
@ -14,7 +14,7 @@ use serde::Deserialize;
|
||||
use crate::errors;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub type Password = aws_kms::AwsKmsValue;
|
||||
pub type Password = aws_kms::core::AwsKmsValue;
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
pub type Password = masking::Secret<String>;
|
||||
|
||||
@ -36,9 +36,9 @@ pub struct Settings {
|
||||
pub log: Log,
|
||||
pub drainer: DrainerSettings,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub kms: aws_kms::AwsKmsConfig,
|
||||
pub kms: aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
pub hc_vault: hashicorp_vault::HashiCorpVaultConfig,
|
||||
pub hc_vault: hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
||||
@ -35,5 +35,6 @@ hex = "0.4.3"
|
||||
|
||||
# First party crates
|
||||
common_utils = { version = "0.1.0", path = "../common_utils" }
|
||||
hyperswitch_interfaces = { version = "0.1.0", path = "../hyperswitch_interfaces" }
|
||||
masking = { version = "0.1.0", path = "../masking" }
|
||||
router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] }
|
||||
|
||||
@ -1,284 +1,7 @@
|
||||
//! Interactions with the AWS KMS SDK
|
||||
|
||||
use std::time::Instant;
|
||||
pub mod core;
|
||||
|
||||
use aws_config::meta::region::RegionProviderChain;
|
||||
use aws_sdk_kms::{config::Region, primitives::Blob, Client};
|
||||
use base64::Engine;
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use masking::{PeekInterface, Secret};
|
||||
use router_env::logger;
|
||||
/// decrypting data using the AWS KMS SDK.
|
||||
pub mod decrypt;
|
||||
|
||||
use crate::{consts, metrics};
|
||||
|
||||
static AWS_KMS_CLIENT: tokio::sync::OnceCell<AwsKmsClient> = tokio::sync::OnceCell::const_new();
|
||||
|
||||
/// Returns a shared AWS KMS client, or initializes a new one if not previously initialized.
|
||||
#[inline]
|
||||
pub async fn get_aws_kms_client(config: &AwsKmsConfig) -> &'static AwsKmsClient {
|
||||
AWS_KMS_CLIENT
|
||||
.get_or_init(|| AwsKmsClient::new(config))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Configuration parameters required for constructing a [`AwsKmsClient`].
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AwsKmsConfig {
|
||||
/// The AWS key identifier of the KMS key used to encrypt or decrypt data.
|
||||
pub key_id: String,
|
||||
|
||||
/// The AWS region to send KMS requests to.
|
||||
pub region: String,
|
||||
}
|
||||
|
||||
/// Client for AWS KMS operations.
|
||||
#[derive(Debug)]
|
||||
pub struct AwsKmsClient {
|
||||
inner_client: Client,
|
||||
key_id: String,
|
||||
}
|
||||
|
||||
impl AwsKmsClient {
|
||||
/// Constructs a new AWS KMS client.
|
||||
pub async fn new(config: &AwsKmsConfig) -> Self {
|
||||
let region_provider = RegionProviderChain::first_try(Region::new(config.region.clone()));
|
||||
let sdk_config = aws_config::from_env().region(region_provider).load().await;
|
||||
|
||||
Self {
|
||||
inner_client: Client::new(&sdk_config),
|
||||
key_id: config.key_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypts the provided base64-encoded encrypted data using the AWS KMS SDK. We assume that
|
||||
/// the SDK has the values required to interact with the AWS KMS APIs (`AWS_ACCESS_KEY_ID` and
|
||||
/// `AWS_SECRET_ACCESS_KEY`) either set in environment variables, or that the SDK is running in
|
||||
/// a machine that is able to assume an IAM role.
|
||||
pub async fn decrypt(&self, data: impl AsRef<[u8]>) -> CustomResult<String, AwsKmsError> {
|
||||
let start = Instant::now();
|
||||
let data = consts::BASE64_ENGINE
|
||||
.decode(data)
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::Base64DecodingFailed)?;
|
||||
let ciphertext_blob = Blob::new(data);
|
||||
|
||||
let decrypt_output = self
|
||||
.inner_client
|
||||
.decrypt()
|
||||
.key_id(&self.key_id)
|
||||
.ciphertext_blob(ciphertext_blob)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
// Logging using `Debug` representation of the error as the `Display`
|
||||
// representation does not hold sufficient information.
|
||||
logger::error!(aws_kms_sdk_error=?error, "Failed to AWS KMS decrypt data");
|
||||
metrics::AWS_KMS_DECRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]);
|
||||
error
|
||||
})
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::DecryptionFailed)?;
|
||||
|
||||
let output = decrypt_output
|
||||
.plaintext
|
||||
.ok_or(AwsKmsError::MissingPlaintextDecryptionOutput)
|
||||
.into_report()
|
||||
.and_then(|blob| {
|
||||
String::from_utf8(blob.into_inner())
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::Utf8DecodingFailed)
|
||||
})?;
|
||||
|
||||
let time_taken = start.elapsed();
|
||||
metrics::AWS_KMS_DECRYPT_TIME.record(&metrics::CONTEXT, time_taken.as_secs_f64(), &[]);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Encrypts the provided String data using the AWS KMS SDK. We assume that
|
||||
/// the SDK has the values required to interact with the AWS KMS APIs (`AWS_ACCESS_KEY_ID` and
|
||||
/// `AWS_SECRET_ACCESS_KEY`) either set in environment variables, or that the SDK is running in
|
||||
/// a machine that is able to assume an IAM role.
|
||||
pub async fn encrypt(&self, data: impl AsRef<[u8]>) -> CustomResult<String, AwsKmsError> {
|
||||
let start = Instant::now();
|
||||
let plaintext_blob = Blob::new(data.as_ref());
|
||||
|
||||
let encrypted_output = self
|
||||
.inner_client
|
||||
.encrypt()
|
||||
.key_id(&self.key_id)
|
||||
.plaintext(plaintext_blob)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
// Logging using `Debug` representation of the error as the `Display`
|
||||
// representation does not hold sufficient information.
|
||||
logger::error!(aws_kms_sdk_error=?error, "Failed to AWS KMS encrypt data");
|
||||
metrics::AWS_KMS_ENCRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]);
|
||||
error
|
||||
})
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::EncryptionFailed)?;
|
||||
|
||||
let output = encrypted_output
|
||||
.ciphertext_blob
|
||||
.ok_or(AwsKmsError::MissingCiphertextEncryptionOutput)
|
||||
.into_report()
|
||||
.map(|blob| consts::BASE64_ENGINE.encode(blob.into_inner()))?;
|
||||
let time_taken = start.elapsed();
|
||||
metrics::AWS_KMS_ENCRYPT_TIME.record(&metrics::CONTEXT, time_taken.as_secs_f64(), &[]);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that could occur during AWS KMS operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AwsKmsError {
|
||||
/// An error occurred when base64 encoding input data.
|
||||
#[error("Failed to base64 encode input data")]
|
||||
Base64EncodingFailed,
|
||||
|
||||
/// An error occurred when base64 decoding input data.
|
||||
#[error("Failed to base64 decode input data")]
|
||||
Base64DecodingFailed,
|
||||
|
||||
/// An error occurred when AWS KMS decrypting input data.
|
||||
#[error("Failed to AWS KMS decrypt input data")]
|
||||
DecryptionFailed,
|
||||
|
||||
/// An error occurred when AWS KMS encrypting input data.
|
||||
#[error("Failed to AWS KMS encrypt input data")]
|
||||
EncryptionFailed,
|
||||
|
||||
/// The AWS KMS decrypted output does not include a plaintext output.
|
||||
#[error("Missing plaintext AWS KMS decryption output")]
|
||||
MissingPlaintextDecryptionOutput,
|
||||
|
||||
/// The AWS KMS encrypted output does not include a ciphertext output.
|
||||
#[error("Missing ciphertext AWS KMS encryption output")]
|
||||
MissingCiphertextEncryptionOutput,
|
||||
|
||||
/// An error occurred UTF-8 decoding AWS KMS decrypted output.
|
||||
#[error("Failed to UTF-8 decode decryption output")]
|
||||
Utf8DecodingFailed,
|
||||
|
||||
/// The AWS KMS client has not been initialized.
|
||||
#[error("The AWS KMS client has not been initialized")]
|
||||
AwsKmsClientNotInitialized,
|
||||
}
|
||||
|
||||
impl AwsKmsConfig {
|
||||
/// Verifies that the [`AwsKmsClient`] configuration is usable.
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
use common_utils::{ext_traits::ConfigExt, fp_utils::when};
|
||||
|
||||
when(self.key_id.is_default_or_empty(), || {
|
||||
Err("KMS AWS key ID must not be empty")
|
||||
})?;
|
||||
|
||||
when(self.region.is_default_or_empty(), || {
|
||||
Err("KMS AWS region must not be empty")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a AWS KMS value that can be decrypted.
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize, Eq, PartialEq)]
|
||||
#[serde(transparent)]
|
||||
pub struct AwsKmsValue(Secret<String>);
|
||||
|
||||
impl common_utils::ext_traits::ConfigExt for AwsKmsValue {
|
||||
fn is_empty_after_trim(&self) -> bool {
|
||||
self.0.peek().is_empty_after_trim()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AwsKmsValue {
|
||||
fn from(value: String) -> Self {
|
||||
Self(Secret::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Secret<String>> for AwsKmsValue {
|
||||
fn from(value: Secret<String>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
#[async_trait::async_trait]
|
||||
impl super::hashicorp_vault::decrypt::VaultFetch for AwsKmsValue {
|
||||
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(AwsKmsValue)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
#[tokio::test]
|
||||
async fn check_aws_kms_encryption() {
|
||||
std::env::set_var("AWS_SECRET_ACCESS_KEY", "YOUR SECRET ACCESS KEY");
|
||||
std::env::set_var("AWS_ACCESS_KEY_ID", "YOUR AWS ACCESS KEY ID");
|
||||
use super::*;
|
||||
let config = AwsKmsConfig {
|
||||
key_id: "YOUR AWS KMS KEY ID".to_string(),
|
||||
region: "AWS REGION".to_string(),
|
||||
};
|
||||
|
||||
let data = "hello".to_string();
|
||||
let binding = data.as_bytes();
|
||||
let kms_encrypted_fingerprint = AwsKmsClient::new(&config)
|
||||
.await
|
||||
.encrypt(binding)
|
||||
.await
|
||||
.expect("aws kms encryption failed");
|
||||
|
||||
println!("{}", kms_encrypted_fingerprint);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn check_aws_kms_decrypt() {
|
||||
std::env::set_var("AWS_SECRET_ACCESS_KEY", "YOUR SECRET ACCESS KEY");
|
||||
std::env::set_var("AWS_ACCESS_KEY_ID", "YOUR AWS ACCESS KEY ID");
|
||||
use super::*;
|
||||
let config = AwsKmsConfig {
|
||||
key_id: "YOUR AWS KMS KEY ID".to_string(),
|
||||
region: "AWS REGION".to_string(),
|
||||
};
|
||||
|
||||
// Should decrypt to hello
|
||||
let data = "AWS KMS ENCRYPTED CIPHER".to_string();
|
||||
let binding = data.as_bytes();
|
||||
let kms_encrypted_fingerprint = AwsKmsClient::new(&config)
|
||||
.await
|
||||
.decrypt(binding)
|
||||
.await
|
||||
.expect("aws kms decryption failed");
|
||||
|
||||
println!("{}", kms_encrypted_fingerprint);
|
||||
}
|
||||
}
|
||||
pub mod implementers;
|
||||
|
||||
299
crates/external_services/src/aws_kms/core.rs
Normal file
299
crates/external_services/src/aws_kms/core.rs
Normal file
@ -0,0 +1,299 @@
|
||||
//! Interactions with the AWS KMS SDK
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use aws_config::meta::region::RegionProviderChain;
|
||||
use aws_sdk_kms::{config::Region, primitives::Blob, Client};
|
||||
use base64::Engine;
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use masking::{PeekInterface, Secret};
|
||||
use router_env::logger;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use crate::hashicorp_vault;
|
||||
use crate::{aws_kms::decrypt::AwsKmsDecrypt, consts, metrics};
|
||||
|
||||
pub(crate) static AWS_KMS_CLIENT: tokio::sync::OnceCell<AwsKmsClient> =
|
||||
tokio::sync::OnceCell::const_new();
|
||||
|
||||
/// Returns a shared AWS KMS client, or initializes a new one if not previously initialized.
|
||||
#[inline]
|
||||
pub async fn get_aws_kms_client(config: &AwsKmsConfig) -> &'static AwsKmsClient {
|
||||
AWS_KMS_CLIENT
|
||||
.get_or_init(|| AwsKmsClient::new(config))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Configuration parameters required for constructing a [`AwsKmsClient`].
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AwsKmsConfig {
|
||||
/// The AWS key identifier of the KMS key used to encrypt or decrypt data.
|
||||
pub key_id: String,
|
||||
|
||||
/// The AWS region to send KMS requests to.
|
||||
pub region: String,
|
||||
}
|
||||
|
||||
/// Client for AWS KMS operations.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AwsKmsClient {
|
||||
inner_client: Client,
|
||||
key_id: String,
|
||||
}
|
||||
|
||||
impl AwsKmsClient {
|
||||
/// Constructs a new AWS KMS client.
|
||||
pub async fn new(config: &AwsKmsConfig) -> Self {
|
||||
let region_provider = RegionProviderChain::first_try(Region::new(config.region.clone()));
|
||||
let sdk_config = aws_config::from_env().region(region_provider).load().await;
|
||||
|
||||
Self {
|
||||
inner_client: Client::new(&sdk_config),
|
||||
key_id: config.key_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypts the provided base64-encoded encrypted data using the AWS KMS SDK. We assume that
|
||||
/// the SDK has the values required to interact with the AWS KMS APIs (`AWS_ACCESS_KEY_ID` and
|
||||
/// `AWS_SECRET_ACCESS_KEY`) either set in environment variables, or that the SDK is running in
|
||||
/// a machine that is able to assume an IAM role.
|
||||
pub async fn decrypt(&self, data: impl AsRef<[u8]>) -> CustomResult<String, AwsKmsError> {
|
||||
let start = Instant::now();
|
||||
let data = consts::BASE64_ENGINE
|
||||
.decode(data)
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::Base64DecodingFailed)?;
|
||||
let ciphertext_blob = Blob::new(data);
|
||||
|
||||
let decrypt_output = self
|
||||
.inner_client
|
||||
.decrypt()
|
||||
.key_id(&self.key_id)
|
||||
.ciphertext_blob(ciphertext_blob)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
// Logging using `Debug` representation of the error as the `Display`
|
||||
// representation does not hold sufficient information.
|
||||
logger::error!(aws_kms_sdk_error=?error, "Failed to AWS KMS decrypt data");
|
||||
metrics::AWS_KMS_DECRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]);
|
||||
error
|
||||
})
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::DecryptionFailed)?;
|
||||
|
||||
let output = decrypt_output
|
||||
.plaintext
|
||||
.ok_or(AwsKmsError::MissingPlaintextDecryptionOutput)
|
||||
.into_report()
|
||||
.and_then(|blob| {
|
||||
String::from_utf8(blob.into_inner())
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::Utf8DecodingFailed)
|
||||
})?;
|
||||
|
||||
let time_taken = start.elapsed();
|
||||
metrics::AWS_KMS_DECRYPT_TIME.record(&metrics::CONTEXT, time_taken.as_secs_f64(), &[]);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Encrypts the provided String data using the AWS KMS SDK. We assume that
|
||||
/// the SDK has the values required to interact with the AWS KMS APIs (`AWS_ACCESS_KEY_ID` and
|
||||
/// `AWS_SECRET_ACCESS_KEY`) either set in environment variables, or that the SDK is running in
|
||||
/// a machine that is able to assume an IAM role.
|
||||
pub async fn encrypt(&self, data: impl AsRef<[u8]>) -> CustomResult<String, AwsKmsError> {
|
||||
let start = Instant::now();
|
||||
let plaintext_blob = Blob::new(data.as_ref());
|
||||
|
||||
let encrypted_output = self
|
||||
.inner_client
|
||||
.encrypt()
|
||||
.key_id(&self.key_id)
|
||||
.plaintext(plaintext_blob)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
// Logging using `Debug` representation of the error as the `Display`
|
||||
// representation does not hold sufficient information.
|
||||
logger::error!(aws_kms_sdk_error=?error, "Failed to AWS KMS encrypt data");
|
||||
metrics::AWS_KMS_ENCRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]);
|
||||
error
|
||||
})
|
||||
.into_report()
|
||||
.change_context(AwsKmsError::EncryptionFailed)?;
|
||||
|
||||
let output = encrypted_output
|
||||
.ciphertext_blob
|
||||
.ok_or(AwsKmsError::MissingCiphertextEncryptionOutput)
|
||||
.into_report()
|
||||
.map(|blob| consts::BASE64_ENGINE.encode(blob.into_inner()))?;
|
||||
let time_taken = start.elapsed();
|
||||
metrics::AWS_KMS_ENCRYPT_TIME.record(&metrics::CONTEXT, time_taken.as_secs_f64(), &[]);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that could occur during KMS operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AwsKmsError {
|
||||
/// An error occurred when base64 encoding input data.
|
||||
#[error("Failed to base64 encode input data")]
|
||||
Base64EncodingFailed,
|
||||
|
||||
/// An error occurred when base64 decoding input data.
|
||||
#[error("Failed to base64 decode input data")]
|
||||
Base64DecodingFailed,
|
||||
|
||||
/// An error occurred when AWS KMS decrypting input data.
|
||||
#[error("Failed to AWS KMS decrypt input data")]
|
||||
DecryptionFailed,
|
||||
|
||||
/// An error occurred when AWS KMS encrypting input data.
|
||||
#[error("Failed to AWS KMS encrypt input data")]
|
||||
EncryptionFailed,
|
||||
|
||||
/// The AWS KMS decrypted output does not include a plaintext output.
|
||||
#[error("Missing plaintext AWS KMS decryption output")]
|
||||
MissingPlaintextDecryptionOutput,
|
||||
|
||||
/// The AWS KMS encrypted output does not include a ciphertext output.
|
||||
#[error("Missing ciphertext AWS KMS encryption output")]
|
||||
MissingCiphertextEncryptionOutput,
|
||||
|
||||
/// An error occurred UTF-8 decoding AWS KMS decrypted output.
|
||||
#[error("Failed to UTF-8 decode decryption output")]
|
||||
Utf8DecodingFailed,
|
||||
|
||||
/// The AWS KMS client has not been initialized.
|
||||
#[error("The AWS KMS client has not been initialized")]
|
||||
AwsKmsClientNotInitialized,
|
||||
}
|
||||
|
||||
impl AwsKmsConfig {
|
||||
/// Verifies that the [`AwsKmsClient`] configuration is usable.
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
use common_utils::{ext_traits::ConfigExt, fp_utils::when};
|
||||
|
||||
when(self.key_id.is_default_or_empty(), || {
|
||||
Err("KMS AWS key ID must not be empty")
|
||||
})?;
|
||||
|
||||
when(self.region.is_default_or_empty(), || {
|
||||
Err("KMS AWS region must not be empty")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a AWS KMS value that can be decrypted.
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize, Eq, PartialEq)]
|
||||
#[serde(transparent)]
|
||||
pub struct AwsKmsValue(Secret<String>);
|
||||
|
||||
impl common_utils::ext_traits::ConfigExt for AwsKmsValue {
|
||||
fn is_empty_after_trim(&self) -> bool {
|
||||
self.0.peek().is_empty_after_trim()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AwsKmsValue {
|
||||
fn from(value: String) -> Self {
|
||||
Self(Secret::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Secret<String>> for AwsKmsValue {
|
||||
fn from(value: Secret<String>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
#[async_trait::async_trait]
|
||||
impl hashicorp_vault::decrypt::VaultFetch for AwsKmsValue {
|
||||
async fn fetch_inner<En>(
|
||||
self,
|
||||
client: &hashicorp_vault::core::HashiCorpVault,
|
||||
) -> error_stack::Result<Self, hashicorp_vault::core::HashiCorpError>
|
||||
where
|
||||
for<'a> En: hashicorp_vault::core::Engine<
|
||||
ReturnType<'a, String> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = error_stack::Result<
|
||||
String,
|
||||
hashicorp_vault::core::HashiCorpError,
|
||||
>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
> + 'a,
|
||||
{
|
||||
self.0.fetch_inner::<En>(client).await.map(AwsKmsValue)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AwsKmsDecrypt for &AwsKmsValue {
|
||||
type Output = String;
|
||||
async fn decrypt_inner(
|
||||
self,
|
||||
aws_kms_client: &AwsKmsClient,
|
||||
) -> CustomResult<Self::Output, AwsKmsError> {
|
||||
aws_kms_client
|
||||
.decrypt(self.0.peek())
|
||||
.await
|
||||
.attach_printable("Failed to decrypt AWS KMS value")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
#[tokio::test]
|
||||
async fn check_aws_kms_encryption() {
|
||||
std::env::set_var("AWS_SECRET_ACCESS_KEY", "YOUR SECRET ACCESS KEY");
|
||||
std::env::set_var("AWS_ACCESS_KEY_ID", "YOUR AWS ACCESS KEY ID");
|
||||
use super::*;
|
||||
let config = AwsKmsConfig {
|
||||
key_id: "YOUR AWS KMS KEY ID".to_string(),
|
||||
region: "AWS REGION".to_string(),
|
||||
};
|
||||
|
||||
let data = "hello".to_string();
|
||||
let binding = data.as_bytes();
|
||||
let kms_encrypted_fingerprint = AwsKmsClient::new(&config)
|
||||
.await
|
||||
.encrypt(binding)
|
||||
.await
|
||||
.expect("aws kms encryption failed");
|
||||
|
||||
println!("{}", kms_encrypted_fingerprint);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn check_aws_kms_decrypt() {
|
||||
std::env::set_var("AWS_SECRET_ACCESS_KEY", "YOUR SECRET ACCESS KEY");
|
||||
std::env::set_var("AWS_ACCESS_KEY_ID", "YOUR AWS ACCESS KEY ID");
|
||||
use super::*;
|
||||
let config = AwsKmsConfig {
|
||||
key_id: "YOUR AWS KMS KEY ID".to_string(),
|
||||
region: "AWS REGION".to_string(),
|
||||
};
|
||||
|
||||
// Should decrypt to hello
|
||||
let data = "AWS KMS ENCRYPTED CIPHER".to_string();
|
||||
let binding = data.as_bytes();
|
||||
let kms_encrypted_fingerprint = AwsKmsClient::new(&config)
|
||||
.await
|
||||
.decrypt(binding)
|
||||
.await
|
||||
.expect("aws kms decryption failed");
|
||||
|
||||
println!("{}", kms_encrypted_fingerprint);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
//! Decrypting data using the AWS KMS SDK.
|
||||
use common_utils::errors::CustomResult;
|
||||
|
||||
use super::*;
|
||||
use crate::aws_kms::core::{AwsKmsClient, AwsKmsError, AWS_KMS_CLIENT};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
/// This trait performs in place decryption of the structure on which this is implemented
|
||||
@ -26,17 +27,3 @@ pub trait AwsKmsDecrypt {
|
||||
self.decrypt_inner(client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AwsKmsDecrypt for &AwsKmsValue {
|
||||
type Output = String;
|
||||
async fn decrypt_inner(
|
||||
self,
|
||||
aws_kms_client: &AwsKmsClient,
|
||||
) -> CustomResult<Self::Output, AwsKmsError> {
|
||||
aws_kms_client
|
||||
.decrypt(self.0.peek())
|
||||
.await
|
||||
.attach_printable("Failed to decrypt AWS KMS value")
|
||||
}
|
||||
}
|
||||
|
||||
41
crates/external_services/src/aws_kms/implementers.rs
Normal file
41
crates/external_services/src/aws_kms/implementers.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! Trait implementations for aws kms client
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_interfaces::{
|
||||
encryption_interface::{EncryptionError, EncryptionManagementInterface},
|
||||
secrets_interface::{SecretManagementInterface, SecretsManagementError},
|
||||
};
|
||||
use masking::{PeekInterface, Secret};
|
||||
|
||||
use crate::aws_kms::core::AwsKmsClient;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EncryptionManagementInterface for AwsKmsClient {
|
||||
async fn encrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError> {
|
||||
self.encrypt(input)
|
||||
.await
|
||||
.change_context(EncryptionError::EncryptionFailed)
|
||||
.map(|val| val.into_bytes())
|
||||
}
|
||||
|
||||
async fn decrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError> {
|
||||
self.decrypt(input)
|
||||
.await
|
||||
.change_context(EncryptionError::DecryptionFailed)
|
||||
.map(|val| val.into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SecretManagementInterface for AwsKmsClient {
|
||||
async fn get_secret(
|
||||
&self,
|
||||
input: Secret<String>,
|
||||
) -> CustomResult<Secret<String>, SecretsManagementError> {
|
||||
self.decrypt(input.peek())
|
||||
.await
|
||||
.change_context(SecretsManagementError::FetchSecretFailed)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
@ -1,215 +1,7 @@
|
||||
//! Interactions with the HashiCorp Vault
|
||||
|
||||
use std::{collections::HashMap, future::Future, pin::Pin};
|
||||
pub mod core;
|
||||
|
||||
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,
|
||||
}
|
||||
pub mod implementers;
|
||||
|
||||
227
crates/external_services/src/hashicorp_vault/core.rs
Normal file
227
crates/external_services/src/hashicorp_vault/core.rs
Normal file
@ -0,0 +1,227 @@
|
||||
//! Interactions with the HashiCorp Vault
|
||||
|
||||
use std::{collections::HashMap, future::Future, pin::Pin};
|
||||
|
||||
use common_utils::{ext_traits::ConfigExt, fp_utils::when};
|
||||
use error_stack::{Report, ResultExt};
|
||||
use masking::{PeekInterface, Secret};
|
||||
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};
|
||||
|
||||
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: Secret<String>,
|
||||
}
|
||||
|
||||
impl HashiCorpVaultConfig {
|
||||
/// Verifies that the [`HashiCorpVault`] configuration is usable.
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
when(self.url.is_default_or_empty(), || {
|
||||
Err("HashiCorp vault url must not be empty")
|
||||
})?;
|
||||
|
||||
when(self.token.is_default_or_empty(), || {
|
||||
Err("HashiCorp vault token must not be empty")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.peek())
|
||||
.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 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,
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
//! Utilities for supporting decryption of data
|
||||
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
use masking::ExposeInterface;
|
||||
|
||||
use crate::hashicorp_vault::core::{Engine, HashiCorpError, HashiCorpVault};
|
||||
|
||||
/// A trait for types that can be asynchronously fetched and decrypted from HashiCorp Vault.
|
||||
#[async_trait::async_trait]
|
||||
pub trait VaultFetch: Sized {
|
||||
@ -14,13 +18,13 @@ pub trait VaultFetch: Sized {
|
||||
///
|
||||
async fn fetch_inner<En>(
|
||||
self,
|
||||
client: &super::HashiCorpVault,
|
||||
) -> error_stack::Result<Self, super::HashiCorpError>
|
||||
client: &HashiCorpVault,
|
||||
) -> error_stack::Result<Self, HashiCorpError>
|
||||
where
|
||||
for<'a> En: super::Engine<
|
||||
for<'a> En: Engine<
|
||||
ReturnType<'a, String> = Pin<
|
||||
Box<
|
||||
dyn Future<Output = error_stack::Result<String, super::HashiCorpError>>
|
||||
dyn Future<Output = error_stack::Result<String, HashiCorpError>>
|
||||
+ Send
|
||||
+ 'a,
|
||||
>,
|
||||
@ -32,13 +36,13 @@ pub trait VaultFetch: Sized {
|
||||
impl VaultFetch for masking::Secret<String> {
|
||||
async fn fetch_inner<En>(
|
||||
self,
|
||||
client: &super::HashiCorpVault,
|
||||
) -> error_stack::Result<Self, super::HashiCorpError>
|
||||
client: &HashiCorpVault,
|
||||
) -> error_stack::Result<Self, HashiCorpError>
|
||||
where
|
||||
for<'a> En: super::Engine<
|
||||
for<'a> En: Engine<
|
||||
ReturnType<'a, String> = Pin<
|
||||
Box<
|
||||
dyn Future<Output = error_stack::Result<String, super::HashiCorpError>>
|
||||
dyn Future<Output = error_stack::Result<String, HashiCorpError>>
|
||||
+ Send
|
||||
+ 'a,
|
||||
>,
|
||||
|
||||
24
crates/external_services/src/hashicorp_vault/implementers.rs
Normal file
24
crates/external_services/src/hashicorp_vault/implementers.rs
Normal file
@ -0,0 +1,24 @@
|
||||
//! Trait implementations for Hashicorp vault client
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_interfaces::secrets_interface::{
|
||||
SecretManagementInterface, SecretsManagementError,
|
||||
};
|
||||
use masking::{ExposeInterface, Secret};
|
||||
|
||||
use crate::hashicorp_vault::core::{HashiCorpVault, Kv2};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SecretManagementInterface for HashiCorpVault {
|
||||
async fn get_secret(
|
||||
&self,
|
||||
input: Secret<String>,
|
||||
) -> CustomResult<Secret<String>, SecretsManagementError> {
|
||||
self.fetch::<Kv2, Secret<String>>(input.expose())
|
||||
.await
|
||||
.map(|val| val.expose().to_owned())
|
||||
.change_context(SecretsManagementError::FetchSecretFailed)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,10 @@ pub mod file_storage;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
pub mod hashicorp_vault;
|
||||
|
||||
pub mod no_encryption;
|
||||
|
||||
pub mod managers;
|
||||
|
||||
/// Crate specific constants
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub mod consts {
|
||||
|
||||
5
crates/external_services/src/managers.rs
Normal file
5
crates/external_services/src/managers.rs
Normal file
@ -0,0 +1,5 @@
|
||||
//! Config and client managers
|
||||
|
||||
pub mod encryption_management;
|
||||
|
||||
pub mod secrets_management;
|
||||
@ -0,0 +1,53 @@
|
||||
//!
|
||||
//! Encryption management util module
|
||||
//!
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
use hyperswitch_interfaces::encryption_interface::{
|
||||
EncryptionError, EncryptionManagementInterface,
|
||||
};
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
use crate::aws_kms;
|
||||
use crate::no_encryption::core::NoEncryption;
|
||||
|
||||
/// Enum representing configuration options for encryption management.
|
||||
#[derive(Debug, Clone, Default, serde::Deserialize)]
|
||||
#[serde(tag = "encryption_manager")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EncryptionManagementConfig {
|
||||
/// AWS KMS configuration
|
||||
#[cfg(feature = "aws_kms")]
|
||||
AwsKms {
|
||||
/// AWS KMS config
|
||||
aws_kms: aws_kms::core::AwsKmsConfig,
|
||||
},
|
||||
|
||||
/// Variant representing no encryption
|
||||
#[default]
|
||||
NoEncryption,
|
||||
}
|
||||
|
||||
impl EncryptionManagementConfig {
|
||||
/// Verifies that the client configuration is usable
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
match self {
|
||||
#[cfg(feature = "aws_kms")]
|
||||
Self::AwsKms { aws_kms } => aws_kms.validate(),
|
||||
|
||||
Self::NoEncryption => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the appropriate encryption client based on the configuration.
|
||||
pub async fn get_encryption_management_client(
|
||||
&self,
|
||||
) -> CustomResult<Box<dyn EncryptionManagementInterface>, EncryptionError> {
|
||||
Ok(match self {
|
||||
#[cfg(feature = "aws_kms")]
|
||||
Self::AwsKms { aws_kms } => Box::new(aws_kms::core::AwsKmsClient::new(aws_kms).await),
|
||||
|
||||
Self::NoEncryption => Box::new(NoEncryption),
|
||||
})
|
||||
}
|
||||
}
|
||||
72
crates/external_services/src/managers/secrets_management.rs
Normal file
72
crates/external_services/src/managers/secrets_management.rs
Normal file
@ -0,0 +1,72 @@
|
||||
//!
|
||||
//! Secrets management util module
|
||||
//!
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_interfaces::secrets_interface::{
|
||||
SecretManagementInterface, SecretsManagementError,
|
||||
};
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
use crate::aws_kms;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use crate::hashicorp_vault;
|
||||
use crate::no_encryption::core::NoEncryption;
|
||||
|
||||
/// Enum representing configuration options for secrets management.
|
||||
#[derive(Debug, Clone, Default, serde::Deserialize)]
|
||||
#[serde(tag = "secrets_manager")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SecretsManagementConfig {
|
||||
/// AWS KMS configuration
|
||||
#[cfg(feature = "aws_kms")]
|
||||
AwsKms {
|
||||
/// AWS KMS config
|
||||
aws_kms: aws_kms::core::AwsKmsConfig,
|
||||
},
|
||||
|
||||
/// HashiCorp-Vault configuration
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
HashiCorpVault {
|
||||
/// HC-Vault config
|
||||
hc_vault: hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
},
|
||||
|
||||
/// Variant representing no encryption
|
||||
#[default]
|
||||
NoEncryption,
|
||||
}
|
||||
|
||||
impl SecretsManagementConfig {
|
||||
/// Verifies that the client configuration is usable
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
match self {
|
||||
#[cfg(feature = "aws_kms")]
|
||||
Self::AwsKms { aws_kms } => aws_kms.validate(),
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
Self::HashiCorpVault { hc_vault } => hc_vault.validate(),
|
||||
Self::NoEncryption => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the appropriate secret management client based on the configuration.
|
||||
pub async fn get_secret_management_client(
|
||||
&self,
|
||||
) -> CustomResult<Box<dyn SecretManagementInterface>, SecretsManagementError> {
|
||||
match self {
|
||||
#[cfg(feature = "aws_kms")]
|
||||
Self::AwsKms { aws_kms } => {
|
||||
Ok(Box::new(aws_kms::core::AwsKmsClient::new(aws_kms).await))
|
||||
}
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
Self::HashiCorpVault { hc_vault } => {
|
||||
hashicorp_vault::core::HashiCorpVault::new(hc_vault)
|
||||
.change_context(SecretsManagementError::ClientCreationFailed)
|
||||
.map(|inner| -> Box<dyn SecretManagementInterface> { Box::new(inner) })
|
||||
}
|
||||
Self::NoEncryption => Ok(Box::new(NoEncryption)),
|
||||
}
|
||||
}
|
||||
}
|
||||
7
crates/external_services/src/no_encryption.rs
Normal file
7
crates/external_services/src/no_encryption.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//!
|
||||
//! No encryption functionalities
|
||||
//!
|
||||
|
||||
pub mod core;
|
||||
|
||||
pub mod implementers;
|
||||
17
crates/external_services/src/no_encryption/core.rs
Normal file
17
crates/external_services/src/no_encryption/core.rs
Normal file
@ -0,0 +1,17 @@
|
||||
//! No encryption core functionalities
|
||||
|
||||
/// No encryption type
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NoEncryption;
|
||||
|
||||
impl NoEncryption {
|
||||
/// Encryption functionality
|
||||
pub fn encrypt(&self, data: impl AsRef<[u8]>) -> Vec<u8> {
|
||||
data.as_ref().into()
|
||||
}
|
||||
|
||||
/// Decryption functionality
|
||||
pub fn decrypt(&self, data: impl AsRef<[u8]>) -> Vec<u8> {
|
||||
data.as_ref().into()
|
||||
}
|
||||
}
|
||||
36
crates/external_services/src/no_encryption/implementers.rs
Normal file
36
crates/external_services/src/no_encryption/implementers.rs
Normal file
@ -0,0 +1,36 @@
|
||||
//! Trait implementations for No encryption client
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use hyperswitch_interfaces::{
|
||||
encryption_interface::{EncryptionError, EncryptionManagementInterface},
|
||||
secrets_interface::{SecretManagementInterface, SecretsManagementError},
|
||||
};
|
||||
use masking::{ExposeInterface, Secret};
|
||||
|
||||
use crate::no_encryption::core::NoEncryption;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EncryptionManagementInterface for NoEncryption {
|
||||
async fn encrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError> {
|
||||
Ok(self.encrypt(input))
|
||||
}
|
||||
|
||||
async fn decrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError> {
|
||||
Ok(self.decrypt(input))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SecretManagementInterface for NoEncryption {
|
||||
async fn get_secret(
|
||||
&self,
|
||||
input: Secret<String>,
|
||||
) -> CustomResult<Secret<String>, SecretsManagementError> {
|
||||
String::from_utf8(self.decrypt(input.expose()))
|
||||
.map(Into::into)
|
||||
.into_report()
|
||||
.change_context(SecretsManagementError::FetchSecretFailed)
|
||||
.attach_printable("Failed to convert decrypted value to UTF-8")
|
||||
}
|
||||
}
|
||||
17
crates/hyperswitch_interfaces/Cargo.toml
Normal file
17
crates/hyperswitch_interfaces/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "hyperswitch_interfaces"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme = "README.md"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.68"
|
||||
dyn-clone = "1.0.11"
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
thiserror = "1.0.40"
|
||||
|
||||
# First party crates
|
||||
common_utils = { version = "0.1.0", path = "../common_utils" }
|
||||
masking = { version = "0.1.0", path = "../masking" }
|
||||
3
crates/hyperswitch_interfaces/README.md
Normal file
3
crates/hyperswitch_interfaces/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Hyperswitch Interfaces
|
||||
|
||||
This crate includes interfaces and its error types
|
||||
29
crates/hyperswitch_interfaces/src/encryption_interface.rs
Normal file
29
crates/hyperswitch_interfaces/src/encryption_interface.rs
Normal file
@ -0,0 +1,29 @@
|
||||
//! Encryption related interface and error types
|
||||
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
|
||||
/// Trait defining the interface for encryption management
|
||||
#[async_trait::async_trait]
|
||||
pub trait EncryptionManagementInterface: Sync + Send + dyn_clone::DynClone {
|
||||
/// Encrypt the given input data
|
||||
async fn encrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError>;
|
||||
|
||||
/// Decrypt the given input data
|
||||
async fn decrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError>;
|
||||
}
|
||||
|
||||
dyn_clone::clone_trait_object!(EncryptionManagementInterface);
|
||||
|
||||
/// Errors that may occur during above encryption functionalities
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum EncryptionError {
|
||||
/// An error occurred when encrypting input data.
|
||||
#[error("Failed to encrypt input data")]
|
||||
EncryptionFailed,
|
||||
|
||||
/// An error occurred when decrypting input data.
|
||||
#[error("Failed to decrypt input data")]
|
||||
DecryptionFailed,
|
||||
}
|
||||
7
crates/hyperswitch_interfaces/src/lib.rs
Normal file
7
crates/hyperswitch_interfaces/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! Hyperswitch interface
|
||||
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
pub mod secrets_interface;
|
||||
|
||||
pub mod encryption_interface;
|
||||
36
crates/hyperswitch_interfaces/src/secrets_interface.rs
Normal file
36
crates/hyperswitch_interfaces/src/secrets_interface.rs
Normal file
@ -0,0 +1,36 @@
|
||||
//! Secrets management interface
|
||||
|
||||
pub mod secret_handler;
|
||||
|
||||
pub mod secret_state;
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
use masking::Secret;
|
||||
|
||||
/// Trait defining the interface for managing application secrets
|
||||
#[async_trait::async_trait]
|
||||
pub trait SecretManagementInterface: Send + Sync {
|
||||
/// Given an input, encrypt/store the secret
|
||||
// async fn store_secret(
|
||||
// &self,
|
||||
// input: Secret<String>,
|
||||
// ) -> CustomResult<String, SecretsManagementError>;
|
||||
|
||||
/// Given an input, decrypt/retrieve the secret
|
||||
async fn get_secret(
|
||||
&self,
|
||||
input: Secret<String>,
|
||||
) -> CustomResult<Secret<String>, SecretsManagementError>;
|
||||
}
|
||||
|
||||
/// Errors that may occur during secret management
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SecretsManagementError {
|
||||
/// An error occurred when retrieving raw data.
|
||||
#[error("Failed to fetch the raw data")]
|
||||
FetchSecretFailed,
|
||||
|
||||
/// Failed while creating kms client
|
||||
#[error("Failed while creating a secrets management client")]
|
||||
ClientCreationFailed,
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
//! Module containing trait for raw secret retrieval
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
|
||||
use crate::secrets_interface::{
|
||||
secret_state::{RawSecret, SecretStateContainer, SecuredSecret},
|
||||
SecretManagementInterface, SecretsManagementError,
|
||||
};
|
||||
|
||||
/// Trait defining the interface for retrieving a raw secret value, given a secured value
|
||||
#[async_trait::async_trait]
|
||||
pub trait SecretsHandler
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Construct `Self` with raw secret value and transitions its type from `SecuredSecret` to `RawSecret`
|
||||
async fn convert_to_raw_secret(
|
||||
value: SecretStateContainer<Self, SecuredSecret>,
|
||||
kms_client: Box<dyn SecretManagementInterface>,
|
||||
) -> CustomResult<SecretStateContainer<Self, RawSecret>, SecretsManagementError>;
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
//! Module to manage encrypted and decrypted states for a given type.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
/// Trait defining the states of a secret
|
||||
pub trait SecretState {}
|
||||
|
||||
/// Decrypted state
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub enum RawSecret {}
|
||||
|
||||
/// Encrypted state
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub enum SecuredSecret {}
|
||||
|
||||
impl SecretState for RawSecret {}
|
||||
impl SecretState for SecuredSecret {}
|
||||
|
||||
/// Struct for managing the encrypted and decrypted states of a given type
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SecretStateContainer<T, S: SecretState> {
|
||||
inner: T,
|
||||
marker: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<T: Clone, S: SecretState> SecretStateContainer<T, S> {
|
||||
///
|
||||
/// Get the inner data while consuming self
|
||||
///
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the reference to inner value
|
||||
///
|
||||
#[inline]
|
||||
pub fn get_inner(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: Deserialize<'de>, S: SecretState> Deserialize<'de> for SecretStateContainer<T, S> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let val = Deserialize::deserialize(deserializer)?;
|
||||
Ok(Self {
|
||||
inner: val,
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SecretStateContainer<T, SecuredSecret> {
|
||||
/// Transition the secret state from `SecuredSecret` to `RawSecret`
|
||||
pub fn transition_state(
|
||||
mut self,
|
||||
decryptor_fn: impl FnOnce(T) -> T,
|
||||
) -> SecretStateContainer<T, RawSecret> {
|
||||
self.inner = decryptor_fn(self.inner);
|
||||
SecretStateContainer {
|
||||
inner: self.inner,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,6 +111,7 @@ diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_
|
||||
euclid = { version = "0.1.0", path = "../euclid", features = ["valued_jit"] }
|
||||
pm_auth = { version = "0.1.0", path = "../pm_auth", package = "pm_auth" }
|
||||
external_services = { version = "0.1.0", path = "../external_services" }
|
||||
hyperswitch_interfaces = { version = "0.1.0", path = "../hyperswitch_interfaces" }
|
||||
kgraph_utils = { version = "0.1.0", path = "../kgraph_utils" }
|
||||
masking = { version = "0.1.0", path = "../masking" }
|
||||
redis_interface = { version = "0.1.0", path = "../redis_interface" }
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use common_utils::errors::CustomResult;
|
||||
use external_services::aws_kms::{decrypt::AwsKmsDecrypt, AwsKmsClient, AwsKmsError};
|
||||
use external_services::aws_kms::{
|
||||
core::{AwsKmsClient, AwsKmsError},
|
||||
decrypt::AwsKmsDecrypt,
|
||||
};
|
||||
use masking::ExposeInterface;
|
||||
|
||||
use crate::configs::settings;
|
||||
|
||||
@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
|
||||
|
||||
use api_models::{enums, payment_methods::RequiredFieldInfo};
|
||||
#[cfg(feature = "aws_kms")]
|
||||
use external_services::aws_kms::AwsKmsValue;
|
||||
use external_services::aws_kms::core::AwsKmsValue;
|
||||
|
||||
use super::settings::{ConnectorFields, Password, PaymentMethodType, RequiredFieldFinal};
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use external_services::hashicorp_vault::{
|
||||
decrypt::VaultFetch, Engine, HashiCorpError, HashiCorpVault,
|
||||
core::{Engine, HashiCorpError, HashiCorpVault},
|
||||
decrypt::VaultFetch,
|
||||
};
|
||||
use masking::ExposeInterface;
|
||||
|
||||
|
||||
@ -12,9 +12,15 @@ use config::{Environment, File};
|
||||
use external_services::aws_kms;
|
||||
#[cfg(feature = "email")]
|
||||
use external_services::email::EmailSettings;
|
||||
use external_services::file_storage::FileStorageConfig;
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
use external_services::hashicorp_vault;
|
||||
use external_services::{
|
||||
file_storage::FileStorageConfig,
|
||||
managers::{
|
||||
encryption_management::EncryptionManagementConfig,
|
||||
secrets_management::SecretsManagementConfig,
|
||||
},
|
||||
};
|
||||
use redis_interface::RedisSettings;
|
||||
pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry};
|
||||
use rust_decimal::Decimal;
|
||||
@ -30,7 +36,7 @@ use crate::{
|
||||
events::EventsConfig,
|
||||
};
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub type Password = aws_kms::AwsKmsValue;
|
||||
pub type Password = aws_kms::core::AwsKmsValue;
|
||||
#[cfg(not(feature = "aws_kms"))]
|
||||
pub type Password = masking::Secret<String>;
|
||||
|
||||
@ -89,10 +95,12 @@ pub struct Settings {
|
||||
pub bank_config: BankRedirectConfig,
|
||||
pub api_keys: ApiKeys,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub kms: aws_kms::AwsKmsConfig,
|
||||
pub kms: aws_kms::core::AwsKmsConfig,
|
||||
pub file_storage: FileStorageConfig,
|
||||
pub encryption_management: EncryptionManagementConfig,
|
||||
pub secrets_management: SecretsManagementConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
pub hc_vault: hashicorp_vault::HashiCorpVaultConfig,
|
||||
pub hc_vault: hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
pub tokenization: TokenizationConfig,
|
||||
pub connector_customer: ConnectorCustomer,
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
@ -368,11 +376,11 @@ pub struct Secrets {
|
||||
pub recon_admin_api_key: String,
|
||||
pub master_enc_key: Password,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub kms_encrypted_jwt_secret: aws_kms::AwsKmsValue,
|
||||
pub kms_encrypted_jwt_secret: aws_kms::core::AwsKmsValue,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub kms_encrypted_admin_api_key: aws_kms::AwsKmsValue,
|
||||
pub kms_encrypted_admin_api_key: aws_kms::core::AwsKmsValue,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub kms_encrypted_recon_admin_api_key: aws_kms::AwsKmsValue,
|
||||
pub kms_encrypted_recon_admin_api_key: aws_kms::core::AwsKmsValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
@ -598,7 +606,7 @@ pub struct ApiKeys {
|
||||
/// Base64-encoded (KMS encrypted) ciphertext of the key used for calculating hashes of API
|
||||
/// keys
|
||||
#[cfg(feature = "aws_kms")]
|
||||
pub kms_encrypted_hash_key: aws_kms::AwsKmsValue,
|
||||
pub kms_encrypted_hash_key: aws_kms::core::AwsKmsValue,
|
||||
|
||||
/// Hex-encoded 32-byte long (64 characters long when hex-encoded) key used for calculating
|
||||
/// hashes of API keys
|
||||
@ -724,6 +732,14 @@ impl Settings {
|
||||
|
||||
self.lock_settings.validate()?;
|
||||
self.events.validate()?;
|
||||
|
||||
self.encryption_management
|
||||
.validate()
|
||||
.map_err(|err| ApplicationError::InvalidConfigurationValueError(err.into()))?;
|
||||
|
||||
self.secrets_management
|
||||
.validate()
|
||||
.map_err(|err| ApplicationError::InvalidConfigurationValueError(err.into()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,9 +38,9 @@ static HASH_KEY: tokio::sync::OnceCell<StrongSecret<[u8; PlaintextApiKey::HASH_K
|
||||
|
||||
pub async fn get_hash_key(
|
||||
api_key_config: &settings::ApiKeys,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::AwsKmsClient,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::core::AwsKmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
hc_client: &external_services::hashicorp_vault::core::HashiCorpVault,
|
||||
) -> errors::RouterResult<&'static StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> {
|
||||
HASH_KEY
|
||||
.get_or_try_init(|| async {
|
||||
@ -57,7 +57,7 @@ pub async fn get_hash_key(
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let hash_key = hash_key
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
@ -153,9 +153,9 @@ impl PlaintextApiKey {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create_api_key(
|
||||
state: AppState,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::AwsKmsClient,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::core::AwsKmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
hc_client: &external_services::hashicorp_vault::core::HashiCorpVault,
|
||||
api_key: api::CreateApiKeyRequest,
|
||||
merchant_id: String,
|
||||
) -> RouterResponse<api::CreateApiKeyResponse> {
|
||||
@ -590,9 +590,9 @@ mod tests {
|
||||
let hash_key = get_hash_key(
|
||||
&settings.api_keys,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
external_services::aws_kms::get_aws_kms_client(&settings.kms).await,
|
||||
external_services::aws_kms::core::get_aws_kms_client(&settings.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&settings.hc_vault)
|
||||
external_services::hashicorp_vault::core::get_hashicorp_client(&settings.hc_vault)
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
|
||||
@ -48,7 +48,7 @@ pub async fn delete_entry_from_blocklist(
|
||||
})?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_fingerprint = aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
let decrypted_fingerprint = aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(blocklist_fingerprint.encrypted_fingerprint)
|
||||
.await
|
||||
@ -244,7 +244,7 @@ pub async fn insert_entry_into_blocklist(
|
||||
})?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_fingerprint = aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
let decrypted_fingerprint = aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(blocklist_fingerprint.encrypted_fingerprint)
|
||||
.await
|
||||
|
||||
@ -189,7 +189,8 @@ async fn create_applepay_session_token(
|
||||
common_merchant_identifier,
|
||||
) = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client = external_services::hashicorp_vault::get_hashicorp_client(
|
||||
let client =
|
||||
external_services::hashicorp_vault::core::get_hashicorp_client(
|
||||
&state.conf.hc_vault,
|
||||
)
|
||||
.await
|
||||
@ -206,7 +207,7 @@ async fn create_applepay_session_token(
|
||||
.apple_pay_merchant_cert
|
||||
.clone(),
|
||||
)
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.expose(),
|
||||
@ -217,7 +218,7 @@ async fn create_applepay_session_token(
|
||||
.apple_pay_merchant_cert_key
|
||||
.clone(),
|
||||
)
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.expose(),
|
||||
@ -228,7 +229,7 @@ async fn create_applepay_session_token(
|
||||
.common_merchant_identifier
|
||||
.clone(),
|
||||
)
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.expose(),
|
||||
@ -260,7 +261,7 @@ async fn create_applepay_session_token(
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_apple_pay_merchant_cert =
|
||||
aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(apple_pay_merchant_cert)
|
||||
.await
|
||||
@ -269,7 +270,7 @@ async fn create_applepay_session_token(
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_apple_pay_merchant_cert_key =
|
||||
aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(apple_pay_merchant_cert_key)
|
||||
.await
|
||||
@ -280,7 +281,7 @@ async fn create_applepay_session_token(
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_merchant_identifier =
|
||||
aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(common_merchant_identifier)
|
||||
.await
|
||||
|
||||
@ -3538,8 +3538,9 @@ impl ApplePayData {
|
||||
) -> 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)
|
||||
let client = external_services::hashicorp_vault::core::get_hashicorp_client(
|
||||
&state.conf.hc_vault,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)
|
||||
.attach_printable("Failed while creating client")?;
|
||||
@ -3547,7 +3548,7 @@ impl ApplePayData {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let output =
|
||||
masking::Secret::new(state.conf.applepay_decrypt_keys.apple_pay_ppc.clone())
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)?
|
||||
.expose();
|
||||
@ -3560,7 +3561,7 @@ impl ApplePayData {
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let cert_data = aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
let cert_data = aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(&apple_pay_ppc)
|
||||
.await
|
||||
@ -3621,8 +3622,9 @@ impl ApplePayData {
|
||||
|
||||
let apple_pay_ppc_key = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client =
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&state.conf.hc_vault)
|
||||
let client = external_services::hashicorp_vault::core::get_hashicorp_client(
|
||||
&state.conf.hc_vault,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)
|
||||
.attach_printable("Failed while creating client")?;
|
||||
@ -3630,7 +3632,7 @@ impl ApplePayData {
|
||||
#[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)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(errors::ApplePayDecryptionError::DecryptionFailed)
|
||||
.attach_printable("Failed while creating client")?
|
||||
@ -3644,7 +3646,7 @@ impl ApplePayData {
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let decrypted_apple_pay_ppc_key = aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
let decrypted_apple_pay_ppc_key = aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(&apple_pay_ppc_key)
|
||||
.await
|
||||
|
||||
@ -349,14 +349,15 @@ 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)
|
||||
let client =
|
||||
external_services::hashicorp_vault::core::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)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(ApiErrorResponse::InternalServerError)?
|
||||
.expose();
|
||||
@ -369,7 +370,7 @@ async fn store_bank_details_in_payment_methods(
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let pm_auth_key = aws_kms::get_aws_kms_client(&state.conf.kms)
|
||||
let pm_auth_key = aws_kms::core::get_aws_kms_client(&state.conf.kms)
|
||||
.await
|
||||
.decrypt(pm_auth_key)
|
||||
.await
|
||||
|
||||
@ -13,7 +13,7 @@ pub async fn verify_merchant_creds_for_applepay(
|
||||
state: AppState,
|
||||
_req: &actix_web::HttpRequest,
|
||||
body: verifications::ApplepayMerchantVerificationRequest,
|
||||
kms_config: &aws_kms::AwsKmsConfig,
|
||||
kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
merchant_id: String,
|
||||
) -> CustomResult<
|
||||
services::ApplicationResponse<ApplepayMerchantResponse>,
|
||||
@ -27,19 +27,19 @@ pub async fn verify_merchant_creds_for_applepay(
|
||||
let encrypted_key = &state.conf.applepay_merchant_configs.merchant_cert_key;
|
||||
let applepay_endpoint = &state.conf.applepay_merchant_configs.applepay_endpoint;
|
||||
|
||||
let applepay_internal_merchant_identifier = aws_kms::get_aws_kms_client(kms_config)
|
||||
let applepay_internal_merchant_identifier = aws_kms::core::get_aws_kms_client(kms_config)
|
||||
.await
|
||||
.decrypt(encrypted_merchant_identifier)
|
||||
.await
|
||||
.change_context(api_error_response::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
let cert_data = aws_kms::get_aws_kms_client(kms_config)
|
||||
let cert_data = aws_kms::core::get_aws_kms_client(kms_config)
|
||||
.await
|
||||
.decrypt(encrypted_cert)
|
||||
.await
|
||||
.change_context(api_error_response::ApiErrorResponse::InternalServerError)?;
|
||||
|
||||
let key_data = aws_kms::get_aws_kms_client(kms_config)
|
||||
let key_data = aws_kms::core::get_aws_kms_client(kms_config)
|
||||
.await
|
||||
.decrypt(encrypted_key)
|
||||
.await
|
||||
|
||||
@ -46,10 +46,10 @@ pub async fn api_key_create(
|
||||
|state, _, payload| async {
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let aws_kms_client =
|
||||
external_services::aws_kms::get_aws_kms_client(&state.clone().conf.kms).await;
|
||||
external_services::aws_kms::core::get_aws_kms_client(&state.clone().conf.kms).await;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let hc_client = external_services::hashicorp_vault::get_hashicorp_client(
|
||||
let hc_client = external_services::hashicorp_vault::core::get_hashicorp_client(
|
||||
&state.clone().conf.hc_vault,
|
||||
)
|
||||
.await
|
||||
|
||||
@ -13,6 +13,7 @@ use external_services::email::{ses::AwsSes, EmailService};
|
||||
use external_services::file_storage::FileStorageInterface;
|
||||
#[cfg(all(feature = "olap", feature = "hashicorp-vault"))]
|
||||
use external_services::hashicorp_vault::decrypt::VaultFetch;
|
||||
use hyperswitch_interfaces::encryption_interface::EncryptionManagementInterface;
|
||||
#[cfg(all(feature = "olap", feature = "aws_kms"))]
|
||||
use masking::PeekInterface;
|
||||
use router_env::tracing_actix_web::RequestId;
|
||||
@ -73,6 +74,7 @@ pub struct AppState {
|
||||
pub pool: crate::analytics::AnalyticsProvider,
|
||||
pub request_id: Option<RequestId>,
|
||||
pub file_storage_client: Box<dyn FileStorageInterface>,
|
||||
pub encryption_client: Box<dyn EncryptionManagementInterface>,
|
||||
}
|
||||
|
||||
impl scheduler::SchedulerAppState for AppState {
|
||||
@ -150,13 +152,20 @@ impl AppState {
|
||||
shut_down_signal: oneshot::Sender<()>,
|
||||
api_client: Box<dyn crate::services::ApiClient>,
|
||||
) -> Self {
|
||||
#[allow(clippy::expect_used)]
|
||||
let encryption_client = conf
|
||||
.encryption_management
|
||||
.get_encryption_management_client()
|
||||
.await
|
||||
.expect("Failed to create encryption client");
|
||||
|
||||
Box::pin(async move {
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let aws_kms_client = aws_kms::get_aws_kms_client(&conf.kms).await;
|
||||
let aws_kms_client = aws_kms::core::get_aws_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)
|
||||
external_services::hashicorp_vault::core::get_hashicorp_client(&conf.hc_vault)
|
||||
.await
|
||||
.expect("Failed while creating hashicorp_client");
|
||||
let testable = storage_impl == StorageImpl::PostgresqlTest;
|
||||
@ -204,7 +213,7 @@ impl AppState {
|
||||
sqlx.password = sqlx
|
||||
.password
|
||||
.clone()
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed while fetching from hashicorp vault");
|
||||
}
|
||||
@ -230,7 +239,7 @@ impl AppState {
|
||||
{
|
||||
conf.connector_onboarding = conf
|
||||
.connector_onboarding
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed to decrypt connector onboarding credentials");
|
||||
}
|
||||
@ -254,7 +263,7 @@ impl AppState {
|
||||
conf.jwekey = conf
|
||||
.jwekey
|
||||
.clone()
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed to decrypt connector onboarding credentials");
|
||||
}
|
||||
@ -287,6 +296,7 @@ impl AppState {
|
||||
pool,
|
||||
request_id: None,
|
||||
file_storage_client,
|
||||
encryption_client,
|
||||
}
|
||||
})
|
||||
.await
|
||||
|
||||
@ -46,10 +46,11 @@ pub async fn get_store(
|
||||
test_transaction: bool,
|
||||
) -> StorageResult<Store> {
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let aws_kms_client = aws_kms::get_aws_kms_client(&config.kms).await;
|
||||
let aws_kms_client = aws_kms::core::get_aws_kms_client(&config.kms).await;
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let hc_client = external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault)
|
||||
let hc_client =
|
||||
external_services::hashicorp_vault::core::get_hashicorp_client(&config.hc_vault)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)?;
|
||||
|
||||
@ -57,7 +58,7 @@ pub async fn get_store(
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let master_config = master_config
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to fetch data from hashicorp vault")?;
|
||||
@ -74,7 +75,7 @@ pub async fn get_store(
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "hashicorp-vault"))]
|
||||
let replica_config = replica_config
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(StorageError::InitializationError)
|
||||
.attach_printable("Failed to fetch data from hashicorp vault")?;
|
||||
@ -128,15 +129,15 @@ pub async fn get_store(
|
||||
#[allow(clippy::expect_used)]
|
||||
async fn get_master_enc_key(
|
||||
conf: &crate::configs::settings::Settings,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::AwsKmsClient,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::core::AwsKmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
hc_client: &external_services::hashicorp_vault::core::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)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.expect("Failed to fetch master enc key");
|
||||
|
||||
|
||||
@ -226,9 +226,9 @@ where
|
||||
api_keys::get_hash_key(
|
||||
&config.api_keys,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
aws_kms::get_aws_kms_client(&config.kms).await,
|
||||
aws_kms::core::get_aws_kms_client(&config.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault)
|
||||
external_services::hashicorp_vault::core::get_hashicorp_client(&config.hc_vault)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?,
|
||||
)
|
||||
@ -289,9 +289,9 @@ static ADMIN_API_KEY: tokio::sync::OnceCell<StrongSecret<String>> =
|
||||
|
||||
pub async fn get_admin_api_key(
|
||||
secrets: &settings::Secrets,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::AwsKmsClient,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::core::AwsKmsClient,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_client: &external_services::hashicorp_vault::HashiCorpVault,
|
||||
hc_client: &external_services::hashicorp_vault::core::HashiCorpVault,
|
||||
) -> RouterResult<&'static StrongSecret<String>> {
|
||||
ADMIN_API_KEY
|
||||
.get_or_try_init(|| async {
|
||||
@ -308,7 +308,7 @@ pub async fn get_admin_api_key(
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let admin_api_key = masking::Secret::new(admin_api_key)
|
||||
.fetch_inner::<external_services::hashicorp_vault::Kv2>(hc_client)
|
||||
.fetch_inner::<external_services::hashicorp_vault::core::Kv2>(hc_client)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to KMS decrypt admin API key")?
|
||||
@ -369,9 +369,9 @@ where
|
||||
let admin_api_key = get_admin_api_key(
|
||||
&conf.secrets,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
aws_kms::get_aws_kms_client(&conf.kms).await,
|
||||
aws_kms::core::get_aws_kms_client(&conf.kms).await,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
external_services::hashicorp_vault::get_hashicorp_client(&conf.hc_vault)
|
||||
external_services::hashicorp_vault::core::get_hashicorp_client(&conf.hc_vault)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed while getting admin api key")?,
|
||||
@ -873,7 +873,7 @@ static JWT_SECRET: tokio::sync::OnceCell<StrongSecret<String>> = tokio::sync::On
|
||||
|
||||
pub async fn get_jwt_secret(
|
||||
secrets: &settings::Secrets,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::AwsKmsClient,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_client: &aws_kms::core::AwsKmsClient,
|
||||
) -> RouterResult<&'static StrongSecret<String>> {
|
||||
JWT_SECRET
|
||||
.get_or_try_init(|| async {
|
||||
@ -901,7 +901,7 @@ where
|
||||
let secret = get_jwt_secret(
|
||||
&conf.secrets,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
aws_kms::get_aws_kms_client(&conf.kms).await,
|
||||
aws_kms::core::get_aws_kms_client(&conf.kms).await,
|
||||
)
|
||||
.await?
|
||||
.peek()
|
||||
@ -972,7 +972,7 @@ static RECON_API_KEY: tokio::sync::OnceCell<StrongSecret<String>> =
|
||||
#[cfg(feature = "recon")]
|
||||
pub async fn get_recon_admin_api_key(
|
||||
secrets: &settings::Secrets,
|
||||
#[cfg(feature = "aws_kms")] kms_client: &aws_kms::AwsKmsClient,
|
||||
#[cfg(feature = "aws_kms")] kms_client: &aws_kms::core::AwsKmsClient,
|
||||
) -> RouterResult<&'static StrongSecret<String>> {
|
||||
RECON_API_KEY
|
||||
.get_or_try_init(|| async {
|
||||
@ -1013,7 +1013,7 @@ where
|
||||
let admin_api_key = get_recon_admin_api_key(
|
||||
&conf.secrets,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
aws_kms::get_aws_kms_client(&conf.kms).await,
|
||||
aws_kms::core::get_aws_kms_client(&conf.kms).await,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ where
|
||||
let jwt_secret = authentication::get_jwt_secret(
|
||||
&settings.secrets,
|
||||
#[cfg(feature = "aws_kms")]
|
||||
external_services::aws_kms::get_aws_kms_client(&settings.kms).await,
|
||||
external_services::aws_kms::core::get_aws_kms_client(&settings.kms).await,
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
|
||||
@ -128,9 +128,9 @@ async fn waited_fetch_and_update_caches(
|
||||
state: &AppState,
|
||||
local_fetch_retry_delay: u64,
|
||||
local_fetch_retry_count: u64,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
for _n in 1..local_fetch_retry_count {
|
||||
sleep(Duration::from_millis(local_fetch_retry_delay)).await;
|
||||
@ -192,9 +192,9 @@ pub async fn get_forex_rates(
|
||||
call_delay: i64,
|
||||
local_fetch_retry_delay: u64,
|
||||
local_fetch_retry_count: u64,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
if let Some(local_rates) = retrieve_forex_from_local().await {
|
||||
if local_rates.is_expired(call_delay) {
|
||||
@ -234,9 +234,9 @@ async fn handler_local_no_data(
|
||||
call_delay: i64,
|
||||
_local_fetch_retry_delay: u64,
|
||||
_local_fetch_retry_count: u64,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match retrieve_forex_from_redis(state).await {
|
||||
Ok(Some(data)) => {
|
||||
@ -281,9 +281,9 @@ async fn handler_local_no_data(
|
||||
async fn successive_fetch_and_save_forex(
|
||||
state: &AppState,
|
||||
stale_redis_data: Option<FxExchangeRatesCacheEntry>,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match acquire_redis_lock(state).await {
|
||||
Ok(lock_acquired) => {
|
||||
@ -351,9 +351,9 @@ async fn fallback_forex_redis_check(
|
||||
state: &AppState,
|
||||
redis_data: FxExchangeRatesCacheEntry,
|
||||
call_delay: i64,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match is_redis_expired(Some(redis_data.clone()).as_ref(), call_delay).await {
|
||||
Some(redis_forex) => {
|
||||
@ -381,9 +381,9 @@ async fn handler_local_expired(
|
||||
state: &AppState,
|
||||
call_delay: i64,
|
||||
local_rates: FxExchangeRatesCacheEntry,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
match retrieve_forex_from_redis(state).await {
|
||||
Ok(redis_data) => {
|
||||
@ -427,14 +427,14 @@ async fn handler_local_expired(
|
||||
|
||||
async fn fetch_forex_rates(
|
||||
state: &AppState,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::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)
|
||||
let client = hashicorp_vault::core::get_hashicorp_client(hc_config)
|
||||
.await
|
||||
.change_context(ForexCacheError::AwsKmsDecryptionFailed)?;
|
||||
|
||||
@ -446,7 +446,7 @@ async fn fetch_forex_rates(
|
||||
.forex_api
|
||||
.api_key
|
||||
.clone()
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(ForexCacheError::AwsKmsDecryptionFailed)?;
|
||||
|
||||
@ -454,7 +454,7 @@ async fn fetch_forex_rates(
|
||||
}
|
||||
.await?;
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let forex_api_key = aws_kms::get_aws_kms_client(aws_kms_config)
|
||||
let forex_api_key = aws_kms::core::get_aws_kms_client(aws_kms_config)
|
||||
.await
|
||||
.decrypt(forex_api_key.peek())
|
||||
.await
|
||||
@ -516,13 +516,13 @@ async fn fetch_forex_rates(
|
||||
|
||||
pub async fn fallback_fetch_forex_rates(
|
||||
state: &AppState,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
|
||||
let fallback_api_key = async {
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
let client = hashicorp_vault::get_hashicorp_client(hc_config)
|
||||
let client = hashicorp_vault::core::get_hashicorp_client(hc_config)
|
||||
.await
|
||||
.change_context(ForexCacheError::AwsKmsDecryptionFailed)?;
|
||||
|
||||
@ -534,7 +534,7 @@ pub async fn fallback_fetch_forex_rates(
|
||||
.forex_api
|
||||
.fallback_api_key
|
||||
.clone()
|
||||
.fetch_inner::<hashicorp_vault::Kv2>(client)
|
||||
.fetch_inner::<hashicorp_vault::core::Kv2>(client)
|
||||
.await
|
||||
.change_context(ForexCacheError::AwsKmsDecryptionFailed)?;
|
||||
|
||||
@ -542,7 +542,7 @@ pub async fn fallback_fetch_forex_rates(
|
||||
}
|
||||
.await?;
|
||||
#[cfg(feature = "aws_kms")]
|
||||
let fallback_forex_api_key = aws_kms::get_aws_kms_client(aws_kms_config)
|
||||
let fallback_forex_api_key = aws_kms::core::get_aws_kms_client(aws_kms_config)
|
||||
.await
|
||||
.decrypt(fallback_api_key.peek())
|
||||
.await
|
||||
@ -691,9 +691,9 @@ pub async fn convert_currency(
|
||||
amount: i64,
|
||||
to_currency: String,
|
||||
from_currency: String,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::AwsKmsConfig,
|
||||
#[cfg(feature = "aws_kms")] aws_kms_config: &aws_kms::core::AwsKmsConfig,
|
||||
#[cfg(feature = "hashicorp-vault")]
|
||||
hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig,
|
||||
hc_config: &external_services::hashicorp_vault::core::HashiCorpVaultConfig,
|
||||
) -> CustomResult<api_models::currency::CurrencyConversionResponse, ForexCacheError> {
|
||||
let rates = get_forex_rates(
|
||||
&state,
|
||||
|
||||
Reference in New Issue
Block a user