#![deny(clippy::unwrap_used)] // 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. //! # RustFS Key Management Service (KMS) //! //! This crate provides a comprehensive Key Management Service (KMS) for RustFS, //! supporting secure key generation, storage, and object encryption capabilities. //! //! ## Features //! //! - **Multiple Backends**: Local file storage, Vault KV2 (plain KV storage), and Vault Transit (optional) //! - **Object Encryption**: Transparent S3-compatible object encryption //! - **Streaming Encryption**: Memory-efficient encryption for large files //! - **Key Management**: Full lifecycle management of encryption keys //! - **S3 Compatibility**: SSE-S3, SSE-KMS, and SSE-C encryption modes //! //! ## Architecture //! //! The KMS follows a three-layer key hierarchy: //! - **Master Keys**: Managed by KMS backends (Local / Vault KV2 / Vault Transit) //! - **Data Encryption Keys (DEK)**: Generated per object, encrypted by master keys //! - **Object Data**: Encrypted using DEKs with AES-256-GCM or ChaCha20-Poly1305 //! //! ## Caching Discipline //! //! KMS may cache stable master-key metadata, but it must not cache or reuse generated //! data encryption keys by master key id alone. A generated DEK and its encrypted //! ciphertext can be bound to the object encryption context, such as the bucket and //! object path. Reusing it for another object can break context validation and would //! also violate the expected per-object DEK model for SSE-S3 and SSE-KMS. //! //! ## Example //! //! ```rust,no_run //! use rustfs_kms::{KmsConfig, init_global_kms_service_manager}; //! use std::path::PathBuf; //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! // Initialize global KMS service manager //! let service_manager = init_global_kms_service_manager(); //! //! // Configure with local backend //! let config = KmsConfig::local(PathBuf::from("./kms_keys")).with_insecure_development_defaults(); //! service_manager.configure(config).await?; //! //! // Start the KMS service //! service_manager.start().await?; //! //! Ok(()) //! } //! ``` // Core modules pub mod api_types; pub mod audit; pub mod backends; pub mod backup; mod cache; pub mod config; pub mod deletion_worker; mod encryption; mod error; pub mod key_impact; pub mod manager; mod persisted_observability; mod policy; pub mod probe; pub mod service; pub mod service_manager; mod time_serde; pub mod types; #[cfg(test)] pub(crate) mod test_support { use crate::persisted_observability::UNKNOWN_FIELDS_METRIC; use metrics_util::MetricKind; use metrics_util::debugging::{DebugValue, DebuggingRecorder}; use serde::Deserializer; use serde::de::value::MapDeserializer; use serde::de::{self, DeserializeOwned, IntoDeserializer, Visitor}; use std::io::{self, Write}; use std::sync::{Arc, Mutex}; #[derive(Clone, Default)] pub(crate) struct CapturedLogs { output: Arc>>, } pub(crate) struct CapturedWriter(CapturedLogs); impl Write for CapturedWriter { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.output.lock().expect("log buffer lock poisoned").extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for CapturedLogs { type Writer = CapturedWriter; fn make_writer(&'a self) -> Self::Writer { CapturedWriter(self.clone()) } } impl CapturedLogs { pub(crate) fn output(&self) -> String { String::from_utf8(self.output.lock().expect("log buffer lock poisoned").clone()) .expect("captured logs should be UTF-8") } } pub(crate) fn unknown_field_metric(recorder: &DebuggingRecorder, record_kind: &str) -> u64 { recorder .snapshotter() .snapshot() .into_vec() .into_iter() .filter_map(|(composite, _unit, _description, value)| { let matches = composite.kind() == MetricKind::Counter && composite.key().name() == UNKNOWN_FIELDS_METRIC && composite .key() .labels() .any(|label| label.key() == "record_kind" && label.value() == record_kind); match (matches, value) { (true, DebugValue::Counter(count)) => Some(count), _ => None, } }) .sum() } enum IgnoredOnlyValue { Json(serde_json::Value), Unknown, } impl<'de> IntoDeserializer<'de, serde_json::Error> for IgnoredOnlyValue { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { self } } impl<'de> Deserializer<'de> for IgnoredOnlyValue { type Error = serde_json::Error; fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { match self { Self::Json(value) => value.deserialize_any(visitor), Self::Unknown => Err(de::Error::custom("unknown value was materialized")), } } fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de>, { match self { Self::Json(value) => value.deserialize_option(visitor), Self::Unknown => Err(de::Error::custom("unknown value was materialized")), } } fn deserialize_newtype_struct(self, name: &'static str, visitor: V) -> Result where V: Visitor<'de>, { match self { Self::Json(value) => value.deserialize_newtype_struct(name, visitor), Self::Unknown => Err(de::Error::custom("unknown value was materialized")), } } fn deserialize_enum( self, name: &'static str, variants: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { match self { Self::Json(value) => value.deserialize_enum(name, variants, visitor), Self::Unknown => Err(de::Error::custom("unknown value was materialized")), } } fn deserialize_ignored_any(self, visitor: V) -> Result where V: Visitor<'de>, { match self { Self::Json(value) => value.deserialize_ignored_any(visitor), Self::Unknown => visitor.visit_unit(), } } serde::forward_to_deserialize_any! { bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf unit unit_struct seq tuple tuple_struct map struct identifier } } pub(crate) fn deserialize_with_ignored_only_unknown( record: serde_json::Value, unknown_field: &str, ) -> Result where T: DeserializeOwned, { let object = record .as_object() .ok_or_else(|| de::Error::custom("test record must be an object"))?; let entries = object .iter() .map(|(key, value)| (key.clone(), IgnoredOnlyValue::Json(value.clone()))) .chain([(unknown_field.to_owned(), IgnoredOnlyValue::Unknown)]); T::deserialize(MapDeserializer::<_, serde_json::Error>::new(entries)) } } // Re-export public API pub use api_types::{ CacheSummary, ConfigureAwsKmsRequest, ConfigureKmsRequest, ConfigureKmsResponse, ConfigureLocalKmsRequest, ConfigureStaticKmsRequest, ConfigureVaultKmsRequest, ConfigureVaultTransitKmsRequest, KmsConfigSummary, KmsStatusResponse, StartKmsRequest, StartKmsResponse, StopKmsResponse, TagKeyRequest, TagKeyResponse, UntagKeyRequest, UntagKeyResponse, UpdateKeyDescriptionRequest, UpdateKeyDescriptionResponse, }; pub use audit::{KmsAuditOperation, KmsAuditOutcome, KmsAuditRecord, KmsAuditSink, redact_encryption_context}; pub use cache::KmsCacheStats; pub use config::*; pub use deletion_worker::DeletionReferenceChecker; pub use encryption::is_data_key_envelope; pub use error::{KmsError, KmsUnavailableError, Result}; pub use key_impact::{KeyImpactReport, KeyReference, KeyReferenceKind, ReferenceCompleteness, ReferenceCoverage, ReferenceScope}; pub use manager::KmsManager; pub use probe::{ProbeFailureKind, ProbeResult, ProbeStatus}; pub use service::{DataKey, ObjectEncryptionService}; pub use service_manager::{ KmsServiceManager, KmsServiceStatus, KmsStartOutcome, get_global_encryption_service, get_global_kms_service_manager, init_global_kms_service_manager, }; pub use types::*; // For backward compatibility - these functions now delegate to the service manager /// Initialize global encryption service (backward compatibility) /// /// This function is now deprecated. Use `init_global_kms_service_manager` and configure via API instead. #[deprecated(note = "Use dynamic KMS configuration via service manager instead")] pub async fn init_global_services(_service: ObjectEncryptionService) -> Result<()> { // For backward compatibility only - not recommended for new code Ok(()) } /// Check if the global encryption service is initialized and healthy pub async fn is_encryption_service_healthy() -> bool { match get_global_encryption_service().await { Some(service) => service.health_check().await.is_ok(), None => false, } } /// Shutdown the global encryption service (backward compatibility) #[deprecated(note = "Use service manager shutdown instead")] pub fn shutdown_global_services() { // For backward compatibility only - service manager handles shutdown now tracing::info!("KMS global services shutdown requested (deprecated)"); } #[cfg(test)] mod tests { use super::*; use std::sync::Arc; use tempfile::TempDir; #[tokio::test] async fn test_global_service_lifecycle() { // Test service manager initialization let manager = init_global_kms_service_manager(); // Test initial status let status = manager.get_status().await; assert_eq!(status, KmsServiceStatus::NotConfigured); // Test configuration and start let temp_dir = TempDir::new().expect("Failed to create temp dir"); let config = KmsConfig::local(temp_dir.path().to_path_buf()).with_insecure_development_defaults(); manager.configure(config).await.expect("Configuration should succeed"); manager.start().await.expect("Start should succeed"); // Test that encryption service is now available assert!(get_global_encryption_service().await.is_some()); // Test health check assert!(is_encryption_service_healthy().await); // Test stop manager.stop().await.expect("Stop should succeed"); } #[tokio::test] async fn test_versioned_service_reconfiguration() { // Test versioned service reconfiguration for zero-downtime let manager = KmsServiceManager::new(); // Initial state: no version assert!(manager.get_service_version().await.is_none()); // Start first service let temp_dir1 = TempDir::new().expect("Failed to create temp dir"); let config1 = KmsConfig::local(temp_dir1.path().to_path_buf()).with_insecure_development_defaults(); manager .configure(config1.clone()) .await .expect("Configuration should succeed"); manager.start().await.expect("Start should succeed"); // Verify version 1 let version1 = manager.get_service_version().await.expect("Service should have version"); assert_eq!(version1, 1); // Get service reference (simulating ongoing operation) let service1 = manager.get_encryption_service().await.expect("Service should be available"); // Reconfigure to new service (zero-downtime) let mut config2 = config1; config2.timeout = std::time::Duration::from_secs(45); manager.reconfigure(config2).await.expect("Reconfiguration should succeed"); // Verify version 2 let version2 = manager.get_service_version().await.expect("Service should have version"); assert_eq!(version2, 2); // Old service reference should still be valid (Arc keeps it alive) // New requests should get version 2 let service2 = manager.get_encryption_service().await.expect("Service should be available"); // Verify they are different instances assert!(!Arc::ptr_eq(&service1, &service2)); // Old service should still work (simulating long-running operation) // This demonstrates zero-downtime: old operations continue, new operations use new service assert!(service1.health_check().await.is_ok()); assert!(service2.health_check().await.is_ok()); } #[tokio::test] async fn test_concurrent_reconfiguration() { // Test that concurrent reconfiguration requests are serialized let manager = Arc::new(KmsServiceManager::new()); let temp_dir = TempDir::new().expect("Failed to create temp dir"); let base_path = temp_dir.path().to_path_buf(); // Initial configuration let config1 = KmsConfig::local(base_path.clone()).with_insecure_development_defaults(); manager.configure(config1).await.expect("Configuration should succeed"); manager.start().await.expect("Start should succeed"); // Spawn multiple concurrent reconfiguration requests let mut handles = Vec::new(); for _i in 0..5 { let manager_clone = manager.clone(); let path = base_path.clone(); let handle = tokio::spawn(async move { let config = KmsConfig::local(path).with_insecure_development_defaults(); manager_clone.reconfigure(config).await }); handles.push(handle); } // Wait for all reconfigurations to complete let mut results = Vec::new(); for handle in handles { results.push(handle.await); } // All should succeed (serialized by mutex) for result in results { assert!(result.expect("Task should complete").is_ok()); } // Final version should be 6 (1 initial + 5 reconfigurations) let final_version = manager.get_service_version().await.expect("Service should have version"); assert_eq!(final_version, 6); } }