mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-04 19:25:40 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3dc92c6dff | |||
| d299b82b21 | |||
| fd6046faf2 |
@@ -1,2 +1,2 @@
|
|||||||
sha256-darwin=9f767b37ed8b1c82da62ea441462d75487785c8086e56f08fb6f6cd89c6e2e52
|
sha256-darwin=b8549d3362a69cca01c2a81f548bb06d5142d8a9ab4509487a656c8b3db1c164
|
||||||
sha256-linux=fbdaf42b220958d4b1e8880e0f8b5a7992d38e21051bb60596dd4538424757d6
|
sha256-linux=7ecd054965b4afa070af6deefdc37b5ca9f6a9b488dd5eef1ad0877378365b2f
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
//! These tests verify that RustFS properly enforces security-sensitive
|
//! These tests verify that RustFS properly enforces security-sensitive
|
||||||
//! controls by issuing real requests against a running server and asserting
|
//! controls by issuing real requests against a running server and asserting
|
||||||
//! the concrete outcome of each control:
|
//! the concrete outcome of each control:
|
||||||
//! - DoS protection (oversized tagging payloads, excessive multipart parts)
|
//! - DoS protection (oversized tagging payloads, out-of-range multipart part numbers)
|
||||||
//! - SSRF prevention (internal/private endpoints rejected for tiering)
|
//! - SSRF prevention (internal/private endpoints rejected for tiering)
|
||||||
//! - Race condition handling (concurrent writes converge without corruption)
|
//! - Race condition handling (concurrent writes converge without corruption)
|
||||||
|
|
||||||
use crate::common::{RustFSTestEnvironment, awscurl_available, awscurl_put, init_logging};
|
use crate::common::{RustFSTestEnvironment, awscurl_available, awscurl_put, init_logging};
|
||||||
use aws_sdk_s3::error::ProvideErrorMetadata;
|
use aws_sdk_s3::error::ProvideErrorMetadata;
|
||||||
use aws_sdk_s3::primitives::ByteStream;
|
use aws_sdk_s3::primitives::ByteStream;
|
||||||
use aws_sdk_s3::types::{CompletedMultipartUpload, CompletedPart, Tag, Tagging};
|
use aws_sdk_s3::types::{Tag, Tagging};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
@@ -88,9 +88,9 @@ async fn test_large_xml_body_rejection() -> Result<(), Box<dyn Error + Send + Sy
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Excessive multipart parts must be rejected.
|
/// Multipart part numbers above the S3 limit must be rejected.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_excessive_multipart_parts() -> Result<(), Box<dyn Error + Send + Sync>> {
|
async fn test_multipart_part_number_above_limit() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
init_logging();
|
init_logging();
|
||||||
let mut env = RustFSTestEnvironment::new().await?;
|
let mut env = RustFSTestEnvironment::new().await?;
|
||||||
env.start_rustfs_server(vec![]).await?;
|
env.start_rustfs_server(vec![]).await?;
|
||||||
@@ -108,18 +108,23 @@ async fn test_excessive_multipart_parts() -> Result<(), Box<dyn Error + Send + S
|
|||||||
|
|
||||||
let upload_id = create_result.upload_id().expect("upload_id should be present").to_string();
|
let upload_id = create_result.upload_id().expect("upload_id should be present").to_string();
|
||||||
|
|
||||||
// Try to complete with too many parts (should be rejected).
|
client
|
||||||
let mut parts = Vec::new();
|
.upload_part()
|
||||||
for i in 1..=10001 {
|
|
||||||
parts.push(CompletedPart::builder().part_number(i).e_tag(format!("etag-{i}")).build());
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = client
|
|
||||||
.complete_multipart_upload()
|
|
||||||
.bucket(&bucket_name)
|
.bucket(&bucket_name)
|
||||||
.key("test-large")
|
.key("test-large")
|
||||||
.upload_id(&upload_id)
|
.upload_id(&upload_id)
|
||||||
.multipart_upload(CompletedMultipartUpload::builder().set_parts(Some(parts)).build())
|
.part_number(10000)
|
||||||
|
.body(ByteStream::from_static(b"upper-bound part"))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let result = client
|
||||||
|
.upload_part()
|
||||||
|
.bucket(&bucket_name)
|
||||||
|
.key("test-large")
|
||||||
|
.upload_id(&upload_id)
|
||||||
|
.part_number(10001)
|
||||||
|
.body(ByteStream::from_static(b"out-of-range part"))
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -133,7 +138,13 @@ async fn test_excessive_multipart_parts() -> Result<(), Box<dyn Error + Send + S
|
|||||||
.await;
|
.await;
|
||||||
let _ = client.delete_bucket().bucket(&bucket_name).send().await;
|
let _ = client.delete_bucket().bucket(&bucket_name).send().await;
|
||||||
|
|
||||||
assert!(result.is_err(), "Server should reject excessive multipart parts");
|
let err = result.expect_err("server must reject excessive multipart parts");
|
||||||
|
let code = err.as_service_error().and_then(ProvideErrorMetadata::code);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
Some("InvalidArgument"),
|
||||||
|
"Part number 10001 should be rejected with InvalidArgument, got code {code:?}, err: {err:?}"
|
||||||
|
);
|
||||||
|
|
||||||
env.stop_server();
|
env.stop_server();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -217,12 +228,8 @@ async fn test_concurrent_object_operations() -> Result<(), Box<dyn Error + Send
|
|||||||
/// Internal/private endpoints must be rejected as remote tier backends (SSRF).
|
/// Internal/private endpoints must be rejected as remote tier backends (SSRF).
|
||||||
///
|
///
|
||||||
/// This issues a real admin AddTier call (`PUT /rustfs/admin/v3/tier`) for each
|
/// This issues a real admin AddTier call (`PUT /rustfs/admin/v3/tier`) for each
|
||||||
/// internal/private endpoint and asserts the server rejects it (non-2xx, so the
|
/// internal/private endpoint and asserts the request reaches the outbound URL
|
||||||
/// signed request helper returns an error). An internal endpoint must never be
|
/// guard. Connectivity or credential failures do not prove SSRF protection.
|
||||||
/// accepted as a tier backend. The rejection may originate from explicit
|
|
||||||
/// SSRF/internal-address filtering or from the backend connectivity/credential
|
|
||||||
/// validation performed during AddTier; either way the security-relevant
|
|
||||||
/// outcome — the internal endpoint is not accepted — is asserted here.
|
|
||||||
///
|
///
|
||||||
/// The admin API is exercised via signed `awscurl` requests, matching the
|
/// The admin API is exercised via signed `awscurl` requests, matching the
|
||||||
/// pattern used by the other admin-API E2E tests in this crate; the test is
|
/// pattern used by the other admin-API E2E tests in this crate; the test is
|
||||||
@@ -263,10 +270,13 @@ async fn test_tiering_url_validation() -> Result<(), Box<dyn Error + Send + Sync
|
|||||||
})
|
})
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let result = awscurl_put(&tier_url, &body, &env.access_key, &env.secret_key).await;
|
let err = awscurl_put(&tier_url, &body, &env.access_key, &env.secret_key)
|
||||||
|
.await
|
||||||
|
.expect_err("AddTier must reject internal endpoints");
|
||||||
|
let rendered = err.to_string();
|
||||||
assert!(
|
assert!(
|
||||||
result.is_err(),
|
rendered.contains("TierAddFailed") && rendered.contains("tier endpoint is not allowed"),
|
||||||
"AddTier must reject internal endpoint {endpoint}, but it was accepted: {result:?}"
|
"AddTier rejected {endpoint} outside the outbound URL guard: {rendered}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user