From eb0248657428f9f11b23caa6e737acc041a8ec80 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Tue, 7 Jul 2026 04:29:24 +0800 Subject: [PATCH] chore(swift): remove placeholder SSE module (#4330) security(swift): remove placeholder SSE module (backlog#646) The Swift `encryption` module was a non-functional stub: `encrypt_data` returned the plaintext unchanged while labeling it AES-256-GCM in the object metadata, and `generate_iv` derived the IV from a timestamp rather than a CSPRNG. It had no production caller (only a `pub mod` declaration and one integration test), so wiring it in as-is would have silently shipped plaintext advertised as ciphertext. We are not supporting Swift server-side encryption for now, so remove the module outright rather than keep a dangerous stub around: - delete crates/protocols/src/swift/encryption.rs - drop `pub mod encryption;` from swift/mod.rs - remove the encryption case (and unused import) from the swift integration test The module reached main dubiously: it was introduced together with the whole Swift API in commit 86e93624 ("fix(heal): canonicalize scanner object-dir repairs (#3864)"), a 1665-file squash whose PR description only covered the heal change and never mentioned Swift or SSE. Verified: cargo fmt; cargo test -p rustfs-protocols --features swift --test swift_simple_integration (10 passed); arch guardrail scripts pass. --- crates/protocols/src/swift/encryption.rs | 523 ------------------ crates/protocols/src/swift/mod.rs | 1 - .../tests/swift_simple_integration.rs | 18 +- 3 files changed, 1 insertion(+), 541 deletions(-) delete mode 100644 crates/protocols/src/swift/encryption.rs diff --git a/crates/protocols/src/swift/encryption.rs b/crates/protocols/src/swift/encryption.rs deleted file mode 100644 index aef9354c6..000000000 --- a/crates/protocols/src/swift/encryption.rs +++ /dev/null @@ -1,523 +0,0 @@ -// 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. - -//! Server-Side Encryption Support for Swift API -//! -//! This module implements automatic server-side encryption for Swift objects, -//! providing encryption at rest with transparent encryption/decryption. -//! -//! # Encryption Algorithm -//! -//! Uses AES-256-GCM (Galois/Counter Mode) which provides: -//! - Confidentiality (AES-256 encryption) -//! - Authenticity (built-in authentication tag) -//! - Performance (hardware acceleration on modern CPUs) -//! -//! # Key Management -//! -//! Supports multiple key sources: -//! - Environment variable (SWIFT_ENCRYPTION_KEY) -//! - Configuration file -//! - External KMS (future: Barbican, AWS KMS, HashiCorp Vault) -//! -//! # Usage -//! -//! Encryption is transparent to clients: -//! -//! ```bash -//! # Objects automatically encrypted on upload -//! swift upload container file.txt -//! -//! # Automatically decrypted on download -//! swift download container file.txt -//! ``` -//! -//! # Metadata -//! -//! Encrypted objects include metadata: -//! - `X-Object-Meta-Crypto-Enabled: true` -//! - `X-Object-Meta-Crypto-Algorithm: AES-256-GCM` -//! - `X-Object-Meta-Crypto-Key-Id: ` -//! - `X-Object-Meta-Crypto-Iv: ` -//! -//! # Key Rotation -//! -//! Objects can be re-encrypted with new keys: -//! - Upload with new key ID -//! - Old encrypted objects remain readable with old keys -//! - Gradual migration to new keys - -use super::{SwiftError, SwiftResult}; -use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; -use std::collections::HashMap; -use tracing::{debug, warn}; - -const LOG_COMPONENT_PROTOCOLS: &str = "protocols"; -const LOG_SUBSYSTEM_SWIFT_ENCRYPTION: &str = "swift_encryption"; -const EVENT_SWIFT_ENCRYPTION_STATE: &str = "swift_encryption_state"; - -/// Encryption algorithm identifier -#[derive(Debug, Clone, PartialEq)] -pub enum EncryptionAlgorithm { - /// AES-256-GCM (recommended) - Aes256Gcm, - /// AES-256-CBC (legacy, less secure) - Aes256Cbc, -} - -impl EncryptionAlgorithm { - pub fn as_str(&self) -> &str { - match self { - EncryptionAlgorithm::Aes256Gcm => "AES-256-GCM", - EncryptionAlgorithm::Aes256Cbc => "AES-256-CBC", - } - } - - /// Parse encryption algorithm from string - /// - /// Note: This could implement `FromStr` trait, but returns `SwiftResult` instead of `Result` - #[allow(clippy::should_implement_trait)] - pub fn from_str(s: &str) -> SwiftResult { - match s { - "AES-256-GCM" => Ok(EncryptionAlgorithm::Aes256Gcm), - "AES-256-CBC" => Ok(EncryptionAlgorithm::Aes256Cbc), - _ => Err(SwiftError::BadRequest(format!("Unsupported encryption algorithm: {s}"))), - } - } -} - -/// Encryption configuration -#[derive(Debug, Clone)] -pub struct EncryptionConfig { - /// Whether encryption is enabled globally - pub enabled: bool, - - /// Default encryption algorithm - pub algorithm: EncryptionAlgorithm, - - /// Master encryption key ID - pub key_id: String, - - /// Master encryption key (32 bytes for AES-256) - pub key: Vec, -} - -impl EncryptionConfig { - /// Create new encryption configuration - pub fn new(enabled: bool, key_id: String, key: Vec) -> SwiftResult { - if enabled && key.len() != 32 { - return Err(SwiftError::BadRequest("Encryption key must be exactly 32 bytes for AES-256".to_string())); - } - - Ok(EncryptionConfig { - enabled, - algorithm: EncryptionAlgorithm::Aes256Gcm, - key_id, - key, - }) - } - - /// Load encryption config from environment - pub fn from_env() -> SwiftResult { - let enabled = std::env::var("SWIFT_ENCRYPTION_ENABLED") - .unwrap_or_else(|_| "false".to_string()) - .parse::() - .unwrap_or(false); - - if !enabled { - // Return disabled config with dummy key - return Ok(EncryptionConfig { - enabled: false, - algorithm: EncryptionAlgorithm::Aes256Gcm, - key_id: "disabled".to_string(), - key: vec![0u8; 32], - }); - } - - let key_id = std::env::var("SWIFT_ENCRYPTION_KEY_ID").unwrap_or_else(|_| "default".to_string()); - - let key_hex = std::env::var("SWIFT_ENCRYPTION_KEY") - .map_err(|_| SwiftError::InternalServerError("SWIFT_ENCRYPTION_KEY not set but encryption is enabled".to_string()))?; - - let key = hex::decode(&key_hex).map_err(|_| SwiftError::BadRequest("Invalid encryption key hex format".to_string()))?; - - Self::new(enabled, key_id, key) - } -} - -/// Encryption metadata stored with encrypted objects -#[derive(Debug, Clone)] -pub struct EncryptionMetadata { - /// Encryption algorithm used - pub algorithm: EncryptionAlgorithm, - - /// Key ID used for encryption - pub key_id: String, - - /// Initialization vector (base64 encoded) - pub iv: String, - - /// Authentication tag for AES-GCM (base64 encoded, optional for CBC) - pub auth_tag: Option, -} - -impl EncryptionMetadata { - /// Create new encryption metadata - pub fn new(algorithm: EncryptionAlgorithm, key_id: String, iv: Vec) -> Self { - EncryptionMetadata { - algorithm, - key_id, - iv: BASE64.encode(&iv), - auth_tag: None, - } - } - - /// Set authentication tag (for AES-GCM) - pub fn with_auth_tag(mut self, tag: Vec) -> Self { - self.auth_tag = Some(BASE64.encode(&tag)); - self - } - - /// Convert to HTTP headers for object metadata - pub fn to_headers(&self) -> HashMap { - let mut headers = HashMap::new(); - headers.insert("x-object-meta-crypto-enabled".to_string(), "true".to_string()); - headers.insert("x-object-meta-crypto-algorithm".to_string(), self.algorithm.as_str().to_string()); - headers.insert("x-object-meta-crypto-key-id".to_string(), self.key_id.clone()); - headers.insert("x-object-meta-crypto-iv".to_string(), self.iv.clone()); - - if let Some(tag) = &self.auth_tag { - headers.insert("x-object-meta-crypto-auth-tag".to_string(), tag.clone()); - } - - headers - } - - /// Parse from object metadata - pub fn from_metadata(metadata: &HashMap) -> SwiftResult> { - // Check if encryption is enabled - let enabled = metadata - .get("x-object-meta-crypto-enabled") - .map(|v| v == "true") - .unwrap_or(false); - - if !enabled { - return Ok(None); - } - - let algorithm_str = metadata - .get("x-object-meta-crypto-algorithm") - .ok_or_else(|| SwiftError::InternalServerError("Missing crypto algorithm metadata".to_string()))?; - - let algorithm = EncryptionAlgorithm::from_str(algorithm_str)?; - - let key_id = metadata - .get("x-object-meta-crypto-key-id") - .ok_or_else(|| SwiftError::InternalServerError("Missing crypto key ID metadata".to_string()))? - .clone(); - - let iv = metadata - .get("x-object-meta-crypto-iv") - .ok_or_else(|| SwiftError::InternalServerError("Missing crypto IV metadata".to_string()))? - .clone(); - - let auth_tag = metadata.get("x-object-meta-crypto-auth-tag").cloned(); - - Ok(Some(EncryptionMetadata { - algorithm, - key_id, - iv, - auth_tag, - })) - } - - /// Decode IV from base64 - pub fn decode_iv(&self) -> SwiftResult> { - BASE64 - .decode(&self.iv) - .map_err(|_| SwiftError::InternalServerError("Invalid IV base64 encoding".to_string())) - } - - /// Decode auth tag from base64 - pub fn decode_auth_tag(&self) -> SwiftResult>> { - match &self.auth_tag { - Some(tag) => { - let decoded = BASE64 - .decode(tag) - .map_err(|_| SwiftError::InternalServerError("Invalid auth tag base64 encoding".to_string()))?; - Ok(Some(decoded)) - } - None => Ok(None), - } - } -} - -/// Check if object should be encrypted based on configuration and headers -pub fn should_encrypt(config: &EncryptionConfig, headers: &http::HeaderMap) -> bool { - // Check if encryption is globally enabled - if !config.enabled { - return false; - } - - // Check if client explicitly disabled encryption - if let Some(disable) = headers.get("x-object-meta-crypto-disable") - && disable.to_str().unwrap_or("") == "true" - { - debug!( - event = EVENT_SWIFT_ENCRYPTION_STATE, - component = LOG_COMPONENT_PROTOCOLS, - subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION, - state = "disabled_by_client", - "swift encryption state changed" - ); - return false; - } - - // Encrypt by default if enabled - true -} - -/// Generate random initialization vector for encryption -/// -/// TODO: Integrate with proper random number generator -/// For now, uses a simple timestamp-based approach (NOT cryptographically secure!) -pub fn generate_iv(size: usize) -> Vec { - use std::time::{SystemTime, UNIX_EPOCH}; - - // WARNING: This is a placeholder! In production, use a proper CSPRNG - // like rand::thread_rng() or getrandom crate - let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos(); - - let mut iv = Vec::with_capacity(size); - let bytes = timestamp.to_le_bytes(); - - // Fill IV with timestamp bytes (repeated if necessary) - for i in 0..size { - iv.push(bytes[i % bytes.len()]); - } - - iv -} - -/// Placeholder for actual encryption (requires crypto crate integration) -/// -/// In production, this would use a proper crypto library like `aes-gcm` or `ring`. -/// This is a stub that demonstrates the API structure. -pub fn encrypt_data(data: &[u8], config: &EncryptionConfig) -> SwiftResult<(Vec, EncryptionMetadata)> { - debug!( - event = EVENT_SWIFT_ENCRYPTION_STATE, - component = LOG_COMPONENT_PROTOCOLS, - subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION, - state = "encrypting", - input_bytes = data.len(), - algorithm = config.algorithm.as_str(), - "swift encryption state changed" - ); - - // Generate IV (12 bytes for GCM, 16 bytes for CBC) - let iv_size = match config.algorithm { - EncryptionAlgorithm::Aes256Gcm => 12, - EncryptionAlgorithm::Aes256Cbc => 16, - }; - let iv = generate_iv(iv_size); - - // TODO: Implement actual encryption - // For now, return unencrypted data with metadata - // In production, integrate with aes-gcm crate: - // - // use aes_gcm::{Aes256Gcm, Key, Nonce}; - // use aes_gcm::aead::{Aead, KeyInit}; - // - // let key = Key::::from_slice(&config.key); - // let cipher = Aes256Gcm::new(key); - // let nonce = Nonce::from_slice(&iv); - // let ciphertext = cipher.encrypt(nonce, data) - // .map_err(|e| SwiftError::InternalServerError(format!("Encryption failed: {}", e)))?; - - warn!( - event = EVENT_SWIFT_ENCRYPTION_STATE, - component = LOG_COMPONENT_PROTOCOLS, - subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION, - result = "not_implemented", - mode = "encrypt", - "swift encryption state changed" - ); - - let metadata = EncryptionMetadata::new(config.algorithm.clone(), config.key_id.clone(), iv); - - // In production, return ciphertext - Ok((data.to_vec(), metadata)) -} - -/// Placeholder for actual decryption (requires crypto crate integration) -/// -/// In production, this would use a proper crypto library like `aes-gcm` or `ring`. -/// This is a stub that demonstrates the API structure. -pub fn decrypt_data(encrypted_data: &[u8], metadata: &EncryptionMetadata, config: &EncryptionConfig) -> SwiftResult> { - debug!( - event = EVENT_SWIFT_ENCRYPTION_STATE, - component = LOG_COMPONENT_PROTOCOLS, - subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION, - state = "decrypting", - input_bytes = encrypted_data.len(), - algorithm = metadata.algorithm.as_str(), - "swift encryption state changed" - ); - - // Verify key ID matches - if metadata.key_id != config.key_id { - return Err(SwiftError::InternalServerError(format!( - "Key ID mismatch: object encrypted with '{}', but current key is '{}'", - metadata.key_id, config.key_id - ))); - } - - // In production, integrate with aes-gcm crate: - // - // use aes_gcm::{Aes256Gcm, Key, Nonce}; - // use aes_gcm::aead::{Aead, KeyInit}; - // - // let key = Key::::from_slice(&config.key); - // let cipher = Aes256Gcm::new(key); - // let nonce = Nonce::from_slice(&iv); - // let plaintext = cipher.decrypt(nonce, encrypted_data) - // .map_err(|e| SwiftError::InternalServerError(format!("Decryption failed: {}", e)))?; - - warn!( - event = EVENT_SWIFT_ENCRYPTION_STATE, - component = LOG_COMPONENT_PROTOCOLS, - subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION, - result = "not_implemented", - mode = "decrypt", - "swift encryption state changed" - ); - - // In production, return plaintext - Ok(encrypted_data.to_vec()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_encryption_config_creation() { - let key = vec![0u8; 32]; // 32 bytes for AES-256 - let config = EncryptionConfig::new(true, "test-key".to_string(), key).unwrap(); - - assert!(config.enabled); - assert_eq!(config.key_id, "test-key"); - assert_eq!(config.key.len(), 32); - } - - #[test] - fn test_encryption_config_invalid_key_size() { - let key = vec![0u8; 16]; // Too short - let result = EncryptionConfig::new(true, "test-key".to_string(), key); - - assert!(result.is_err()); - } - - #[test] - fn test_encryption_algorithm_conversion() { - assert_eq!(EncryptionAlgorithm::Aes256Gcm.as_str(), "AES-256-GCM"); - assert_eq!(EncryptionAlgorithm::Aes256Cbc.as_str(), "AES-256-CBC"); - - assert!(EncryptionAlgorithm::from_str("AES-256-GCM").is_ok()); - assert!(EncryptionAlgorithm::from_str("AES-256-CBC").is_ok()); - assert!(EncryptionAlgorithm::from_str("INVALID").is_err()); - } - - #[test] - fn test_encryption_metadata_to_headers() { - let metadata = EncryptionMetadata::new( - EncryptionAlgorithm::Aes256Gcm, - "test-key".to_string(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - ); - - let headers = metadata.to_headers(); - - assert_eq!(headers.get("x-object-meta-crypto-enabled"), Some(&"true".to_string())); - assert_eq!(headers.get("x-object-meta-crypto-algorithm"), Some(&"AES-256-GCM".to_string())); - assert_eq!(headers.get("x-object-meta-crypto-key-id"), Some(&"test-key".to_string())); - assert!(headers.contains_key("x-object-meta-crypto-iv")); - } - - #[test] - fn test_encryption_metadata_from_metadata() { - let mut metadata_map = HashMap::new(); - metadata_map.insert("x-object-meta-crypto-enabled".to_string(), "true".to_string()); - metadata_map.insert("x-object-meta-crypto-algorithm".to_string(), "AES-256-GCM".to_string()); - metadata_map.insert("x-object-meta-crypto-key-id".to_string(), "test-key".to_string()); - metadata_map.insert( - "x-object-meta-crypto-iv".to_string(), - BASE64.encode([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), - ); - - let metadata = EncryptionMetadata::from_metadata(&metadata_map).unwrap(); - - assert!(metadata.is_some()); - let metadata = metadata.unwrap(); - assert_eq!(metadata.algorithm, EncryptionAlgorithm::Aes256Gcm); - assert_eq!(metadata.key_id, "test-key"); - } - - #[test] - fn test_encryption_metadata_from_metadata_not_encrypted() { - let metadata_map = HashMap::new(); - let result = EncryptionMetadata::from_metadata(&metadata_map).unwrap(); - - assert!(result.is_none()); - } - - #[test] - fn test_should_encrypt() { - let key = vec![0u8; 32]; - let config = EncryptionConfig::new(true, "test".to_string(), key).unwrap(); - - let headers = http::HeaderMap::new(); - assert!(should_encrypt(&config, &headers)); - - // Test with disabled config - let disabled_config = EncryptionConfig::new(false, "test".to_string(), vec![0u8; 32]).unwrap(); - assert!(!should_encrypt(&disabled_config, &headers)); - } - - #[test] - fn test_generate_iv() { - let iv1 = generate_iv(12); - std::thread::sleep(std::time::Duration::from_nanos(1)); // Ensure timestamp changes - let iv2 = generate_iv(12); - - assert_eq!(iv1.len(), 12); - assert_eq!(iv2.len(), 12); - // IVs should be different (random) - // Note: This uses a placeholder implementation. In production, use proper CSPRNG. - assert_ne!(iv1, iv2); - } - - #[test] - fn test_encrypt_decrypt_roundtrip() { - let key = vec![0u8; 32]; - let config = EncryptionConfig::new(true, "test-key".to_string(), key).unwrap(); - - let plaintext = b"Hello, World!"; - let (ciphertext, metadata) = encrypt_data(plaintext, &config).unwrap(); - - let decrypted = decrypt_data(&ciphertext, &metadata, &config).unwrap(); - - assert_eq!(decrypted, plaintext); - } -} diff --git a/crates/protocols/src/swift/mod.rs b/crates/protocols/src/swift/mod.rs index 3e12f1b6b..03f908f79 100644 --- a/crates/protocols/src/swift/mod.rs +++ b/crates/protocols/src/swift/mod.rs @@ -39,7 +39,6 @@ pub mod bulk; pub mod container; pub mod cors; pub mod dlo; -pub mod encryption; pub mod errors; pub mod expiration; pub mod expiration_worker; diff --git a/crates/protocols/tests/swift_simple_integration.rs b/crates/protocols/tests/swift_simple_integration.rs index 85ccbcf42..783587bc1 100644 --- a/crates/protocols/tests/swift_simple_integration.rs +++ b/crates/protocols/tests/swift_simple_integration.rs @@ -16,25 +16,9 @@ #![cfg(feature = "swift")] -use rustfs_protocols::swift::{encryption, quota, ratelimit, slo, symlink, sync, tempurl, versioning}; +use rustfs_protocols::swift::{quota, ratelimit, slo, symlink, sync, tempurl, versioning}; use std::collections::HashMap; -/// Test that encryption metadata can coexist with user metadata -#[test] -fn test_encryption_with_user_metadata() { - let key = vec![0u8; 32]; - let config = encryption::EncryptionConfig::new(true, "test-key".to_string(), key).unwrap(); - - let plaintext = b"Sensitive data"; - let (_ciphertext, enc_metadata) = encryption::encrypt_data(plaintext, &config).unwrap(); - - let mut all_metadata = enc_metadata.to_headers(); - all_metadata.insert("x-object-meta-author".to_string(), "alice".to_string()); - - assert_eq!(all_metadata.get("x-object-meta-crypto-enabled"), Some(&"true".to_string())); - assert_eq!(all_metadata.get("x-object-meta-author"), Some(&"alice".to_string())); -} - /// Test sync configuration parsing #[test] fn test_sync_config_parsing() {