swarm-secret-client: bound vault requests with a 30s timeout
vaultrs sets no request timeout by default, so a store that accepts a connection and never answers hung every SecretStore call forever. connect_with_identity now sets VAULT_REQUEST_TIMEOUT; a stalled store surfaces as Error::Vault.
This commit is contained in:
parent
7129ee0015
commit
7c16212fdd
2 changed files with 36 additions and 13 deletions
|
|
@ -1,10 +1,12 @@
|
||||||
//! A logged-in handle on the store, built from this deployment's environment.
|
//! A logged-in handle on the store, built from this deployment's environment.
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use rustify_derive::Endpoint;
|
use rustify_derive::Endpoint;
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use vaultrs::{
|
use vaultrs::{
|
||||||
api::pki::{requests::GenerateCertificateRequest, responses::GenerateCertificateResponse},
|
api::pki::{requests::GenerateCertificateRequest, responses::GenerateCertificateResponse},
|
||||||
client::{Client, VaultClient, VaultClientSettingsBuilder},
|
client::{Client, VaultClient, VaultClientSettings, VaultClientSettingsBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Error, mtls, path::MOUNT};
|
use crate::{Error, mtls, path::MOUNT};
|
||||||
|
|
@ -23,6 +25,13 @@ pub const ENV_CACERT: &str = "BAO_CACERT";
|
||||||
/// The auth mount a cert login goes through, unless a caller says otherwise.
|
/// The auth mount a cert login goes through, unless a caller says otherwise.
|
||||||
pub const DEFAULT_CERT_MOUNT: &str = "cert";
|
pub const DEFAULT_CERT_MOUNT: &str = "cert";
|
||||||
|
|
||||||
|
/// Upper bound on one request to the store, login included.
|
||||||
|
///
|
||||||
|
/// vaultrs sets no timeout by default, so a store that accepts the connection
|
||||||
|
/// and never answers would hold every [`SecretStore`] call open forever; with
|
||||||
|
/// this bound the call fails with [`Error::Vault`] instead.
|
||||||
|
const VAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
/// Where the store is and which identity we present to it.
|
/// Where the store is and which identity we present to it.
|
||||||
///
|
///
|
||||||
/// Separate from the connect so the environment can be checked without one:
|
/// Separate from the connect so the environment can be checked without one:
|
||||||
|
|
@ -37,6 +46,22 @@ pub struct Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
/// The vaultrs settings for this store, presenting `identity`.
|
||||||
|
fn client_settings(
|
||||||
|
&self,
|
||||||
|
identity: Option<reqwest::Identity>,
|
||||||
|
) -> Result<VaultClientSettings, Error> {
|
||||||
|
let mut builder = VaultClientSettingsBuilder::default();
|
||||||
|
builder
|
||||||
|
.address(self.address.clone())
|
||||||
|
.identity(identity)
|
||||||
|
.timeout(Some(VAULT_REQUEST_TIMEOUT));
|
||||||
|
if let Some(ca) = &self.ca_path {
|
||||||
|
builder.ca_certs(vec![ca.clone()]);
|
||||||
|
}
|
||||||
|
builder.build().map_err(|e| Error::Settings(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Read the `BAO_*` variables from the process environment.
|
/// Read the `BAO_*` variables from the process environment.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
@ -147,16 +172,7 @@ impl SecretStore {
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let identity = reqwest::Identity::from_pem(identity_pem).map_err(Error::Tls)?;
|
let identity = reqwest::Identity::from_pem(identity_pem).map_err(Error::Tls)?;
|
||||||
|
|
||||||
let mut builder = VaultClientSettingsBuilder::default();
|
let built = settings.client_settings(Some(identity))?;
|
||||||
builder
|
|
||||||
.address(settings.address.clone())
|
|
||||||
.identity(Some(identity));
|
|
||||||
if let Some(ca) = &settings.ca_path {
|
|
||||||
builder.ca_certs(vec![ca.clone()]);
|
|
||||||
}
|
|
||||||
let built = builder
|
|
||||||
.build()
|
|
||||||
.map_err(|e| Error::Settings(e.to_string()))?;
|
|
||||||
|
|
||||||
let mut inner = VaultClient::new(built)?;
|
let mut inner = VaultClient::new(built)?;
|
||||||
let auth = vaultrs::auth::cert::login(&inner, cert_mount, cert_role).await?;
|
let auth = vaultrs::auth::cert::login(&inner, cert_mount, cert_role).await?;
|
||||||
|
|
@ -471,6 +487,13 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_client_is_built_with_a_request_timeout() {
|
||||||
|
let s = Settings::from_lookup(full).expect("every required variable is set");
|
||||||
|
let built = s.client_settings(None).expect("settings build");
|
||||||
|
assert_eq!(built.timeout, Some(VAULT_REQUEST_TIMEOUT));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_complete_environment_is_accepted() {
|
fn a_complete_environment_is_accepted() {
|
||||||
// The control: without this, every assertion below could be passing
|
// The control: without this, every assertion below could be passing
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,8 @@ pub enum Error {
|
||||||
#[error("the store's settings are unusable: {0}")]
|
#[error("the store's settings are unusable: {0}")]
|
||||||
Settings(String),
|
Settings(String),
|
||||||
|
|
||||||
/// The store refused us, was unreachable, or answered something we could
|
/// The store refused us, was unreachable, did not answer within the
|
||||||
/// not parse.
|
/// client's request timeout, or answered something we could not parse.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Vault(#[from] Box<vaultrs::error::ClientError>),
|
Vault(#[from] Box<vaultrs::error::ClientError>),
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue