use super::SecretBackend;
use crate::{Mecha10Error, Result};
pub struct VaultBackend {
client: reqwest::Client,
addr: String,
token: String,
mount_path: String,
}
impl VaultBackend {
pub fn new(addr: String, token: String, mount_path: String) -> Self {
Self {
client: reqwest::Client::new(),
addr,
token,
mount_path,
}
}
pub async fn from_env(mount_path: &str) -> Result<Self> {
let addr = std::env::var("VAULT_ADDR")
.map_err(|_| Mecha10Error::Configuration("VAULT_ADDR environment variable not set".to_string()))?;
let token = std::env::var("VAULT_TOKEN")
.map_err(|_| Mecha10Error::Configuration("VAULT_TOKEN environment variable not set".to_string()))?;
Ok(Self::new(addr, token, mount_path.to_string()))
}
fn make_path(&self, key: &str) -> String {
format!("/v1/{}/data/{}", self.mount_path, key)
}
fn check_status(response: &reqwest::Response) -> Result<()> {
if !response.status().is_success() {
return Err(Mecha10Error::Other(format!(
"Vault returned error status: {}",
response.status()
)));
}
Ok(())
}
}
#[async_trait::async_trait]
impl SecretBackend for VaultBackend {
async fn get(&self, key: &str) -> Result<String> {
let url = format!("{}{}", self.addr, self.make_path(key));
let response = self
.client
.get(&url)
.header("X-Vault-Token", &self.token)
.send()
.await
.map_err(|e| Mecha10Error::Other(format!("Failed to connect to Vault: {}", e)))?;
Self::check_status(&response)?;
let data: serde_json::Value = response
.json()
.await
.map_err(|e| Mecha10Error::Other(format!("Failed to parse Vault response: {}", e)))?;
let secret = data
.get("data")
.and_then(|d| d.get("data"))
.and_then(|d| d.get("value"))
.and_then(|v| v.as_str())
.ok_or_else(|| Mecha10Error::Other(format!("Secret '{}' not found in Vault or invalid format", key)))?;
Ok(secret.to_string())
}
async fn set(&self, key: &str, value: &str) -> Result<()> {
let url = format!("{}{}", self.addr, self.make_path(key));
let payload = serde_json::json!({
"data": {
"value": value
}
});
let response = self
.client
.post(&url)
.header("X-Vault-Token", &self.token)
.json(&payload)
.send()
.await
.map_err(|e| Mecha10Error::Other(format!("Failed to connect to Vault: {}", e)))?;
Self::check_status(&response)?;
Ok(())
}
}