diff --git a/swarm-secret-client/src/client.rs b/swarm-secret-client/src/client.rs index f81961b8..f7394e3b 100644 --- a/swarm-secret-client/src/client.rs +++ b/swarm-secret-client/src/client.rs @@ -1,10 +1,12 @@ //! A logged-in handle on the store, built from this deployment's environment. +use std::time::Duration; + use rustify_derive::Endpoint; use serde::{Serialize, de::DeserializeOwned}; use vaultrs::{ api::pki::{requests::GenerateCertificateRequest, responses::GenerateCertificateResponse}, - client::{Client, VaultClient, VaultClientSettingsBuilder}, + client::{Client, VaultClient, VaultClientSettings, VaultClientSettingsBuilder}, }; 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. 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. /// /// Separate from the connect so the environment can be checked without one: @@ -37,6 +46,22 @@ pub struct Settings { } impl Settings { + /// The vaultrs settings for this store, presenting `identity`. + fn client_settings( + &self, + identity: Option, + ) -> Result { + 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. /// /// # Errors @@ -147,16 +172,7 @@ impl SecretStore { ) -> Result { let identity = reqwest::Identity::from_pem(identity_pem).map_err(Error::Tls)?; - let mut builder = VaultClientSettingsBuilder::default(); - 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 built = settings.client_settings(Some(identity))?; let mut inner = VaultClient::new(built)?; 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] fn a_complete_environment_is_accepted() { // The control: without this, every assertion below could be passing diff --git a/swarm-secret-client/src/lib.rs b/swarm-secret-client/src/lib.rs index 1d9a6b5e..4ef48beb 100644 --- a/swarm-secret-client/src/lib.rs +++ b/swarm-secret-client/src/lib.rs @@ -74,8 +74,8 @@ pub enum Error { #[error("the store's settings are unusable: {0}")] Settings(String), - /// The store refused us, was unreachable, or answered something we could - /// not parse. + /// The store refused us, was unreachable, did not answer within the + /// client's request timeout, or answered something we could not parse. #[error(transparent)] Vault(#[from] Box),