use std::collections::{HashMap, HashSet};
use crate::Error;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::map::Map;
use serde_json::Value;
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)]
pub struct SecretEngine {
pub path: String,
pub r#type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub config: Option<SecretsEngineConfig>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)]
pub struct SecretsEngineConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_lease_ttl: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_lease_ttl: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub force_no_cache: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audit_non_hmac_request_keys: Option<HashSet<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audit_non_hmac_response_keys: Option<HashSet<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub listing_visibility: Option<ListingVisibility>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub passthrough_request_headers: Option<HashSet<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_response_headers: Option<HashSet<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub options: Option<HashMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub local: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seal_wrap: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)]
pub struct SecretsEngineTune {
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_lease_ttl: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_lease_ttl: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audit_non_hmac_request_keys: Option<HashSet<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audit_non_hmac_response_keys: Option<HashSet<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub listing_visibility: Option<ListingVisibility>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub passthrough_request_headers: Option<HashSet<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_response_headers: Option<HashSet<String>>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ListingVisibility {
Unauth,
Hidden,
}
#[async_trait]
pub trait Mounts {
async fn list(&self) -> Result<HashMap<String, SecretEngine>, Error>;
async fn enable(&self, engine: &SecretEngine) -> Result<crate::Response, Error>;
async fn disable(&self, path: &str) -> Result<crate::Response, Error>;
async fn get(&self, path: &str) -> Result<SecretsEngineConfig, Error>;
async fn tune(&self, path: &str, config: &SecretsEngineTune) -> Result<crate::Response, Error>;
}
#[async_trait]
impl<T> Mounts for T
where
T: crate::Vault + Send + Sync,
{
async fn list(&self) -> Result<HashMap<String, SecretEngine>, Error> {
let values: HashMap<String, Map<String, Value>> = self.get("sys/mounts").await?.data()?;
let values: Result<HashMap<String, SecretEngine>, Error> = values
.into_iter()
.map(|(path, mut map)| {
let path = path.trim_end_matches('/').to_string();
let _ = map.insert("path".to_string(), serde_json::Value::String(path.clone()));
let value = Value::Object(map);
let engine = serde_json::from_value(value)?;
Ok((path, engine))
})
.collect();
Ok(values?)
}
async fn enable(&self, engine: &SecretEngine) -> Result<crate::Response, Error> {
let mut value = serde_json::to_value(engine)?;
let path = value["path"].take();
let path = format!("sys/mounts/{}", path.as_str().expect("To be a string"));
self.post(&path, &value, false).await
}
async fn disable(&self, path: &str) -> Result<crate::Response, Error> {
let path = format!("sys/mounts/{}", path);
self.delete(&path, false).await
}
async fn get(&self, path: &str) -> Result<SecretsEngineConfig, Error> {
let path = format!("sys/mounts/{}/tune", path);
self.get(&path).await?.data()
}
async fn tune(&self, path: &str, config: &SecretsEngineTune) -> Result<crate::Response, Error> {
let path = format!("sys/mounts/{}/tune", path);
self.post(&path, config, false).await
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::Vault;
pub(crate) struct Mount<T>
where
T: Vault + Send + Sync,
{
pub(crate) path: String,
pub(crate) client: T,
}
impl<T> Mount<T>
where
T: Vault + Send + Sync + Clone,
{
pub(crate) async fn new(client: &T, config: &SecretEngine) -> Self {
let response = Mounts::enable(&client, &config).await.unwrap();
assert!(response.ok().unwrap().is_none());
Mount {
path: config.path.clone(),
client: client.clone(),
}
}
}
impl<T> Drop for Mount<T>
where
T: Vault + Send + Sync,
{
fn drop(&mut self) {
let response =
futures::executor::block_on(Mounts::disable(&self.client, &self.path)).unwrap();
assert!(response.ok().unwrap().is_none());
}
}
#[tokio::test(threaded_scheduler)]
async fn can_list_mounts() {
let client = crate::tests::vault_client();
let _ = Mounts::list(&client).await.unwrap();
}
#[tokio::test(threaded_scheduler)]
async fn can_mount_and_unmount_kv() {
let client = crate::tests::vault_client();
let path = crate::tests::uuid();
let engine = SecretEngine {
path: path.clone(),
r#type: "kv".to_string(),
..Default::default()
};
let response = Mounts::enable(&client, &engine).await.unwrap();
assert!(response.ok().unwrap().is_none());
let mounts = Mounts::list(&client).await.unwrap();
assert!(mounts.get(&path).is_some());
let _ = Mounts::get(&client, &path).await.unwrap();
let _ = Mounts::tune(
&client,
&path,
&SecretsEngineTune {
description: Some("hello world".to_string()),
..Default::default()
},
)
.await
.unwrap();
let response = Mounts::disable(&client, &path).await.unwrap();
assert!(response.ok().unwrap().is_none());
let mounts = Mounts::list(&client).await.unwrap();
assert!(mounts.get(&path).is_none());
}
}