commit feed596a958849249421de2a0818443873dcd978 Author: Mathieu Trossevin Date: Tue Dec 19 15:11:03 2023 +0100 Initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..200acc2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sd-credentials" +version = "0.1.0" +edition = "2021" +license = "MIT" +authors = ["Mathieu Trossevin "] +repository = "https://gitea.evolix.org/mtrossevin/storefd/" +description = "A simple crate to recover secrets passed by systemd (or anything else that use $CREDENTIAL_DIRECTORY)." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = [] +# For some inane reason this seems to also require tokio but I am not adding it as a dependency here as nothing I implement actually require tokio. +secret-vault = ["dep:secret-vault", "dep:async-trait"] + +[dependencies] +async-trait = { version = "0.1.74", optional = true } +cap-std = "2.0.0" +secret-vault = { version = "1.10.1", optional = true } +secret-vault-value = "0.3.8" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2d7659e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,65 @@ +#[cfg(feature = "secret-vault")] +use std::collections::HashMap; +use std::path::Path; + +#[cfg(feature = "secret-vault")] +use secret_vault::{SecretSource, SecretVaultRef, SecretVaultResult, Secret, SecretMetadata, errors::SecretVaultError}; + +use secret_vault_value::SecretValue; + +#[derive(Debug)] +pub struct CredentialLoader(cap_std::fs::Dir); + +impl CredentialLoader { + pub fn new() -> Option { + let credential_directory = std::env::var_os("CREDENTIALS_DIRECTORY")?; + let dir = std::fs::File::open(credential_directory).ok()?; + let dir = cap_std::fs::Dir::from_std_file(dir); + + Some(Self(dir)) + } + + pub fn get_file>(&self, credential: P) -> std::io::Result { + self.0.open(credential) + } + + pub fn get>(&self, credential: P) -> std::io::Result { + Ok(SecretValue::new(self.0.read(credential)?)) + } +} + +#[cfg(feature = "secret-vault")] +#[async_trait::async_trait] +impl SecretSource for CredentialLoader { + fn name(&self) -> String { + "CredentialLoader".to_string() + } + + async fn get_secrets(&self, references: &[SecretVaultRef]) -> SecretVaultResult> { + let mut result_map: HashMap = HashMap::default(); + + for secret_ref in references { + let secret_name = secret_ref.key.secret_name.as_ref(); + let secret_version = secret_ref.key.secret_version.as_ref().map(|sv| format!("_v{sv}")).unwrap_or_default(); + let secret_file_name = format!("{secret_name}{secret_version}"); + + match self.get(secret_file_name) { + Ok(secret_value) => { + let metadata = SecretMetadata::create_from_ref(secret_ref); + result_map.insert(secret_ref.clone(), Secret::new(secret_value, metadata)); + } + Err(err) if secret_ref.required => { + return Err(SecretVaultError::DataNotFoundError( + secret_vault::errors::SecretVaultDataNotFoundError::new( + secret_vault::errors::SecretVaultErrorPublicGenericDetails::new("SECRET_NOT_FOUND".into()), + format!( + "Secret is required but corresponding file is not available {secret_file_name:?}: {err}" + )) + )) + } + Err(_err) => {} + } + } + todo!(); + } +} \ No newline at end of file