mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-22 12:26:37 +00:00
feat(kms): enhance KMS service manager with runtime state and persistence support
This commit is contained in:
@@ -89,7 +89,7 @@ pub use error::{KmsError, KmsUnavailableError, Result};
|
|||||||
pub use manager::KmsManager;
|
pub use manager::KmsManager;
|
||||||
pub use service::{DataKey, ObjectEncryptionService};
|
pub use service::{DataKey, ObjectEncryptionService};
|
||||||
pub use service_manager::{
|
pub use service_manager::{
|
||||||
KmsServiceManager, KmsServiceStatus, get_global_encryption_service, get_global_kms_service_manager,
|
KmsServiceManager, KmsServiceStatus, KmsStartOutcome, get_global_encryption_service, get_global_kms_service_manager,
|
||||||
init_global_kms_service_manager,
|
init_global_kms_service_manager,
|
||||||
};
|
};
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ use crate::error::{KmsError, Result};
|
|||||||
use crate::manager::KmsManager;
|
use crate::manager::KmsManager;
|
||||||
use crate::service::ObjectEncryptionService;
|
use crate::service::ObjectEncryptionService;
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
|
use std::future::Future;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc, OnceLock,
|
Arc, OnceLock,
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
};
|
};
|
||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::Mutex;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
const LOG_COMPONENT_KMS: &str = "kms";
|
const LOG_COMPONENT_KMS: &str = "kms";
|
||||||
@@ -44,6 +45,13 @@ pub enum KmsServiceStatus {
|
|||||||
Error(String),
|
Error(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum KmsStartOutcome {
|
||||||
|
Started,
|
||||||
|
Restarted,
|
||||||
|
AlreadyRunning,
|
||||||
|
}
|
||||||
|
|
||||||
/// Service version information for zero-downtime reconfiguration
|
/// Service version information for zero-downtime reconfiguration
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ServiceVersion {
|
struct ServiceVersion {
|
||||||
@@ -55,16 +63,17 @@ struct ServiceVersion {
|
|||||||
manager: Arc<KmsManager>,
|
manager: Arc<KmsManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct RuntimeState {
|
||||||
|
config: Option<KmsConfig>,
|
||||||
|
status: KmsServiceStatus,
|
||||||
|
current_service: Option<ServiceVersion>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Dynamic KMS service manager with versioned services for zero-downtime reconfiguration
|
/// Dynamic KMS service manager with versioned services for zero-downtime reconfiguration
|
||||||
pub struct KmsServiceManager {
|
pub struct KmsServiceManager {
|
||||||
/// Current service version (if running)
|
/// Atomically published configuration, status, and current service.
|
||||||
/// Uses ArcSwap for atomic, lock-free service switching
|
state: ArcSwap<RuntimeState>,
|
||||||
/// This allows instant atomic updates without blocking readers
|
|
||||||
current_service: ArcSwap<Option<ServiceVersion>>,
|
|
||||||
/// Current configuration
|
|
||||||
config: Arc<RwLock<Option<KmsConfig>>>,
|
|
||||||
/// Current status
|
|
||||||
status: Arc<RwLock<KmsServiceStatus>>,
|
|
||||||
/// Version counter (monotonically increasing)
|
/// Version counter (monotonically increasing)
|
||||||
version_counter: Arc<AtomicU64>,
|
version_counter: Arc<AtomicU64>,
|
||||||
/// Mutex to protect lifecycle operations (start, stop, reconfigure)
|
/// Mutex to protect lifecycle operations (start, stop, reconfigure)
|
||||||
@@ -76,9 +85,11 @@ impl KmsServiceManager {
|
|||||||
/// Create a new KMS service manager (not configured)
|
/// Create a new KMS service manager (not configured)
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current_service: ArcSwap::from_pointee(None),
|
state: ArcSwap::from_pointee(RuntimeState {
|
||||||
config: Arc::new(RwLock::new(None)),
|
config: None,
|
||||||
status: Arc::new(RwLock::new(KmsServiceStatus::NotConfigured)),
|
status: KmsServiceStatus::NotConfigured,
|
||||||
|
current_service: None,
|
||||||
|
}),
|
||||||
version_counter: Arc::new(AtomicU64::new(0)),
|
version_counter: Arc::new(AtomicU64::new(0)),
|
||||||
lifecycle_mutex: Arc::new(Mutex::new(())),
|
lifecycle_mutex: Arc::new(Mutex::new(())),
|
||||||
}
|
}
|
||||||
@@ -86,39 +97,65 @@ impl KmsServiceManager {
|
|||||||
|
|
||||||
/// Get current service status
|
/// Get current service status
|
||||||
pub async fn get_status(&self) -> KmsServiceStatus {
|
pub async fn get_status(&self) -> KmsServiceStatus {
|
||||||
self.status.read().await.clone()
|
self.state.load().status.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current configuration (if any)
|
/// Get current configuration (if any)
|
||||||
pub async fn get_config(&self) -> Option<KmsConfig> {
|
pub async fn get_config(&self) -> Option<KmsConfig> {
|
||||||
self.config.read().await.clone()
|
self.state.load().config.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get configuration for status and management responses without static key material.
|
/// Get configuration for status and management responses without static key material.
|
||||||
pub async fn get_redacted_config(&self) -> Option<KmsConfig> {
|
pub async fn get_redacted_config(&self) -> Option<KmsConfig> {
|
||||||
let mut config = self.config.read().await.clone()?;
|
let mut config = self.state.load().config.clone()?;
|
||||||
|
Self::redact_config(&mut config);
|
||||||
|
Some(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get status and redacted configuration from the same published snapshot.
|
||||||
|
pub async fn get_redacted_state(&self) -> (KmsServiceStatus, Option<KmsConfig>) {
|
||||||
|
let state = self.state.load();
|
||||||
|
let mut config = state.config.clone();
|
||||||
|
if let Some(config) = &mut config {
|
||||||
|
Self::redact_config(config);
|
||||||
|
}
|
||||||
|
(state.status.clone(), config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redact_config(config: &mut KmsConfig) {
|
||||||
if let BackendConfig::Static(static_config) = &mut config.backend_config {
|
if let BackendConfig::Static(static_config) = &mut config.backend_config {
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
static_config.secret_key.zeroize();
|
static_config.secret_key.zeroize();
|
||||||
}
|
}
|
||||||
Some(config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure KMS with new configuration
|
/// Configure KMS with new configuration
|
||||||
pub async fn configure(&self, new_config: KmsConfig) -> Result<()> {
|
pub async fn configure(&self, new_config: KmsConfig) -> Result<()> {
|
||||||
|
self.configure_with_persistence(new_config, || async { Ok(()) }).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure KMS and publish the in-memory state only after persistence succeeds.
|
||||||
|
///
|
||||||
|
/// The persistence callback runs under the lifecycle lock and must not call
|
||||||
|
/// another lifecycle method on this manager.
|
||||||
|
pub async fn configure_with_persistence<Persist, PersistFuture>(&self, new_config: KmsConfig, persist: Persist) -> Result<()>
|
||||||
|
where
|
||||||
|
Persist: FnOnce() -> PersistFuture,
|
||||||
|
PersistFuture: Future<Output = Result<()>>,
|
||||||
|
{
|
||||||
new_config.validate()?;
|
new_config.validate()?;
|
||||||
|
let _guard = self.lifecycle_mutex.lock().await;
|
||||||
// Update configuration
|
if self.state.load().current_service.is_some() {
|
||||||
{
|
return Err(KmsError::configuration_error(
|
||||||
let mut config = self.config.write().await;
|
"Cannot configure KMS while it is running; use reconfigure instead",
|
||||||
*config = Some(new_config.clone());
|
));
|
||||||
}
|
|
||||||
|
|
||||||
// Update status
|
|
||||||
{
|
|
||||||
let mut status = self.status.write().await;
|
|
||||||
*status = KmsServiceStatus::Configured;
|
|
||||||
}
|
}
|
||||||
|
persist().await?;
|
||||||
|
self.state.store(Arc::new(RuntimeState {
|
||||||
|
config: Some(new_config),
|
||||||
|
status: KmsServiceStatus::Configured,
|
||||||
|
current_service: None,
|
||||||
|
}));
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
event = EVENT_KMS_SERVICE_STATE,
|
event = EVENT_KMS_SERVICE_STATE,
|
||||||
@@ -136,19 +173,35 @@ impl KmsServiceManager {
|
|||||||
self.start_internal().await
|
self.start_internal().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start or restart KMS with the running-state decision serialized with the lifecycle action.
|
||||||
|
pub async fn start_or_restart(&self, force: bool) -> Result<KmsStartOutcome> {
|
||||||
|
let _guard = self.lifecycle_mutex.lock().await;
|
||||||
|
let running = self.state.load().current_service.is_some();
|
||||||
|
if running && !force {
|
||||||
|
return Ok(KmsStartOutcome::AlreadyRunning);
|
||||||
|
}
|
||||||
|
self.start_internal().await?;
|
||||||
|
Ok(if running {
|
||||||
|
KmsStartOutcome::Restarted
|
||||||
|
} else {
|
||||||
|
KmsStartOutcome::Started
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal start implementation (called within lifecycle mutex)
|
/// Internal start implementation (called within lifecycle mutex)
|
||||||
async fn start_internal(&self) -> Result<()> {
|
async fn start_internal(&self) -> Result<()> {
|
||||||
let config = {
|
let state = self.state.load_full();
|
||||||
let config_guard = self.config.read().await;
|
let config = match state.config.as_ref() {
|
||||||
match config_guard.as_ref() {
|
Some(config) => config.clone(),
|
||||||
Some(config) => config.clone(),
|
None => {
|
||||||
None => {
|
let err_msg = "Cannot start KMS: no configuration provided";
|
||||||
let err_msg = "Cannot start KMS: no configuration provided";
|
error!("{}", err_msg);
|
||||||
error!("{}", err_msg);
|
self.state.store(Arc::new(RuntimeState {
|
||||||
let mut status = self.status.write().await;
|
config: None,
|
||||||
*status = KmsServiceStatus::Error(err_msg.to_string());
|
status: KmsServiceStatus::Error(err_msg.to_string()),
|
||||||
return Err(KmsError::configuration_error(err_msg));
|
current_service: None,
|
||||||
}
|
}));
|
||||||
|
return Err(KmsError::configuration_error(err_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,17 +214,9 @@ impl KmsServiceManager {
|
|||||||
"KMS service starting"
|
"KMS service starting"
|
||||||
);
|
);
|
||||||
|
|
||||||
match self.create_service_version(&config).await {
|
match self.create_healthy_service_version(&config).await {
|
||||||
Ok(service_version) => {
|
Ok(service_version) => {
|
||||||
// Atomically update to new service version (lock-free, instant)
|
self.publish_running(config, service_version);
|
||||||
// ArcSwap::store() is a true atomic operation using CAS
|
|
||||||
self.current_service.store(Arc::new(Some(service_version)));
|
|
||||||
|
|
||||||
// Update status
|
|
||||||
{
|
|
||||||
let mut status = self.status.write().await;
|
|
||||||
*status = KmsServiceStatus::Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
event = EVENT_KMS_SERVICE_STATE,
|
event = EVENT_KMS_SERVICE_STATE,
|
||||||
@@ -185,13 +230,24 @@ impl KmsServiceManager {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("Failed to create KMS backend: {e}");
|
let err_msg = format!("Failed to create KMS backend: {e}");
|
||||||
error!("{}", err_msg);
|
error!("{}", err_msg);
|
||||||
let mut status = self.status.write().await;
|
if state.current_service.is_none() {
|
||||||
*status = KmsServiceStatus::Error(err_msg.clone());
|
self.state.store(Arc::new(RuntimeState {
|
||||||
|
config: state.config.clone(),
|
||||||
|
status: KmsServiceStatus::Error(err_msg.clone()),
|
||||||
|
current_service: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
Err(KmsError::backend_error(&err_msg))
|
Err(KmsError::backend_error(&err_msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace the running service without exposing a stopped interval.
|
||||||
|
pub async fn restart(&self) -> Result<()> {
|
||||||
|
let _guard = self.lifecycle_mutex.lock().await;
|
||||||
|
self.start_internal().await
|
||||||
|
}
|
||||||
|
|
||||||
/// Stop KMS service
|
/// Stop KMS service
|
||||||
///
|
///
|
||||||
/// Note: This stops accepting new operations, but existing operations using
|
/// Note: This stops accepting new operations, but existing operations using
|
||||||
@@ -213,15 +269,16 @@ impl KmsServiceManager {
|
|||||||
|
|
||||||
// Atomically clear current service version (lock-free, instant)
|
// Atomically clear current service version (lock-free, instant)
|
||||||
// Note: Existing Arc references will keep the service alive until operations complete
|
// Note: Existing Arc references will keep the service alive until operations complete
|
||||||
self.current_service.store(Arc::new(None));
|
let state = self.state.load_full();
|
||||||
|
self.state.store(Arc::new(RuntimeState {
|
||||||
// Update status (keep configuration)
|
config: state.config.clone(),
|
||||||
{
|
status: if state.config.is_some() {
|
||||||
let mut status = self.status.write().await;
|
KmsServiceStatus::Configured
|
||||||
if !matches!(*status, KmsServiceStatus::NotConfigured) {
|
} else {
|
||||||
*status = KmsServiceStatus::Configured;
|
KmsServiceStatus::NotConfigured
|
||||||
}
|
},
|
||||||
}
|
current_service: None,
|
||||||
|
}));
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
event = EVENT_KMS_SERVICE_STATE,
|
event = EVENT_KMS_SERVICE_STATE,
|
||||||
@@ -244,6 +301,22 @@ impl KmsServiceManager {
|
|||||||
/// This ensures zero downtime during reconfiguration, even for long-running
|
/// This ensures zero downtime during reconfiguration, even for long-running
|
||||||
/// operations like encrypting large files.
|
/// operations like encrypting large files.
|
||||||
pub async fn reconfigure(&self, new_config: KmsConfig) -> Result<()> {
|
pub async fn reconfigure(&self, new_config: KmsConfig) -> Result<()> {
|
||||||
|
self.reconfigure_with_persistence(new_config, || async { Ok(()) }).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reconfigure KMS after the candidate is healthy and persistence succeeds.
|
||||||
|
///
|
||||||
|
/// The persistence callback runs under the lifecycle lock and must not call
|
||||||
|
/// another lifecycle method on this manager.
|
||||||
|
pub async fn reconfigure_with_persistence<Persist, PersistFuture>(
|
||||||
|
&self,
|
||||||
|
new_config: KmsConfig,
|
||||||
|
persist: Persist,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
Persist: FnOnce() -> PersistFuture,
|
||||||
|
PersistFuture: Future<Output = Result<()>>,
|
||||||
|
{
|
||||||
let _guard = self.lifecycle_mutex.lock().await;
|
let _guard = self.lifecycle_mutex.lock().await;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
@@ -255,29 +328,16 @@ impl KmsServiceManager {
|
|||||||
);
|
);
|
||||||
new_config.validate()?;
|
new_config.validate()?;
|
||||||
|
|
||||||
// Configure with new config
|
|
||||||
{
|
|
||||||
let mut config = self.config.write().await;
|
|
||||||
*config = Some(new_config.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new service version without stopping old one
|
// Create new service version without stopping old one
|
||||||
// This allows existing operations to continue while new operations use new service
|
// This allows existing operations to continue while new operations use new service
|
||||||
match self.create_service_version(&new_config).await {
|
match self.create_healthy_service_version(&new_config).await {
|
||||||
Ok(new_service_version) => {
|
Ok(new_service_version) => {
|
||||||
// Get old version for logging (lock-free read)
|
// Get old version for logging (lock-free read)
|
||||||
let old_version = self.current_service.load().as_ref().as_ref().map(|sv| sv.version);
|
let old_version = self.state.load().current_service.as_ref().map(|sv| sv.version);
|
||||||
|
|
||||||
// Atomically switch to new service version (lock-free, instant CAS operation)
|
persist().await?;
|
||||||
// This is a true atomic operation - no waiting for locks, instant switch
|
|
||||||
// Old service will be dropped when no more Arc references exist
|
|
||||||
self.current_service.store(Arc::new(Some(new_service_version.clone())));
|
|
||||||
|
|
||||||
// Update status
|
self.publish_running(new_config, new_service_version.clone());
|
||||||
{
|
|
||||||
let mut status = self.status.write().await;
|
|
||||||
*status = KmsServiceStatus::Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(old_ver) = old_version {
|
if let Some(old_ver) = old_version {
|
||||||
info!(
|
info!(
|
||||||
@@ -304,8 +364,6 @@ impl KmsServiceManager {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("Failed to reconfigure KMS: {e}");
|
let err_msg = format!("Failed to reconfigure KMS: {e}");
|
||||||
error!("{}", err_msg);
|
error!("{}", err_msg);
|
||||||
let mut status = self.status.write().await;
|
|
||||||
*status = KmsServiceStatus::Error(err_msg.clone());
|
|
||||||
Err(KmsError::backend_error(&err_msg))
|
Err(KmsError::backend_error(&err_msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +374,7 @@ impl KmsServiceManager {
|
|||||||
/// Returns the manager from the current service version.
|
/// Returns the manager from the current service version.
|
||||||
/// Uses lock-free atomic load for optimal performance.
|
/// Uses lock-free atomic load for optimal performance.
|
||||||
pub async fn get_manager(&self) -> Option<Arc<KmsManager>> {
|
pub async fn get_manager(&self) -> Option<Arc<KmsManager>> {
|
||||||
self.current_service.load().as_ref().as_ref().map(|sv| sv.manager.clone())
|
self.state.load().current_service.as_ref().map(|sv| sv.manager.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get encryption service (if running)
|
/// Get encryption service (if running)
|
||||||
@@ -326,7 +384,7 @@ impl KmsServiceManager {
|
|||||||
/// This ensures new operations always use the latest service version,
|
/// This ensures new operations always use the latest service version,
|
||||||
/// while existing operations continue using their Arc references.
|
/// while existing operations continue using their Arc references.
|
||||||
pub async fn get_encryption_service(&self) -> Option<Arc<ObjectEncryptionService>> {
|
pub async fn get_encryption_service(&self) -> Option<Arc<ObjectEncryptionService>> {
|
||||||
self.current_service.load().as_ref().as_ref().map(|sv| sv.service.clone())
|
self.state.load().current_service.as_ref().map(|sv| sv.service.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current service version number
|
/// Get current service version number
|
||||||
@@ -334,14 +392,16 @@ impl KmsServiceManager {
|
|||||||
/// Useful for monitoring and debugging.
|
/// Useful for monitoring and debugging.
|
||||||
/// Uses lock-free atomic load.
|
/// Uses lock-free atomic load.
|
||||||
pub async fn get_service_version(&self) -> Option<u64> {
|
pub async fn get_service_version(&self) -> Option<u64> {
|
||||||
self.current_service.load().as_ref().as_ref().map(|sv| sv.version)
|
self.state.load().current_service.as_ref().map(|sv| sv.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Health check for the KMS service
|
/// Health check for the KMS service
|
||||||
pub async fn health_check(&self) -> Result<bool> {
|
pub async fn health_check(&self) -> Result<bool> {
|
||||||
let manager = self.get_manager().await;
|
let checked_state = self.state.load_full();
|
||||||
match manager {
|
match checked_state.current_service.as_ref() {
|
||||||
Some(manager) => {
|
Some(service_version) => {
|
||||||
|
let manager = service_version.manager.clone();
|
||||||
|
let checked_version = service_version.version;
|
||||||
// Perform health check on the backend
|
// Perform health check on the backend
|
||||||
match manager.health_check().await {
|
match manager.health_check().await {
|
||||||
Ok(healthy) => {
|
Ok(healthy) => {
|
||||||
@@ -352,9 +412,8 @@ impl KmsServiceManager {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("KMS health check error: {}", e);
|
error!("KMS health check error: {}", e);
|
||||||
// Update status to error
|
let _guard = self.lifecycle_mutex.lock().await;
|
||||||
let mut status = self.status.write().await;
|
self.mark_health_error_if_current(checked_version, &e);
|
||||||
*status = KmsServiceStatus::Error(format!("Health check failed: {e}"));
|
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,6 +472,33 @@ impl KmsServiceManager {
|
|||||||
manager: kms_manager,
|
manager: kms_manager,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_healthy_service_version(&self, config: &KmsConfig) -> Result<ServiceVersion> {
|
||||||
|
let service_version = self.create_service_version(config).await?;
|
||||||
|
if !service_version.manager.health_check().await? {
|
||||||
|
return Err(KmsError::backend_error("KMS backend health check failed"));
|
||||||
|
}
|
||||||
|
Ok(service_version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn publish_running(&self, config: KmsConfig, service_version: ServiceVersion) {
|
||||||
|
self.state.store(Arc::new(RuntimeState {
|
||||||
|
config: Some(config),
|
||||||
|
status: KmsServiceStatus::Running,
|
||||||
|
current_service: Some(service_version),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_health_error_if_current(&self, checked_version: u64, error: &KmsError) {
|
||||||
|
let current = self.state.load_full();
|
||||||
|
if current.current_service.as_ref().map(|version| version.version) == Some(checked_version) {
|
||||||
|
self.state.store(Arc::new(RuntimeState {
|
||||||
|
config: current.config.clone(),
|
||||||
|
status: KmsServiceStatus::Error(format!("Health check failed: {error}")),
|
||||||
|
current_service: current.current_service.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KmsServiceManager {
|
impl Default for KmsServiceManager {
|
||||||
@@ -445,6 +531,11 @@ pub async fn get_global_encryption_service() -> Option<Arc<ObjectEncryptionServi
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
|
||||||
|
|
||||||
|
fn static_config(key_id: &str, fill: u8) -> KmsConfig {
|
||||||
|
KmsConfig::static_kms(key_id.to_string(), BASE64_STANDARD.encode([fill; 32]))
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn configure_rejects_insecure_development_defaults_before_state_update() {
|
async fn configure_rejects_insecure_development_defaults_before_state_update() {
|
||||||
@@ -462,8 +553,6 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn redacted_config_omits_static_key_material() {
|
async fn redacted_config_omits_static_key_material() {
|
||||||
use base64::Engine as _;
|
|
||||||
|
|
||||||
let manager = KmsServiceManager::new();
|
let manager = KmsServiceManager::new();
|
||||||
let encoded_key = base64::engine::general_purpose::STANDARD.encode([0x5au8; 32]);
|
let encoded_key = base64::engine::general_purpose::STANDARD.encode([0x5au8; 32]);
|
||||||
manager
|
manager
|
||||||
@@ -477,4 +566,134 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert!(static_config.secret_key.is_empty());
|
assert!(static_config.secret_key.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn configure_persistence_failure_leaves_state_unchanged() {
|
||||||
|
let manager = KmsServiceManager::new();
|
||||||
|
|
||||||
|
let result = manager
|
||||||
|
.configure_with_persistence(static_config("key-a", 0x11), || async { Err(KmsError::backend_error("persist failed")) })
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(manager.get_status().await, KmsServiceStatus::NotConfigured);
|
||||||
|
assert!(manager.get_config().await.is_none());
|
||||||
|
assert!(manager.get_encryption_service().await.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn configure_rejects_running_service_without_changing_snapshot() {
|
||||||
|
let manager = KmsServiceManager::new();
|
||||||
|
manager.configure(static_config("key-a", 0x11)).await.expect("configure");
|
||||||
|
manager.start().await.expect("start");
|
||||||
|
let version = manager.get_service_version().await;
|
||||||
|
|
||||||
|
let result = manager.configure(static_config("key-b", 0x22)).await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(manager.get_status().await, KmsServiceStatus::Running);
|
||||||
|
assert_eq!(manager.get_service_version().await, version);
|
||||||
|
assert_eq!(
|
||||||
|
manager.get_config().await.and_then(|config| config.default_key_id),
|
||||||
|
Some("key-a".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reconfigure_persistence_failure_keeps_old_running_snapshot() {
|
||||||
|
let manager = KmsServiceManager::new();
|
||||||
|
manager.configure(static_config("key-a", 0x11)).await.expect("configure");
|
||||||
|
manager.start().await.expect("start");
|
||||||
|
let old_version = manager.get_service_version().await;
|
||||||
|
let old_service = manager.get_encryption_service().await.expect("old service");
|
||||||
|
|
||||||
|
let result = manager
|
||||||
|
.reconfigure_with_persistence(static_config("key-b", 0x22), || async {
|
||||||
|
Err(KmsError::backend_error("persist failed"))
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(manager.get_status().await, KmsServiceStatus::Running);
|
||||||
|
assert_eq!(manager.get_service_version().await, old_version);
|
||||||
|
assert_eq!(
|
||||||
|
manager.get_config().await.and_then(|config| config.default_key_id),
|
||||||
|
Some("key-a".to_string())
|
||||||
|
);
|
||||||
|
assert!(Arc::ptr_eq(
|
||||||
|
&old_service,
|
||||||
|
&manager.get_encryption_service().await.expect("old service remains")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reconfigure_candidate_failure_keeps_old_running_snapshot() {
|
||||||
|
let manager = KmsServiceManager::new();
|
||||||
|
manager.configure(static_config("key-a", 0x11)).await.expect("configure");
|
||||||
|
manager.start().await.expect("start");
|
||||||
|
let old_version = manager.get_service_version().await;
|
||||||
|
let invalid_parent = tempfile::NamedTempFile::new().expect("temporary file");
|
||||||
|
let invalid_config = KmsConfig::local(invalid_parent.path().join("keys")).with_insecure_development_defaults();
|
||||||
|
|
||||||
|
let result = manager.reconfigure(invalid_config).await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(manager.get_status().await, KmsServiceStatus::Running);
|
||||||
|
assert_eq!(manager.get_service_version().await, old_version);
|
||||||
|
assert_eq!(
|
||||||
|
manager.get_config().await.and_then(|config| config.default_key_id),
|
||||||
|
Some("key-a".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn restart_never_unpublishes_the_running_service() {
|
||||||
|
let manager = Arc::new(KmsServiceManager::new());
|
||||||
|
manager.configure(static_config("key-a", 0x11)).await.expect("configure");
|
||||||
|
manager.start().await.expect("start");
|
||||||
|
let old_version = manager.get_service_version().await.expect("old version");
|
||||||
|
let restarting = {
|
||||||
|
let manager = manager.clone();
|
||||||
|
tokio::spawn(async move { manager.restart().await })
|
||||||
|
};
|
||||||
|
|
||||||
|
while !restarting.is_finished() {
|
||||||
|
assert!(manager.get_encryption_service().await.is_some());
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
}
|
||||||
|
restarting.await.expect("restart task").expect("restart");
|
||||||
|
|
||||||
|
assert!(manager.get_encryption_service().await.is_some());
|
||||||
|
assert!(manager.get_service_version().await.expect("new version") > old_version);
|
||||||
|
assert_eq!(manager.get_status().await, KmsServiceStatus::Running);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_or_restart_decides_under_the_lifecycle_lock() {
|
||||||
|
let manager = KmsServiceManager::new();
|
||||||
|
manager.configure(static_config("key-a", 0x11)).await.expect("configure");
|
||||||
|
|
||||||
|
assert_eq!(manager.start_or_restart(false).await.expect("initial start"), KmsStartOutcome::Started);
|
||||||
|
let first_version = manager.get_service_version().await.expect("first version");
|
||||||
|
assert_eq!(
|
||||||
|
manager.start_or_restart(false).await.expect("already running"),
|
||||||
|
KmsStartOutcome::AlreadyRunning
|
||||||
|
);
|
||||||
|
assert_eq!(manager.get_service_version().await, Some(first_version));
|
||||||
|
assert_eq!(manager.start_or_restart(true).await.expect("forced restart"), KmsStartOutcome::Restarted);
|
||||||
|
assert!(manager.get_service_version().await.expect("restarted version") > first_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stale_health_failure_cannot_poison_new_service_status() {
|
||||||
|
let manager = KmsServiceManager::new();
|
||||||
|
manager.configure(static_config("key-a", 0x11)).await.expect("configure");
|
||||||
|
manager.start().await.expect("start");
|
||||||
|
let old_version = manager.get_service_version().await.expect("old version");
|
||||||
|
manager.restart().await.expect("restart");
|
||||||
|
|
||||||
|
manager.mark_health_error_if_current(old_version, &KmsError::backend_error("stale failure"));
|
||||||
|
|
||||||
|
assert_eq!(manager.get_status().await, KmsServiceStatus::Running);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,36 +363,27 @@ impl Operation for ConfigureKmsHandler {
|
|||||||
// Convert request to KmsConfig
|
// Convert request to KmsConfig
|
||||||
let kms_config = configure_request.to_kms_config();
|
let kms_config = configure_request.to_kms_config();
|
||||||
|
|
||||||
// Configure the service
|
let persisted_config = kms_config.clone();
|
||||||
let (success, message, status) = match service_manager.configure(kms_config.clone()).await {
|
let (success, message, status) = match service_manager
|
||||||
|
.configure_with_persistence(kms_config, || async move {
|
||||||
|
save_kms_config(&persisted_config)
|
||||||
|
.await
|
||||||
|
.map_err(|error| rustfs_kms::KmsError::backend_error(format!("Failed to persist KMS configuration: {error}")))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
// Persist the configuration to cluster storage
|
let status = service_manager.get_status().await;
|
||||||
if let Err(e) = save_kms_config(&kms_config).await {
|
info!(
|
||||||
let error_msg = format!("KMS configured in memory but failed to persist: {e}");
|
component = LOG_COMPONENT_ADMIN,
|
||||||
error!(
|
subsystem = LOG_SUBSYSTEM_KMS,
|
||||||
component = LOG_COMPONENT_ADMIN,
|
event = "kms_service_state",
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
operation = "configure",
|
||||||
event = "kms_service_state",
|
state = "configured",
|
||||||
operation = "configure",
|
status = ?status,
|
||||||
state = "persist_failed",
|
"admin kms dynamic state"
|
||||||
error = %e,
|
);
|
||||||
"admin kms dynamic state"
|
(true, "KMS configured successfully".to_string(), status)
|
||||||
);
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
(false, error_msg, status)
|
|
||||||
} else {
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
info!(
|
|
||||||
component = LOG_COMPONENT_ADMIN,
|
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
|
||||||
event = "kms_service_state",
|
|
||||||
operation = "configure",
|
|
||||||
state = "configured",
|
|
||||||
status = ?status,
|
|
||||||
"admin kms dynamic state"
|
|
||||||
);
|
|
||||||
(true, "KMS configured successfully".to_string(), status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let error_msg = format!("Failed to configure KMS: {e}");
|
let error_msg = format!("Failed to configure KMS: {e}");
|
||||||
@@ -499,125 +490,61 @@ impl Operation for StartKmsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let service_manager = kms_service_manager_from_context();
|
let service_manager = kms_service_manager_from_context();
|
||||||
|
let force = start_request.force.unwrap_or(false);
|
||||||
// Check if already running and force flag
|
let (success, message, status) = match service_manager.start_or_restart(force).await {
|
||||||
let current_status = service_manager.get_status().await;
|
Ok(rustfs_kms::KmsStartOutcome::Started) => {
|
||||||
if matches!(current_status, KmsServiceStatus::Running) && !start_request.force.unwrap_or(false) {
|
let status = service_manager.get_status().await;
|
||||||
warn!(
|
info!(
|
||||||
component = LOG_COMPONENT_ADMIN,
|
component = LOG_COMPONENT_ADMIN,
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
subsystem = LOG_SUBSYSTEM_KMS,
|
||||||
event = "kms_service_state",
|
event = "kms_service_state",
|
||||||
operation = "start",
|
operation = "start",
|
||||||
state = "already_running",
|
state = "running",
|
||||||
"admin kms dynamic state"
|
status = ?status,
|
||||||
);
|
"admin kms dynamic state"
|
||||||
let response = StartKmsResponse {
|
);
|
||||||
success: false,
|
(true, "KMS service started successfully".to_string(), status)
|
||||||
message: "KMS service is already running. Use force=true to restart.".to_string(),
|
}
|
||||||
status: current_status,
|
Ok(rustfs_kms::KmsStartOutcome::Restarted) => {
|
||||||
};
|
let status = service_manager.get_status().await;
|
||||||
let json_response = match serde_json::to_string(&response) {
|
info!(
|
||||||
Ok(json) => json,
|
component = LOG_COMPONENT_ADMIN,
|
||||||
Err(e) => {
|
subsystem = LOG_SUBSYSTEM_KMS,
|
||||||
error!(
|
event = "kms_service_state",
|
||||||
component = LOG_COMPONENT_ADMIN,
|
operation = "restart",
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
state = "running",
|
||||||
event = EVENT_ADMIN_KMS_DYNAMIC_STATE,
|
status = ?status,
|
||||||
operation = "start",
|
"admin kms dynamic state"
|
||||||
result = "response_serialize_failed",
|
);
|
||||||
error = %e,
|
(true, "KMS service restarted successfully".to_string(), status)
|
||||||
"admin kms dynamic state"
|
}
|
||||||
);
|
Ok(rustfs_kms::KmsStartOutcome::AlreadyRunning) => {
|
||||||
return Ok(S3Response::new((
|
let status = service_manager.get_status().await;
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
warn!(
|
||||||
Body::from("Serialization error".to_string()),
|
component = LOG_COMPONENT_ADMIN,
|
||||||
)));
|
subsystem = LOG_SUBSYSTEM_KMS,
|
||||||
}
|
event = "kms_service_state",
|
||||||
};
|
operation = "start",
|
||||||
return Ok(S3Response::new((StatusCode::OK, Body::from(json_response))));
|
state = "already_running",
|
||||||
}
|
"admin kms dynamic state"
|
||||||
|
);
|
||||||
// Start the service (or restart if force=true)
|
(false, "KMS service is already running. Use force=true to restart.".to_string(), status)
|
||||||
let (success, message, status) =
|
}
|
||||||
if start_request.force.unwrap_or(false) && matches!(current_status, KmsServiceStatus::Running) {
|
Err(e) => {
|
||||||
// Force restart
|
let error_msg = format!("Failed to start or restart KMS service: {e}");
|
||||||
match service_manager.stop().await {
|
error!(
|
||||||
Ok(()) => match service_manager.start().await {
|
component = LOG_COMPONENT_ADMIN,
|
||||||
Ok(()) => {
|
subsystem = LOG_SUBSYSTEM_KMS,
|
||||||
let status = service_manager.get_status().await;
|
event = "kms_service_state",
|
||||||
info!(
|
operation = "start",
|
||||||
component = LOG_COMPONENT_ADMIN,
|
state = "start_failed",
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
error = %e,
|
||||||
event = "kms_service_state",
|
"admin kms dynamic state"
|
||||||
operation = "restart",
|
);
|
||||||
state = "running",
|
let status = service_manager.get_status().await;
|
||||||
status = ?status,
|
(false, error_msg, status)
|
||||||
"admin kms dynamic state"
|
}
|
||||||
);
|
};
|
||||||
(true, "KMS service restarted successfully".to_string(), status)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let error_msg = format!("Failed to restart KMS service: {e}");
|
|
||||||
error!(
|
|
||||||
component = LOG_COMPONENT_ADMIN,
|
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
|
||||||
event = "kms_service_state",
|
|
||||||
operation = "restart",
|
|
||||||
state = "start_failed",
|
|
||||||
error = %e,
|
|
||||||
"admin kms dynamic state"
|
|
||||||
);
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
(false, error_msg, status)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
let error_msg = format!("Failed to stop KMS service for restart: {e}");
|
|
||||||
error!(
|
|
||||||
component = LOG_COMPONENT_ADMIN,
|
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
|
||||||
event = "kms_service_state",
|
|
||||||
operation = "restart",
|
|
||||||
state = "stop_failed",
|
|
||||||
error = %e,
|
|
||||||
"admin kms dynamic state"
|
|
||||||
);
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
(false, error_msg, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Normal start
|
|
||||||
match service_manager.start().await {
|
|
||||||
Ok(()) => {
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
info!(
|
|
||||||
component = LOG_COMPONENT_ADMIN,
|
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
|
||||||
event = "kms_service_state",
|
|
||||||
operation = "start",
|
|
||||||
state = "running",
|
|
||||||
status = ?status,
|
|
||||||
"admin kms dynamic state"
|
|
||||||
);
|
|
||||||
(true, "KMS service started successfully".to_string(), status)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let error_msg = format!("Failed to start KMS service: {e}");
|
|
||||||
error!(
|
|
||||||
component = LOG_COMPONENT_ADMIN,
|
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
|
||||||
event = "kms_service_state",
|
|
||||||
operation = "start",
|
|
||||||
state = "start_failed",
|
|
||||||
error = %e,
|
|
||||||
"admin kms dynamic state"
|
|
||||||
);
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
(false, error_msg, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = StartKmsResponse {
|
let response = StartKmsResponse {
|
||||||
success,
|
success,
|
||||||
@@ -774,8 +701,7 @@ impl Operation for GetKmsStatusHandler {
|
|||||||
|
|
||||||
let service_manager = kms_service_manager_from_context();
|
let service_manager = kms_service_manager_from_context();
|
||||||
|
|
||||||
let status = service_manager.get_status().await;
|
let (status, config) = service_manager.get_redacted_state().await;
|
||||||
let config = service_manager.get_redacted_config().await;
|
|
||||||
|
|
||||||
// Get backend type and health status
|
// Get backend type and health status
|
||||||
let backend_type = config.as_ref().map(|c| c.backend.clone());
|
let backend_type = config.as_ref().map(|c| c.backend.clone());
|
||||||
@@ -908,36 +834,27 @@ impl Operation for ReconfigureKmsHandler {
|
|||||||
// Convert request to KmsConfig
|
// Convert request to KmsConfig
|
||||||
let kms_config = configure_request.to_kms_config();
|
let kms_config = configure_request.to_kms_config();
|
||||||
|
|
||||||
// Reconfigure the service (stops, reconfigures, and starts)
|
let persisted_config = kms_config.clone();
|
||||||
let (success, message, status) = match service_manager.reconfigure(kms_config.clone()).await {
|
let (success, message, status) = match service_manager
|
||||||
|
.reconfigure_with_persistence(kms_config, || async move {
|
||||||
|
save_kms_config(&persisted_config)
|
||||||
|
.await
|
||||||
|
.map_err(|error| rustfs_kms::KmsError::backend_error(format!("Failed to persist KMS configuration: {error}")))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
// Persist the configuration to cluster storage
|
let status = service_manager.get_status().await;
|
||||||
if let Err(e) = save_kms_config(&kms_config).await {
|
info!(
|
||||||
let error_msg = format!("KMS reconfigured in memory but failed to persist: {e}");
|
component = LOG_COMPONENT_ADMIN,
|
||||||
error!(
|
subsystem = LOG_SUBSYSTEM_KMS,
|
||||||
component = LOG_COMPONENT_ADMIN,
|
event = "kms_service_state",
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
operation = "reconfigure",
|
||||||
event = "kms_service_state",
|
state = "reconfigured",
|
||||||
operation = "reconfigure",
|
status = ?status,
|
||||||
state = "persist_failed",
|
"admin kms dynamic state"
|
||||||
error = %e,
|
);
|
||||||
"admin kms dynamic state"
|
(true, "KMS reconfigured and restarted successfully".to_string(), status)
|
||||||
);
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
(false, error_msg, status)
|
|
||||||
} else {
|
|
||||||
let status = service_manager.get_status().await;
|
|
||||||
info!(
|
|
||||||
component = LOG_COMPONENT_ADMIN,
|
|
||||||
subsystem = LOG_SUBSYSTEM_KMS,
|
|
||||||
event = "kms_service_state",
|
|
||||||
operation = "reconfigure",
|
|
||||||
state = "reconfigured",
|
|
||||||
status = ?status,
|
|
||||||
"admin kms dynamic state"
|
|
||||||
);
|
|
||||||
(true, "KMS reconfigured and restarted successfully".to_string(), status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let error_msg = format!("Failed to reconfigure KMS: {e}");
|
let error_msg = format!("Failed to reconfigure KMS: {e}");
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ use super::handles::{
|
|||||||
IamHandle, KmsHandle, default_action_credential_interface, default_boot_time_interface, default_bucket_metadata_interface,
|
IamHandle, KmsHandle, default_action_credential_interface, default_boot_time_interface, default_bucket_metadata_interface,
|
||||||
default_bucket_monitor_interface, default_buffer_config_interface, default_deployment_id_interface,
|
default_bucket_monitor_interface, default_buffer_config_interface, default_deployment_id_interface,
|
||||||
default_endpoints_interface, default_expiry_state_interface, default_federated_identity_interface,
|
default_endpoints_interface, default_expiry_state_interface, default_federated_identity_interface,
|
||||||
default_internode_metrics_interface, default_kms_runtime_interface, default_local_node_name_interface,
|
default_internode_metrics_interface, default_local_node_name_interface, default_lock_client_interface,
|
||||||
default_lock_client_interface, default_lock_clients_interface, default_notification_system_interface,
|
default_lock_clients_interface, default_notification_system_interface, default_notify_interface,
|
||||||
default_notify_interface, default_outbound_tls_runtime_interface, default_performance_metrics_interface,
|
default_outbound_tls_runtime_interface, default_performance_metrics_interface, default_region_interface,
|
||||||
default_region_interface, default_replication_pool_interface, default_replication_stats_interface,
|
default_replication_pool_interface, default_replication_stats_interface, default_runtime_port_interface,
|
||||||
default_runtime_port_interface, default_s3select_db_interface, default_scanner_metrics_interface,
|
default_s3select_db_interface, default_scanner_metrics_interface, default_server_config_interface,
|
||||||
default_server_config_interface, default_storage_class_interface, default_tier_config_interface,
|
default_storage_class_interface, default_tier_config_interface, default_transition_state_interface,
|
||||||
default_transition_state_interface,
|
|
||||||
};
|
};
|
||||||
use super::interfaces::{
|
use super::interfaces::{
|
||||||
ActionCredentialInterface, BootTimeInterface, BucketMetadataInterface, BucketMonitorInterface, BufferConfigInterface,
|
ActionCredentialInterface, BootTimeInterface, BucketMetadataInterface, BucketMonitorInterface, BufferConfigInterface,
|
||||||
@@ -80,6 +79,7 @@ pub struct AppContext {
|
|||||||
impl AppContext {
|
impl AppContext {
|
||||||
pub fn new(object_store: Arc<ECStore>, iam: Arc<dyn IamInterface>, kms: Arc<dyn KmsInterface>) -> Self {
|
pub fn new(object_store: Arc<ECStore>, iam: Arc<dyn IamInterface>, kms: Arc<dyn KmsInterface>) -> Self {
|
||||||
let object_data_cache = ObjectDataCacheAdapter::from_env_or_disabled();
|
let object_data_cache = ObjectDataCacheAdapter::from_env_or_disabled();
|
||||||
|
let kms_runtime = Arc::new(crate::app::context::handles::KmsRuntimeHandle::new(kms.handle()));
|
||||||
// Let ecstore probe this cache inside get_object_reader, after
|
// Let ecstore probe this cache inside get_object_reader, after
|
||||||
// metadata resolution but before the erasure data read (backlog#802).
|
// metadata resolution but before the erasure data read (backlog#802).
|
||||||
crate::app::object_data_cache::register_object_data_cache_body_hook(Arc::clone(&object_data_cache));
|
crate::app::object_data_cache::register_object_data_cache_body_hook(Arc::clone(&object_data_cache));
|
||||||
@@ -94,7 +94,7 @@ impl AppContext {
|
|||||||
iam,
|
iam,
|
||||||
federated_identity: default_federated_identity_interface(),
|
federated_identity: default_federated_identity_interface(),
|
||||||
kms,
|
kms,
|
||||||
kms_runtime: default_kms_runtime_interface(),
|
kms_runtime,
|
||||||
outbound_tls_runtime: default_outbound_tls_runtime_interface(),
|
outbound_tls_runtime: default_outbound_tls_runtime_interface(),
|
||||||
notify: default_notify_interface(),
|
notify: default_notify_interface(),
|
||||||
notification_system: default_notification_system_interface(),
|
notification_system: default_notification_system_interface(),
|
||||||
|
|||||||
@@ -128,12 +128,19 @@ impl KmsInterface for KmsHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Default KMS runtime interface adapter.
|
/// Default KMS runtime interface adapter.
|
||||||
#[derive(Default)]
|
pub struct KmsRuntimeHandle {
|
||||||
pub struct KmsRuntimeHandle;
|
kms: Option<Arc<KmsServiceManager>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KmsRuntimeHandle {
|
||||||
|
pub fn new(kms: Arc<KmsServiceManager>) -> Self {
|
||||||
|
Self { kms: Some(kms) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl KmsRuntimeInterface for KmsRuntimeHandle {
|
impl KmsRuntimeInterface for KmsRuntimeHandle {
|
||||||
fn service_manager(&self) -> Option<Arc<KmsServiceManager>> {
|
fn service_manager(&self) -> Option<Arc<KmsServiceManager>> {
|
||||||
runtime_sources::kms_service_manager()
|
self.kms.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +492,9 @@ pub fn default_notification_system_interface() -> Arc<dyn NotificationSystemInte
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_kms_runtime_interface() -> Arc<dyn KmsRuntimeInterface> {
|
pub fn default_kms_runtime_interface() -> Arc<dyn KmsRuntimeInterface> {
|
||||||
Arc::new(KmsRuntimeHandle)
|
Arc::new(KmsRuntimeHandle {
|
||||||
|
kms: runtime_sources::kms_service_manager(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_outbound_tls_runtime_interface() -> Arc<dyn OutboundTlsRuntimeInterface> {
|
pub fn default_outbound_tls_runtime_interface() -> Arc<dyn OutboundTlsRuntimeInterface> {
|
||||||
@@ -607,10 +616,10 @@ pub fn default_buffer_config_interface() -> Arc<dyn BufferConfigInterface> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
ServerConfigHandle, default_federated_identity_interface, federated_identity_interface,
|
KmsRuntimeHandle, KmsServiceManager, ServerConfigHandle, default_federated_identity_interface,
|
||||||
publish_default_federated_identity_service, runtime_sources,
|
federated_identity_interface, publish_default_federated_identity_service, runtime_sources,
|
||||||
};
|
};
|
||||||
use crate::app::context::interfaces::ServerConfigInterface;
|
use crate::app::context::interfaces::{KmsRuntimeInterface, ServerConfigInterface};
|
||||||
use rustfs_config::server_config::Config;
|
use rustfs_config::server_config::Config;
|
||||||
use rustfs_iam::{
|
use rustfs_iam::{
|
||||||
federation::{FederatedIdentityRegistry, FederatedIdentityService, oidc::StandardOidcAdapter},
|
federation::{FederatedIdentityRegistry, FederatedIdentityService, oidc::StandardOidcAdapter},
|
||||||
@@ -733,4 +742,19 @@ mod tests {
|
|||||||
"handle B must serve its own credentials"
|
"handle B must serve its own credentials"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn kms_runtime_handles_keep_injected_managers_isolated() {
|
||||||
|
let manager_a = Arc::new(KmsServiceManager::new());
|
||||||
|
let manager_b = Arc::new(KmsServiceManager::new());
|
||||||
|
let handle_a = KmsRuntimeHandle::new(manager_a.clone());
|
||||||
|
let handle_b = KmsRuntimeHandle::new(manager_b.clone());
|
||||||
|
|
||||||
|
assert!(Arc::ptr_eq(&handle_a.service_manager().expect("manager A"), &manager_a));
|
||||||
|
assert!(Arc::ptr_eq(&handle_b.service_manager().expect("manager B"), &manager_b));
|
||||||
|
assert!(!Arc::ptr_eq(
|
||||||
|
&handle_a.service_manager().expect("manager A"),
|
||||||
|
&handle_b.service_manager().expect("manager B")
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ pub(crate) mod ecstore_object {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(all(test, feature = "rio-v2"))]
|
||||||
pub(crate) mod ecstore_test_support {
|
pub(crate) mod ecstore_test_support {
|
||||||
pub(crate) use rustfs_ecstore::api::bitrot::create_bitrot_reader;
|
pub(crate) use rustfs_ecstore::api::bitrot::create_bitrot_reader;
|
||||||
pub(crate) use rustfs_ecstore::api::disk::{DiskAPI, DiskOption, endpoint::Endpoint, new_disk};
|
pub(crate) use rustfs_ecstore::api::disk::{DiskAPI, DiskOption, endpoint::Endpoint, new_disk};
|
||||||
|
|||||||
Reference in New Issue
Block a user