use std::collections::HashMap;
use std::fmt;
use std::io::Read;
use std::num::NonZeroU64;
use std::result::Result as StdResult;
use std::str::FromStr;
use crate::client::error::{Error, Result};
use base64;
use reqwest::{
self,
blocking::{Client, Response},
header::CONTENT_TYPE,
Method,
};
use serde::de::{self, DeserializeOwned, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::TryInto;
use chrono::{DateTime, FixedOffset, NaiveDateTime};
use serde_json;
use std::time::Duration;
use url::Url;
pub mod error;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct VaultDuration(pub Duration);
impl VaultDuration {
pub fn seconds(s: u64) -> VaultDuration {
VaultDuration(Duration::from_secs(s))
}
pub fn minutes(m: u64) -> VaultDuration {
VaultDuration::seconds(m * 60)
}
pub fn hours(h: u64) -> VaultDuration {
VaultDuration::minutes(h * 60)
}
pub fn days(d: u64) -> VaultDuration {
VaultDuration::hours(d * 24)
}
}
impl Serialize for VaultDuration {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u64(self.0.as_secs())
}
}
struct VaultDurationVisitor;
impl<'de> Visitor<'de> for VaultDurationVisitor {
type Value = VaultDuration;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a positive integer")
}
fn visit_u64<E>(self, value: u64) -> StdResult<Self::Value, E>
where
E: de::Error,
{
Ok(VaultDuration(Duration::from_secs(value)))
}
}
impl<'de> Deserialize<'de> for VaultDuration {
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_u64(VaultDurationVisitor)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum VaultNumUses {
Unlimited,
Limited(NonZeroU64),
}
impl Default for VaultNumUses {
fn default() -> Self {
VaultNumUses::Unlimited
}
}
impl From<u64> for VaultNumUses {
fn from(v: u64) -> Self {
match NonZeroU64::new(v) {
Some(non_zero) => VaultNumUses::Limited(non_zero),
None => VaultNumUses::Unlimited,
}
}
}
impl Serialize for VaultNumUses {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self {
VaultNumUses::Unlimited => serializer.serialize_u64(0),
VaultNumUses::Limited(val) => serializer.serialize_u64(val.clone().into()),
}
}
}
struct VaultNumUsesVisitor;
impl<'de> Visitor<'de> for VaultNumUsesVisitor {
type Value = VaultNumUses;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a positive integer")
}
fn visit_u64<E>(self, value: u64) -> StdResult<Self::Value, E>
where
E: de::Error,
{
Ok(value.into())
}
}
impl<'de> Deserialize<'de> for VaultNumUses {
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_u64(VaultNumUsesVisitor)
}
}
#[derive(Debug)]
pub struct VaultNaiveDateTime(pub NaiveDateTime);
struct VaultNaiveDateTimeVisitor;
impl<'de> Visitor<'de> for VaultNaiveDateTimeVisitor {
type Value = VaultNaiveDateTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a positive integer")
}
fn visit_u64<E>(self, value: u64) -> StdResult<Self::Value, E>
where
E: de::Error,
{
let date_time = NaiveDateTime::from_timestamp_opt(value as i64, 0);
match date_time {
Some(dt) => Ok(VaultNaiveDateTime(dt)),
None => Err(E::custom(format!(
"Could not parse: `{}` as a unix timestamp",
value,
))),
}
}
}
impl<'de> Deserialize<'de> for VaultNaiveDateTime {
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_u64(VaultNaiveDateTimeVisitor)
}
}
#[derive(Debug)]
pub struct VaultDateTime(pub DateTime<FixedOffset>);
struct VaultDateTimeVisitor;
impl<'de> Visitor<'de> for VaultDateTimeVisitor {
type Value = VaultDateTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a timestamp string")
}
fn visit_str<E>(self, value: &str) -> StdResult<Self::Value, E>
where
E: de::Error,
{
let date_time = DateTime::parse_from_rfc3339(value);
match date_time {
Ok(dt) => Ok(VaultDateTime(dt)),
Err(e) => Err(E::custom(format!(
"Could not parse: `{}` as an RFC 3339 timestamp. Error: \
`{:?}`",
value, e
))),
}
}
}
impl<'de> Deserialize<'de> for VaultDateTime {
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(VaultDateTimeVisitor)
}
}
#[derive(Debug)]
pub struct VaultClient<T> {
pub host: Url,
pub token: String,
client: Client,
pub data: Option<VaultResponse<T>>,
secret_backend: String,
}
#[derive(Deserialize, Debug)]
pub struct TokenData {
pub accessor: Option<String>,
pub creation_time: VaultNaiveDateTime,
pub creation_ttl: Option<VaultDuration>,
pub display_name: String,
pub explicit_max_ttl: Option<VaultDuration>,
pub id: String,
pub last_renewal_time: Option<VaultDuration>,
pub meta: Option<HashMap<String, String>>,
pub num_uses: VaultNumUses,
pub orphan: bool,
pub path: String,
pub policies: Vec<String>,
pub renewable: Option<bool>,
pub role: Option<String>,
pub ttl: VaultDuration,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct SecretDataWrapper<D> {
pub data: D,
}
#[derive(Deserialize, Serialize, Debug)]
struct SecretData {
value: String,
}
#[derive(Deserialize, Serialize, Debug)]
struct TransitDecryptedData {
plaintext: String,
}
#[derive(Deserialize, Serialize, Debug)]
struct TransitEncryptedData {
ciphertext: String,
}
#[derive(Deserialize, Debug)]
pub struct Auth {
pub client_token: String,
pub accessor: Option<String>,
pub policies: Vec<String>,
pub metadata: Option<HashMap<String, String>>,
pub lease_duration: Option<VaultDuration>,
pub renewable: bool,
}
#[derive(Deserialize, Debug)]
pub struct VaultResponse<D> {
pub request_id: String,
pub lease_id: Option<String>,
pub renewable: Option<bool>,
pub lease_duration: Option<VaultDuration>,
pub data: Option<D>,
pub warnings: Option<Vec<String>>,
pub auth: Option<Auth>,
pub wrap_info: Option<WrapInfo>,
}
impl<D> From<VaultResponse<SecretDataWrapper<D>>> for VaultResponse<D> {
fn from(v: VaultResponse<SecretDataWrapper<D>>) -> Self {
Self {
request_id: v.request_id,
lease_id: v.lease_id,
renewable: v.renewable,
lease_duration: v.lease_duration,
data: v.data.map(|value| value.data),
warnings: v.warnings,
auth: v.auth,
wrap_info: v.wrap_info,
}
}
}
#[derive(Deserialize, Debug)]
pub struct WrapInfo {
pub ttl: VaultDuration,
pub token: String,
pub creation_time: VaultDateTime,
pub wrapped_accessor: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct WrapData {
response: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum TokenType {
Batch,
Service,
Default,
DefaultBatch,
DefaultService,
}
#[derive(Deserialize, Debug)]
pub struct AppRoleProperties {
pub bind_secret_id: bool,
pub local_secret_ids: bool,
pub secret_id_bound_cidrs: Option<Vec<String>>,
pub secret_id_num_uses: VaultNumUses,
pub secret_id_ttl: VaultDuration,
pub token_bound_cidrs: Option<Vec<String>>,
pub token_explicit_max_ttl: Option<VaultDuration>,
pub token_no_default_policy: Option<bool>,
pub token_max_ttl: VaultDuration,
pub token_num_uses: VaultNumUses,
pub token_period: Option<VaultDuration>,
pub token_policies: Option<Vec<String>>,
pub token_ttl: VaultDuration,
pub token_type: TokenType,
}
#[derive(Deserialize, Serialize, Debug)]
struct AppIdPayload {
app_id: String,
user_id: String,
}
#[derive(Deserialize, Serialize, Debug)]
struct AppRolePayload {
role_id: String,
secret_id: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct PostgresqlLogin {
pub password: String,
pub username: String,
}
#[derive(Deserialize, Serialize, Debug)]
struct PoliciesResponse {
policies: Vec<String>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ListResponse {
pub keys: Vec<String>,
}
#[derive(Deserialize, Serialize, Debug)]
struct RenewTokenOptions {
token: String,
increment: Option<u64>,
}
#[derive(Deserialize, Serialize, Debug)]
struct RenewLeaseOptions {
lease_id: String,
increment: Option<u64>,
}
#[derive(Default, Serialize, Debug)]
pub struct TokenOptions {
id: Option<String>,
policies: Option<Vec<String>>,
no_parent: Option<bool>,
no_default_policy: Option<bool>,
renewable: Option<bool>,
ttl: Option<String>,
explicit_max_ttl: Option<String>,
display_name: Option<String>,
num_uses: VaultNumUses,
}
impl TokenOptions {
pub fn id<S: Into<String>>(mut self, id: S) -> Self {
self.id = Some(id.into());
self
}
pub fn policies<I>(mut self, policies: I) -> Self
where
I: IntoIterator,
I::Item: Into<String>,
{
self.policies = Some(policies.into_iter().map(|p| p.into()).collect());
self
}
pub fn default_policy(mut self, enable: bool) -> Self {
self.no_default_policy = Some(!enable);
self
}
pub fn orphan(mut self, orphan: bool) -> Self {
self.no_parent = Some(!orphan);
self
}
pub fn renewable(mut self, renewable: bool) -> Self {
self.renewable = Some(renewable);
self
}
pub fn display_name<S>(mut self, name: S) -> Self
where
S: Into<String>,
{
self.display_name = Some(name.into());
self
}
pub fn number_of_uses<D: Into<VaultNumUses>>(mut self, uses: D) -> Self {
self.num_uses = uses.into();
self
}
pub fn ttl<D: Into<VaultDuration>>(mut self, ttl: D) -> Self {
self.ttl = Some(format!("{}s", ttl.into().0.as_secs()));
self
}
pub fn explicit_max_ttl<D: Into<VaultDuration>>(mut self, ttl: D) -> Self {
self.explicit_max_ttl = Some(format!("{}s", ttl.into().0.as_secs()));
self
}
}
#[derive(Debug)]
pub enum HttpVerb {
GET,
POST,
PUT,
DELETE,
LIST,
}
#[derive(Debug, Serialize)]
struct SecretContainer<T: Serialize> {
data: T,
}
#[derive(Debug, Deserialize, Serialize)]
struct DefaultSecretType<T: AsRef<str>> {
value: T,
}
#[derive(Debug)]
pub enum EndpointResponse<D> {
VaultResponse(VaultResponse<D>),
Empty,
}
impl VaultClient<TokenData> {
pub fn new<U, T: Into<String>>(host: U, token: T) -> Result<VaultClient<TokenData>>
where
U: TryInto<Url, Err = Error>,
{
let host = host.try_into()?;
let client = Client::new();
let token = token.into();
let res = handle_reqwest_response(
client
.get(host.join("/v1/auth/token/lookup-self")?)
.header("X-Vault-Token", token.clone())
.send(),
)?;
let decoded: VaultResponse<TokenData> = parse_vault_response(res)?;
Ok(VaultClient {
host,
token,
client,
data: Some(decoded),
secret_backend: "secret".into(),
})
}
pub fn new_from_reqwest<U, T: Into<String>>(
host: U,
token: T,
cli: Client,
) -> Result<VaultClient<TokenData>>
where
U: TryInto<Url, Err = Error>,
{
let host = host.try_into()?;
let client = cli;
let token = token.into();
let res = handle_reqwest_response(
client
.get(host.join("/v1/auth/token/lookup-self")?)
.header("X-Vault-Token", token.clone())
.send(),
)?;
let decoded: VaultResponse<TokenData> = parse_vault_response(res)?;
Ok(VaultClient {
host,
token,
client,
data: Some(decoded),
secret_backend: "secret".into(),
})
}
}
impl VaultClient<()> {
#[deprecated(since = "0.6.1")]
pub fn new_app_id<U, S1: Into<String>, S2: Into<String>>(
host: U,
app_id: S1,
user_id: S2,
) -> Result<VaultClient<()>>
where
U: TryInto<Url, Err = Error>,
{
let host = host.try_into()?;
let client = Client::new();
let payload = serde_json::to_string(&AppIdPayload {
app_id: app_id.into(),
user_id: user_id.into(),
})?;
let res = handle_reqwest_response(
client
.post(host.join("/v1/auth/app-id/login")?)
.body(payload)
.send(),
)?;
let decoded: VaultResponse<()> = parse_vault_response(res)?;
let token = match decoded.auth {
Some(ref auth) => auth.client_token.clone(),
None => {
return Err(Error::Vault(format!(
"No client token found in response: `{:?}`",
&decoded.auth
)))
}
};
Ok(VaultClient {
host,
token,
client,
data: Some(decoded),
secret_backend: "secret".into(),
})
}
pub fn new_app_role<U, R, S>(
host: U,
role_id: R,
secret_id: Option<S>,
) -> Result<VaultClient<()>>
where
U: TryInto<Url, Err = Error>,
R: Into<String>,
S: Into<String>,
{
let host = host.try_into()?;
let client = Client::new();
let secret_id = match secret_id {
Some(s) => Some(s.into()),
None => None,
};
let payload = serde_json::to_string(&AppRolePayload {
role_id: role_id.into(),
secret_id,
})?;
let res = handle_reqwest_response(
client
.post(host.join("/v1/auth/approle/login")?)
.body(payload)
.send(),
)?;
let decoded: VaultResponse<()> = parse_vault_response(res)?;
let token = match decoded.auth {
Some(ref auth) => auth.client_token.clone(),
None => {
return Err(Error::Vault(format!(
"No client token found in response: `{:?}`",
&decoded.auth
)))
}
};
Ok(VaultClient {
host,
token,
client,
data: Some(decoded),
secret_backend: "secret".into(),
})
}
pub fn new_no_lookup<U, S: Into<String>>(host: U, token: S) -> Result<VaultClient<()>>
where
U: TryInto<Url, Err = Error>,
{
let client = Client::new();
let host = host.try_into()?;
Ok(VaultClient {
host,
token: token.into(),
client,
data: None,
secret_backend: "secret".into(),
})
}
}
impl<T> VaultClient<T>
where
T: DeserializeOwned,
{
pub fn secret_backend<S1: Into<String>>(&mut self, backend_name: S1) {
self.secret_backend = backend_name.into();
}
pub fn renew(&mut self) -> Result<()> {
let res = self.post::<_, String>("/v1/auth/token/renew-self", None, None)?;
let vault_res: VaultResponse<T> = parse_vault_response(res)?;
if let Some(ref mut data) = self.data {
data.auth = vault_res.auth;
}
Ok(())
}
pub fn renew_token<S: Into<String>>(&self, token: S, increment: Option<u64>) -> Result<Auth> {
let body = serde_json::to_string(&RenewTokenOptions {
token: token.into(),
increment,
})?;
let res = self.post::<_, String>("/v1/auth/token/renew", Some(&body), None)?;
let vault_res: VaultResponse<()> = parse_vault_response(res)?;
vault_res
.auth
.ok_or_else(|| Error::Vault("No auth data returned while renewing token".to_owned()))
}
pub fn revoke(self) -> Result<()> {
let _ = self.post::<_, String>("/v1/auth/token/revoke-self", None, None)?;
Ok(())
}
pub fn renew_lease<S: Into<String>>(
&self,
lease_id: S,
increment: Option<u64>,
) -> Result<VaultResponse<()>> {
let body = serde_json::to_string(&RenewLeaseOptions {
lease_id: lease_id.into(),
increment,
})?;
let res = self.put::<_, String>("/v1/sys/leases/renew", Some(&body), None)?;
let vault_res: VaultResponse<()> = parse_vault_response(res)?;
Ok(vault_res)
}
pub fn lookup(&self) -> Result<VaultResponse<TokenData>> {
let res = self.get::<_, String>("/v1/auth/token/lookup-self", None)?;
let vault_res: VaultResponse<TokenData> = parse_vault_response(res)?;
Ok(vault_res)
}
pub fn create_token(&self, opts: &TokenOptions) -> Result<Auth> {
let body = serde_json::to_string(opts)?;
let res = self.post::<_, String>("/v1/auth/token/create", Some(&body), None)?;
let vault_res: VaultResponse<()> = parse_vault_response(res)?;
vault_res
.auth
.ok_or_else(|| Error::Vault("Created token did not include auth data".into()))
}
pub fn set_secret<S1: Into<String>, S2: AsRef<str>>(&self, key: S1, value: S2) -> Result<()> {
let secret = DefaultSecretType {
value: value.as_ref(),
};
self.set_custom_secret(key, &secret)
}
pub fn set_custom_secret<S1, S2>(&self, secret_name: S1, secret: &S2) -> Result<()>
where
S1: Into<String>,
S2: Serialize,
{
let secret = SecretContainer { data: secret };
let json = serde_json::to_string(&secret)?;
let _ = self.put::<_, String>(
&format!("/v1/{}/data/{}", self.secret_backend, secret_name.into())[..],
Some(&json),
None,
)?;
Ok(())
}
pub fn list_secrets<S: AsRef<str>>(&self, key: S) -> Result<Vec<String>> {
let res = self.list::<_, String>(
&format!("/v1/{}/metadata/{}", self.secret_backend, key.as_ref())[..],
None,
None,
)?;
let decoded: VaultResponse<ListResponse> = parse_vault_response(res)?;
match decoded.data {
Some(data) => Ok(data.keys),
_ => Err(Error::Vault(format!(
"No secrets found in response: `{:#?}`",
decoded
))),
}
}
pub fn get_secret<S: AsRef<str>>(&self, key: S) -> Result<String> {
let secret: DefaultSecretType<String> = self.get_custom_secret(key)?;
Ok(secret.value)
}
pub fn get_custom_secret<S: AsRef<str>, S2: DeserializeOwned + std::fmt::Debug>(
&self,
secret_name: S,
) -> Result<S2> {
let res = self.get::<_, String>(
&format!("/v1/{}/data/{}", self.secret_backend, secret_name.as_ref())[..],
None,
)?;
let decoded: VaultResponse<SecretDataWrapper<S2>> = parse_vault_response(res)?;
match decoded.data {
Some(data) => Ok(data.data),
_ => Err(Error::Vault(format!(
"No secret found in response: `{:#?}`",
decoded
))),
}
}
pub fn get_secret_wrapped<S1: AsRef<str>, S2: AsRef<str>>(
&self,
key: S1,
wrap_ttl: S2,
) -> Result<VaultResponse<()>> {
let res = self.get(
&format!("/v1/{}/data/{}", self.secret_backend, key.as_ref())[..],
Some(wrap_ttl.as_ref()),
)?;
parse_vault_response(res)
}
pub fn get_unwrapped_response(&self) -> Result<VaultResponse<HashMap<String, String>>> {
let res = self.post::<_, String>("/v1/sys/wrapping/unwrap", None, None)?;
let result: VaultResponse<SecretDataWrapper<HashMap<String, String>>> =
parse_vault_response(res)?;
Ok(result.into())
}
pub fn get_app_role_properties<S: AsRef<str>>(
&self,
role_name: S,
) -> Result<VaultResponse<AppRoleProperties>> {
let res = self.get::<_, String>(
&format!("/v1/auth/approle/role/{}", role_name.as_ref()),
None,
)?;
parse_vault_response(res)
}
pub fn transit_encrypt<S1: Into<String>, S2: AsRef<[u8]>>(
&self,
mountpoint: Option<String>,
key: S1,
plaintext: S2,
) -> Result<Vec<u8>> {
let path = mountpoint.unwrap_or_else(|| "transit".to_owned());
let encoded_plaintext = base64::encode(plaintext.as_ref());
let res = self.post::<_, String>(
&format!("/v1/{}/encrypt/{}", path, key.into())[..],
Some(&format!("{{\"plaintext\": \"{}\"}}", encoded_plaintext)[..]),
None,
)?;
let decoded: VaultResponse<TransitEncryptedData> = parse_vault_response(res)?;
let payload = match decoded.data {
Some(data) => data.ciphertext,
_ => {
return Err(Error::Vault(format!(
"No ciphertext found in response: `{:#?}`",
decoded
)))
}
};
if !payload.starts_with("vault:v1:") {
return Err(Error::Vault(format!(
"Unrecognized ciphertext format: `{:#?}`",
payload
)));
};
let encoded_ciphertext = payload.trim_start_matches("vault:v1:");
let encrypted = base64::decode(encoded_ciphertext)?;
Ok(encrypted)
}
pub fn transit_decrypt<S1: Into<String>, S2: AsRef<[u8]>>(
&self,
mountpoint: Option<String>,
key: S1,
ciphertext: S2,
) -> Result<Vec<u8>> {
let path = mountpoint.unwrap_or_else(|| "transit".to_owned());
let encoded_ciphertext = "vault:v1:".to_owned() + &base64::encode(ciphertext.as_ref());
let res = self.post::<_, String>(
&format!("/v1/{}/decrypt/{}", path, key.into())[..],
Some(&format!("{{\"ciphertext\": \"{}\"}}", encoded_ciphertext)[..]),
None,
)?;
let decoded: VaultResponse<TransitDecryptedData> = parse_vault_response(res)?;
let decrypted = match decoded.data {
Some(data) => data.plaintext,
_ => {
return Err(Error::Vault(format!(
"No plaintext found in response: `{:#?}`",
decoded
)))
}
};
let plaintext = base64::decode(&decrypted)?;
Ok(plaintext)
}
pub fn call_endpoint<D: DeserializeOwned>(
&self,
http_verb: HttpVerb,
endpoint: &str,
wrap_ttl: Option<&str>,
body: Option<&str>,
) -> Result<EndpointResponse<D>> {
let url = format!("/v1/{}", endpoint);
match http_verb {
HttpVerb::GET => {
let mut res = self.get(&url, wrap_ttl)?;
parse_endpoint_response(&mut res)
}
HttpVerb::POST => {
let mut res = self.post(&url, body, wrap_ttl)?;
parse_endpoint_response(&mut res)
}
HttpVerb::PUT => {
let mut res = self.put(&url, body, wrap_ttl)?;
parse_endpoint_response(&mut res)
}
HttpVerb::DELETE => {
let mut res = self.delete(&url)?;
parse_endpoint_response(&mut res)
}
HttpVerb::LIST => {
let mut res = self.list(&url, body, wrap_ttl)?;
parse_endpoint_response(&mut res)
}
}
}
pub fn get_wrapping_token_for_endpoint(
&self,
http_verb: HttpVerb,
endpoint: &str,
wrap_ttl: &str,
body: Option<&str>,
) -> Result<String> {
let res = self.call_endpoint::<()>(http_verb, endpoint.as_ref(), Some(wrap_ttl), body)?;
match res {
EndpointResponse::VaultResponse(res) => match res.wrap_info {
Some(wrap_info) => Ok(wrap_info.token),
_ => Err(Error::Vault(format!(
"wrap_info is missing in response: {:?}",
res
))),
},
EndpointResponse::Empty => Err(Error::Vault("Received an empty response".to_string())),
}
}
pub fn delete_secret(&self, key: &str) -> Result<()> {
let _ = self.delete(&format!("/v1/{}/data/{}", self.secret_backend, key)[..])?;
Ok(())
}
pub fn get_postgresql_backend(&self, name: &str) -> Result<VaultResponse<PostgresqlLogin>> {
self.get_secret_engine_creds("postgresql", name)
}
pub fn get_secret_engine_creds<K>(&self, backend: &str, name: &str) -> Result<VaultResponse<K>>
where
K: DeserializeOwned,
{
let res = self.get::<_, String>(&format!("/v1/{}/creds/{}", backend, name)[..], None)?;
let decoded: VaultResponse<K> = parse_vault_response(res)?;
Ok(decoded)
}
pub fn policies(&self) -> Result<Vec<String>> {
let res = self.get::<_, String>("/v1/sys/policy", None)?;
let decoded: PoliciesResponse = parse_vault_response(res)?;
Ok(decoded.policies)
}
fn get<S1: AsRef<str>, S2: Into<String>>(
&self,
endpoint: S1,
wrap_ttl: Option<S2>,
) -> Result<Response> {
let h = self.host.join(endpoint.as_ref())?;
match wrap_ttl {
Some(wrap_ttl) => Ok(handle_reqwest_response(
self.client
.request(Method::GET, h)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.header("X-Vault-Wrap-TTL", wrap_ttl.into())
.send(),
)?),
None => Ok(handle_reqwest_response(
self.client
.request(Method::GET, h)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.send(),
)?),
}
}
fn delete<S: AsRef<str>>(&self, endpoint: S) -> Result<Response> {
Ok(handle_reqwest_response(
self.client
.request(Method::DELETE, self.host.join(endpoint.as_ref())?)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.send(),
)?)
}
fn post<S1: AsRef<str>, S2: Into<String>>(
&self,
endpoint: S1,
body: Option<&str>,
wrap_ttl: Option<S2>,
) -> Result<Response> {
let h = self.host.join(endpoint.as_ref())?;
let body = if let Some(body) = body {
body.to_string()
} else {
String::new()
};
match wrap_ttl {
Some(wrap_ttl) => Ok(handle_reqwest_response(
self.client
.request(Method::POST, h)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.header("X-Vault-Wrap-TTL", wrap_ttl.into())
.body(body)
.send(),
)?),
None => Ok(handle_reqwest_response(
self.client
.request(Method::POST, h)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.body(body)
.send(),
)?),
}
}
fn put<S1: AsRef<str>, S2: Into<String>>(
&self,
endpoint: S1,
body: Option<&str>,
wrap_ttl: Option<S2>,
) -> Result<Response> {
let h = self.host.join(endpoint.as_ref())?;
let body = if let Some(body) = body {
body.to_string()
} else {
String::new()
};
match wrap_ttl {
Some(wrap_ttl) => Ok(handle_reqwest_response(
self.client
.request(Method::PUT, h)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.header("X-Vault-Wrap-TTL", wrap_ttl.into())
.body(body)
.send(),
)?),
None => Ok(handle_reqwest_response(
self.client
.request(Method::PUT, h)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.body(body)
.send(),
)?),
}
}
fn list<S1: AsRef<str>, S2: Into<String>>(
&self,
endpoint: S1,
body: Option<&str>,
wrap_ttl: Option<S2>,
) -> Result<Response> {
let h = self.host.join(endpoint.as_ref())?;
let body = if let Some(body) = body {
body.to_string()
} else {
String::new()
};
match wrap_ttl {
Some(wrap_ttl) => Ok(handle_reqwest_response(
self.client
.request(
Method::from_str("LIST".into()).expect("Failed to parse LIST to Method"),
h,
)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.header("X-Vault-Wrap-TTL", wrap_ttl.into())
.body(body)
.send(),
)?),
None => Ok(handle_reqwest_response(
self.client
.request(
Method::from_str("LIST".into()).expect("Failed to parse LIST to Method"),
h,
)
.header("X-Vault-Token", self.token.to_string())
.header(CONTENT_TYPE, "application/json")
.body(body)
.send(),
)?),
}
}
}
fn handle_reqwest_response(res: StdResult<Response, reqwest::Error>) -> Result<Response> {
let mut res = res?;
if res.status().is_success() {
Ok(res)
} else {
let mut error_msg = String::new();
let _ = res.read_to_string(&mut error_msg).unwrap_or({
error_msg.push_str("Could not read vault response.");
0
});
Err(Error::VaultResponse(
format!(
"Vault request failed: {:?}, error message: `{}`",
res, error_msg
),
res,
))
}
}
pub fn parse_vault_response<T>(res: Response) -> Result<T>
where
T: DeserializeOwned,
{
trace!("Response: {:?}", &res);
Ok(serde_json::from_reader(res)?)
}
fn parse_endpoint_response<T>(res: &mut Response) -> Result<EndpointResponse<T>>
where
T: DeserializeOwned,
{
let mut body = String::new();
let _ = res.read_to_string(&mut body)?;
trace!("Response: {:?}", &body);
if body.is_empty() {
Ok(EndpointResponse::Empty)
} else {
Ok(EndpointResponse::VaultResponse(serde_json::from_str(
&body,
)?))
}
}