test(e2e): require authorization denial codes (#6497)

This commit is contained in:
Zhengchao An
2026-08-24 14:35:28 +08:00
committed by GitHub
parent 114bb4acec
commit d293ed71e5
4 changed files with 97 additions and 35 deletions
+10 -4
View File
@@ -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();
+32 -14
View File
@@ -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(
@@ -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;
+13 -5
View File
@@ -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(())