mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 03:46:37 +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::auth::{check_key_valid, get_session_token};
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
use crate::license::license_check;
|
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 crate::storage::access::{ReqInfo, authorize_request};
|
||||||
use aws_sdk_s3::primitives::ByteStream as AwsByteStream;
|
use aws_sdk_s3::primitives::ByteStream as AwsByteStream;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@@ -2216,12 +2218,10 @@ fn is_public_health_path(path: &str) -> bool {
|
|||||||
path == HEALTH_PREFIX || path == HEALTH_READY_PATH
|
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> {
|
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}"));
|
return std::borrow::Cow::Owned(format!("{ADMIN_PREFIX}{suffix}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2445,7 +2445,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn canonicalize_admin_path_maps_compat_prefix_to_rustfs_prefix() {
|
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/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("/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]
|
#[test]
|
||||||
@@ -2453,6 +2459,10 @@ mod tests {
|
|||||||
assert!(is_admin_path("/rustfs/admin/v3/info"));
|
assert!(is_admin_path("/rustfs/admin/v3/info"));
|
||||||
assert!(is_admin_path("/minio/admin/v3/info"));
|
assert!(is_admin_path("/minio/admin/v3/info"));
|
||||||
assert!(!is_admin_path("/bucket/object"));
|
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]
|
#[test]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use crate::server::hybrid::HybridBody;
|
|||||||
use crate::server::{
|
use crate::server::{
|
||||||
ADMIN_PREFIX, CONSOLE_PREFIX, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX,
|
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,
|
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::apply_cors_headers;
|
||||||
use crate::storage::request_context::{RequestContext, extract_request_id_from_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 {
|
fn is_empty_body_admin_path(method: &Method, uri: &http::Uri) -> bool {
|
||||||
let path = uri.path();
|
let path = uri.path();
|
||||||
match *method {
|
match *method {
|
||||||
|
Method::GET => is_admin_path(path),
|
||||||
Method::PUT => matches!(
|
Method::PUT => matches!(
|
||||||
path,
|
path,
|
||||||
"/minio/admin/v3/set-user-status"
|
"/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]
|
#[tokio::test]
|
||||||
async fn empty_body_layer_inserts_zero_content_length_for_admin_post() {
|
async fn empty_body_layer_inserts_zero_content_length_for_admin_post() {
|
||||||
let capture = HeaderCaptureService::default();
|
let capture = HeaderCaptureService::default();
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub(crate) use module_switch::{
|
|||||||
pub(crate) use prefix::{
|
pub(crate) use prefix::{
|
||||||
ADMIN_PREFIX, CONSOLE_PREFIX, FAVICON_PATH, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, LICENSE,
|
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,
|
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::DependencyReadiness;
|
||||||
pub(crate) use readiness::DependencyReadinessReport;
|
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.
|
/// This alias allows stock MinIO admin tooling to reach RustFS handlers.
|
||||||
pub(crate) const MINIO_ADMIN_PREFIX: &str = "/minio/admin";
|
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
|
/// Environment variable name for overriding the default
|
||||||
/// administrative prefix path.
|
/// administrative prefix path.
|
||||||
pub(crate) const RUSTFS_ADMIN_PREFIX: &str = "/rustfs/admin/v3";
|
pub(crate) const RUSTFS_ADMIN_PREFIX: &str = "/rustfs/admin/v3";
|
||||||
|
|||||||
Reference in New Issue
Block a user