fix(admin): normalize empty admin GET content length (#3160)

Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
GatewayJ
2026-06-03 12:12:04 +08:00
committed by GitHub
parent cc07946782
commit cc9e4bb207
4 changed files with 90 additions and 7 deletions
+16 -6
View File
@@ -18,7 +18,9 @@ use crate::app::object_usecase::DefaultObjectUsecase;
use crate::auth::{check_key_valid, get_session_token};
use crate::error::ApiError;
use crate::license::license_check;
use crate::server::{ADMIN_PREFIX, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH};
use crate::server::{
ADMIN_PREFIX, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH, is_admin_path,
};
use crate::storage::access::{ReqInfo, authorize_request};
use aws_sdk_s3::primitives::ByteStream as AwsByteStream;
use bytes::Bytes;
@@ -2216,12 +2218,10 @@ fn is_public_health_path(path: &str) -> bool {
path == HEALTH_PREFIX || path == HEALTH_READY_PATH
}
fn is_admin_path(path: &str) -> bool {
path.starts_with(ADMIN_PREFIX) || path.starts_with(MINIO_ADMIN_PREFIX)
}
fn canonicalize_admin_path(path: &str) -> std::borrow::Cow<'_, str> {
if let Some(suffix) = path.strip_prefix(MINIO_ADMIN_PREFIX) {
if is_admin_path(path)
&& let Some(suffix) = path.strip_prefix(MINIO_ADMIN_PREFIX)
{
return std::borrow::Cow::Owned(format!("{ADMIN_PREFIX}{suffix}"));
}
@@ -2445,7 +2445,13 @@ mod tests {
#[test]
fn canonicalize_admin_path_maps_compat_prefix_to_rustfs_prefix() {
assert_eq!(canonicalize_admin_path("/minio/admin/v3/info").as_ref(), "/rustfs/admin/v3/info");
assert_eq!(canonicalize_admin_path("/minio/admin").as_ref(), "/rustfs/admin");
assert_eq!(canonicalize_admin_path("/rustfs/admin/v3/info").as_ref(), "/rustfs/admin/v3/info");
assert_eq!(
canonicalize_admin_path("/minio/administrator/object").as_ref(),
"/minio/administrator/object"
);
assert_eq!(canonicalize_admin_path("/minio/adminx/object").as_ref(), "/minio/adminx/object");
}
#[test]
@@ -2453,6 +2459,10 @@ mod tests {
assert!(is_admin_path("/rustfs/admin/v3/info"));
assert!(is_admin_path("/minio/admin/v3/info"));
assert!(!is_admin_path("/bucket/object"));
assert!(!is_admin_path("/rustfs/administrator/object"));
assert!(!is_admin_path("/minio/administrator/object"));
assert!(!is_admin_path("/rustfs/adminx/object"));
assert!(!is_admin_path("/minio/adminx/object"));
}
#[test]
+64
View File
@@ -20,6 +20,7 @@ use crate::server::hybrid::HybridBody;
use crate::server::{
ADMIN_PREFIX, CONSOLE_PREFIX, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX,
MINIO_ADMIN_V3_PREFIX, RPC_PREFIX, RUSTFS_ADMIN_PREFIX, active_http_requests, collect_dependency_readiness_report,
is_admin_path,
};
use crate::storage::apply_cors_headers;
use crate::storage::request_context::{RequestContext, extract_request_id_from_headers};
@@ -289,6 +290,7 @@ fn should_force_zero_content_length_for_empty_body_route<B>(req: &HttpRequest<B>
fn is_empty_body_admin_path(method: &Method, uri: &http::Uri) -> bool {
let path = uri.path();
match *method {
Method::GET => is_admin_path(path),
Method::PUT => matches!(
path,
"/minio/admin/v3/set-user-status"
@@ -1428,6 +1430,68 @@ mod tests {
}
}
#[test]
fn admin_empty_body_get_without_content_length_is_normalized() {
let paths = [
format!("{MINIO_ADMIN_PREFIX}/v3/is-admin"),
format!("{MINIO_ADMIN_PREFIX}/v3/accountinfo"),
format!("{MINIO_ADMIN_PREFIX}/v3/info"),
format!("{ADMIN_PREFIX}/v3/is-admin"),
format!("{ADMIN_PREFIX}/v3/accountinfo"),
format!("{ADMIN_PREFIX}/v3/info"),
];
for path in paths {
let request = Request::builder()
.method(Method::GET)
.uri(path.as_str())
.body(())
.expect("request");
assert!(
should_force_zero_content_length_for_empty_body_route(&request),
"{path} should force Content-Length: 0"
);
}
}
#[test]
fn non_admin_get_without_content_length_is_not_normalized() {
let paths = [
"/bucket/object",
"/rustfs/administrator/object",
"/minio/administrator/object",
"/rustfs/adminx/object",
"/minio/adminx/object",
];
for path in paths {
let request = Request::builder().method(Method::GET).uri(path).body(()).expect("request");
assert!(
!should_force_zero_content_length_for_empty_body_route(&request),
"{path} should not force Content-Length: 0"
);
}
}
#[tokio::test]
async fn empty_body_layer_inserts_zero_content_length_for_admin_get() {
let capture = HeaderCaptureService::default();
let headers = capture.headers();
let mut service = EmptyBodyContentLengthCompatLayer.layer(capture);
let request = Request::builder()
.method(Method::GET)
.uri("/rustfs/admin/v3/accountinfo")
.body(())
.expect("request");
let _ = service.call(request).await.expect("service call");
let headers = headers.lock().expect("captured headers").take().expect("captured headers");
assert_eq!(headers.get(http::header::CONTENT_LENGTH).unwrap(), "0");
}
#[tokio::test]
async fn empty_body_layer_inserts_zero_content_length_for_admin_post() {
let capture = HeaderCaptureService::default();
+1 -1
View File
@@ -49,7 +49,7 @@ pub(crate) use module_switch::{
pub(crate) use prefix::{
ADMIN_PREFIX, CONSOLE_PREFIX, FAVICON_PATH, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, LICENSE,
MINIO_ADMIN_PREFIX, MINIO_ADMIN_V3_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH, RPC_PREFIX, RUSTFS_ADMIN_PREFIX,
TONIC_PREFIX, VERSION,
TONIC_PREFIX, VERSION, is_admin_path,
};
pub(crate) use readiness::DependencyReadiness;
pub(crate) use readiness::DependencyReadinessReport;
+9
View File
@@ -43,6 +43,15 @@ pub(crate) const ADMIN_PREFIX: &str = "/rustfs/admin";
/// This alias allows stock MinIO admin tooling to reach RustFS handlers.
pub(crate) const MINIO_ADMIN_PREFIX: &str = "/minio/admin";
/// Returns true for the admin prefix itself or slash-delimited children.
pub(crate) fn is_admin_path(path: &str) -> bool {
has_path_prefix(path, ADMIN_PREFIX) || has_path_prefix(path, MINIO_ADMIN_PREFIX)
}
fn has_path_prefix(path: &str, prefix: &str) -> bool {
path == prefix || path.strip_prefix(prefix).is_some_and(|suffix| suffix.starts_with('/'))
}
/// Environment variable name for overriding the default
/// administrative prefix path.
pub(crate) const RUSTFS_ADMIN_PREFIX: &str = "/rustfs/admin/v3";