#[cfg(feature = "check_approle")]
use crate::model::vault::AppRolesResponse;
#[cfg(feature = "wrapped")]
use crate::model::{approle::AppRole, vault::EmptyResponse};
use crate::{
constants::X_VAULT_TOKEN,
error::{Error, Result},
model::vault::{AttResponse, AuthResponse, Login, SecretDataResponse},
util::{as_empty, as_json, req, EMPTY_BODY},
Config,
};
use futures::FutureExt;
use reqwest::{
header::{HeaderMap, HeaderValue},
Client, Method,
};
use serde::de::DeserializeOwned;
use tracing::{error, trace};
use uuid::Uuid;
#[cfg(feature = "wrapped")]
pub async fn secrets<T>(config: &Config) -> Result<T>
where
T: DeserializeOwned + Clone,
{
let client = build_client()?;
if let Ok(true) = check_approle(&client, config).await {
let wrap_token = get_wrap_token(&client, config).await?;
let secret_id = get_approle_secret_id(&client, &wrap_token, config).await?;
let client_token = get_client_token(&client, secret_id, config).await?;
let secrets = get_secret_data(&client, &client_token, config).await?;
revoke_token(&client, &client_token, config).await?;
Ok(secrets)
} else {
Err(Error::wrapped_response())
}
}
#[cfg(not(feature = "wrapped"))]
pub async fn secrets<T>(config: &Config) -> Result<T>
where
T: DeserializeOwned + Clone,
{
let client = build_client()?;
let secret_id = get_approle_secret_id(&client, config.wrapping_token(), config).await?;
let client_token = get_client_token(&client, secret_id, config).await?;
let secrets = get_secret_data(&client, &client_token, config).await?;
revoke_token(&client, &client_token, config).await?;
Ok(secrets)
}
#[cfg(windows)]
fn build_client() -> Result<Client> {
Client::builder().build().map_err(Error::reqwest_error)
}
#[cfg(not(windows))]
fn build_client() -> Result<Client> {
Client::builder()
.trust_dns(true)
.use_rustls_tls()
.build()
.map_err(Error::reqwest_error)
}
#[cfg(feature = "check_approle")]
async fn check_approle(client: &Client, config: &Config) -> Result<bool> {
trace!("Checking the approle against toad");
let approles_res: AppRolesResponse = req(
client,
Method::GET,
config.toad_approles_url(),
None,
EMPTY_BODY,
)
.then(as_json)
.await
.map_err(Error::approles_check_failed)?;
let approles = approles_res
.data()
.as_ref()
.ok_or_else(Error::approles_response)?;
Ok(approles.keys().contains(config.app_role()))
}
#[cfg(all(feature = "wrapped", not(feature = "check_approle")))]
#[allow(clippy::unused_async)]
async fn check_approle(_: &Client, _: &Config) -> Result<bool> {
Ok(true)
}
#[cfg(feature = "wrapped")]
async fn get_wrap_token(client: &Client, config: &Config) -> Result<String> {
trace!("Getting the wrapping token from toad");
let app_role = AppRole::new(config.app_role());
let wrap_tok_res: EmptyResponse = req(
client,
Method::POST,
config.toad_approle_url(),
None,
Some(app_role),
)
.then(as_json)
.await
.map_err(Error::token_wrap_failed)?;
let wrap_info = wrap_tok_res
.wrap_info()
.as_ref()
.ok_or_else(Error::wrapped_response)?;
Ok(wrap_info.token().clone())
}
async fn get_approle_secret_id(client: &Client, wrap_token: &str, config: &Config) -> Result<Uuid> {
trace!("Getting the app role secret id from vault");
let headers = add_x_vault_token_header(wrap_token)?;
let unwrap_res: SecretDataResponse = req(
client,
Method::POST,
config.unwrap_url(),
Some(headers),
EMPTY_BODY,
)
.then(as_json)
.await
.map_err(Error::token_unwrap_failed)?;
let data = unwrap_res.data().as_ref().ok_or_else(Error::vault_data)?;
Ok(*data.secret_id())
}
async fn get_client_token(client: &Client, secret_id: Uuid, config: &Config) -> Result<String> {
trace!("Getting the client token from vault");
let login_res: AuthResponse = req(
client,
Method::POST,
config.login_url(),
None,
Some(
Login::builder()
.role_id(*config.role_id())
.secret_id(secret_id)
.build(),
),
)
.then(as_json)
.await
.map_err(Error::auth_failed)?;
let auth = login_res.auth().as_ref().ok_or_else(Error::vault_auth)?;
Ok(auth.client_token().clone())
}
async fn get_secret_data<T>(client: &Client, client_token: &str, config: &Config) -> Result<T>
where
T: DeserializeOwned + Clone,
{
trace!("Getting the secret data from vault");
let headers = add_x_vault_token_header(client_token)?;
let secrets_res: AttResponse<T> = req(
client,
Method::GET,
config.secrets_url(),
Some(headers),
EMPTY_BODY,
)
.then(as_json)
.await
.map_err(Error::data_retrieval_failed)?;
let data = secrets_res.data().as_ref().ok_or_else(Error::vault_data)?;
Ok(data.data().clone())
}
async fn revoke_token(client: &Client, client_token: &str, config: &Config) -> Result<()> {
trace!("Revoking the client token");
let headers = add_x_vault_token_header(client_token)?;
if let Err(err) = req(
client,
Method::POST,
config.revoke_url(),
Some(headers),
EMPTY_BODY,
)
.then(as_empty)
.await
.map_err(Error::token_revoke_failed)
{
error!("{err}");
}
Ok(())
}
fn add_x_vault_token_header(value: &str) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
let _h = headers.insert(
X_VAULT_TOKEN,
HeaderValue::from_str(value).map_err(Error::invalid_header_error)?,
);
Ok(headers)
}