use dirs;
use log::debug;
use reqwest::{self, Url};
use serde::Deserialize;
use serde_json;
use std::collections::BTreeMap;
use std::env;
use std::fs::File;
use std::io::Read;
use crate::backend::Backend;
use crate::errors::*;
use crate::secretfile::{Location, Secretfile, SecretfileLookup};
mod kubernetes;
use self::kubernetes::vault_kubernetes_token;
fn default_addr() -> Result<String> {
env::var("VAULT_ADDR").map_err(|_| Error::MissingVaultAddr)
}
fn default_token(addr: &reqwest::Url) -> Result<String> {
(|| -> Result<String> {
if let Ok(token) = env::var("VAULT_TOKEN") {
Ok(token)
} else if let Some(token) = vault_kubernetes_token(addr)? {
Ok(token)
} else {
let mut path = dirs::home_dir().ok_or(Error::NoHomeDirectory)?;
path.push(".vault-token");
let mut f = File::open(path)?;
let mut token = String::new();
f.read_to_string(&mut token)?;
Ok(token)
}
})()
.map_err(|err| Error::MissingVaultToken(Box::new(err)))
}
#[derive(Debug, Deserialize)]
struct Secret {
data: BTreeMap<String, String>,
lease_duration: u64,
}
pub struct Client {
client: reqwest::Client,
addr: reqwest::Url,
token: String,
secrets: BTreeMap<String, Secret>,
}
impl Client {
pub fn is_enabled() -> bool {
default_addr().is_ok()
}
pub fn default() -> Result<Client> {
let client = reqwest::Client::new();
let addr = default_addr()?.parse()?;
let token = default_token(&addr)?;
Client::new(client, addr, token)
}
fn new<U, S>(client: reqwest::Client, addr: U, token: S) -> Result<Client>
where
U: Into<Url>,
S: Into<String>,
{
Ok(Client {
client,
addr: addr.into(),
token: token.into(),
secrets: BTreeMap::new(),
})
}
fn get_secret(&self, path: &str) -> Result<Secret> {
let url = self.addr.join(&format!("v1/{}", path))?;
debug!("Getting secret {}", url);
let mkerr = |err| Error::Url {
url: url.clone(),
cause: Box::new(err),
};
let mut res = self
.client
.get(url.clone())
.header("Connection", "close")
.header("X-Vault-Token", &self.token[..])
.send()
.map_err(|err| (&mkerr)(Error::Other(err.into())))?;
let mut body = String::new();
res.read_to_string(&mut body)?;
if res.status().is_success() {
Ok(serde_json::from_str(&body)?)
} else {
let status = res.status().to_owned();
Err(mkerr(Error::UnexpectedHttpStatus {
status,
body: body.trim().to_owned(),
}))
}
}
fn get_loc(
&mut self,
searched_for: &str,
loc: Option<Location>,
) -> Result<String> {
match loc {
None => Err(Error::MissingEntry {
name: searched_for.to_owned(),
}),
Some(Location::PathWithKey(ref path, ref key)) => {
if !self.secrets.contains_key(path) {
let secret = self.get_secret(path)?;
self.secrets.insert(path.to_owned(), secret);
}
let secret = &self.secrets[path];
secret
.data
.get(key)
.ok_or_else(|| Error::MissingKeyInSecret {
secret: path.to_owned(),
key: key.to_owned(),
})
.map(|v| v.clone())
}
Some(Location::Path(ref path)) => Err(Error::MissingKeyInPath {
path: path.to_owned(),
}),
}
}
}
impl Backend for Client {
fn name(&self) -> &'static str {
"vault"
}
fn var(&mut self, secretfile: &Secretfile, credential: &str) -> Result<String> {
let loc = secretfile.var(credential).cloned();
self.get_loc(credential, loc)
}
fn file(&mut self, secretfile: &Secretfile, path: &str) -> Result<String> {
let loc = secretfile.file(path).cloned();
self.get_loc(path, loc)
}
}