diff --git a/crates/credentials/src/credentials.rs b/crates/credentials/src/credentials.rs index db1069f95..e70698907 100644 --- a/crates/credentials/src/credentials.rs +++ b/crates/credentials/src/credentials.rs @@ -243,7 +243,9 @@ fn derive_rpc_secret(access_key: &str, secret_key: &str) -> Option { return None; } - let mut mac = ::new_from_slice(secret_key.as_bytes()).expect("HMAC can take key of any size"); + let Ok(mut mac) = ::new_from_slice(secret_key.as_bytes()) else { + return None; + }; mac.update(RPC_SECRET_DERIVATION_CONTEXT); mac.update(&[0]); mac.update(access_key.as_bytes()); @@ -284,7 +286,7 @@ pub fn try_get_rpc_token() -> std::io::Result { #[deprecated(note = "use try_get_rpc_token to handle missing RPC secrets explicitly")] pub fn get_rpc_token() -> String { - try_get_rpc_token().expect(RPC_SECRET_REQUIRED_MESSAGE) + try_get_rpc_token().unwrap_or_default() } /// A wrapper struct for masking sensitive strings in Debug implementations. @@ -596,6 +598,15 @@ mod tests { assert_string_return(get_rpc_token); } + #[allow(deprecated)] + #[test] + fn test_get_rpc_token_matches_fallible_api_contract() { + match try_get_rpc_token() { + Ok(secret) => assert_eq!(get_rpc_token(), secret), + Err(_) => assert_eq!(get_rpc_token(), ""), + } + } + #[test] fn test_resolve_rpc_secret_accepts_non_default_secret() { assert_eq!( diff --git a/crates/policy/src/policy/opa.rs b/crates/policy/src/policy/opa.rs index cfa122c3b..5c90112dd 100644 --- a/crates/policy/src/policy/opa.rs +++ b/crates/policy/src/policy/opa.rs @@ -36,7 +36,25 @@ pub struct AuthZPlugin { args: Args, } -fn check() -> Result<(), String> { +#[derive(Debug, thiserror::Error)] +pub enum OpaConfigError { + #[error("Missing required env var: {0}")] + MissingRequiredEnv(&'static str), + #[error("Invalid env vars: {0:?}")] + InvalidEnvVars(HashMap), + #[error("Error getting env var {name}: {source:?}")] + EnvRead { + name: &'static str, + #[source] + source: env::VarError, + }, + #[error("OPA returned an error: {0}")] + InvalidStatus(reqwest::StatusCode), + #[error("Error connecting to OPA: {0}")] + Connection(reqwest::Error), +} + +fn check() -> Result<(), OpaConfigError> { let env_list = env::vars(); let mut candidate = HashMap::new(); let prefix = format!("{ENV_PREFIX}{POLICY_PLUGIN_SUB_SYS}").to_uppercase(); @@ -48,17 +66,17 @@ fn check() -> Result<(), String> { //check required env vars if candidate.remove(ENV_POLICY_PLUGIN_OPA_URL).is_none() { - return Err(format!("Missing required env var: {ENV_POLICY_PLUGIN_OPA_URL}")); + return Err(OpaConfigError::MissingRequiredEnv(ENV_POLICY_PLUGIN_OPA_URL)); } // check optional env vars candidate.remove(ENV_POLICY_PLUGIN_AUTH_TOKEN); if !candidate.is_empty() { - return Err(format!("Invalid env vars: {candidate:?}")); + return Err(OpaConfigError::InvalidEnvVars(candidate)); } Ok(()) } -async fn validate(config: &Args) -> Result<(), String> { +async fn validate(config: &Args) -> Result<(), OpaConfigError> { let client = reqwest::Client::new(); match client.post(&config.url).send().await { @@ -68,31 +86,34 @@ async fn validate(config: &Args) -> Result<(), String> { info!("OPA is ready to accept requests."); } _ => { - return Err(format!("OPA returned an error: {}", resp.status())); + return Err(OpaConfigError::InvalidStatus(resp.status())); } }; } Err(err) => { - return Err(format!("Error connecting to OPA: {err}")); + return Err(OpaConfigError::Connection(err)); } }; Ok(()) } -pub async fn lookup_config() -> Result { +pub async fn lookup_config() -> Result { let args = Args::default(); - let get_cfg = - |cfg: &str| -> Result { env::var(cfg).map_err(|e| format!("Error getting env var {cfg}: {e:?}")) }; + let get_cfg = |cfg: &'static str| -> Result { + env::var(cfg).map_err(|source| OpaConfigError::EnvRead { name: cfg, source }) + }; - let url = get_cfg(ENV_POLICY_PLUGIN_OPA_URL); - if url.is_err() { - info!("OPA is not enabled."); - return Ok(args); - } + let url = match get_cfg(ENV_POLICY_PLUGIN_OPA_URL) { + Ok(url) => url, + Err(_) => { + info!("OPA is not enabled."); + return Ok(args); + } + }; check()?; let args = Args { - url: url.ok().unwrap(), + url, auth_token: get_cfg(ENV_POLICY_PLUGIN_AUTH_TOKEN).unwrap_or_default(), }; validate(&args).await?; @@ -111,7 +132,10 @@ impl AuthZPlugin { .http2_keep_alive_interval(Some(Duration::from_secs(30))) .http2_keep_alive_timeout(Duration::from_secs(15)) .build() - .unwrap(); + .unwrap_or_else(|err| { + error!("failed to build OPA HTTP client, falling back to default reqwest client: {err}"); + reqwest::Client::new() + }); Self { client, args: config } } @@ -235,7 +259,7 @@ mod tests { temp_env::with_var("RUSTFS_POLICY_PLUGIN_AUTH_TOKEN", Some("test-token"), || { let result = check(); assert!(result.is_err()); - assert!(result.unwrap_err().contains("Missing required env var")); + assert!(matches!(result.unwrap_err(), OpaConfigError::MissingRequiredEnv(_))); }); }); } @@ -250,7 +274,7 @@ mod tests { || { let result = check(); assert!(result.is_err()); - assert!(result.unwrap_err().contains("Invalid env vars")); + assert!(matches!(result.unwrap_err(), OpaConfigError::InvalidEnvVars(_))); }, ); } @@ -270,6 +294,21 @@ mod tests { }); } + #[test] + fn test_lookup_config_uses_env_url_without_unwrap_path() { + temp_env::with_vars( + [ + ("RUSTFS_POLICY_PLUGIN_URL", Some("http://localhost:8181")), + ("RUSTFS_POLICY_PLUGIN_AUTH_TOKEN", Some("token")), + ], + || { + let rt = tokio::runtime::Runtime::new().unwrap(); + let result = rt.block_on(async { lookup_config().await }); + assert!(result.is_err(), "lookup should fail validation without panicking when OPA is unreachable"); + }, + ); + } + #[test] fn test_args_enable() { // Test Args enable method diff --git a/crates/trusted-proxies/src/global.rs b/crates/trusted-proxies/src/global.rs index 7d0e7033e..752c759d0 100644 --- a/crates/trusted-proxies/src/global.rs +++ b/crates/trusted-proxies/src/global.rs @@ -31,9 +31,30 @@ static METRICS: OnceLock> = OnceLock::new(); /// Global instance of the trusted proxy layer. static PROXY_LAYER: OnceLock = OnceLock::new(); +/// Disabled fallback layer used when legacy trusted proxies are not enabled. +static DISABLED_PROXY_LAYER: OnceLock = OnceLock::new(); + /// Global flag indicating if the trusted proxy middleware is enabled. static ENABLED: OnceLock = OnceLock::new(); +fn load_config() -> &'static Arc { + CONFIG.get_or_init(|| Arc::new(ConfigLoader::from_env_or_default())) +} + +fn load_metrics(config: &AppConfig, enabled: bool) -> &'static Option { + METRICS.get_or_init(|| { + if config.monitoring.metrics_enabled { + Some(default_proxy_metrics(enabled)) + } else { + None + } + }) +} + +fn disabled_layer() -> &'static LegacyTrustedProxyLayer { + DISABLED_PROXY_LAYER.get_or_init(LegacyTrustedProxyLayer::disabled) +} + /// Initializes the global trusted proxy system. /// /// This function should be called once at the start of the application. @@ -54,24 +75,11 @@ pub fn init() { return; } - let config = CONFIG.get_or_init(|| Arc::new(ConfigLoader::from_env_or_default())).clone(); + let config = load_config().clone(); + let metrics = load_metrics(&config, enabled).clone(); - METRICS.get_or_init(|| { - if config.monitoring.metrics_enabled { - Some(default_proxy_metrics(enabled)) - } else { - None - } - }); - - PROXY_LAYER.get_or_init(|| { - LegacyTrustedProxyLayer::with_cache_config( - config.proxy.clone(), - config.cache.clone(), - METRICS.get().and_then(|m| m.clone()), - enabled, - ) - }); + PROXY_LAYER + .get_or_init(|| LegacyTrustedProxyLayer::with_cache_config(config.proxy.clone(), config.cache.clone(), metrics, enabled)); tracing::info!( event = "trusted_proxies.lifecycle", @@ -90,25 +98,27 @@ pub fn init() { /// Returns a reference to the global trusted proxy layer. /// /// This layer can be used to wrap Axum services or other Tower-compatible services. -/// -/// # Panics -/// -/// Panics if `init()` has not been called. pub fn layer() -> &'static LegacyTrustedProxyLayer { - PROXY_LAYER - .get() - .expect("Trusted proxy system not initialized. Call init() first.") + if let Some(layer) = PROXY_LAYER.get() { + return layer; + } + + init(); + + if let Some(layer) = PROXY_LAYER.get() { + return layer; + } + + disabled_layer() } /// Returns a reference to the global configuration. -/// -/// # Panics -/// -/// Panics if `init()` has not been called. pub fn config() -> &'static AppConfig { - CONFIG - .get() - .expect("Trusted proxy system not initialized. Call init() first.") + if CONFIG.get().is_none() { + init(); + } + + load_config().as_ref() } /// Returns a reference to the global metrics collector, if enabled. @@ -120,3 +130,23 @@ pub fn metrics() -> Option<&'static ProxyMetrics> { pub fn is_enabled() -> bool { *ENABLED.get_or_init(|| rustfs_utils::get_env_bool(ENV_TRUSTED_PROXY_ENABLED, DEFAULT_TRUSTED_PROXY_ENABLED)) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn legacy_layer_is_available_without_explicit_init() { + let layer = layer(); + assert_eq!(layer.is_enabled(), is_enabled()); + } + + #[test] + fn legacy_config_is_available_without_explicit_init() { + let expected = ConfigLoader::from_env_or_default(); + let config = config(); + + assert_eq!(config.server_addr, expected.server_addr); + assert_eq!(config.monitoring.metrics_enabled, expected.monitoring.metrics_enabled); + } +} diff --git a/rustfs/src/app/context.rs b/rustfs/src/app/context.rs index 8b1a48cbd..79ad92a22 100644 --- a/rustfs/src/app/context.rs +++ b/rustfs/src/app/context.rs @@ -508,7 +508,7 @@ mod tests { impl BootTimeInterface for TestBootTimeInterface { fn get(&self) -> Option { - self.boot_time.clone() + self.boot_time } } diff --git a/rustfs/src/app/multipart_usecase.rs b/rustfs/src/app/multipart_usecase.rs index efb822ade..8dae799e3 100644 --- a/rustfs/src/app/multipart_usecase.rs +++ b/rustfs/src/app/multipart_usecase.rs @@ -655,7 +655,7 @@ impl DefaultMultipartUsecase { let server_side_encryption = Some(material.server_side_encryption.clone()); let ssekms_key_id = material.kms_key_id.clone(); - let mut encryption_metadata = encryption_material_to_metadata(&material); + let mut encryption_metadata = encryption_material_to_metadata(&material)?; if material.key_kind == crate::storage::sse::EncryptionKeyKind::Object { mark_encrypted_multipart_metadata(&mut encryption_metadata); } @@ -1473,7 +1473,8 @@ mod tests { .await .expect("prepare multipart encryption") .expect("managed multipart session material"); - let mut session_metadata = encryption_material_to_metadata(&session_material); + let mut session_metadata = + encryption_material_to_metadata(&session_material).expect("multipart session metadata should be generated"); mark_encrypted_multipart_metadata(&mut session_metadata); let part_one_plaintext = vec![0x31; rustfs_rio::DEFAULT_ENCRYPTION_BLOCK_SIZE + 23]; diff --git a/rustfs/src/app/object_usecase.rs b/rustfs/src/app/object_usecase.rs index d983fc2d1..10b148794 100644 --- a/rustfs/src/app/object_usecase.rs +++ b/rustfs/src/app/object_usecase.rs @@ -2298,7 +2298,7 @@ impl DefaultObjectUsecase { write_plan = write_plan.with_encryption(material.write_encryption(None)); - let encryption_metadata = encryption_material_to_metadata(&material); + let encryption_metadata = encryption_material_to_metadata(&material)?; metadata.extend(encryption_metadata.clone()); opts.user_defined.extend(encryption_metadata); } @@ -3333,7 +3333,7 @@ impl DefaultObjectUsecase { write_plan = write_plan.with_encryption(material.write_encryption(None)); - user_defined.extend(encryption_material_to_metadata(&material)); + user_defined.extend(encryption_material_to_metadata(&material)?); } reader = write_plan.apply(reader, actual_size).map_err(ApiError::from)?; @@ -4875,7 +4875,7 @@ impl DefaultObjectUsecase { write_plan = write_plan.with_encryption(material.write_encryption(None)); - let encryption_metadata = encryption_material_to_metadata(&material); + let encryption_metadata = encryption_material_to_metadata(&material)?; metadata.extend(encryption_metadata.clone()); opts.user_defined.extend(encryption_metadata); } diff --git a/rustfs/src/storage/ecfs_extend.rs b/rustfs/src/storage/ecfs_extend.rs index 687590c1e..3c0acbf17 100644 --- a/rustfs/src/storage/ecfs_extend.rs +++ b/rustfs/src/storage/ecfs_extend.rs @@ -381,7 +381,12 @@ pub(crate) fn parse_object_lock_retention(retention: Option "The retain until date must be in the future".to_string(), )); } - retain_until.format(&Rfc3339).unwrap() + retain_until.format(&Rfc3339).map_err(|_| { + S3Error::with_message( + S3ErrorCode::InvalidArgument, + "The retain until date is not a supported RFC3339 timestamp".to_string(), + ) + })? } else { String::default() }; diff --git a/rustfs/src/storage/ecfs_test.rs b/rustfs/src/storage/ecfs_test.rs index 46dc89967..d558cfc9b 100644 --- a/rustfs/src/storage/ecfs_test.rs +++ b/rustfs/src/storage/ecfs_test.rs @@ -829,6 +829,7 @@ mod tests { #[test] fn test_parse_object_lock_retention() { use time::macros::datetime; + use time::{OffsetDateTime, UtcOffset}; // [1] Normal case: No retention specified (empty metadata) assert!(parse_object_lock_retention(None).is_ok()); assert!(parse_object_lock_retention(None).unwrap().is_empty()); @@ -894,6 +895,19 @@ mod tests { let err = parse_object_lock_retention(Some(past_date_retention)).unwrap_err(); assert_eq!(err.code().as_str(), S3ErrorCode::InvalidArgument.as_str()); assert_eq!(err.message(), Some("The retain until date must be in the future")); + + // [8] Error case: timestamps that cannot be serialized as RFC3339 should return InvalidArgument instead of panicking + let unsupported_rfc3339_date = datetime!(2030-01-01 00:00:00 UTC) + .to_offset(UtcOffset::from_hms(5, 30, 45).expect("offset with seconds should be constructible")); + assert!(unsupported_rfc3339_date > OffsetDateTime::now_utc()); + + let unsupported_date_retention = ObjectLockRetention { + mode: Some(ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::COMPLIANCE)), + retain_until_date: Some(unsupported_rfc3339_date.into()), + }; + let err = parse_object_lock_retention(Some(unsupported_date_retention)).unwrap_err(); + assert_eq!(err.code().as_str(), S3ErrorCode::InvalidArgument.as_str()); + assert_eq!(err.message(), Some("The retain until date is not a supported RFC3339 timestamp")); } #[test] diff --git a/rustfs/src/storage/sse.rs b/rustfs/src/storage/sse.rs index deacdff61..fb351b9f5 100644 --- a/rustfs/src/storage/sse.rs +++ b/rustfs/src/storage/sse.rs @@ -52,7 +52,7 @@ //! }; //! //! if let Some(material) = sse_encryption(request).await? { -//! metadata.extend(encryption_material_to_metadata(&material)); +//! metadata.extend(encryption_material_to_metadata(&material)?); //! } //! //! // Unified decryption API @@ -250,9 +250,9 @@ async fn prepare_sse_configuration( }) }) }); - if effective_sse.is_none() { + let Some(effective_sse) = effective_sse else { return Ok(None); - } + }; debug!( component = LOG_COMPONENT_STORAGE, @@ -264,7 +264,7 @@ async fn prepare_sse_configuration( "Resolved effective SSE configuration" ); - let effective_kms_key_id = resolve_effective_kms_key_id(effective_sse.as_ref(), ssekms_key_id, || { + let effective_kms_key_id = resolve_effective_kms_key_id(Some(&effective_sse), ssekms_key_id, || { bucket_sse_config.rules.first().and_then(|rule| { rule.apply_server_side_encryption_by_default .as_ref() @@ -273,7 +273,7 @@ async fn prepare_sse_configuration( }); Ok(Some(SseConfiguration { - effective_sse: effective_sse.unwrap(), + effective_sse, effective_kms_key_id, })) } else if let Err(e) = bucket_sse_config_result { @@ -867,17 +867,18 @@ fn canonical_sse_path(bucket: &str, object: &str) -> String { } #[cfg(feature = "rio-v2")] -fn derive_object_key(external_key: [u8; 32]) -> [u8; 32] { +fn derive_object_key(external_key: [u8; 32]) -> Result<[u8; 32], ApiError> { let mut random = [0u8; 32]; rand::rng().fill(&mut random); - let mut mac = HmacSha256::new_from_slice(&external_key).expect("32-byte HMAC key"); + let mut mac = HmacSha256::new_from_slice(&external_key) + .map_err(|err| ApiError::from(StorageError::other(format!("Invalid HMAC key for object-key derivation: {err}"))))?; mac.update(OBJECT_KEY_DERIVATION_CONTEXT); mac.update(&random); let mut object_key = [0u8; 32]; object_key.copy_from_slice(mac.finalize().into_bytes().as_slice()); - object_key + Ok(object_key) } #[cfg(feature = "rio-v2")] @@ -887,8 +888,9 @@ fn derive_sealing_key( domain: &str, bucket: &str, object: &str, -) -> [u8; 32] { - let mut mac = HmacSha256::new_from_slice(&external_key).expect("32-byte HMAC key"); +) -> Result<[u8; 32], ApiError> { + let mut mac = HmacSha256::new_from_slice(&external_key) + .map_err(|err| ApiError::from(StorageError::other(format!("Invalid HMAC key for sealing-key derivation: {err}"))))?; mac.update(&iv); mac.update(domain.as_bytes()); mac.update(MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM.as_bytes()); @@ -896,7 +898,7 @@ fn derive_sealing_key( let mut sealing_key = [0u8; 32]; sealing_key.copy_from_slice(mac.finalize().into_bytes().as_slice()); - sealing_key + Ok(sealing_key) } #[cfg(feature = "rio-v2")] @@ -909,7 +911,7 @@ fn seal_object_key( ) -> Result { let mut iv = [0u8; SEALED_KEY_IV_SIZE]; rand::rng().fill(&mut iv); - let sealing_key = derive_sealing_key(external_key, iv, managed_sse_domain(sse_type), bucket, object); + let sealing_key = derive_sealing_key(external_key, iv, managed_sse_domain(sse_type), bucket, object)?; let mut header = [0u8; DARE_HEADER_SIZE]; header[0] = DARE_VERSION_20; @@ -957,7 +959,7 @@ fn unseal_object_key( return Err(ApiError::from(StorageError::other("Invalid sealed object-key payload header"))); } - let sealing_key = derive_sealing_key(external_key, sealed.iv, managed_sse_domain(sse_type), bucket, object); + let sealing_key = derive_sealing_key(external_key, sealed.iv, managed_sse_domain(sse_type), bucket, object)?; let plaintext = decrypt_sealed_object_key_payload(sealing_key, header, &sealed.sealed_key)?; let object_key: [u8; 32] = plaintext @@ -1017,7 +1019,7 @@ pub(crate) fn build_ssec_read_headers( headers } -pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> HashMap { +pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> Result, ApiError> { let mut metadata = HashMap::new(); match material.sse_type { @@ -1054,7 +1056,7 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> HashMap let encrypted_data_key = material .encrypted_data_key .as_deref() - .expect("managed SSE materials must carry an encrypted data key"); + .ok_or_else(|| ApiError::from(StorageError::other("managed SSE materials must carry an encrypted data key")))?; metadata.insert( "x-amz-server-side-encryption".to_string(), material.server_side_encryption.as_str().to_string(), @@ -1149,7 +1151,7 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> HashMap } } - metadata + Ok(metadata) } // ============================================================================ @@ -1374,7 +1376,7 @@ async fn apply_ssec_prepare_encryption_material( key: sse_key, key_md5: sse_key_md5.clone(), })?; - let object_key = derive_object_key(validated.key_bytes); + let object_key = derive_object_key(validated.key_bytes)?; let sealed_key = seal_object_key(object_key, validated.key_bytes, SSEType::SseC, bucket, key)?; (object_key, [0; 12], EncryptionKeyKind::Object, Some(sealed_key)) } else { @@ -1421,7 +1423,7 @@ async fn apply_ssec_encryption_material( #[cfg(feature = "rio-v2")] let (key_bytes, base_nonce, key_kind, managed_sealed_key) = { - let object_key = derive_object_key(validated.key_bytes); + let object_key = derive_object_key(validated.key_bytes)?; let sealed_key = seal_object_key(object_key, validated.key_bytes, SSEType::SseC, bucket, key)?; (object_key, [0; 12], EncryptionKeyKind::Object, Some(sealed_key)) }; @@ -1557,7 +1559,7 @@ async fn apply_managed_encryption_material( let algorithm = server_side_encryption.as_str().to_string(); #[cfg(feature = "rio-v2")] let (key_bytes, base_nonce, key_kind, managed_sealed_key) = { - let object_key = derive_object_key(data_key.plaintext_key); + let object_key = derive_object_key(data_key.plaintext_key)?; let sealed_key = seal_object_key(object_key, data_key.plaintext_key, encryption_type, bucket, key)?; (object_key, [0u8; 12], EncryptionKeyKind::Object, Some(sealed_key)) }; @@ -2685,7 +2687,8 @@ mod tests { .await .expect("prepare should accept ssec headers"); assert!(material.is_some()); - let metadata = encryption_material_to_metadata(&material.expect("ssec metadata should be generated")); + let metadata = encryption_material_to_metadata(&material.expect("ssec metadata should be generated")) + .expect("ssec metadata should be generated"); assert_eq!(metadata.get("x-amz-server-side-encryption").unwrap(), "AES256"); assert_eq!(metadata.get("x-amz-server-side-encryption-customer-algorithm").unwrap(), "AES256"); } @@ -2716,7 +2719,7 @@ mod tests { .expect("ssec metadata should be generated"); assert_eq!(material.key_kind, EncryptionKeyKind::Object); - let metadata = encryption_material_to_metadata(&material); + let metadata = encryption_material_to_metadata(&material).expect("ssec metadata should serialize"); assert!(metadata.contains_key(MINIO_INTERNAL_ENCRYPTION_IV_HEADER)); assert!(metadata.contains_key(MINIO_INTERNAL_ENCRYPTION_SSEC_SEALED_KEY_HEADER)); } @@ -2736,7 +2739,8 @@ mod tests { key_kind: EncryptionKeyKind::Direct, managed_kms_context: None, managed_sealed_key: None, - }); + }) + .expect("ssec original-size metadata should serialize"); assert_eq!(metadata.get(SSEC_ORIGINAL_SIZE_HEADER).map(String::as_str), Some("1024")); } @@ -2842,7 +2846,8 @@ mod tests { key_kind: EncryptionKeyKind::Direct, managed_kms_context: None, managed_sealed_key: None, - }); + }) + .expect("managed SSE metadata should serialize"); assert_eq!(metadata.get("x-amz-server-side-encryption").map(String::as_str), Some("aws:kms")); assert_eq!( @@ -2900,7 +2905,7 @@ mod tests { .await .expect("sse-kms encryption") .expect("managed sse-kms material"); - let metadata = encryption_material_to_metadata(&material); + let metadata = encryption_material_to_metadata(&material).expect("kms metadata should serialize"); let encoded_context = metadata .get(MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER) .expect("minio kms context header should exist"); @@ -2963,7 +2968,8 @@ mod tests { key_kind: EncryptionKeyKind::Direct, managed_kms_context: None, managed_sealed_key: None, - }); + }) + .expect("managed SSE metadata should serialize"); assert_eq!( metadata.get(MINIO_INTERNAL_ENCRYPTION_IV_HEADER).map(String::as_str), @@ -3011,7 +3017,7 @@ mod tests { let material = sse_encryption(request).await.expect("sse-s3 encryption should succeed"); let material = material.expect("managed sse-s3 encryption should return material"); - let metadata = encryption_material_to_metadata(&material); + let metadata = encryption_material_to_metadata(&material).expect("managed SSE-S3 metadata should serialize"); assert_eq!(material.kms_key_id, None); assert_eq!(metadata.get("x-amz-server-side-encryption").map(String::as_str), Some("AES256")); @@ -3104,7 +3110,7 @@ mod tests { .expect("managed sse material"); assert_eq!(material.key_kind, EncryptionKeyKind::Object); - let metadata = encryption_material_to_metadata(&material); + let metadata = encryption_material_to_metadata(&material).expect("managed SSE metadata should serialize"); assert!(!metadata.contains_key(INTERNAL_ENCRYPTION_KEY_HEADER)); assert!(!metadata.contains_key(INTERNAL_ENCRYPTION_IV_HEADER)); @@ -3169,7 +3175,7 @@ mod tests { assert_eq!(material.key_kind, EncryptionKeyKind::Object); - let metadata = encryption_material_to_metadata(&material); + let metadata = encryption_material_to_metadata(&material).expect("sse-c metadata should serialize"); assert_eq!( metadata.get(MINIO_INTERNAL_ENCRYPTION_ALGORITHM_HEADER).map(String::as_str), Some(MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM) diff --git a/scripts/layer-dependency-baseline.txt b/scripts/layer-dependency-baseline.txt index 5d7f676ff..61ca4a7f0 100644 --- a/scripts/layer-dependency-baseline.txt +++ b/scripts/layer-dependency-baseline.txt @@ -15,52 +15,51 @@ # accepted - reviewed and intentionally allowed # todo - should be resolved in a future refactor -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_bucket_meta_hook|bucket use-case calls site replication hooks -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_delete_bucket_hook|bucket use-case calls site replication hooks -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_make_bucket_hook|bucket use-case calls site replication hooks -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectVersionsParams|bucket use-case uses current S3 API bucket helper types -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectsV2Params|bucket use-case uses current S3 API bucket helper types -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_buckets_output|bucket use-case uses current S3 API bucket response helpers -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_object_versions_output|bucket use-case uses current S3 API bucket response helpers -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_output|bucket use-case uses current S3 API bucket response helpers -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_v2_output|bucket use-case uses current S3 API bucket response helpers -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_object_versions_params|bucket use-case uses current S3 API bucket query parser -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_objects_v2_params|bucket use-case uses current S3 API bucket query parser -accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::common::rustfs_owner|bucket use-case uses current S3 API owner helper -accepted|rustfs/src/app/lifecycle_transition_api_test.rs|app->interface|crate::storage::ecfs::FS|app test exercises ECFS through current interface path -accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::ListMultipartUploadsParams|multipart use-case uses current S3 API multipart helper types -accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_multipart_uploads_output|multipart use-case uses current S3 API multipart response helpers -accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_parts_output|multipart use-case uses current S3 API multipart response helpers -accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_multipart_uploads_params|multipart use-case uses current S3 API multipart query parser -accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params|multipart use-case uses current S3 API multipart query parser -accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_upload_part_number|multipart use-case uses current S3 API multipart part parser -accepted|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::ecfs|object use-case uses current ECFS interface helpers -accepted|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params|object use-case uses current S3 API multipart query parser -accepted|rustfs/src/init.rs|infra->interface|crate::admin|init wires the admin module during process setup -accepted|rustfs/src/protocols/client.rs|infra->interface|crate::storage::ecfs::FS|protocol client delegates to the current ECFS S3 implementation -accepted|rustfs/src/server/audit.rs|infra->app|crate::app::context::resolve_server_config|config resolution uses global AppContext -accepted|rustfs/src/server/event.rs|infra->app|crate::app::context::resolve_server_config|config resolution uses global AppContext -accepted|rustfs/src/server/http.rs|infra->interface|crate::admin|HTTP server routes to admin handlers -accepted|rustfs/src/server/layer.rs|infra->app|crate::app::context::resolve_kms_runtime_service_manager|HTTP layer reads KMS runtime through AppContext resolver -accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::console::is_console_path|pure path predicate for routing -accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::HealthProbe|HTTP layer builds health responses -accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::build_health_response_parts|HTTP layer builds health responses -accepted|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_endpoints_handle|readiness checks endpoint availability through AppContext resolver -accepted|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_iam_ready|readiness checks IAM availability through AppContext resolver -accepted|rustfs/src/startup_iam.rs|infra->app|crate::app::context::AppContext|startup wires IAM bootstrap through AppContext -accepted|rustfs/src/storage/ecfs_extend.rs|infra->app|crate::app::context::resolve_buffer_config|storage buffer sizing reads runtime config through AppContext resolver -accepted|rustfs/src/storage/ecfs_extend.rs|infra->interface|crate::storage::ecfs::ListObjectUnorderedQuery|storage extension uses current ECFS interface query type -accepted|rustfs/src/storage/ecfs_extend.rs|infra->app|crate::app::context::resolve_buffer_config|buffer config resolution uses global AppContext -accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::FS|storage tests exercise current ECFS interface path -accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::validate_object_lock_configuration_input|storage tests exercise current ECFS object-lock validator -accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_initiator|storage tests use current S3 API initiator helper -accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_owner|storage tests use current S3 API owner helper -accepted|rustfs/src/storage/rpc/health.rs|infra->app|crate::app::context::resolve_local_node_name|RPC health labels node identity through AppContext resolver -accepted|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_iam_handle|node RPC IAM operations use AppContext resolver fallback -accepted|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_lock_client|node RPC locking uses AppContext resolver fallback -accepted|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_dynamic_config_runtime_state|node RPC reloads admin runtime state -accepted|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_runtime_config_snapshot|node RPC reloads admin runtime state -accepted|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::site_replication::reload_site_replication_runtime_state|node RPC reloads admin runtime state -accepted|cycle|app<->infra|existing intra-crate cross-layer references -accepted|cycle|app<->interface|existing intra-crate cross-layer references -accepted|cycle|infra<->interface|existing intra-crate cross-layer references +cycle|app<->infra +cycle|app<->interface +cycle|infra<->interface +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_bucket_meta_hook +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_delete_bucket_hook +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_make_bucket_hook +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectVersionsParams +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectsV2Params +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_buckets_output +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_object_versions_output +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_output +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_v2_output +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_object_versions_params +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_objects_v2_params +dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::common::rustfs_owner +dep|rustfs/src/app/lifecycle_transition_api_test.rs|app->interface|crate::storage::ecfs::FS +dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::ListMultipartUploadsParams +dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_multipart_uploads_output +dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_parts_output +dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_multipart_uploads_params +dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params +dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_upload_part_number +dep|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::ecfs +dep|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params +dep|rustfs/src/init.rs|infra->interface|crate::admin +dep|rustfs/src/protocols/client.rs|infra->interface|crate::storage::ecfs::FS +dep|rustfs/src/server/audit.rs|infra->app|crate::app::context::resolve_server_config +dep|rustfs/src/server/event.rs|infra->app|crate::app::context::resolve_server_config +dep|rustfs/src/server/http.rs|infra->interface|crate::admin +dep|rustfs/src/server/layer.rs|infra->app|crate::app::context::resolve_kms_runtime_service_manager +dep|rustfs/src/server/layer.rs|infra->interface|crate::admin::console::is_console_path +dep|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::HealthProbe +dep|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::build_health_response_parts +dep|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_endpoints_handle +dep|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_iam_ready +dep|rustfs/src/startup_iam.rs|infra->app|crate::app::context::AppContext +dep|rustfs/src/storage/ecfs_extend.rs|infra->app|crate::app::context::resolve_buffer_config +dep|rustfs/src/storage/ecfs_extend.rs|infra->interface|crate::storage::ecfs::ListObjectUnorderedQuery +dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::FS +dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::validate_object_lock_configuration_input +dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_initiator +dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_owner +dep|rustfs/src/storage/rpc/health.rs|infra->app|crate::app::context::resolve_local_node_name +dep|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_iam_handle +dep|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_lock_client +dep|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_dynamic_config_runtime_state +dep|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_runtime_config_snapshot +dep|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::site_replication::reload_site_replication_runtime_state