vault_audit_tools/
vault_api.rs1use anyhow::{anyhow, Context, Result};
29use reqwest::Client;
30use serde::de::DeserializeOwned;
31use serde_json::Value;
32use std::env;
33use std::fs;
34
35pub fn should_skip_verify(insecure_flag: bool) -> bool {
41 if insecure_flag {
42 return true;
43 }
44
45 env::var("VAULT_SKIP_VERIFY")
47 .ok()
48 .and_then(|v| {
49 v.parse::<bool>().ok().or_else(|| {
50 match v.to_lowercase().as_str() {
52 "1" | "true" | "yes" => Some(true),
53 _ => Some(false),
54 }
55 })
56 })
57 .unwrap_or(false)
58}
59
60#[derive(Debug, Clone)]
64pub struct VaultClient {
65 addr: String,
66 token: String,
67 client: Client,
68}
69
70impl VaultClient {
71 pub fn new(addr: &str, token: String) -> Result<Self> {
73 Self::new_with_skip_verify(addr, token, false)
74 }
75
76 pub fn new_with_skip_verify(addr: &str, token: String, skip_verify: bool) -> Result<Self> {
78 let client = Client::builder()
79 .danger_accept_invalid_certs(skip_verify)
80 .build()
81 .context("Failed to create HTTP client")?;
82
83 Ok(Self {
84 addr: addr.trim_end_matches('/').to_string(),
85 token,
86 client,
87 })
88 }
89
90 #[allow(dead_code)]
92 pub fn from_env() -> Result<Self> {
93 let addr = env::var("VAULT_ADDR").unwrap_or_else(|_| "http://127.0.0.1:8200".to_string());
94
95 let token = if let Ok(token) = env::var("VAULT_TOKEN") {
96 token
97 } else if let Ok(token_file) = env::var("VAULT_TOKEN_FILE") {
98 fs::read_to_string(&token_file)
99 .with_context(|| format!("Failed to read token from file: {}", token_file))?
100 .trim()
101 .to_string()
102 } else {
103 return Err(anyhow!(
104 "VAULT_TOKEN or VAULT_TOKEN_FILE must be set. Provide a token via:\n\
105 - Environment variable: export VAULT_TOKEN=hvs.xxxxx\n\
106 - Token file: export VAULT_TOKEN_FILE=/path/to/token"
107 ));
108 };
109
110 Self::new(&addr, token)
111 }
112
113 pub fn from_options(
115 vault_addr: Option<&str>,
116 vault_token: Option<&str>,
117 skip_verify: bool,
118 ) -> Result<Self> {
119 let addr = vault_addr
120 .map(std::string::ToString::to_string)
121 .or_else(|| env::var("VAULT_ADDR").ok())
122 .unwrap_or_else(|| "http://127.0.0.1:8200".to_string());
123
124 let token = if let Some(t) = vault_token {
125 t.to_string()
126 } else if let Ok(t) = env::var("VAULT_TOKEN") {
127 t
128 } else if let Ok(token_file) = env::var("VAULT_TOKEN_FILE") {
129 fs::read_to_string(&token_file)
130 .with_context(|| format!("Failed to read token from file: {}", token_file))?
131 .trim()
132 .to_string()
133 } else {
134 return Err(anyhow!(
135 "VAULT_TOKEN or VAULT_TOKEN_FILE must be set. Provide a token via:\n\
136 - Command-line: --vault-token hvs.xxxxx\n\
137 - Environment variable: export VAULT_TOKEN=hvs.xxxxx\n\
138 - Token file: export VAULT_TOKEN_FILE=/path/to/token"
139 ));
140 };
141
142 Self::new_with_skip_verify(&addr, token, skip_verify)
143 }
144
145 pub async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
147 let url = format!("{}{}", self.addr, path);
148
149 let response = self
150 .client
151 .get(&url)
152 .header("X-Vault-Token", &self.token)
153 .send()
154 .await
155 .context("Failed to send request to Vault")?;
156
157 let status = response.status();
158 let body = response
159 .text()
160 .await
161 .context("Failed to read response body")?;
162
163 if !status.is_success() {
164 return Err(anyhow!(
165 "Vault API request failed with status {}: {}",
166 status,
167 body
168 ));
169 }
170
171 serde_json::from_str(&body)
172 .with_context(|| format!("Failed to parse JSON response from {}", path))
173 }
174
175 pub async fn get_json(&self, path: &str) -> Result<Value> {
177 self.get(path).await
178 }
179
180 pub async fn get_text(&self, path: &str) -> Result<String> {
182 let url = format!("{}{}", self.addr, path);
183
184 let response = self
185 .client
186 .get(&url)
187 .header("X-Vault-Token", &self.token)
188 .send()
189 .await
190 .context("Failed to send request to Vault")?;
191
192 let status = response.status();
193 let body = response
194 .text()
195 .await
196 .context("Failed to read response body")?;
197
198 if !status.is_success() {
199 return Err(anyhow!(
200 "Vault API request failed with status {}: {}",
201 status,
202 body
203 ));
204 }
205
206 Ok(body)
207 }
208
209 pub fn addr(&self) -> &str {
211 &self.addr
212 }
213}
214
215pub fn extract_data<T: DeserializeOwned>(value: Value) -> Result<T> {
217 if let Some(data) = value.get("data") {
218 serde_json::from_value(data.clone())
219 .context("Failed to deserialize data from Vault response")
220 } else {
221 serde_json::from_value(value).context("Failed to deserialize Vault response")
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_client_creation() {
231 let client = VaultClient::new("http://127.0.0.1:8200", "test-token".to_string());
232 assert!(client.is_ok());
233 }
234
235 #[test]
236 fn test_addr_trimming() {
237 let client = VaultClient::new("http://127.0.0.1:8200/", "test-token".to_string()).unwrap();
238 assert_eq!(client.addr(), "http://127.0.0.1:8200");
239 }
240}