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.
This commit is contained in:
Zhengchao An
2026-07-07 04:29:24 +08:00
committed by GitHub
parent 3bc8d79fe5
commit eb02486574
3 changed files with 1 additions and 541 deletions
-523
View File
@@ -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: <key_id>`
//! - `X-Object-Meta-Crypto-Iv: <base64_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<Self, ParseError>`
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> SwiftResult<Self> {
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<u8>,
}
impl EncryptionConfig {
/// Create new encryption configuration
pub fn new(enabled: bool, key_id: String, key: Vec<u8>) -> SwiftResult<Self> {
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<Self> {
let enabled = std::env::var("SWIFT_ENCRYPTION_ENABLED")
.unwrap_or_else(|_| "false".to_string())
.parse::<bool>()
.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<String>,
}
impl EncryptionMetadata {
/// Create new encryption metadata
pub fn new(algorithm: EncryptionAlgorithm, key_id: String, iv: Vec<u8>) -> 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<u8>) -> Self {
self.auth_tag = Some(BASE64.encode(&tag));
self
}
/// Convert to HTTP headers for object metadata
pub fn to_headers(&self) -> HashMap<String, String> {
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<String, String>) -> SwiftResult<Option<Self>> {
// 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<Vec<u8>> {
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<Option<Vec<u8>>> {
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<u8> {
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<u8>, 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::<Aes256Gcm>::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<Vec<u8>> {
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::<Aes256Gcm>::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);
}
}
-1
View File
@@ -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;
@@ -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() {