mecha10_core/secrets/
vault.rs1use super::SecretBackend;
4use crate::{Mecha10Error, Result};
5
6pub struct VaultBackend {
30 client: reqwest::Client,
31 addr: String,
32 token: String,
33 mount_path: String,
34}
35
36impl VaultBackend {
37 pub fn new(addr: String, token: String, mount_path: String) -> Self {
45 Self {
46 client: reqwest::Client::new(),
47 addr,
48 token,
49 mount_path,
50 }
51 }
52
53 pub async fn from_env(mount_path: &str) -> Result<Self> {
57 let addr = std::env::var("VAULT_ADDR")
58 .map_err(|_| Mecha10Error::Configuration("VAULT_ADDR environment variable not set".to_string()))?;
59
60 let token = std::env::var("VAULT_TOKEN")
61 .map_err(|_| Mecha10Error::Configuration("VAULT_TOKEN environment variable not set".to_string()))?;
62
63 Ok(Self::new(addr, token, mount_path.to_string()))
64 }
65
66 fn make_path(&self, key: &str) -> String {
67 format!("/v1/{}/data/{}", self.mount_path, key)
69 }
70}
71
72#[async_trait::async_trait]
73impl SecretBackend for VaultBackend {
74 async fn get(&self, key: &str) -> Result<String> {
75 let url = format!("{}{}", self.addr, self.make_path(key));
76
77 let response = self
78 .client
79 .get(&url)
80 .header("X-Vault-Token", &self.token)
81 .send()
82 .await
83 .map_err(|e| Mecha10Error::Other(format!("Failed to connect to Vault: {}", e)))?;
84
85 if !response.status().is_success() {
86 return Err(Mecha10Error::Other(format!(
87 "Vault returned error status: {}",
88 response.status()
89 )));
90 }
91
92 let data: serde_json::Value = response
93 .json()
94 .await
95 .map_err(|e| Mecha10Error::Other(format!("Failed to parse Vault response: {}", e)))?;
96
97 let secret = data
99 .get("data")
100 .and_then(|d| d.get("data"))
101 .and_then(|d| d.get("value"))
102 .and_then(|v| v.as_str())
103 .ok_or_else(|| Mecha10Error::Other(format!("Secret '{}' not found in Vault or invalid format", key)))?;
104
105 Ok(secret.to_string())
106 }
107
108 async fn set(&self, key: &str, value: &str) -> Result<()> {
109 let url = format!("{}{}", self.addr, self.make_path(key));
110
111 let payload = serde_json::json!({
112 "data": {
113 "value": value
114 }
115 });
116
117 let response = self
118 .client
119 .post(&url)
120 .header("X-Vault-Token", &self.token)
121 .json(&payload)
122 .send()
123 .await
124 .map_err(|e| Mecha10Error::Other(format!("Failed to connect to Vault: {}", e)))?;
125
126 if !response.status().is_success() {
127 return Err(Mecha10Error::Other(format!(
128 "Vault returned error status: {}",
129 response.status()
130 )));
131 }
132
133 Ok(())
134 }
135}