diff --git a/crates/e2e_test/src/admin_auth_test.rs b/crates/e2e_test/src/admin_auth_test.rs index 96e7878c4..1a70bce0c 100644 --- a/crates/e2e_test/src/admin_auth_test.rs +++ b/crates/e2e_test/src/admin_auth_test.rs @@ -33,6 +33,7 @@ mod tests { use crate::common::{RustFSTestEnvironment, init_logging, local_http_client, rustfs_binary_path}; use aws_sdk_s3::config::{Credentials, Region}; + use aws_sdk_s3::error::ProvideErrorMetadata; use aws_sdk_s3::{Client, Config}; use http::header::HOST; use rustfs_signer::constants::UNSIGNED_PAYLOAD; @@ -368,10 +369,15 @@ mod tests { reqwest::StatusCode::FORBIDDEN, "stale root must be rejected on the admin API after rotation, body: {body}" ); - let s3_old = s3_client_with(&env, &old_ak, &old_sk).list_buckets().send().await; - assert!( - s3_old.is_err(), - "stale root must be rejected on the S3 plane after rotation, got: {s3_old:?}" + let s3_old = s3_client_with(&env, &old_ak, &old_sk) + .list_buckets() + .send() + .await + .expect_err("stale root must be rejected on the S3 plane after rotation"); + assert_eq!( + s3_old.as_service_error().and_then(ProvideErrorMetadata::code), + Some("InvalidAccessKeyId"), + "stale root must receive InvalidAccessKeyId after rotation: {s3_old:?}" ); env.stop_server(); diff --git a/crates/e2e_test/src/admin_iam_crud_test.rs b/crates/e2e_test/src/admin_iam_crud_test.rs index 84b50709c..d9212977f 100644 --- a/crates/e2e_test/src/admin_iam_crud_test.rs +++ b/crates/e2e_test/src/admin_iam_crud_test.rs @@ -30,6 +30,7 @@ use crate::common::{ RustFSTestEnvironment, admin_ok, admin_request, admin_request_with_session_token, build_test_sts_client, init_logging, }; use aws_sdk_s3::config::{Credentials, Region}; +use aws_sdk_s3::error::ProvideErrorMetadata; use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::{Client, Config}; use reqwest::StatusCode; @@ -411,8 +412,13 @@ async fn test_admin_user_policy_service_account_crud_lifecycle() -> TestResult { .key("before-attach") .body(ByteStream::from_static(b"x")) .send() - .await; - assert!(denied.is_err(), "user without a policy must not be able to write to {bucket}"); + .await + .expect_err("user without a policy must not be able to write to the bucket"); + assert_eq!( + denied.as_service_error().and_then(ProvideErrorMetadata::code), + Some("AccessDenied"), + "user without a policy must receive AccessDenied: {denied:?}" + ); // --- attach policy: the credential actually gains S3 access ----------------- admin_ok( @@ -499,13 +505,19 @@ async fn test_admin_user_policy_service_account_crud_lifecycle() -> TestResult { .body(ByteStream::from_static(b"x")) .send() .await; - if revoked.is_err() { - break; + match revoked { + Ok(_) if tokio::time::Instant::now() >= deadline => { + return Err("deleted service account credential still works".into()); + } + Ok(_) => sleep(Duration::from_millis(500)).await, + Err(error) => { + let code = error.as_service_error().and_then(ProvideErrorMetadata::code); + if matches!(code, Some("AccessDenied" | "InvalidAccessKeyId")) { + break; + } + return Err(format!("deleted service account must fail with an authorization error, got {error:?}").into()); + } } - if tokio::time::Instant::now() >= deadline { - return Err("deleted service account credential still works".into()); - } - sleep(Duration::from_millis(500)).await; } // Disable then remove the user; the credential must stop working. @@ -525,13 +537,19 @@ async fn test_admin_user_policy_service_account_crud_lifecycle() -> TestResult { .body(ByteStream::from_static(b"x")) .send() .await; - if disabled.is_err() { - break; + match disabled { + Ok(_) if tokio::time::Instant::now() >= deadline => { + return Err("disabled user credential still works".into()); + } + Ok(_) => sleep(Duration::from_millis(500)).await, + Err(error) => { + let code = error.as_service_error().and_then(ProvideErrorMetadata::code); + if matches!(code, Some("AccessDenied" | "InvalidAccessKeyId")) { + break; + } + return Err(format!("disabled user must fail with an authorization error, got {error:?}").into()); + } } - if tokio::time::Instant::now() >= deadline { - return Err("disabled user credential still works".into()); - } - sleep(Duration::from_millis(500)).await; } admin_ok( diff --git a/crates/e2e_test/src/existing_object_tag_policy_test.rs b/crates/e2e_test/src/existing_object_tag_policy_test.rs index 7668773ed..24ffeb9eb 100644 --- a/crates/e2e_test/src/existing_object_tag_policy_test.rs +++ b/crates/e2e_test/src/existing_object_tag_policy_test.rs @@ -18,6 +18,7 @@ use crate::common::{RustFSTestEnvironment, awscurl_delete, awscurl_post_sts_form_urlencoded, awscurl_put, init_logging}; use aws_sdk_s3::config::{Credentials, Region}; +use aws_sdk_s3::error::ProvideErrorMetadata; use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::types::{Delete, ObjectIdentifier, Tag, Tagging}; use aws_sdk_s3::{Client, Config}; @@ -208,10 +209,17 @@ async fn test_e2e_iam_policy_existing_object_tag_get_object() -> Result<(), Box< let _ = out.body.collect().await?; put_object_tag_kv(&admin, &bucket, key, "security", "private").await?; - let denied = uclient.get_object().bucket(&bucket).key(key).send().await; - assert!( - denied.is_err(), - "GetObject must be denied when ExistingObjectTag no longer matches IAM policy" + let denied = uclient + .get_object() + .bucket(&bucket) + .key(key) + .send() + .await + .expect_err("GetObject must be denied when ExistingObjectTag no longer matches IAM policy"); + assert_eq!( + denied.as_service_error().and_then(ProvideErrorMetadata::code), + Some("AccessDenied"), + "IAM ExistingObjectTag mismatch must return AccessDenied: {denied:?}" ); cleanup_bucket_and_object(&admin, &bucket, key).await; @@ -245,8 +253,13 @@ async fn test_e2e_bucket_policy_existing_object_tag_get_object() -> Result<(), B .bucket(&bucket) .key(key) .send() - .await; - assert!(deny_before.is_err(), "without bucket policy, user must be denied"); + .await + .expect_err("without bucket policy, user must be denied"); + assert_eq!( + deny_before.as_service_error().and_then(ProvideErrorMetadata::code), + Some("AccessDenied"), + "missing bucket policy must return AccessDenied: {deny_before:?}" + ); let bp = serde_json::json!({ "Version": "2012-10-17", @@ -268,8 +281,18 @@ async fn test_e2e_bucket_policy_existing_object_tag_get_object() -> Result<(), B let _ = ok.body.collect().await?; put_object_tag_kv(&admin, &bucket, key, "security", "private").await?; - let denied = uclient.get_object().bucket(&bucket).key(key).send().await; - assert!(denied.is_err(), "GetObject must fail when tag no longer satisfies bucket policy"); + let denied = uclient + .get_object() + .bucket(&bucket) + .key(key) + .send() + .await + .expect_err("GetObject must fail when tag no longer satisfies bucket policy"); + assert_eq!( + denied.as_service_error().and_then(ProvideErrorMetadata::code), + Some("AccessDenied"), + "bucket-policy ExistingObjectTag mismatch must return AccessDenied: {denied:?}" + ); cleanup_bucket_and_object(&admin, &bucket, key).await; admin_remove_user(&env, &user).await; @@ -335,10 +358,17 @@ async fn test_e2e_sts_assume_role_session_policy_existing_object_tag() -> Result let _ = ok.body.collect().await?; put_object_tag_kv(&parent_client, &bucket, key, "security", "private").await?; - let denied = session_client.get_object().bucket(&bucket).key(key).send().await; - assert!( - denied.is_err(), - "session policy must deny GetObject when ExistingObjectTag no longer matches" + let denied = session_client + .get_object() + .bucket(&bucket) + .key(key) + .send() + .await + .expect_err("session policy must deny GetObject when ExistingObjectTag no longer matches"); + assert_eq!( + denied.as_service_error().and_then(ProvideErrorMetadata::code), + Some("AccessDenied"), + "STS ExistingObjectTag mismatch must return AccessDenied: {denied:?}" ); cleanup_bucket_and_object(&admin, &bucket, key).await; diff --git a/crates/e2e_test/src/quota_test.rs b/crates/e2e_test/src/quota_test.rs index 85d54b3f8..8e5059e7e 100644 --- a/crates/e2e_test/src/quota_test.rs +++ b/crates/e2e_test/src/quota_test.rs @@ -682,18 +682,26 @@ mod integration_tests { assert!(resp.contains("quota_limit")); // Normal user sets quota — should be denied - let set_resp = awscurl_put( + let set_error = awscurl_put( &get_url, &serde_json::json!({"quota": 2048, "quota_type": "HARD"}).to_string(), normal_ak, normal_sk, ) - .await; - assert!(set_resp.is_err(), "normal user should not be able to set quota"); + .await + .expect_err("normal user should not be able to set quota") + .to_string(); + assert!(set_error.contains("AccessDenied"), "quota denial must return AccessDenied: {set_error}"); // Normal user clears quota — should be denied - let del_resp = awscurl_delete(&get_url, normal_ak, normal_sk).await; - assert!(del_resp.is_err(), "normal user should not be able to clear quota"); + let delete_error = awscurl_delete(&get_url, normal_ak, normal_sk) + .await + .expect_err("normal user should not be able to clear quota") + .to_string(); + assert!( + delete_error.contains("AccessDenied"), + "quota deletion denial must return AccessDenied: {delete_error}" + ); env.cleanup_bucket().await?; Ok(())