Skip to content

Commit

Permalink
refactor: introducing secrets_interface and encryption_interface crates
Browse files Browse the repository at this point in the history
  • Loading branch information
Chethan-rao committed Feb 2, 2024
1 parent 63c383f commit 54f09c6
Show file tree
Hide file tree
Showing 19 changed files with 484 additions and 5 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions crates/encryption_interface/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "encryption_interface"
version = "0.1.0"
edition.workspace = true

[dependencies]
async-trait = "0.1.68"
dyn-clone = "1.0.11"
thiserror = "1.0.40"

# First party crates
common_utils = { version = "0.1.0", path = "../common_utils" }
25 changes: 25 additions & 0 deletions crates/encryption_interface/src/encryption_management.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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: String) -> CustomResult<String, EncryptionError>;

/// Decrypt the given input data
async fn decrypt(&self, input: String) -> CustomResult<String, 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,
}
6 changes: 6 additions & 0 deletions crates/encryption_interface/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Encryption related interface and error types
#![warn(missing_docs, missing_debug_implementations)]

/// Module for encryption-related functionality
pub mod encryption_management;
2 changes: 2 additions & 0 deletions crates/external_services/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,7 @@ hex = "0.4.3"

# First party crates
common_utils = { version = "0.1.0", path = "../common_utils" }
encryption_interface = { version = "0.1.0", path = "../encryption_interface" }
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"] }
secrets_interface = { version = "0.1.0", path = "../secrets_interface" }
43 changes: 40 additions & 3 deletions crates/external_services/src/aws_kms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use aws_config::meta::region::RegionProviderChain;
use aws_sdk_kms::{config::Region, primitives::Blob, Client};
use base64::Engine;
use common_utils::errors::CustomResult;
use encryption_interface::encryption_management::{EncryptionError, EncryptionManagementInterface};
use error_stack::{IntoReport, ResultExt};
use masking::{PeekInterface, Secret};
use router_env::logger;
use secrets_interface::secrets_management::{SecretManagementInterface, SecretsManagementError};
/// decrypting data using the AWS KMS SDK.
pub mod decrypt;

Expand Down Expand Up @@ -36,7 +38,7 @@ pub struct AwsKmsConfig {
}

/// Client for AWS KMS operations.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct AwsKmsClient {
inner_client: Client,
key_id: String,
Expand Down Expand Up @@ -136,13 +138,48 @@ impl AwsKmsClient {
}
}

/// Errors that could occur during AWS KMS operations.
#[async_trait::async_trait]
impl EncryptionManagementInterface for AwsKmsClient {
async fn encrypt(&self, input: String) -> CustomResult<String, EncryptionError> {
self.encrypt(input)
.await
.change_context(EncryptionError::EncryptionFailed)
}

async fn decrypt(&self, input: String) -> CustomResult<String, EncryptionError> {
self.decrypt(input)
.await
.change_context(EncryptionError::DecryptionFailed)
}
}

#[async_trait::async_trait]
impl SecretManagementInterface for AwsKmsClient {
async fn store_secret(
&self,
input: Secret<String>,
) -> CustomResult<String, SecretsManagementError> {
self.encrypt(input.peek())
.await
.change_context(SecretsManagementError::EncryptionFailed)
}

async fn get_secret(
&self,
input: Secret<String>,
) -> CustomResult<String, SecretsManagementError> {
self.decrypt(input.peek())
.await
.change_context(SecretsManagementError::DecryptionFailed)
}
}

/// 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,
Expand Down
52 changes: 52 additions & 0 deletions crates/external_services/src/encryption_management.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//!
//! Encryption management util module
//!
use common_utils::errors::CustomResult;
use encryption_interface::encryption_management::{EncryptionError, EncryptionManagementInterface};

#[cfg(feature = "aws_kms")]
use crate::aws_kms;
use crate::no_encryption::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::AwsKmsConfig,
},

/// Varient representing no encryption

Check warning on line 24 in crates/external_services/src/encryption_management.rs

View workflow job for this annotation

GitHub Actions / Spell check

"Varient" should be "Variant".
#[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> {
let client: Box<dyn EncryptionManagementInterface> = match self {
#[cfg(feature = "aws_kms")]
Self::AwsKms { aws_kms } => Box::new(aws_kms::AwsKmsClient::new(aws_kms).await),

Self::NoEncryption => Box::new(NoEncryption),
};
Ok(client)
}
}
44 changes: 43 additions & 1 deletion crates/external_services/src/hashicorp_vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
use std::{collections::HashMap, future::Future, pin::Pin};

use common_utils::{errors::CustomResult, ext_traits::ConfigExt, fp_utils::when};
use error_stack::{Report, ResultExt};
use masking::{ExposeInterface, Secret};
use secrets_interface::secrets_management::{SecretManagementInterface, SecretsManagementError};
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};

/// Utilities for supporting decryption of data
Expand All @@ -27,6 +30,18 @@ pub struct HashiCorpVaultConfig {
pub token: String,
}

impl HashiCorpVaultConfig {
pub(super) fn validate(&self) -> Result<(), &'static str> {
when(self.url.is_default_or_empty(), || {
Err("HashiCorp url must not be empty")
})?;

when(self.token.is_default_or_empty(), || {
Err("HashiCorp token must not be empty")
})
}
}

/// Asynchronously retrieves a HashiCorp Vault client based on the provided configuration.
///
/// # Parameters
Expand Down Expand Up @@ -158,7 +173,7 @@ pub trait FromEncoded: Sized {
fn from_encoded(input: String) -> Option<Self>;
}

impl FromEncoded for masking::Secret<String> {
impl FromEncoded for Secret<String> {
fn from_encoded(input: String) -> Option<Self> {
Some(input.into())
}
Expand Down Expand Up @@ -213,3 +228,30 @@ pub enum HashiCorpError {
#[error("Failed while parsing the response")]
ParseError,
}

#[async_trait::async_trait]
impl SecretManagementInterface for HashiCorpVault {
#[allow(clippy::todo)]
async fn store_secret(
&self,
_: Secret<String>,
) -> CustomResult<String, SecretsManagementError> {
todo!()
}

async fn get_secret(
&self,
input: Secret<String>,
) -> CustomResult<String, SecretsManagementError> {
self.fetch::<Kv2, Secret<String>>(input.expose())
.await
.map(|val| val.expose().to_owned())
.change_context(SecretsManagementError::DecryptionFailed)
}
}

impl From<Box<HashiCorpVault>> for Box<dyn SecretManagementInterface> {
fn from(vault_box: Box<HashiCorpVault>) -> Self {
vault_box
}
}
6 changes: 6 additions & 0 deletions crates/external_services/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ pub mod file_storage;
#[cfg(feature = "hashicorp-vault")]
pub mod hashicorp_vault;

pub mod no_encryption;

pub mod encryption_management;

pub mod secrets_management;

/// Crate specific constants
#[cfg(feature = "aws_kms")]
pub mod consts {
Expand Down
52 changes: 52 additions & 0 deletions crates/external_services/src/no_encryption.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//!
//! No encryption functionalities
//!
use common_utils::errors::CustomResult;
use encryption_interface::encryption_management::{EncryptionError, EncryptionManagementInterface};
use masking::{ExposeInterface, Secret};
use secrets_interface::secrets_management::{SecretManagementInterface, SecretsManagementError};

/// No encryption type
#[derive(Debug, Clone)]
pub struct NoEncryption;

impl NoEncryption {
/// Encryption functionality
pub fn encrypt(&self, data: String) -> String {
data
}

/// Decryption functionality
pub fn decrypt(&self, data: String) -> String {
data
}
}

#[async_trait::async_trait]
impl EncryptionManagementInterface for NoEncryption {
async fn encrypt(&self, input: String) -> CustomResult<String, EncryptionError> {
Ok(self.encrypt(input))
}

async fn decrypt(&self, input: String) -> CustomResult<String, EncryptionError> {
Ok(self.decrypt(input))
}
}

#[async_trait::async_trait]
impl SecretManagementInterface for NoEncryption {
async fn store_secret(
&self,
input: Secret<String>,
) -> CustomResult<String, SecretsManagementError> {
Ok(self.encrypt(input.expose()))
}

async fn get_secret(
&self,
input: Secret<String>,
) -> CustomResult<String, SecretsManagementError> {
Ok(self.decrypt(input.expose()))
}
}
Loading

0 comments on commit 54f09c6

Please sign in to comment.