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:
Chethan Rao
2024-02-12 13:30:44 +05:30
committed by GitHub
parent 33df3520d1
commit b6754a7de8
44 changed files with 1157 additions and 628 deletions

14
Cargo.lock generated
View File

@ -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",

View File

@ -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")]

View File

@ -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"),
)

View File

@ -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)]

View File

@ -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"] }

View File

@ -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;

View 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);
}
}

View File

@ -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")
}
}

View 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)
}
}

View File

@ -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;

View 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,
}

View File

@ -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,
>,

View 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)
}
}

View File

@ -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 {

View File

@ -0,0 +1,5 @@
//! Config and client managers
pub mod encryption_management;
pub mod secrets_management;

View File

@ -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),
})
}
}

View 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)),
}
}
}

View File

@ -0,0 +1,7 @@
//!
//! No encryption functionalities
//!
pub mod core;
pub mod implementers;

View 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()
}
}

View 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")
}
}

View 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" }

View File

@ -0,0 +1,3 @@
# Hyperswitch Interfaces
This crate includes interfaces and its error types

View 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,
}

View File

@ -0,0 +1,7 @@
//! Hyperswitch interface
#![warn(missing_docs, missing_debug_implementations)]
pub mod secrets_interface;
pub mod encryption_interface;

View 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,
}

View File

@ -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>;
}

View File

@ -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,
}
}
}

View File

@ -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" }

View File

@ -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;

View File

@ -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};

View File

@ -1,5 +1,6 @@
use external_services::hashicorp_vault::{
decrypt::VaultFetch, Engine, HashiCorpError, HashiCorpVault,
core::{Engine, HashiCorpError, HashiCorpVault},
decrypt::VaultFetch,
};
use masking::ExposeInterface;

View File

@ -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(())
}
}

View File

@ -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(),
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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?;

View File

@ -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)

View File

@ -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,