chore(deps): refresh s3s and dependencies (#6665)

* chore(deps): refresh s3s and related dependencies

Update the RustFS s3s git dependency to the requested f4dedc905 revision and keep the resolved dependency refresh from Cargo.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(api): adapt s3s upload stream error mapping

Detect the s3s upload stream SHA256 mismatch through the error chain without relying on the removed crate-root re-export.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(auth): preserve SigV2 S3 compatibility

Keep RustFS S3 service configuration explicit after the s3s default disables SigV2.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-26 21:25:16 +08:00
committed by GitHub
parent 62a767a52d
commit ba15588ce8
5 changed files with 120 additions and 66 deletions
+16 -5
View File
@@ -10373,6 +10373,17 @@ mod tests {
use tokio::io::{AsyncRead, ReadBuf};
use tokio_tar::{Builder, EntryType, Header};
#[derive(Debug)]
struct MockUploadStreamSha256Mismatch;
impl std::fmt::Display for MockUploadStreamSha256Mismatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("UploadStreamError: Sha256Mismatch")
}
}
impl std::error::Error for MockUploadStreamSha256Mismatch {}
#[tokio::test]
async fn cancelled_eager_put_commit_owner_reaps_stalled_storage_task() {
let health = Arc::new(ObjectTrafficHealth::enabled_for_test(Duration::ZERO));
@@ -16386,20 +16397,20 @@ mod tests {
#[test]
fn s3s_body_error_to_io_preserves_upload_stream_error_source() {
let error = s3s_body_error_to_io(Box::new(s3s::UploadStreamError::Sha256Mismatch));
let error = s3s_body_error_to_io(Box::new(MockUploadStreamSha256Mismatch));
assert!(matches!(
error
.get_ref()
.and_then(|source| source.downcast_ref::<s3s::UploadStreamError>()),
Some(s3s::UploadStreamError::Sha256Mismatch)
.and_then(|source| source.downcast_ref::<MockUploadStreamSha256Mismatch>()),
Some(MockUploadStreamSha256Mismatch)
));
}
#[tokio::test]
async fn read_small_put_body_maps_upload_stream_sha256_mismatch_to_bad_digest() {
let body = StreamReader::new(futures::stream::iter(vec![Err::<Bytes, std::io::Error>(s3s_body_error_to_io(Box::new(
s3s::UploadStreamError::Sha256Mismatch,
MockUploadStreamSha256Mismatch,
)))]));
let error = read_small_put_body_exact_direct(body, 1)
@@ -16411,7 +16422,7 @@ mod tests {
#[tokio::test]
async fn read_zero_copy_put_body_maps_upload_stream_sha256_mismatch_to_bad_digest() {
let body = futures::stream::iter(vec![Err::<Bytes, s3s::UploadStreamError>(s3s::UploadStreamError::Sha256Mismatch)]);
let body = futures::stream::iter(vec![Err::<Bytes, MockUploadStreamSha256Mismatch>(MockUploadStreamSha256Mismatch)]);
let error = match read_zero_copy_put_body_exact(body, 1).await {
Ok(_) => panic!("SHA256 mismatch should reject the zero-copy PUT body"),
+38 -10
View File
@@ -226,7 +226,7 @@ where
}
fn error_chain_has_upload_stream_sha256_mismatch(err: &(dyn std::error::Error + 'static)) -> bool {
if matches!(err.downcast_ref::<s3s::UploadStreamError>(), Some(s3s::UploadStreamError::Sha256Mismatch)) {
if err.to_string() == "UploadStreamError: Sha256Mismatch" {
return true;
}
@@ -239,7 +239,7 @@ fn error_chain_has_upload_stream_sha256_mismatch(err: &(dyn std::error::Error +
let mut current = err.source();
while let Some(err) = current {
if matches!(err.downcast_ref::<s3s::UploadStreamError>(), Some(s3s::UploadStreamError::Sha256Mismatch)) {
if err.to_string() == "UploadStreamError: Sha256Mismatch" {
return true;
}
current = err.source();
@@ -452,6 +452,34 @@ mod tests {
use s3s::{S3Error, S3ErrorCode};
use std::io::{Error as IoError, ErrorKind};
#[derive(Debug)]
enum MockUploadStreamError {
Underlying(IoError),
Sha256Mismatch,
LengthMismatch,
Incomplete,
}
impl std::fmt::Display for MockUploadStreamError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Underlying(err) => write!(f, "UploadStreamError: Underlying: {err}"),
Self::Sha256Mismatch => f.write_str("UploadStreamError: Sha256Mismatch"),
Self::LengthMismatch => f.write_str("UploadStreamError: LengthMismatch"),
Self::Incomplete => f.write_str("UploadStreamError: Incomplete"),
}
}
}
impl std::error::Error for MockUploadStreamError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Underlying(err) => Some(err),
Self::Sha256Mismatch | Self::LengthMismatch | Self::Incomplete => None,
}
}
}
#[test]
fn test_api_error_from_io_error() {
let io_error = IoError::new(ErrorKind::PermissionDenied, "permission denied");
@@ -515,11 +543,11 @@ mod tests {
#[test]
fn upload_stream_sha256_mismatch_maps_to_bad_digest() {
let api_error = ApiError::from(IoError::other(s3s::UploadStreamError::Sha256Mismatch));
let api_error = ApiError::from(IoError::other(MockUploadStreamError::Sha256Mismatch));
assert_eq!(api_error.code, S3ErrorCode::BadDigest);
assert_eq!(api_error.message, ApiError::error_code_to_message(&S3ErrorCode::BadDigest));
let api_error = ApiError::from(StorageError::Io(IoError::other(s3s::UploadStreamError::Sha256Mismatch)));
let api_error = ApiError::from(StorageError::Io(IoError::other(MockUploadStreamError::Sha256Mismatch)));
assert_eq!(api_error.code, S3ErrorCode::BadDigest);
assert_eq!(api_error.message, ApiError::error_code_to_message(&S3ErrorCode::BadDigest));
}
@@ -527,9 +555,9 @@ mod tests {
#[test]
fn other_upload_stream_errors_do_not_map_to_bad_digest() {
let errors = [
s3s::UploadStreamError::Underlying(Box::new(IoError::other("underlying body error"))),
s3s::UploadStreamError::LengthMismatch,
s3s::UploadStreamError::Incomplete,
MockUploadStreamError::Underlying(IoError::other("underlying body error")),
MockUploadStreamError::LengthMismatch,
MockUploadStreamError::Incomplete,
];
for error in errors {
@@ -538,9 +566,9 @@ mod tests {
}
let errors = [
s3s::UploadStreamError::Underlying(Box::new(IoError::other("underlying body error"))),
s3s::UploadStreamError::LengthMismatch,
s3s::UploadStreamError::Incomplete,
MockUploadStreamError::Underlying(IoError::other("underlying body error")),
MockUploadStreamError::LengthMismatch,
MockUploadStreamError::Incomplete,
];
for error in errors {
+17 -2
View File
@@ -151,6 +151,14 @@ static HTTP_STATUS_CLASS_METRICS: std::sync::LazyLock<[HttpStatusClassMetrics; 6
std::sync::LazyLock::new(|| HTTP_STATUS_CLASS_LABELS.map(HttpStatusClassMetrics::new));
static HTTP_TRANSPORT_FAILURES_COUNTER: std::sync::LazyLock<metrics::Counter> =
std::sync::LazyLock::new(|| counter!(METRIC_HTTP_SERVER_FAILURES_TOTAL, LABEL_HTTP_STATUS_CLASS => "transport"));
fn rustfs_s3_config() -> S3Config {
let mut s3_config = S3Config::default();
s3_config.normalize_forward_slash_path = true;
s3_config.enable_sig_v2 = true;
s3_config
}
const LOG_COMPONENT_SERVER: &str = "server";
const LOG_SUBSYSTEM_HTTP: &str = "http";
const LOG_SUBSYSTEM_TRANSPORT: &str = "transport";
@@ -922,8 +930,7 @@ pub async fn start_http_server(
// `PUT /bucket//foo/bar` are rejected downstream with InvalidArgument
// (ObjectNamePrefixAsSlash, issue #2427). MinIO collapses these slashes instead of preserving them,
// so `//foo/bar` is stored and served as `foo/bar`.
let mut s3_config = S3Config::default();
s3_config.normalize_forward_slash_path = true;
let s3_config = rustfs_s3_config();
b.set_config(Arc::new(StaticConfigProvider::new(Arc::new(s3_config))));
// Virtual-hosted-style requests are only set up for S3 API when server domains are configured and console is disabled
@@ -2257,6 +2264,14 @@ mod tests {
assert_eq!(HTTP_STATUS_CLASS_LABELS[HTTP_STATUS_UNKNOWN_INDEX], "unknown");
}
#[test]
fn rustfs_s3_config_preserves_compatibility_over_s3s_defaults() {
let s3_config = rustfs_s3_config();
assert!(s3_config.normalize_forward_slash_path);
assert!(s3_config.enable_sig_v2);
}
#[test]
#[serial_test::serial]
fn cached_http_metric_handles_preserve_metric_labels() {