From aa84d34bf88840781502be671396b5e7bd9d57d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Wed, 11 Mar 2026 21:59:12 +0800 Subject: [PATCH] fix(auth): preserve IAMAuth clone and correct missing-key error (#2123) --- rustfs/src/admin/handlers/heal.rs | 19 ++++----- rustfs/src/admin/route_registration_test.rs | 1 + rustfs/src/auth.rs | 45 +++++++++++++++------ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/rustfs/src/admin/handlers/heal.rs b/rustfs/src/admin/handlers/heal.rs index 797f34ae..714c3919 100644 --- a/rustfs/src/admin/handlers/heal.rs +++ b/rustfs/src/admin/handlers/heal.rs @@ -21,7 +21,6 @@ use matchit::Params; use rustfs_common::heal_channel::HealOpts; use rustfs_config::MAX_HEAL_REQUEST_SIZE; use rustfs_ecstore::bucket::utils::is_valid_object_prefix; -use rustfs_ecstore::error::StorageError; use rustfs_ecstore::store_utils::is_reserved_or_invalid_bucket; use rustfs_utils::path::path_join; use s3s::{Body, S3Request, S3Response, S3Result, s3_error}; @@ -141,8 +140,7 @@ impl Operation for HealHandler { #[derive(Default)] struct HealResp { resp_bytes: Vec, - _api_err: Option, - _err_body: String, + api_err: Option, } let heal_path = path_join(&[PathBuf::from(hip.bucket.clone()), PathBuf::from(hip.obj_prefix.clone())]); @@ -156,7 +154,6 @@ impl Operation for HealHandler { spawn(async move { match rustfs_common::heal_channel::query_heal_status(heal_path_str, client_token).await { Ok(_) => { - // TODO: Get actual response from channel let _ = tx_clone .send(HealResp { resp_bytes: vec![], @@ -167,7 +164,7 @@ impl Operation for HealHandler { Err(e) => { let _ = tx_clone .send(HealResp { - _api_err: Some(StorageError::other(e)), + api_err: Some(e), ..Default::default() }) .await; @@ -181,7 +178,6 @@ impl Operation for HealHandler { spawn(async move { match rustfs_common::heal_channel::cancel_heal_task(heal_path_str).await { Ok(_) => { - // TODO: Get actual response from channel let _ = tx_clone .send(HealResp { resp_bytes: vec![], @@ -192,7 +188,7 @@ impl Operation for HealHandler { Err(e) => { let _ = tx_clone .send(HealResp { - _api_err: Some(StorageError::other(e)), + api_err: Some(e), ..Default::default() }) .await; @@ -229,7 +225,7 @@ impl Operation for HealHandler { // Error - send error response let _ = tx_clone .send(HealResp { - _api_err: Some(StorageError::other(e)), + api_err: Some(e), ..Default::default() }) .await; @@ -239,7 +235,12 @@ impl Operation for HealHandler { } match rx.recv().await { - Some(result) => Ok(S3Response::new((StatusCode::OK, Body::from(result.resp_bytes)))), + Some(result) => { + if let Some(api_err) = result.api_err { + return Err(s3_error!(InternalError, "{api_err}")); + } + Ok(S3Response::new((StatusCode::OK, Body::from(result.resp_bytes)))) + } None => Ok(S3Response::new((StatusCode::INTERNAL_SERVER_ERROR, Body::from(vec![])))), } } diff --git a/rustfs/src/admin/route_registration_test.rs b/rustfs/src/admin/route_registration_test.rs index ee2fc485..488d39e9 100644 --- a/rustfs/src/admin/route_registration_test.rs +++ b/rustfs/src/admin/route_registration_test.rs @@ -85,6 +85,7 @@ fn test_register_routes_cover_representative_admin_paths() { assert_route(&router, Method::GET, &admin_path("/v3/rebalance/status")); assert_route(&router, Method::POST, &admin_path("/v3/heal/test-bucket")); assert_route(&router, Method::POST, &admin_path("/v3/heal/test-bucket/prefix")); + assert_route(&router, Method::POST, &admin_path("/v3/background-heal/status")); assert_route(&router, Method::GET, &admin_path("/v3/tier")); assert_route(&router, Method::POST, &admin_path("/v3/tier/clear")); diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs index bc5f3ba0..f8fdb303 100644 --- a/rustfs/src/auth.rs +++ b/rustfs/src/auth.rs @@ -91,26 +91,32 @@ pub enum AuthType { StreamingUnsignedTrailer, } -#[derive(Debug)] pub struct IAMAuth { simple_auth: SimpleAuth, + access_key: String, + secret_key: SecretKey, } impl Clone for IAMAuth { fn clone(&self) -> Self { - // Since SimpleAuth doesn't implement Clone, we create a new one - // This is a simplified implementation - in a real scenario, you might need - // to store the credentials separately to properly clone Self { - simple_auth: SimpleAuth::new(), + simple_auth: SimpleAuth::from_single(self.access_key.clone(), self.secret_key.clone()), + access_key: self.access_key.clone(), + secret_key: self.secret_key.clone(), } } } impl IAMAuth { pub fn new(ak: impl Into, sk: impl Into) -> Self { - let simple_auth = SimpleAuth::from_single(ak, sk); - Self { simple_auth } + let access_key = ak.into(); + let secret_key = sk.into(); + let simple_auth = SimpleAuth::from_single(access_key.clone(), secret_key.clone()); + Self { + simple_auth, + access_key, + secret_key, + } } } @@ -143,6 +149,10 @@ impl S3Auth for IAMAuth { return Ok(SecretKey::from(String::new())); } + if access_key == self.access_key { + return Ok(self.secret_key.clone()); + } + if let Ok(key) = self.simple_auth.get_secret_key(access_key).await { return Ok(key); } @@ -264,17 +274,19 @@ pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result< .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("check claims failed1 {e}")))?; if !ok { - if let Some(u) = u - && u.credentials.status == "off" - { + let Some(u) = u else { + return Err(s3_error!(InvalidAccessKeyId, "check key failed")); + }; + + if u.credentials.status == "off" { return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); } - return Err(s3_error!(InvalidRequest, "ErrAccessKeyDisabled")); + return Err(s3_error!(InvalidRequest, "check key failed")); } let Some(u) = u else { - return Err(s3_error!(InvalidRequest, "check key failed")); + return Err(s3_error!(InvalidAccessKeyId, "check key failed")); }; cred = u.credentials; @@ -899,6 +911,15 @@ mod tests { assert_eq!(size_of_val(&iam_auth), size_of::()); } + #[tokio::test] + async fn test_iam_auth_clone_preserves_bootstrap_secret() { + let iam_auth = IAMAuth::new("test-ak", SecretKey::from("test-sk")); + let cloned = iam_auth.clone(); + + let secret = cloned.get_secret_key("test-ak").await; + assert!(secret.is_ok()); + } + #[tokio::test] async fn test_iam_auth_get_secret_key_empty_access_key() { let iam_auth = IAMAuth::new("test-ak", SecretKey::from("test-sk"));