mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 01:09:23 +00:00
test(e2e): require authorization denial codes (#6497)
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::common::{RustFSTestEnvironment, init_logging, local_http_client, rustfs_binary_path};
|
use crate::common::{RustFSTestEnvironment, init_logging, local_http_client, rustfs_binary_path};
|
||||||
use aws_sdk_s3::config::{Credentials, Region};
|
use aws_sdk_s3::config::{Credentials, Region};
|
||||||
|
use aws_sdk_s3::error::ProvideErrorMetadata;
|
||||||
use aws_sdk_s3::{Client, Config};
|
use aws_sdk_s3::{Client, Config};
|
||||||
use http::header::HOST;
|
use http::header::HOST;
|
||||||
use rustfs_signer::constants::UNSIGNED_PAYLOAD;
|
use rustfs_signer::constants::UNSIGNED_PAYLOAD;
|
||||||
@@ -368,10 +369,15 @@ mod tests {
|
|||||||
reqwest::StatusCode::FORBIDDEN,
|
reqwest::StatusCode::FORBIDDEN,
|
||||||
"stale root must be rejected on the admin API after rotation, body: {body}"
|
"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;
|
let s3_old = s3_client_with(&env, &old_ak, &old_sk)
|
||||||
assert!(
|
.list_buckets()
|
||||||
s3_old.is_err(),
|
.send()
|
||||||
"stale root must be rejected on the S3 plane after rotation, got: {s3_old:?}"
|
.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();
|
env.stop_server();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use crate::common::{
|
|||||||
RustFSTestEnvironment, admin_ok, admin_request, admin_request_with_session_token, build_test_sts_client, init_logging,
|
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::config::{Credentials, Region};
|
||||||
|
use aws_sdk_s3::error::ProvideErrorMetadata;
|
||||||
use aws_sdk_s3::primitives::ByteStream;
|
use aws_sdk_s3::primitives::ByteStream;
|
||||||
use aws_sdk_s3::{Client, Config};
|
use aws_sdk_s3::{Client, Config};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
@@ -411,8 +412,13 @@ async fn test_admin_user_policy_service_account_crud_lifecycle() -> TestResult {
|
|||||||
.key("before-attach")
|
.key("before-attach")
|
||||||
.body(ByteStream::from_static(b"x"))
|
.body(ByteStream::from_static(b"x"))
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await
|
||||||
assert!(denied.is_err(), "user without a policy must not be able to write to {bucket}");
|
.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 -----------------
|
// --- attach policy: the credential actually gains S3 access -----------------
|
||||||
admin_ok(
|
admin_ok(
|
||||||
@@ -499,13 +505,19 @@ async fn test_admin_user_policy_service_account_crud_lifecycle() -> TestResult {
|
|||||||
.body(ByteStream::from_static(b"x"))
|
.body(ByteStream::from_static(b"x"))
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
if revoked.is_err() {
|
match revoked {
|
||||||
break;
|
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.
|
// 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"))
|
.body(ByteStream::from_static(b"x"))
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
if disabled.is_err() {
|
match disabled {
|
||||||
break;
|
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(
|
admin_ok(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
use crate::common::{RustFSTestEnvironment, awscurl_delete, awscurl_post_sts_form_urlencoded, awscurl_put, init_logging};
|
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::config::{Credentials, Region};
|
||||||
|
use aws_sdk_s3::error::ProvideErrorMetadata;
|
||||||
use aws_sdk_s3::primitives::ByteStream;
|
use aws_sdk_s3::primitives::ByteStream;
|
||||||
use aws_sdk_s3::types::{Delete, ObjectIdentifier, Tag, Tagging};
|
use aws_sdk_s3::types::{Delete, ObjectIdentifier, Tag, Tagging};
|
||||||
use aws_sdk_s3::{Client, Config};
|
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?;
|
let _ = out.body.collect().await?;
|
||||||
|
|
||||||
put_object_tag_kv(&admin, &bucket, key, "security", "private").await?;
|
put_object_tag_kv(&admin, &bucket, key, "security", "private").await?;
|
||||||
let denied = uclient.get_object().bucket(&bucket).key(key).send().await;
|
let denied = uclient
|
||||||
assert!(
|
.get_object()
|
||||||
denied.is_err(),
|
.bucket(&bucket)
|
||||||
"GetObject must be denied when ExistingObjectTag no longer matches IAM policy"
|
.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;
|
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)
|
.bucket(&bucket)
|
||||||
.key(key)
|
.key(key)
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await
|
||||||
assert!(deny_before.is_err(), "without bucket policy, user must be denied");
|
.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!({
|
let bp = serde_json::json!({
|
||||||
"Version": "2012-10-17",
|
"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?;
|
let _ = ok.body.collect().await?;
|
||||||
|
|
||||||
put_object_tag_kv(&admin, &bucket, key, "security", "private").await?;
|
put_object_tag_kv(&admin, &bucket, key, "security", "private").await?;
|
||||||
let denied = uclient.get_object().bucket(&bucket).key(key).send().await;
|
let denied = uclient
|
||||||
assert!(denied.is_err(), "GetObject must fail when tag no longer satisfies bucket policy");
|
.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;
|
cleanup_bucket_and_object(&admin, &bucket, key).await;
|
||||||
admin_remove_user(&env, &user).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?;
|
let _ = ok.body.collect().await?;
|
||||||
|
|
||||||
put_object_tag_kv(&parent_client, &bucket, key, "security", "private").await?;
|
put_object_tag_kv(&parent_client, &bucket, key, "security", "private").await?;
|
||||||
let denied = session_client.get_object().bucket(&bucket).key(key).send().await;
|
let denied = session_client
|
||||||
assert!(
|
.get_object()
|
||||||
denied.is_err(),
|
.bucket(&bucket)
|
||||||
"session policy must deny GetObject when ExistingObjectTag no longer matches"
|
.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;
|
cleanup_bucket_and_object(&admin, &bucket, key).await;
|
||||||
|
|||||||
@@ -682,18 +682,26 @@ mod integration_tests {
|
|||||||
assert!(resp.contains("quota_limit"));
|
assert!(resp.contains("quota_limit"));
|
||||||
|
|
||||||
// Normal user sets quota — should be denied
|
// Normal user sets quota — should be denied
|
||||||
let set_resp = awscurl_put(
|
let set_error = awscurl_put(
|
||||||
&get_url,
|
&get_url,
|
||||||
&serde_json::json!({"quota": 2048, "quota_type": "HARD"}).to_string(),
|
&serde_json::json!({"quota": 2048, "quota_type": "HARD"}).to_string(),
|
||||||
normal_ak,
|
normal_ak,
|
||||||
normal_sk,
|
normal_sk,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
assert!(set_resp.is_err(), "normal user should not be able to set quota");
|
.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
|
// Normal user clears quota — should be denied
|
||||||
let del_resp = awscurl_delete(&get_url, normal_ak, normal_sk).await;
|
let delete_error = awscurl_delete(&get_url, normal_ak, normal_sk)
|
||||||
assert!(del_resp.is_err(), "normal user should not be able to clear quota");
|
.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?;
|
env.cleanup_bucket().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user