diff --git a/Cargo.lock b/Cargo.lock index 0ba96b1..fb4672f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -791,6 +791,7 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "ulid", + "vaultrs", ] [[package]] @@ -843,14 +844,38 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] @@ -863,17 +888,28 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.66", ] +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", "quote", "syn 2.0.66", ] @@ -888,6 +924,37 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "diesel" version = "2.2.4" @@ -966,7 +1033,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" dependencies = [ - "darling", + "darling 0.20.10", "either", "heck 0.5.0", "proc-macro2", @@ -980,6 +1047,15 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1430,6 +1506,12 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + [[package]] name = "itoa" version = "1.0.11" @@ -2086,6 +2168,47 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + [[package]] name = "ring" version = "0.17.8" @@ -2144,6 +2267,40 @@ dependencies = [ "semver", ] +[[package]] +name = "rustify" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c02e25271068de581e03ac3bb44db60165ff1a10d92b9530192ccb898bc706" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "http 0.2.12", + "reqwest", + "rustify_derive", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "rustify_derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7345f32672da54338227b727bd578c897859ddfaad8952e0b0d787fb4e58f07d" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "serde_urlencoded", + "syn 1.0.109", + "synstructure", +] + [[package]] name = "rustls" version = "0.21.12" @@ -2452,6 +2609,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -2520,6 +2683,39 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -2953,6 +3149,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -2991,6 +3193,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vaultrs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb996bb053adadc767f8b0bda2a80bc2b67d24fe89f2b959ae919e200d79a19" +dependencies = [ + "async-trait", + "bytes", + "derive_builder", + "http 0.2.12", + "reqwest", + "rustify", + "rustify_derive", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -3055,6 +3277,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -3104,6 +3338,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "whoami" version = "1.5.1" @@ -3285,6 +3525,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "xmlparser" version = "0.13.6" diff --git a/Cargo.toml b/Cargo.toml index 5541d2f..23873f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,20 @@ rust-version = "1.78" [patch.crates-io] tokio-rustls = { git = "https://github.com/rustls/tokio-rustls", rev = "3a153acec6c4d189eb5de501b2155b4484b8651b" } # main +# All the AES,AWS,Vault features will be used as a runtime feature flag rather than compiletime [features] -mtls = ["dep:rustls", "dep:rustls-pemfile","axum-server/tls-rustls"] -aws = ["dep:aws-config", "dep:aws-sdk-kms"] -release = ["aws","mtls"] +aes = [] +mtls = ["dep:rustls", "dep:rustls-pemfile", "axum-server/tls-rustls"] +aws = [] +release = ["aws", "mtls"] +vault = [] [dependencies] cargo_metadata = "0.18.1" error-stack = "0.4.1" thiserror = "1.0.58" -aws-config = { version = "1.5.0", optional = true} -aws-sdk-kms = { version = "1.29.0", optional = true} +aws-config = { version = "1.5.0" } +aws-sdk-kms = { version = "1.29.0" } axum = { version = "0.7.5", features = ["macros"] } axum-server = {git = "https://github.com/dracarys18/axum-server.git",rev ="5430efefd14b43cbbc8e4faa50ff9274b8b8b7aa"} base64 = "0.22.1" @@ -54,6 +57,7 @@ rayon = "1.10.0" once_cell = "1.19.0" hyper = "1.3.1" blake3 = "1.5.4" +vaultrs = { version = "0.7.2" } [build-dependencies] cargo_metadata = "0.18.1" diff --git a/README.md b/README.md index eb0bc3a..6187f07 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,4 @@ The encryption service mainly has following functionalities:- - All the Data Encryption Keys are Encrypted by either by securely generated AES-256 Key or a hosted Key Management Service (AWS KMS, Hashicorp Vault etc.) ![Architectural diagram](./docs/images/FlowDiagram.png) + diff --git a/docker/hashcorp_vault.yml b/docker/hashcorp_vault.yml new file mode 100644 index 0000000..e44dba6 --- /dev/null +++ b/docker/hashcorp_vault.yml @@ -0,0 +1,18 @@ +networks: + cripta_net: + +services: + hashicorpvault-initialize: + image: hashicorp/vault:latest + # Give 5secs of time after staring of hashicorpvault service and before connecting to initialize the vault with encryption key. + command: | + /bin/sh -c "sleep 5 && vault secrets enable transit && vault write -f transit/keys/orders" + networks: + - cripta_net + environment: + VAULT_ADDR: "http://hashicorpvault:8200" + VAULT_TOKEN: vault_token + depends_on: + hashicorpvault: + condition: service_started + diff --git a/src/bin/cripta.rs b/src/bin/cripta.rs index a198184..f913c79 100644 --- a/src/bin/cripta.rs +++ b/src/bin/cripta.rs @@ -16,6 +16,7 @@ use std::sync::Arc; #[tokio::main] async fn main() { let config = config::Config::with_config_path(config::Environment::which(), None); + config.validate(); let _guard = observability::setup(&config.log, []); @@ -23,7 +24,7 @@ async fn main() { .parse() .expect("Unable to parse host"); - logger::info!("Application started [{:?}] [{:?}]", config.server, config); + logger::info!("Application starting [{:?}] [{:?}]", config.server, config); let state = Arc::new(AppState::from_config(config).await); diff --git a/src/config.rs b/src/config.rs index 7a90521..d7e94c0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,22 +1,26 @@ +use crate::crypto::aes256::GcmAes256; use crate::{ - crypto::{EncryptionClient, KeyManagerClient}, + crypto::KeyManagerClient, env::observability::LogConfig, + errors::{self, CustomResult}, }; use config::File; use serde::Deserialize; +use std::sync::Arc; -#[cfg(not(feature = "aws"))] -use crate::crypto::aes256::GcmAes256; - -#[cfg(feature = "aws")] use crate::services::aws::{AwsKmsClient, AwsKmsConfig}; -#[cfg(feature = "aws")] use aws_sdk_kms::primitives::Blob; -#[cfg(feature = "aws")] use masking::PeekInterface; +use crate::crypto::vault::{Vault, VaultSettings}; + +use vaultrs::{ + client::{VaultClient, VaultClientSettingsBuilder}, + transit, +}; + use std::path::PathBuf; pub mod vars { @@ -56,8 +60,7 @@ impl SecretContainer { /// Panics when secret cannot be decrypted with KMS #[allow(clippy::expect_used, unused_variables)] pub async fn expose(&self, config: &Config) -> masking::Secret { - #[cfg(feature = "aws")] - { + if cfg!(feature = "aws") { use base64::Engine; let kms = AwsKmsClient::new(&config.secrets.kms_config).await; @@ -80,10 +83,42 @@ impl SecretContainer { let secret = String::from_utf8(decrypted_output).expect("Invalid secret"); masking::Secret::new(secret) - } + } else if cfg!(feature = "vault") { + use base64::Engine; - #[cfg(not(feature = "aws"))] - self.0.clone() + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(&config.secrets.vault_config.url) + .token(config.secrets.vault_config.vault_token.peek()) + .build() + .expect("Unable to build HashiCorp Vault Settings"), + ) + .expect("Unable to build HashiCorp Vault client"); + + let cypher_text = self.0.peek(); + + let b64_encoded_str = transit::data::decrypt( + &client, + &config.secrets.vault_config.mount_point, + &config.secrets.vault_config.encryption_key, + cypher_text, + None, + ) + .await + .expect("Failed while decrypting vault encrypted secret") + .plaintext; + + return masking::Secret::new( + String::from_utf8( + crate::consts::base64::BASE64_ENGINE + .decode(b64_encoded_str) + .expect("Failed to base64 decode the vault data"), + ) + .expect("Invalid secret"), + ); + } else { + self.0.clone() + } } } @@ -97,8 +132,8 @@ pub struct Config { pub server: Server, pub metrics_server: Server, pub database: Database, - pub log: LogConfig, pub secrets: Secrets, + pub log: LogConfig, pub pool_config: PoolConfig, #[cfg(feature = "mtls")] pub certs: Certs, @@ -122,12 +157,14 @@ pub struct Certs { pub root_ca: SecretContainer, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Default)] pub struct Secrets { - #[cfg(not(feature = "aws"))] + #[serde(default)] pub master_key: GcmAes256, - #[cfg(feature = "aws")] + #[serde(default)] pub kms_config: AwsKmsConfig, + #[serde(default)] + pub vault_config: VaultSettings, pub access_token: masking::Secret, pub hash_context: masking::Secret, } @@ -138,6 +175,21 @@ pub struct Server { pub host: String, } +impl Secrets { + fn validate(&self) -> CustomResult<(), errors::ParsingError> { + if cfg!(feature = "aws") && (self.kms_config == AwsKmsConfig::default()) { + Err(error_stack::report!(errors::ParsingError::DecodingFailed( + "AWS config is not provided".to_string() + ))) + } else if cfg!(feature = "vault") && (self.vault_config == VaultSettings::default()) { + Err(error_stack::report!(errors::ParsingError::DecodingFailed( + "Vault config is not provided".to_string() + ))) + } else { + Ok(()) + } + } +} impl Config { pub fn config_path(environment: Environment, explicit_config_path: Option) -> PathBuf { let mut config_path = PathBuf::new(); @@ -171,20 +223,28 @@ impl Config { serde_path_to_error::deserialize(config) .expect("Unable to deserialize application configuration") } + /// # Panics + /// + /// Panics for a validation fail + #[allow(clippy::panic, clippy::expect_used)] + pub fn validate(&self) { + self.secrets + .validate() + .expect("Failed to valdiate secrets some missing configuration found") + } } impl Secrets { pub async fn create_keymanager_client(self) -> KeyManagerClient { - #[cfg(feature = "aws")] - { + if cfg!(feature = "aws") { let client = AwsKmsClient::new(&self.kms_config).await; - EncryptionClient::new(client) - } - - #[cfg(not(feature = "aws"))] - { + KeyManagerClient::new(Arc::new(client)) + } else if cfg!(feature = "vault") { + let client = Vault::new(self.vault_config); + KeyManagerClient::new(Arc::new(client)) + } else { let client = self.master_key; - EncryptionClient::new(client) + KeyManagerClient::new(Arc::new(client)) } } } diff --git a/src/core/crypto/crux.rs b/src/core/crypto/crux.rs index 692c9ab..de581aa 100644 --- a/src/core/crypto/crux.rs +++ b/src/core/crypto/crux.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use crate::{ app::AppState, - crypto::{aes256::GcmAes256, Crypto, KeyManagement, Source}, + crypto::{aes256::GcmAes256, Crypto, Source}, errors::{self, SwitchError}, storage::types::{DataKey, DataKeyNew}, types::{ diff --git a/src/core/datakey/create.rs b/src/core/datakey/create.rs index 6c0e9eb..56ed006 100644 --- a/src/core/datakey/create.rs +++ b/src/core/datakey/create.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use crate::{ app::AppState, core::{crypto::KeyEncrypter, custodian::Custodian}, - crypto::KeyManagement, env::observability as logger, errors::{self, SwitchError}, storage::dek::DataKeyStorageInterface, diff --git a/src/core/datakey/rotate.rs b/src/core/datakey/rotate.rs index f1879ae..1a23176 100644 --- a/src/core/datakey/rotate.rs +++ b/src/core/datakey/rotate.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use crate::{ app::AppState, core::{crypto::KeyEncrypter, custodian::Custodian}, - crypto::KeyManagement, env::observability as logger, errors::{self, SwitchError}, storage::dek::DataKeyStorageInterface, diff --git a/src/crypto.rs b/src/crypto.rs index 3165989..2af4d73 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -9,19 +9,19 @@ pub(crate) mod aes256; pub(crate) mod blake3; -#[cfg(feature = "aws")] -use crate::services::aws::AwsKmsClient; - -#[cfg(not(feature = "aws"))] use crate::crypto::aes256::GcmAes256; +use crate::services::aws::AwsKmsClient; -#[cfg(feature = "aws")] pub(crate) mod kms; +pub(crate) mod vault; +use crate::crypto::vault::Vault; + #[derive(Clone, EnumString, Display)] pub enum Source { KMS, AESLocal, + HashicorpVault, } #[async_trait::async_trait] @@ -52,7 +52,6 @@ pub trait KeyManagement { ) -> CustomResult>, errors::CryptoError>; } -#[cfg(feature = "aws")] #[async_trait::async_trait] impl KeyManagement for AwsKmsClient { async fn generate_key( @@ -73,8 +72,6 @@ impl KeyManagement for AwsKmsClient { ::decrypt(self, input).await } } - -#[cfg(not(feature = "aws"))] #[async_trait::async_trait] impl KeyManagement for GcmAes256 { async fn generate_key( @@ -96,31 +93,47 @@ impl KeyManagement for GcmAes256 { } } -pub struct EncryptionClient { - client: Arc, +#[async_trait::async_trait] +impl KeyManagement for Vault { + async fn generate_key( + &self, + ) -> CustomResult<(Source, StrongSecret<[u8; 32]>), errors::CryptoError> { + ::generate_key(self).await + } + async fn encrypt_key( + &self, + input: StrongSecret>, + ) -> CustomResult>, errors::CryptoError> { + ::encrypt(self, input).await + } + async fn decrypt_key( + &self, + input: StrongSecret>, + ) -> CustomResult>, errors::CryptoError> { + ::decrypt(self, input).await + } } -impl EncryptionClient { - pub fn new(client: T) -> Self { - Self { - client: Arc::new(client), - } - } +pub type Backend = dyn KeyManagement + Send + Sync; + +pub struct KeyManagerClient { + client: Arc, } -#[cfg(feature = "aws")] -pub type KeyManagerClient = EncryptionClient; -#[cfg(not(feature = "aws"))] -pub type KeyManagerClient = EncryptionClient; +impl KeyManagerClient { + pub fn new(client: Arc) -> Self { + Self { client } + } +} -impl EncryptionClient { - pub fn client(&self) -> &T { +impl KeyManagerClient { + pub fn client(&self) -> &Arc { &self.client } } -impl Deref for EncryptionClient { - type Target = T; +impl Deref for KeyManagerClient { + type Target = Arc; fn deref(&self) -> &Self::Target { self.client() } diff --git a/src/crypto/aes256.rs b/src/crypto/aes256.rs index 36584ad..001c4c3 100644 --- a/src/crypto/aes256.rs +++ b/src/crypto/aes256.rs @@ -15,7 +15,7 @@ use ring::aead::{self, BoundKey, OpeningKey, SealingKey, UnboundKey}; use masking::StrongSecret; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct GcmAes256 { key: StrongSecret<[u8; 32]>, } diff --git a/src/crypto/vault.rs b/src/crypto/vault.rs new file mode 100644 index 0000000..daaeca3 --- /dev/null +++ b/src/crypto/vault.rs @@ -0,0 +1,126 @@ +use crate::consts::base64::BASE64_ENGINE; +use crate::crypto::{Crypto, Source}; +use crate::env::observability as logger; +use crate::errors::{self, CryptoError, CustomResult}; +use base64::Engine; +use error_stack::report; +use futures::Future; +use masking::{PeekInterface, StrongSecret}; +use serde::Deserialize; +use std::pin::Pin; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; +use vaultrs::{api, transit}; + +#[derive(Debug, Deserialize, Clone, Default, PartialEq, Eq)] +pub struct VaultSettings { + pub url: String, + pub mount_point: String, + pub encryption_key: String, + pub vault_token: masking::Secret, +} + +pub struct Vault { + inner_client: VaultClient, + settings: VaultSettings, +} + +impl Vault { + #[allow(clippy::expect_used)] + pub fn new(settings: VaultSettings) -> Self { + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(&settings.url) + .token(settings.vault_token.peek()) + .build() + .expect("Unable to build HashiCorp Vault Settings"), + ) + .expect("Unable to build HashiCorp Vault client"); + Self { + inner_client: client, + settings, + } + } +} + +#[async_trait::async_trait] +impl Crypto for Vault { + type DataReturn<'a> = Pin< + Box< + dyn Future>, errors::CryptoError>> + + 'a + + Send, + >, + >; + + async fn generate_key( + &self, + ) -> CustomResult<(Source, StrongSecret<[u8; 32]>), errors::CryptoError> { + //According to Vault transit engine can genarate high entropy random bytes of different lengths. + //https://developer.hashicorp.com/vault/docs/secrets/transit + let response = transit::generate::random_bytes( + &self.inner_client, + &self.settings.mount_point, + api::transit::OutputFormat::Base64, + api::transit::requests::RandomBytesSource::All, + None, + ) + .await + .map_err(|err| report!(err).change_context(errors::CryptoError::KeyGeneration))?; + let key = BASE64_ENGINE + .decode(response.random_bytes) + .map_err(|err| report!(err).change_context(CryptoError::KeyGeneration))?; + let buffer: [u8; 32] = key.try_into().map_err(|err: Vec| { + let err_bytes = format!("{:?}", err); + logger::debug!(err_bytes); + report!(CryptoError::KeyGeneration) + })?; + Ok((Source::HashicorpVault, buffer.into())) + } + + fn encrypt(&self, input: StrongSecret>) -> Self::DataReturn<'_> { + let b64_text = BASE64_ENGINE.encode(input.peek()); + Box::pin(async move { + Ok(transit::data::encrypt( + &self.inner_client, + &self.settings.mount_point, + &self.settings.encryption_key, + &b64_text, + None, + ) + .await + .map_err(|err| { + report!(err).change_context(CryptoError::EncryptionFailed("HashiCorp Vault")) + })? + .ciphertext + .as_bytes() + .to_vec() + .into()) + }) + } + + fn decrypt(&self, input: StrongSecret>) -> Self::DataReturn<'_> { + Box::pin(async move { + let cypher_text = String::from_utf8(input.peek().to_vec()).map_err(|err| { + report!(err).change_context(CryptoError::DecryptionFailed("Vault")) + })?; + let b64_encoded_str = transit::data::decrypt( + &self.inner_client, + &self.settings.mount_point, + &self.settings.encryption_key, + &cypher_text, + None, + ) + .await + .map_err(|err| { + report!(err).change_context(CryptoError::DecryptionFailed("HashiCorp Vault")) + })? + .plaintext; + Ok(BASE64_ENGINE + .decode(b64_encoded_str) + .map_err(|err| { + report!(err).change_context(CryptoError::DecryptionFailed("HashiCorp Vault")) + })? + .into()) + }) + } +} diff --git a/src/errors/crypto.rs b/src/errors/crypto.rs index e91cef2..0474bd7 100644 --- a/src/errors/crypto.rs +++ b/src/errors/crypto.rs @@ -1,6 +1,5 @@ use error_stack::report; -#[cfg(feature = "aws")] use crate::env::observability as logger; #[derive(Debug, thiserror::Error)] @@ -47,7 +46,6 @@ impl super::SwitchError for Result { } } -#[cfg(feature = "aws")] impl super::SwitchError for Result> { @@ -59,7 +57,6 @@ impl super::SwitchError } } -#[cfg(feature = "aws")] impl super::SwitchError for Result> { @@ -71,7 +68,6 @@ impl super::SwitchError } } -#[cfg(feature = "aws")] impl super::SwitchError for Result< T, diff --git a/src/services.rs b/src/services.rs index a834244..ab4e8f7 100644 --- a/src/services.rs +++ b/src/services.rs @@ -1,2 +1 @@ -#[cfg(feature = "aws")] pub(crate) mod aws; diff --git a/src/services/aws/config.rs b/src/services/aws/config.rs index 648bc68..77b507b 100644 --- a/src/services/aws/config.rs +++ b/src/services/aws/config.rs @@ -2,7 +2,7 @@ use aws_config::{meta::region::RegionProviderChain, BehaviorVersion}; use aws_sdk_kms::{config::Region, Client}; /// Configuration parameters required for constructing a [`AwsKmsClient`]. -#[derive(Clone, Debug, Default, serde::Deserialize)] +#[derive(Clone, Debug, Default, serde::Deserialize, PartialEq, Eq)] #[serde(default)] pub struct AwsKmsConfig { /// The AWS key identifier of the KMS key used to encrypt or decrypt data.