mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-06 13:27:43 +00:00
fix(api): bound bucket rate limiter keys (#5706)
This commit is contained in:
@@ -84,6 +84,7 @@ pub(crate) const RATE_LIMIT_SCOPE_CONSOLE: &str = "console";
|
||||
pub(crate) const LABEL_RATE_LIMIT_DIMENSION: &str = "dimension";
|
||||
pub(crate) const RATE_LIMIT_DIMENSION_CLIENT_IP: &str = "client_ip";
|
||||
const RATE_LIMIT_DIMENSION_BUCKET: &str = "bucket";
|
||||
const MAX_BUCKET_NAME_LENGTH: usize = 63;
|
||||
|
||||
// Header names shared with the Swift error mapping (crates/protocols swift/errors.rs).
|
||||
const X_RATE_LIMIT_LIMIT: &str = "x-ratelimit-limit";
|
||||
@@ -332,6 +333,10 @@ fn strip_vh_prefix<'a>(host: &'a str, domain: &str) -> Option<&'a str> {
|
||||
}
|
||||
}
|
||||
|
||||
fn bounded_bucket_name(bucket: &str) -> Option<&str> {
|
||||
(!bucket.is_empty() && bucket.len() <= MAX_BUCKET_NAME_LENGTH).then_some(bucket)
|
||||
}
|
||||
|
||||
/// Extract the bucket a request addresses, if any.
|
||||
///
|
||||
/// Mirrors s3s host routing: on a configured virtual-hosted-style domain the
|
||||
@@ -355,13 +360,13 @@ fn request_bucket<'a, B>(req: &'a Request<B>, vh_domains: &[String]) -> Option<&
|
||||
break;
|
||||
}
|
||||
if let Some(bucket) = strip_vh_prefix(host, domain) {
|
||||
return Some(bucket);
|
||||
return bounded_bucket_name(bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bucket = path.trim_start_matches('/').split('/').next().unwrap_or("");
|
||||
(!bucket.is_empty()).then_some(bucket)
|
||||
bounded_bucket_name(bucket)
|
||||
}
|
||||
|
||||
/// Paths exempt from S3 API rate limiting.
|
||||
@@ -796,6 +801,19 @@ mod tests {
|
||||
assert_eq!(request_bucket(&root, &domains), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_bucket_rejects_oversized_path_segment() {
|
||||
let domains: Vec<String> = vec![];
|
||||
let maximum = "a".repeat(MAX_BUCKET_NAME_LENGTH);
|
||||
let maximum_path = format!("/{maximum}/object");
|
||||
let maximum_req = request_with_host("s3.example.com", &maximum_path);
|
||||
assert_eq!(request_bucket(&maximum_req, &domains), Some(maximum.as_str()));
|
||||
|
||||
let oversized_path = format!("/{}/object", "a".repeat(MAX_BUCKET_NAME_LENGTH + 1));
|
||||
let oversized_req = request_with_host("s3.example.com", &oversized_path);
|
||||
assert_eq!(request_bucket(&oversized_req, &domains), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_bucket_resolves_virtual_hosted_style_against_domains() {
|
||||
let domains = vec!["s3.example.com".to_string(), "s3.example.com:9000".to_string()];
|
||||
@@ -818,6 +836,18 @@ mod tests {
|
||||
assert_eq!(request_bucket(&other, &domains), Some("photos"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_bucket_rejects_oversized_virtual_host_prefix() {
|
||||
let domains = vec!["s3.example.com".to_string()];
|
||||
let maximum = "a".repeat(MAX_BUCKET_NAME_LENGTH);
|
||||
let maximum_req = request_with_host(&format!("{maximum}.s3.example.com"), "/object");
|
||||
assert_eq!(request_bucket(&maximum_req, &domains), Some(maximum.as_str()));
|
||||
|
||||
let oversized = "a".repeat(MAX_BUCKET_NAME_LENGTH + 1);
|
||||
let oversized_req = request_with_host(&format!("{oversized}.s3.example.com"), "/object");
|
||||
assert_eq!(request_bucket(&oversized_req, &domains), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_bucket_skips_admin_and_catalog_namespaces() {
|
||||
let domains: Vec<String> = vec![];
|
||||
|
||||
Reference in New Issue
Block a user