mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
fix(admin): normalize empty admin GET content length (#3160)
Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user