#[cfg(feature = "auth")]
mod auth;
pub mod error;
#[cfg(feature = "metadata")]
pub mod metadata;
use std::fs::File;
use std::io::prelude::*;
#[cfg(feature = "metadata")]
use std::sync::Arc;
use serde::Deserialize;
use vaultrs::api::kv2::responses::ReadSecretMetadataResponse;
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};
use vaultrs::error::ClientError;
use vaultrs::kv2;
use crate::error::VaultierError;
#[cfg(feature = "write")]
use serde::Serialize;
#[cfg(feature = "write")]
use vaultrs::api::kv2::requests::SetSecretRequestOptions;
#[cfg(feature = "write")]
use vaultrs::api::kv2::responses::SecretVersionMetadata;
use crate::error::Result;
#[cfg(feature = "auth")]
use crate::auth::login;
#[cfg(feature = "token")]
const VAULT_TOKEN_PATH: &str = "/vault/secrets/token";
pub struct SecretClient {
client: VaultClient,
mount: String,
base_path: String,
#[cfg(feature = "metadata")]
address: Arc<str>,
#[cfg(feature = "metadata")]
token: Arc<str>,
#[cfg(feature = "metadata")]
http_client: reqwest::Client,
}
pub struct WriteSecretOptions<'a, A> {
pub data: A,
pub path: Option<&'a str>,
pub version: Option<u32>,
}
pub struct SecretWithMetaData<A> {
pub data: A,
pub metadata: ReadSecretMetadataResponse,
}
impl SecretClient {
#[cfg(feature = "token")]
pub fn new(
address: &str,
mount: String,
base_path: String,
token: Option<String>,
#[cfg(feature = "metadata")] http_client: reqwest::Client,
) -> Result<SecretClient> {
let token = match token {
Some(token) => token,
None => read_token_from(VAULT_TOKEN_PATH)?,
};
Self::create_internal(
address,
mount,
base_path,
&token,
#[cfg(feature = "metadata")]
http_client,
)
}
#[cfg(feature = "auth")]
pub async fn create(
address: &str,
auth_mount: &str,
role: &str,
mount: String,
base_path: String,
#[cfg(feature = "metadata")] http_client: reqwest::Client,
) -> Result<SecretClient> {
let auth = login(address, auth_mount, role).await?;
Self::create_internal(
address,
mount,
base_path,
&auth.client_token,
#[cfg(feature = "metadata")]
http_client,
)
}
fn create_internal(
address: &str,
mount: String,
base_path: String,
token: &str,
#[cfg(feature = "metadata")] http_client: reqwest::Client,
) -> Result<SecretClient> {
let client = VaultClient::new(
VaultClientSettingsBuilder::default()
.address(address)
.token(token)
.build()?,
)?;
Ok(SecretClient {
client,
mount,
base_path,
#[cfg(feature = "metadata")]
address: Arc::from(address),
#[cfg(feature = "metadata")]
token: Arc::from(token),
#[cfg(feature = "metadata")]
http_client,
})
}
#[cfg(feature = "read")]
pub async fn read_secrets<A>(&self) -> Result<A>
where
A: for<'de> Deserialize<'de>,
{
self.read_secrets_internal::<A>(&self.base_path, None).await
}
#[cfg(feature = "read")]
pub async fn read_secrets_from<A>(&self, path: &str) -> Result<A>
where
A: for<'de> Deserialize<'de>,
{
let path = format!("{}/{}", self.base_path, path);
self.read_secrets_internal::<A>(&path, None).await
}
#[cfg(feature = "read")]
pub async fn read_secrets_with_metadata<A>(
&self,
path: Option<&str>,
) -> Result<SecretWithMetaData<A>>
where
A: for<'de> Deserialize<'de>,
{
let path = path.unwrap_or(&self.base_path);
let metadata: vaultrs::api::kv2::responses::ReadSecretMetadataResponse =
kv2::read_metadata(&self.client, &self.mount, path).await?;
let data = self
.read_secrets_internal(path, Some(metadata.current_version))
.await?;
Ok(SecretWithMetaData { data, metadata })
}
#[cfg(feature = "read")]
async fn read_secrets_internal<A>(&self, path: &str, version: Option<u64>) -> Result<A>
where
A: for<'de> Deserialize<'de>,
{
let secrets = match version {
Some(version) => kv2::read_version::<A>(&self.client, &self.mount, path, version).await,
None => kv2::read::<A>(&self.client, &self.mount, path).await,
};
if let Err(ClientError::APIError { code: 404, .. }) = secrets {
return Err(VaultierError::PathNotFound(format!(
"{mount}/data/{path}",
mount = self.mount
)));
}
Ok(secrets?)
}
#[cfg(feature = "write")]
pub async fn set_secrets<A>(&self, data: &A) -> Result<SecretVersionMetadata>
where
A: Serialize,
{
self.set_secrets_internal(&self.base_path, data).await
}
#[cfg(feature = "write")]
pub async fn set_secrets_in<A>(&self, path: &str, data: &A) -> Result<SecretVersionMetadata>
where
A: Serialize,
{
let path = format!("{}/{}", self.base_path, path);
self.set_secrets_internal(&path, data).await
}
#[cfg(feature = "write")]
async fn set_secrets_internal<A>(&self, path: &str, data: &A) -> Result<SecretVersionMetadata>
where
A: Serialize,
{
let auth_info = kv2::set(&self.client, &self.mount, path, data).await?;
Ok(auth_info)
}
#[cfg(feature = "write")]
pub async fn set_secrets_with_options<A>(
&self,
options: WriteSecretOptions<'_, A>,
) -> Result<SecretVersionMetadata>
where
A: Serialize,
{
let path = options.path.unwrap_or_else(|| &self.base_path);
let auth_info = match options.version {
Some(cas) => {
kv2::set_with_options(
&self.client,
&self.mount,
path,
&options.data,
SetSecretRequestOptions { cas },
)
.await?
}
None => kv2::set(&self.client, &self.mount, path, &options.data).await?,
};
Ok(auth_info)
}
#[cfg(feature = "metadata")]
pub async fn set_metadata(&self, metadata: &metadata::Metadata<'_>) -> Result<()> {
let url = url::Url::parse(&format!(
"{address}/v1/{mount}/metadata/{path}",
address = self.address,
mount = self.mount,
path = self.base_path
))?;
metadata::set_metadata_internal(self, url, metadata).await
}
#[cfg(feature = "metadata")]
pub async fn set_metadata_in(
&self,
path: &str,
metadata: &metadata::Metadata<'_>,
) -> Result<()> {
let url = url::Url::parse(&format!(
"{address}/v1/{mount}/metadata/{base_path}/{path}",
address = self.address,
mount = self.mount,
base_path = self.base_path
))?;
metadata::set_metadata_internal(self, url, metadata).await
}
}
fn read_token_from(path: &str) -> Result<String> {
let mut file = File::open(path)?;
let mut token = String::new();
file.read_to_string(&mut token)?;
Ok(token)
}