1#![deny(
2 missing_docs,
3 missing_debug_implementations,
4 trivial_casts,
5 trivial_numeric_casts,
6 unsafe_code,
7 unstable_features,
8 unused_import_braces,
9 unused_qualifications,
10 unused_results
11)]
12#![cfg_attr(test, deny(warnings))]
13#![cfg_attr(feature = "clippy", allow(unstable_features))]
14#![cfg_attr(feature = "clippy", feature(plugin))]
15#![cfg_attr(feature = "clippy", plugin(clippy))]
16#![cfg_attr(feature = "clippy", deny(clippy))]
17
18extern crate base64;
21extern crate reqwest;
22#[macro_use]
23extern crate log;
24#[macro_use]
25extern crate quick_error;
26pub extern crate chrono;
27extern crate serde;
28pub extern crate url;
29
30pub mod client;
32pub use crate::client::error::{Error, Result};
33pub use crate::client::VaultClient as Client;
34use url::Url;
35
36pub trait TryInto<T>: Sized {
46 type Err;
48
49 fn try_into(self) -> ::std::result::Result<T, Self::Err>;
51}
52
53pub trait TryFrom<T>: Sized {
57 type Err;
59
60 fn try_from(_: T) -> ::std::result::Result<Self, Self::Err>;
62}
63
64impl<T, U> TryInto<U> for T
65where
66 U: TryFrom<T>,
67{
68 type Err = U::Err;
69
70 fn try_into(self) -> ::std::result::Result<U, U::Err> {
71 U::try_from(self)
72 }
73}
74
75impl TryFrom<Url> for Url {
76 type Err = Error;
77 fn try_from(u: Url) -> ::std::result::Result<Self, Self::Err> {
78 Ok(u)
79 }
80}
81
82impl<'a> TryFrom<&'a Url> for Url {
83 type Err = Error;
84 fn try_from(u: &Url) -> ::std::result::Result<Self, Self::Err> {
85 Ok(u.clone())
86 }
87}
88
89impl<'a> TryFrom<&'a str> for Url {
90 type Err = Error;
91 fn try_from(s: &str) -> ::std::result::Result<Self, Self::Err> {
92 match Url::parse(s) {
93 Ok(u) => Ok(u),
94 Err(e) => Err(e.into()),
95 }
96 }
97}
98
99impl<'a> TryFrom<&'a String> for Url {
100 type Err = Error;
101 fn try_from(s: &String) -> ::std::result::Result<Self, Self::Err> {
102 match Url::parse(s) {
103 Ok(u) => Ok(u),
104 Err(e) => Err(e.into()),
105 }
106 }
107}
108
109impl TryFrom<String> for Url {
110 type Err = Error;
111 fn try_from(s: String) -> ::std::result::Result<Self, Self::Err> {
112 match Url::parse(&s) {
113 Ok(u) => Ok(u),
114 Err(e) => Err(e.into()),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use crate::client::HttpVerb::*;
122 use crate::client::VaultClient as Client;
123 use crate::client::{self, EndpointResponse};
124 use crate::Error;
125 use reqwest::StatusCode;
126 use serde::{Deserialize, Serialize};
127 use serde_json::Value;
128
129 const HOST: &str = "http://127.0.0.1:8200";
131 const TOKEN: &str = "test12345";
133
134 #[test]
135 fn it_can_create_a_client() {
136 let _ = Client::new(HOST, TOKEN).unwrap();
137 }
138
139 #[test]
140 fn it_can_create_a_client_from_a_string_reference() {
141 let _ = Client::new(&HOST.to_string(), TOKEN).unwrap();
142 }
143
144 #[test]
145 fn it_can_create_a_client_from_a_string() {
146 let _ = Client::new(HOST.to_string(), TOKEN).unwrap();
147 }
148
149 #[test]
150 fn it_can_query_secrets() {
151 let client = Client::new(HOST, TOKEN).unwrap();
152 let res = client.set_secret("hello_query", "world");
153 assert!(res.is_ok());
154 let res = client.get_secret("hello_query").unwrap();
155 assert_eq!(res, "world");
156 }
157
158 #[test]
159 fn it_can_store_json_secrets() {
160 let client = Client::new(HOST, TOKEN).unwrap();
161 let json = "{\"foo\": {\"bar\": [\"baz\"]}}";
162 let res = client.set_secret("json_secret", json);
163 assert!(res.is_ok());
164 let res = client.get_secret("json_secret").unwrap();
165 assert_eq!(res, json)
166 }
167
168 #[test]
169 fn it_can_list_secrets() {
170 let client = Client::new(HOST, TOKEN).unwrap();
171
172 let _res = client.set_secret("hello/fred", "world").unwrap();
173 let res = client.set_secret("hello/bob", "world");
175 assert!(res.is_ok());
176
177 let res = client.list_secrets("hello");
178 assert!(res.is_ok());
179 assert_eq!(res.unwrap(), ["bob", "fred"]);
180
181 let res = client.list_secrets("hello/");
182 assert!(res.is_ok());
183 assert_eq!(res.unwrap(), ["bob", "fred"]);
184 }
185
186 #[test]
187 fn it_can_detect_404_status() {
188 let client = Client::new(HOST, TOKEN).unwrap();
189
190 let res = client.list_secrets("non/existent/key");
191 assert!(res.is_err());
192
193 if let Err(Error::VaultResponse(_, response)) = res {
194 assert_eq!(response.status(), StatusCode::NOT_FOUND);
195 } else {
196 panic!("Error should match on VaultResponse with reqwest response.");
197 }
198 }
199
200 #[test]
201 fn it_can_write_secrets_with_newline() {
202 let client = Client::new(HOST, TOKEN).unwrap();
203
204 let res = client.set_secret("hello_set", "world\n");
205 assert!(res.is_ok());
206 let res = client.get_secret("hello_set").unwrap();
207 assert_eq!(res, "world\n");
208 }
209
210 #[test]
211 fn it_returns_err_on_forbidden() {
212 let client = Client::new(HOST, "test123456");
213 assert!(client.is_err());
215 }
216
217 #[test]
218 fn it_can_delete_a_secret() {
219 let client = Client::new(HOST, TOKEN).unwrap();
220
221 let res = client.set_secret("hello_delete", "world");
222 assert!(res.is_ok());
223 let res = client.get_secret("hello_delete").unwrap();
224 assert_eq!(res, "world");
225 let res = client.delete_secret("hello_delete");
226 assert!(res.is_ok());
227 let res = client.get_secret("hello_delete");
228 assert!(res.is_err());
229 }
230
231 #[test]
232 fn it_can_perform_approle_workflow() {
233 use std::collections::HashMap;
234
235 let c = Client::new(HOST, TOKEN).unwrap();
236 let mut body = "{\"type\":\"approle\"}";
237 let _: EndpointResponse<()> = c
242 .call_endpoint(DELETE, "sys/auth/approle", None, None)
243 .unwrap();
244 let mut res: EndpointResponse<()> = c
246 .call_endpoint(POST, "sys/auth/approle", None, Some(body))
247 .unwrap();
248 panic_non_empty(&res);
249 body = "{\"secret_id_ttl\":\"10m\", \"token_ttl\":\"20m\", \"token_max_ttl\":\"30m\", \
251 \"secret_id_num_uses\":40}";
252 res = c
253 .call_endpoint(POST, "auth/approle/role/test_role", None, Some(body))
254 .unwrap();
255 panic_non_empty(&res);
256
257 let _ = c.get_app_role_properties("test_role").unwrap();
259
260 let res: EndpointResponse<HashMap<String, String>> = c
262 .call_endpoint(GET, "auth/approle/role/test_role/role-id", None, None)
263 .unwrap();
264 let data = match res {
265 EndpointResponse::VaultResponse(res) => res.data.unwrap(),
266 _ => panic!("expected vault response, got: {:?}", res),
267 };
268 let role_id = &data["role_id"];
269 assert!(!role_id.is_empty());
270
271 let res: EndpointResponse<HashMap<String, Value>> = c
273 .call_endpoint(POST, "auth/approle/role/test_role/secret-id", None, None)
274 .unwrap();
275 let data = match res {
276 EndpointResponse::VaultResponse(res) => res.data.unwrap(),
277 _ => panic!("expected vault response, got: {:?}", res),
278 };
279 let secret_id = &data["secret_id"].as_str().unwrap();
280
281 let _ = Client::new_app_role(HOST, &role_id[..], Some(&secret_id[..])).unwrap();
283
284 let res = c
286 .call_endpoint(DELETE, "sys/auth/approle", None, None)
287 .unwrap();
288 panic_non_empty(&res);
289 }
290
291 #[test]
292 fn it_can_read_a_wrapped_secret() {
293 let client = Client::new(HOST, TOKEN).unwrap();
294 let res = client.set_secret("hello_delete_2", "second world");
295 assert!(res.is_ok());
296 let res = client.get_secret_wrapped("hello_delete_2", "2m").unwrap();
298 let wrapping_token = res.wrap_info.unwrap().token;
299 let c2 = Client::new_no_lookup(HOST, wrapping_token).unwrap();
301 let res = c2.get_unwrapped_response().unwrap();
303 assert_eq!(res.data.unwrap()["value"], "second world");
304 }
305
306 #[test]
307 fn it_can_store_policies() {
308 let c = Client::new("http://127.0.0.1:8200/", TOKEN).unwrap();
310 let body = "{\"policy\":\"{}\"}";
311 let res: EndpointResponse<()> = c
313 .call_endpoint(PUT, "sys/policy/test_policy_1", None, Some(body))
314 .unwrap();
315 panic_non_empty(&res);
316 let res: EndpointResponse<()> = c
317 .call_endpoint(PUT, "sys/policy/test_policy_2", None, Some(body))
318 .unwrap();
319 panic_non_empty(&res);
320 let client_policies = c.policies().unwrap();
321 let expected_policies = ["default", "test_policy_1", "test_policy_2", "root"];
322 let _ = expected_policies
323 .iter()
324 .map(|p| {
325 assert!(client_policies.contains(&(*p).to_string()));
326 })
327 .last();
328 let token_name = "policy_test_token".to_string();
329 let token_opts = client::TokenOptions::default()
330 .policies(vec!["test_policy_1", "test_policy_2"].into_iter())
331 .default_policy(false)
332 .id(&token_name[..])
333 .ttl(client::VaultDuration::minutes(1));
334 let _ = c.create_token(&token_opts).unwrap();
335 let body = format!("{{\"token\":\"{}\"}}", &token_name);
336 let res: EndpointResponse<client::TokenData> = c
337 .call_endpoint(POST, "auth/token/lookup", None, Some(&body))
338 .unwrap();
339 match res {
340 EndpointResponse::VaultResponse(res) => {
341 let data = res.data.unwrap();
342 let mut policies = data.policies;
343 policies.sort();
344 assert_eq!(policies, ["test_policy_1", "test_policy_2"]);
345 }
346 _ => panic!("expected vault response, got: {:?}", res),
347 }
348 let res: EndpointResponse<()> = c
350 .call_endpoint(DELETE, "sys/policy/test_policy_1", None, None)
351 .unwrap();
352 panic_non_empty(&res);
353 let res: EndpointResponse<()> = c
354 .call_endpoint(DELETE, "sys/policy/test_policy_2", None, None)
355 .unwrap();
356 panic_non_empty(&res);
357 }
358
359 #[test]
360 fn it_can_list_things() {
361 let c = Client::new(HOST, TOKEN).unwrap();
362 let _ = c
363 .create_token(&client::TokenOptions::default().ttl(client::VaultDuration::minutes(1)))
364 .unwrap();
365 let res: EndpointResponse<client::ListResponse> = c
366 .call_endpoint(LIST, "auth/token/accessors", None, None)
367 .unwrap();
368 match res {
369 EndpointResponse::VaultResponse(res) => {
370 let data = res.data.unwrap();
371 assert!(data.keys.len() > 2);
372 }
373 _ => panic!("expected vault response, got: {:?}", res),
374 }
375 }
376
377 #[test]
378 fn it_can_encrypt_decrypt_transit() {
379 let key_id = "test-vault-rs";
380 let plaintext = b"data\0to\0encrypt";
381
382 let client = Client::new(HOST, TOKEN).unwrap();
383 let enc_resp = client.transit_encrypt(None, key_id, plaintext);
384 let encrypted = enc_resp.unwrap();
385 let dec_resp = client.transit_decrypt(None, key_id, encrypted);
386 let payload = dec_resp.unwrap();
387 assert_eq!(plaintext, payload.as_slice());
388 }
389
390 fn panic_non_empty(res: &EndpointResponse<()>) {
392 match *res {
393 EndpointResponse::Empty => {}
394 _ => panic!("expected empty response, received: {:?}", res),
395 }
396 }
397
398 #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
399 struct CustomSecretType {
400 name: String,
401 }
402
403 #[test]
404 fn it_can_set_and_get_a_custom_secret_type() {
405 let input = CustomSecretType {
406 name: "test".into(),
407 };
408
409 let client = Client::new(HOST, TOKEN).unwrap();
410
411 let res = client.set_custom_secret("custom_type", &input);
412 assert!(res.is_ok());
413 let res: CustomSecretType = client.get_custom_secret("custom_type").unwrap();
414 assert_eq!(res, input);
415 }
416}