refactor(kms): route Vault clients through a rotatable credential provider (#5481)

* fix(kms): repair vault test call sites missed by the timeout refactor

Three offline tests still constructed VaultKmsClient with the pre-#5472
single-argument signature, leaving cargo test -p rustfs-kms unable to
compile. Pass the same 30s attempt timeout the neighbouring tests use.

* refactor(kms): route Vault clients through a rotatable credential provider

Both Vault backends previously built a VaultClient in their constructor
and held it for the lifetime of the backend, which leaves no seam for
re-authentication: rotating credentials would require tearing down the
whole backend.

Introduce backends/vault_credentials with a TokenSource trait (only
StaticToken for now; AppRole login and agent token files land in
follow-ups) and a VaultCredentialProvider that owns the authenticated
client behind an ArcSwap. Request paths take a per-call snapshot via
current(), so a future rotation swaps in a new client generation without
interrupting calls already in flight. Tokens held by this crate are
zeroized on drop, and Debug output of every credential-carrying type is
redacted (covered by a leak regression test).

Behavior is unchanged: static token, namespace, and per-attempt timeout
feed the same VaultClientSettings as before, and AppRole configurations
are still rejected at construction with the same message.
This commit is contained in:
Zhengchao An
2026-07-31 00:48:24 +08:00
committed by GitHub
parent 699ef14ddd
commit 8368017fb2
4 changed files with 373 additions and 93 deletions
+1
View File
@@ -22,6 +22,7 @@ use std::collections::HashMap;
pub mod local;
pub mod static_kms;
pub mod vault;
pub(crate) mod vault_credentials;
pub mod vault_transit;
/// Abstract KMS client interface that all backends must implement
+29 -46
View File
@@ -14,6 +14,7 @@
//! Vault-based KMS backend implementation using vaultrs
use crate::backends::vault_credentials::{VaultClientHandle, VaultConnectionSettings, VaultCredentialProvider, token_source_for};
use crate::backends::{BackendInfo, KmsBackend, KmsClient};
use crate::config::{KmsConfig, VaultConfig};
use crate::encryption::{AesDekCrypto, DataKeyEnvelope, DekCrypto, generate_key_material};
@@ -24,16 +25,14 @@ use base64::{Engine as _, engine::general_purpose};
use jiff::Zoned;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tracing::{debug, info, warn};
use vaultrs::{
client::{VaultClient, VaultClientSettingsBuilder},
kv2,
};
use vaultrs::kv2;
/// Vault KMS client implementation
pub struct VaultKmsClient {
client: VaultClient,
credentials: VaultCredentialProvider,
config: VaultConfig,
/// Mount path for the KV engine (typically "kv" or "secret")
kv_mount: String,
@@ -100,44 +99,18 @@ impl VaultKmsClient {
///
/// `attempt_timeout` caps every HTTP request issued through this client.
pub async fn new(config: VaultConfig, attempt_timeout: Duration) -> Result<Self> {
// Create client settings
let mut settings_builder = VaultClientSettingsBuilder::default();
settings_builder.address(&config.address);
// Defense in depth against stalled connections: vaultrs leaves the
// underlying reqwest client without any timeout by default, so a hung
// request would otherwise wait forever regardless of the
// operation-level retry policy.
settings_builder.timeout(Some(attempt_timeout));
// Set authentication token based on method
let token = match &config.auth_method {
crate::config::VaultAuthMethod::Token { token } => token.clone(),
crate::config::VaultAuthMethod::AppRole { .. } => {
// For AppRole authentication, we would need to first authenticate
// and get a token. For simplicity, we'll require a token for now.
return Err(KmsError::backend_error(
"AppRole authentication not yet implemented. Please use token authentication.",
));
}
let source = token_source_for(&config.auth_method)?;
let settings = VaultConnectionSettings {
address: config.address.clone(),
namespace: config.namespace.clone(),
attempt_timeout,
};
settings_builder.token(&token);
if let Some(namespace) = &config.namespace {
settings_builder.namespace(Some(namespace.clone()));
}
let settings = settings_builder
.build()
.map_err(|e| KmsError::backend_error(format!("Failed to build Vault client settings: {e}")))?;
let client =
VaultClient::new(settings).map_err(|e| KmsError::backend_error(format!("Failed to create Vault client: {e}")))?;
let credentials = VaultCredentialProvider::new(settings, source).await?;
info!(address = %config.address, "Vault KMS backend connected");
Ok(Self {
client,
credentials,
kv_mount: config.kv_mount.clone(),
key_path_prefix: config.key_path_prefix.clone(),
config,
@@ -145,6 +118,14 @@ impl VaultKmsClient {
})
}
/// Snapshot the authenticated Vault client for a single request.
///
/// Every Vault call takes its own snapshot so a credential rotation
/// applies to subsequent calls without interrupting in-flight ones.
fn vault(&self) -> Arc<VaultClientHandle> {
self.credentials.current()
}
/// Get the full path for a key in Vault
fn key_path(&self, key_id: &str) -> String {
format!("{}/{}", self.key_path_prefix, key_id)
@@ -193,7 +174,7 @@ impl VaultKmsClient {
async fn store_key_data(&self, key_id: &str, key_data: &VaultKeyData) -> Result<()> {
let path = self.key_path(key_id);
kv2::set(&self.client, &self.kv_mount, &path, key_data)
kv2::set(&self.vault().client, &self.kv_mount, &path, key_data)
.await
.map_err(|e| KmsError::backend_error(format!("Failed to store key in Vault: {e}")))?;
@@ -242,11 +223,13 @@ impl VaultKmsClient {
async fn get_key_data(&self, key_id: &str) -> Result<VaultKeyData> {
let path = self.key_path(key_id);
let secret: VaultKeyData = kv2::read(&self.client, &self.kv_mount, &path).await.map_err(|e| match e {
vaultrs::error::ClientError::ResponseWrapError => KmsError::key_not_found(key_id),
vaultrs::error::ClientError::APIError { code: 404, .. } => KmsError::key_not_found(key_id),
_ => KmsError::backend_error(format!("Failed to read key from Vault: {e}")),
})?;
let secret: VaultKeyData = kv2::read(&self.vault().client, &self.kv_mount, &path)
.await
.map_err(|e| match e {
vaultrs::error::ClientError::ResponseWrapError => KmsError::key_not_found(key_id),
vaultrs::error::ClientError::APIError { code: 404, .. } => KmsError::key_not_found(key_id),
_ => KmsError::backend_error(format!("Failed to read key from Vault: {e}")),
})?;
debug!("Retrieved key {} from Vault, tags: {:?}", key_id, secret.tags);
Ok(secret)
@@ -255,7 +238,7 @@ impl VaultKmsClient {
/// List all keys stored in Vault
async fn list_vault_keys(&self) -> Result<Vec<String>> {
// List keys under the prefix
match kv2::list(&self.client, &self.kv_mount, &self.key_path_prefix).await {
match kv2::list(&self.vault().client, &self.kv_mount, &self.key_path_prefix).await {
Ok(keys) => {
debug!("Found {} keys in Vault", keys.len());
Ok(keys)
@@ -279,7 +262,7 @@ impl VaultKmsClient {
// For this specific key path, we can safely delete the metadata
// since each key has its own unique path under the prefix
kv2::delete_metadata(&self.client, &self.kv_mount, &path)
kv2::delete_metadata(&self.vault().client, &self.kv_mount, &path)
.await
.map_err(|e| match e {
vaultrs::error::ClientError::APIError { code: 404, .. } => KmsError::key_not_found(key_id),
@@ -0,0 +1,304 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Credential plumbing shared by the Vault KV2 and Transit backends.
//!
//! [`VaultCredentialProvider`] owns the authenticated [`VaultClient`] and hands
//! out per-request snapshots. Backends take a fresh snapshot via
//! [`VaultCredentialProvider::current`] for every Vault call instead of holding
//! a client for their own lifetime: a future credential rotation then applies
//! to the next call, while calls already in flight finish on the generation
//! they captured (their `Arc` keeps it alive).
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use arc_swap::ArcSwap;
use async_trait::async_trait;
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::config::{VaultAuthMethod, redacted_secret};
use crate::error::{KmsError, Result};
/// A Vault token handed out by a [`TokenSource`].
///
/// The crate's copy of the token is zeroized on drop. This cannot cover the
/// copy `vaultrs` keeps inside its client settings (or the HTTP headers built
/// from it); it bounds how long the token lingers in memory owned by this
/// module.
///
/// AppRole login (PR-2) will extend this with the lease metadata returned by
/// the login endpoint (`lease_duration`, `renewable`, accessor).
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub(crate) struct TokenLease {
token: String,
}
impl TokenLease {
pub(crate) fn new(token: String) -> Self {
Self { token }
}
/// Expose the raw token for handing to the Vault client builder.
pub(crate) fn expose(&self) -> &str {
&self.token
}
}
impl fmt::Debug for TokenLease {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TokenLease")
.field("token", &redacted_secret(&self.token))
.finish()
}
}
/// Source of Vault authentication tokens.
///
/// Only [`StaticToken`] exists today. The trait is async and fallible so
/// future sources can perform I/O when acquiring a token without changing the
/// provider:
/// - `AppRoleLogin` (PR-2): performs an `auth/approle/login` round trip and
/// returns the issued token with its lease metadata;
/// - `TokenFile` (PR-3): re-reads an agent-managed token file.
#[async_trait]
pub(crate) trait TokenSource: fmt::Debug + Send + Sync {
/// Acquire a token for a new client generation.
///
/// Called once at provider construction today; rotation (PR-2) will call
/// it again for every re-authentication.
async fn acquire(&self) -> Result<TokenLease>;
}
/// Token source for [`VaultAuthMethod::Token`]: always yields the token fixed
/// at configuration time.
pub(crate) struct StaticToken {
token: TokenLease,
}
impl StaticToken {
pub(crate) fn new(token: String) -> Self {
Self {
token: TokenLease::new(token),
}
}
}
#[async_trait]
impl TokenSource for StaticToken {
async fn acquire(&self) -> Result<TokenLease> {
Ok(self.token.clone())
}
}
impl fmt::Debug for StaticToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TokenLease::fmt already redacts the token value.
f.debug_struct("StaticToken").field("token", &self.token).finish()
}
}
/// Map the configured auth method onto a token source.
///
/// AppRole is still rejected at construction time; PR-2 replaces this arm with
/// an `AppRoleLogin` source.
pub(crate) fn token_source_for(auth_method: &VaultAuthMethod) -> Result<Box<dyn TokenSource>> {
match auth_method {
VaultAuthMethod::Token { token } => Ok(Box::new(StaticToken::new(token.clone()))),
VaultAuthMethod::AppRole { .. } => Err(KmsError::backend_error(
"AppRole authentication not yet implemented. Please use token authentication.",
)),
}
}
/// Connection parameters shared by every client generation.
#[derive(Debug, Clone)]
pub(crate) struct VaultConnectionSettings {
pub(crate) address: String,
pub(crate) namespace: Option<String>,
/// Per-attempt HTTP timeout applied to the underlying reqwest client.
pub(crate) attempt_timeout: Duration,
}
impl VaultConnectionSettings {
/// Build an authenticated client for one generation.
fn build_client(&self, token: &TokenLease) -> Result<VaultClient> {
let mut settings_builder = VaultClientSettingsBuilder::default();
settings_builder.address(&self.address);
// Defense in depth against stalled connections: vaultrs leaves the
// underlying reqwest client without any timeout by default, so a hung
// request would otherwise wait forever regardless of the
// operation-level retry policy.
settings_builder.timeout(Some(self.attempt_timeout));
settings_builder.token(token.expose());
if let Some(namespace) = &self.namespace {
settings_builder.namespace(Some(namespace.clone()));
}
let settings = settings_builder
.build()
.map_err(|e| KmsError::backend_error(format!("Failed to build Vault client settings: {e}")))?;
VaultClient::new(settings).map_err(|e| KmsError::backend_error(format!("Failed to create Vault client: {e}")))
}
}
/// One authenticated client generation.
///
/// Request paths hold the handle (via `Arc`) for the duration of a single
/// Vault call, so a rotation that swaps in a newer generation never tears the
/// client out from under an in-flight request.
pub(crate) struct VaultClientHandle {
/// Monotonic counter identifying the credential generation this client was
/// built from. Static tokens never rotate, so only generation 0 exists
/// today; rotation (PR-2) bumps it on every re-authentication.
pub(crate) generation: u64,
pub(crate) client: VaultClient,
}
impl fmt::Debug for VaultClientHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// `VaultClient` embeds its settings, including the token, so it must
// never appear in Debug output.
f.debug_struct("VaultClientHandle")
.field("generation", &self.generation)
.finish_non_exhaustive()
}
}
/// Owns the authenticated Vault client for a backend and hands out
/// per-request snapshots.
///
/// The provider keeps neither the settings nor the source after construction
/// because a static token can never be refreshed. Rotation (PR-2) will retain
/// both and add a refresh path that acquires a fresh lease, rebuilds the
/// client, and stores it under a bumped generation.
pub(crate) struct VaultCredentialProvider {
current: ArcSwap<VaultClientHandle>,
}
impl VaultCredentialProvider {
/// Authenticate with `source` and build the initial client generation.
pub(crate) async fn new(settings: VaultConnectionSettings, source: Box<dyn TokenSource>) -> Result<Self> {
let lease = source.acquire().await?;
let client = settings.build_client(&lease)?;
Ok(Self {
current: ArcSwap::from_pointee(VaultClientHandle { generation: 0, client }),
})
}
/// Snapshot the current client generation.
///
/// Take one snapshot per Vault call: the returned `Arc` pins the
/// generation for exactly that call, so a concurrent rotation applies to
/// the next call without interrupting this one.
pub(crate) fn current(&self) -> Arc<VaultClientHandle> {
self.current.load_full()
}
}
impl fmt::Debug for VaultCredentialProvider {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VaultCredentialProvider")
.field("current", &self.current.load())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::REDACTED_SECRET;
const TEST_TOKEN: &str = "vault-token-debug-leak-canary";
fn test_settings() -> VaultConnectionSettings {
VaultConnectionSettings {
address: "http://127.0.0.1:8200".to_string(),
namespace: Some("team-namespace".to_string()),
attempt_timeout: Duration::from_secs(30),
}
}
/// Building a provider never contacts Vault, so these tests run offline.
async fn test_provider() -> VaultCredentialProvider {
VaultCredentialProvider::new(test_settings(), Box::new(StaticToken::new(TEST_TOKEN.to_string())))
.await
.expect("provider construction must not require a live Vault")
}
#[tokio::test]
async fn test_static_token_snapshots_pin_one_generation() {
let provider = test_provider().await;
let first = provider.current();
let second = provider.current();
assert_eq!(first.generation, 0);
assert!(
Arc::ptr_eq(&first, &second),
"without rotation every snapshot must return the same client generation"
);
}
#[tokio::test]
async fn test_static_token_source_yields_configured_token() {
let source = token_source_for(&VaultAuthMethod::Token {
token: TEST_TOKEN.to_string(),
})
.expect("token auth must map to a source");
let lease = source.acquire().await.expect("static acquire cannot fail");
assert_eq!(lease.expose(), TEST_TOKEN);
}
/// Behavior pin: AppRole keeps failing at construction with the same
/// user-visible message until the login source lands (PR-2).
#[test]
fn test_approle_auth_method_still_rejected() {
let error = token_source_for(&VaultAuthMethod::AppRole {
role_id: "role".to_string(),
secret_id: "approle-secret-canary".to_string(),
})
.expect_err("approle must stay rejected until the login source lands");
let rendered = error.to_string();
assert!(rendered.contains("AppRole authentication not yet implemented"), "got: {rendered}");
assert!(!rendered.contains("approle-secret-canary"), "error must not echo the secret id");
}
/// Leak regression: the Debug output of every credential-carrying type
/// must stay free of the token literal.
#[tokio::test]
async fn test_credential_types_debug_redacts_token() {
let provider = test_provider().await;
let handle = provider.current();
let lease = TokenLease::new(TEST_TOKEN.to_string());
let source = StaticToken::new(TEST_TOKEN.to_string());
for rendered in [
format!("{provider:?}"),
format!("{handle:?}"),
format!("{lease:?}"),
format!("{source:?}"),
] {
assert!(!rendered.contains(TEST_TOKEN), "debug output must not leak the vault token: {rendered}");
}
assert!(format!("{lease:?}").contains(REDACTED_SECRET));
}
}
+39 -47
View File
@@ -14,6 +14,7 @@
//! Vault Transit-based KMS backend.
use crate::backends::vault_credentials::{VaultClientHandle, VaultConnectionSettings, VaultCredentialProvider, token_source_for};
use crate::backends::{BackendInfo, KmsBackend, KmsClient};
use crate::config::{KmsConfig, VaultTransitConfig};
use crate::encryption::{DataKeyEnvelope, generate_key_material};
@@ -24,6 +25,7 @@ use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use jiff::Zoned;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use vaultrs::{
@@ -33,7 +35,6 @@ use vaultrs::{
CreateKeyRequestBuilder, DecryptDataRequestBuilder, EncryptDataRequestBuilder, UpdateKeyConfigurationRequestBuilder,
},
},
client::{VaultClient, VaultClientSettingsBuilder},
kv2,
transit::{data, key},
};
@@ -128,7 +129,7 @@ impl From<TransitKeyMetadataPersisted> for TransitKeyMetadata {
}
pub struct VaultTransitKmsClient {
client: VaultClient,
credentials: VaultCredentialProvider,
config: VaultTransitConfig,
/// KV v2 mount path for persisting transit key metadata
metadata_kv_mount: String,
@@ -142,38 +143,16 @@ impl VaultTransitKmsClient {
///
/// `attempt_timeout` caps every HTTP request issued through this client.
pub async fn new(config: VaultTransitConfig, attempt_timeout: Duration) -> Result<Self> {
let mut settings_builder = VaultClientSettingsBuilder::default();
settings_builder.address(&config.address);
// Defense in depth against stalled connections: vaultrs leaves the
// underlying reqwest client without any timeout by default, so a hung
// request would otherwise wait forever regardless of the
// operation-level retry policy.
settings_builder.timeout(Some(attempt_timeout));
let token = match &config.auth_method {
crate::config::VaultAuthMethod::Token { token } => token.clone(),
crate::config::VaultAuthMethod::AppRole { .. } => {
return Err(KmsError::backend_error(
"AppRole authentication not yet implemented. Please use token authentication.",
));
}
let source = token_source_for(&config.auth_method)?;
let settings = VaultConnectionSettings {
address: config.address.clone(),
namespace: config.namespace.clone(),
attempt_timeout,
};
settings_builder.token(&token);
if let Some(namespace) = &config.namespace {
settings_builder.namespace(Some(namespace.clone()));
}
let settings = settings_builder
.build()
.map_err(|e| KmsError::backend_error(format!("Failed to build Vault client settings: {e}")))?;
let client =
VaultClient::new(settings).map_err(|e| KmsError::backend_error(format!("Failed to create Vault client: {e}")))?;
let credentials = VaultCredentialProvider::new(settings, source).await?;
Ok(Self {
client,
credentials,
metadata_kv_mount: config.metadata_kv_mount.clone(),
metadata_key_prefix: config.metadata_key_prefix.clone(),
config,
@@ -181,6 +160,14 @@ impl VaultTransitKmsClient {
})
}
/// Snapshot the authenticated Vault client for a single request.
///
/// Every Vault call takes its own snapshot so a credential rotation
/// applies to subsequent calls without interrupting in-flight ones.
fn vault(&self) -> Arc<VaultClientHandle> {
self.credentials.current()
}
fn canonicalize_context(encryption_context: &HashMap<String, String>) -> Result<Option<String>> {
if encryption_context.is_empty() {
return Ok(None);
@@ -205,7 +192,7 @@ impl VaultTransitKmsClient {
}
async fn read_transit_key(&self, key_id: &str) -> Result<vaultrs::api::transit::responses::ReadKeyResponse> {
key::read(&self.client, &self.config.mount_path, key_id)
key::read(&self.vault().client, &self.config.mount_path, key_id)
.await
.or_else(|e| Self::map_vault_error(key_id, e, "read"))
}
@@ -213,7 +200,7 @@ impl VaultTransitKmsClient {
async fn create_transit_key(&self, key_id: &str) -> Result<()> {
let mut builder = CreateKeyRequestBuilder::default();
builder.key_type(KeyType::Aes256Gcm96);
key::create(&self.client, &self.config.mount_path, key_id, Some(&mut builder))
key::create(&self.vault().client, &self.config.mount_path, key_id, Some(&mut builder))
.await
.map_err(|e| KmsError::backend_error(format!("Failed to create Vault Transit key {key_id}: {e}")))
}
@@ -230,7 +217,7 @@ impl VaultTransitKmsClient {
builder.associated_data(aad);
}
let response = data::encrypt(&self.client, &self.config.mount_path, key_id, &plaintext_b64, Some(&mut builder))
let response = data::encrypt(&self.vault().client, &self.config.mount_path, key_id, &plaintext_b64, Some(&mut builder))
.await
.map_err(|e| KmsError::backend_error(format!("Failed to encrypt data with Vault Transit key {key_id}: {e}")))?;
@@ -248,7 +235,7 @@ impl VaultTransitKmsClient {
builder.associated_data(aad);
}
let response = data::decrypt(&self.client, &self.config.mount_path, key_id, ciphertext, Some(&mut builder))
let response = data::decrypt(&self.vault().client, &self.config.mount_path, key_id, ciphertext, Some(&mut builder))
.await
.map_err(|e| KmsError::backend_error(format!("Failed to decrypt data with Vault Transit key {key_id}: {e}")))?;
@@ -263,7 +250,7 @@ impl VaultTransitKmsClient {
async fn read_metadata_from_kv(&self, key_id: &str) -> Result<Option<TransitKeyMetadata>> {
let path = self.metadata_key_path(key_id);
match kv2::read::<TransitKeyMetadataPersisted>(&self.client, &self.metadata_kv_mount, &path).await {
match kv2::read::<TransitKeyMetadataPersisted>(&self.vault().client, &self.metadata_kv_mount, &path).await {
Ok(persisted) => Ok(Some(persisted.into())),
Err(vaultrs::error::ClientError::ResponseWrapError)
| Err(vaultrs::error::ClientError::APIError { code: 404, .. }) => Ok(None),
@@ -274,7 +261,7 @@ impl VaultTransitKmsClient {
async fn write_metadata_to_kv(&self, key_id: &str, metadata: &TransitKeyMetadata) -> Result<()> {
let path = self.metadata_key_path(key_id);
let persisted: TransitKeyMetadataPersisted = metadata.clone().into();
kv2::set(&self.client, &self.metadata_kv_mount, &path, &persisted)
kv2::set(&self.vault().client, &self.metadata_kv_mount, &path, &persisted)
.await
.map(|_| ())
.map_err(|e| KmsError::backend_error(format!("Failed to write transit key metadata to Vault KV: {e}")))
@@ -282,7 +269,7 @@ impl VaultTransitKmsClient {
async fn delete_metadata_from_kv(&self, key_id: &str) -> Result<()> {
let path = self.metadata_key_path(key_id);
match kv2::delete_metadata(&self.client, &self.metadata_kv_mount, &path).await {
match kv2::delete_metadata(&self.vault().client, &self.metadata_kv_mount, &path).await {
Ok(_) => Ok(()),
Err(vaultrs::error::ClientError::ResponseWrapError)
| Err(vaultrs::error::ClientError::APIError { code: 404, .. }) => Ok(()),
@@ -496,7 +483,7 @@ impl KmsClient for VaultTransitKmsClient {
}
async fn list_keys(&self, request: &ListKeysRequest, _context: Option<&OperationContext>) -> Result<ListKeysResponse> {
let all_keys = key::list(&self.client, &self.config.mount_path)
let all_keys = key::list(&self.vault().client, &self.config.mount_path)
.await
.map_err(|e| KmsError::backend_error(format!("Failed to list Vault Transit keys: {e}")))?
.keys;
@@ -566,7 +553,7 @@ impl KmsClient for VaultTransitKmsClient {
}
async fn rotate_key(&self, key_id: &str, _context: Option<&OperationContext>) -> Result<MasterKeyInfo> {
key::rotate(&self.client, &self.config.mount_path, key_id)
key::rotate(&self.vault().client, &self.config.mount_path, key_id)
.await
.map_err(|e| KmsError::backend_error(format!("Failed to rotate Vault Transit key {key_id}: {e}")))?;
@@ -589,7 +576,7 @@ impl KmsClient for VaultTransitKmsClient {
}
async fn health_check(&self) -> Result<()> {
key::list(&self.client, &self.config.mount_path)
key::list(&self.vault().client, &self.config.mount_path)
.await
.map(|_| ())
.map_err(|e| KmsError::backend_error(format!("Vault Transit health check failed: {e}")))
@@ -710,13 +697,18 @@ impl KmsBackend for VaultTransitKmsBackend {
if !self.client.read_transit_key(&key_id).await?.deletion_allowed {
let mut update_builder = UpdateKeyConfigurationRequestBuilder::default();
update_builder.deletion_allowed(true);
key::update(&self.client.client, &self.client.config.mount_path, &key_id, Some(&mut update_builder))
.await
.map_err(|e| {
KmsError::backend_error(format!("Failed to allow deletion of Vault Transit key {key_id}: {e}"))
})?;
key::update(
&self.client.vault().client,
&self.client.config.mount_path,
&key_id,
Some(&mut update_builder),
)
.await
.map_err(|e| {
KmsError::backend_error(format!("Failed to allow deletion of Vault Transit key {key_id}: {e}"))
})?;
}
key::delete(&self.client.client, &self.client.config.mount_path, &key_id)
key::delete(&self.client.vault().client, &self.client.config.mount_path, &key_id)
.await
.map_err(|e| KmsError::backend_error(format!("Failed to delete Vault Transit key {key_id}: {e}")))?;
self.client.delete_key_metadata(&key_id).await?;