fix issue where list on vhost-bucket would list bucket instead of bucket content

This commit is contained in:
Trinity Pointard 2021-11-11 14:12:22 +01:00 committed by Gitea
parent 57df9c6e2d
commit 02158ee666

View file

@ -84,9 +84,6 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
let path = req.uri().path().to_string(); let path = req.uri().path().to_string();
let path = percent_encoding::percent_decode_str(&path).decode_utf8()?; let path = percent_encoding::percent_decode_str(&path).decode_utf8()?;
let (api_key, content_sha256) = check_signature(&garage, &req).await?; let (api_key, content_sha256) = check_signature(&garage, &req).await?;
if path == "/" {
return handle_list_buckets(&api_key);
}
let authority = req let authority = req
.headers() .headers()
@ -94,14 +91,20 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
.ok_or_else(|| Error::BadRequest("HOST header required".to_owned()))? .ok_or_else(|| Error::BadRequest("HOST header required".to_owned()))?
.to_str()?; .to_str()?;
// Get bucket
let host = authority_to_host(authority)?; let host = authority_to_host(authority)?;
let (bucket, key) = parse_bucket_key( let bucket = garage
&path, .config
Some(&host), .s3_api
garage.config.s3_api.root_domain.as_deref(), .root_domain
)?; .as_ref()
.and_then(|root_domain| host_to_bucket(&host, &root_domain));
if path == "/" && bucket.is_none() {
return handle_list_buckets(&api_key);
}
let (bucket, key) = parse_bucket_key(&path, bucket)?;
let allowed = match req.method() { let allowed = match req.method() {
&Method::HEAD | &Method::GET => api_key.allow_read(bucket), &Method::HEAD | &Method::GET => api_key.allow_read(bucket),
_ => api_key.allow_write(bucket), _ => api_key.allow_write(bucket),
@ -152,7 +155,7 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
let copy_source = req.headers().get("x-amz-copy-source").unwrap().to_str()?; let copy_source = req.headers().get("x-amz-copy-source").unwrap().to_str()?;
let copy_source = let copy_source =
percent_encoding::percent_decode_str(copy_source).decode_utf8()?; percent_encoding::percent_decode_str(copy_source).decode_utf8()?;
let (source_bucket, source_key) = parse_bucket_key(&copy_source, None, None)?; let (source_bucket, source_key) = parse_bucket_key(&copy_source, None)?;
if !api_key.allow_read(source_bucket) { if !api_key.allow_read(source_bucket) {
return Err(Error::Forbidden(format!( return Err(Error::Forbidden(format!(
"Reading from bucket {} not allowed for this key", "Reading from bucket {} not allowed for this key",
@ -260,26 +263,23 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
} }
} }
/// Extract the bucket name and the key name from an HTTP path /// Extract the bucket name and the key name from an HTTP path and possibly Host header
/// ///
/// S3 internally manages only buckets and keys. This function splits /// S3 internally manages only buckets and keys. This function splits
/// an HTTP path to get the corresponding bucket name and key. /// an HTTP path to get the corresponding bucket name and key.
fn parse_bucket_key<'a>( fn parse_bucket_key<'a>(
path: &'a str, path: &'a str,
host: Option<&'a str>, host_bucket: Option<&'a str>,
root: Option<&str>,
) -> Result<(&'a str, Option<&'a str>), Error> { ) -> Result<(&'a str, Option<&'a str>), Error> {
let path = path.trim_start_matches('/'); let path = path.trim_start_matches('/');
if host.and(root).is_some() { if let Some(bucket) = host_bucket {
if let Some(bucket) = host_to_bucket(host.unwrap(), root.unwrap()) {
if !path.is_empty() { if !path.is_empty() {
return Ok((bucket, Some(path))); return Ok((bucket, Some(path)));
} else { } else {
return Ok((bucket, None)); return Ok((bucket, None));
} }
} }
}
let (bucket, key) = match path.find('/') { let (bucket, key) = match path.find('/') {
Some(i) => { Some(i) => {
@ -304,7 +304,7 @@ mod tests {
#[test] #[test]
fn parse_bucket_containing_a_key() -> Result<(), Error> { fn parse_bucket_containing_a_key() -> Result<(), Error> {
let (bucket, key) = parse_bucket_key("/my_bucket/a/super/file.jpg", None, None)?; let (bucket, key) = parse_bucket_key("/my_bucket/a/super/file.jpg", None)?;
assert_eq!(bucket, "my_bucket"); assert_eq!(bucket, "my_bucket");
assert_eq!(key.expect("key must be set"), "a/super/file.jpg"); assert_eq!(key.expect("key must be set"), "a/super/file.jpg");
Ok(()) Ok(())
@ -312,10 +312,10 @@ mod tests {
#[test] #[test]
fn parse_bucket_containing_no_key() -> Result<(), Error> { fn parse_bucket_containing_no_key() -> Result<(), Error> {
let (bucket, key) = parse_bucket_key("/my_bucket/", None, None)?; let (bucket, key) = parse_bucket_key("/my_bucket/", None)?;
assert_eq!(bucket, "my_bucket"); assert_eq!(bucket, "my_bucket");
assert!(key.is_none()); assert!(key.is_none());
let (bucket, key) = parse_bucket_key("/my_bucket", None, None)?; let (bucket, key) = parse_bucket_key("/my_bucket", None)?;
assert_eq!(bucket, "my_bucket"); assert_eq!(bucket, "my_bucket");
assert!(key.is_none()); assert!(key.is_none());
Ok(()) Ok(())
@ -323,74 +323,30 @@ mod tests {
#[test] #[test]
fn parse_bucket_containing_no_bucket() { fn parse_bucket_containing_no_bucket() {
let parsed = parse_bucket_key("", None, None); let parsed = parse_bucket_key("", None);
assert!(parsed.is_err()); assert!(parsed.is_err());
let parsed = parse_bucket_key("/", None, None); let parsed = parse_bucket_key("/", None);
assert!(parsed.is_err()); assert!(parsed.is_err());
let parsed = parse_bucket_key("////", None, None); let parsed = parse_bucket_key("////", None);
assert!(parsed.is_err()); assert!(parsed.is_err());
} }
#[test] #[test]
fn parse_bucket_with_vhost_and_key() -> Result<(), Error> { fn parse_bucket_with_vhost_and_key() -> Result<(), Error> {
let (bucket, key) = parse_bucket_key( let (bucket, key) = parse_bucket_key("/a/super/file.jpg", Some("my-bucket"))?;
"/a/super/file.jpg",
Some("my-bucket.garage.tld"),
Some("garage.tld"),
)?;
assert_eq!(bucket, "my-bucket"); assert_eq!(bucket, "my-bucket");
assert_eq!(key.expect("key must be set"), "a/super/file.jpg"); assert_eq!(key.expect("key must be set"), "a/super/file.jpg");
let (bucket, key) = parse_bucket_key(
"/my_bucket/a/super/file.jpg",
Some("not-garage.tld"),
Some("garage.tld"),
)?;
assert_eq!(bucket, "my_bucket");
assert_eq!(key.expect("key must be set"), "a/super/file.jpg");
Ok(()) Ok(())
} }
#[test] #[test]
fn parse_bucket_with_vhost_no_key() -> Result<(), Error> { fn parse_bucket_with_vhost_no_key() -> Result<(), Error> {
let (bucket, key) = parse_bucket_key("", Some("my-bucket.garage.tld"), Some("garage.tld"))?; let (bucket, key) = parse_bucket_key("", Some("my-bucket"))?;
assert_eq!(bucket, "my-bucket"); assert_eq!(bucket, "my-bucket");
assert!(key.is_none()); assert!(key.is_none());
let (bucket, key) = let (bucket, key) = parse_bucket_key("/", Some("my-bucket"))?;
parse_bucket_key("/", Some("my-bucket.garage.tld"), Some("garage.tld"))?;
assert_eq!(bucket, "my-bucket"); assert_eq!(bucket, "my-bucket");
assert!(key.is_none()); assert!(key.is_none());
Ok(()) Ok(())
} }
#[test]
fn parse_bucket_missmatch_vhost() {
let test_vec = [
"/my_bucket/a/super/file.jpg",
"/my_bucket/",
"/my_bucket",
"",
"/",
"////",
];
let eq = |l, r| match (l, r) {
(Ok(l), Ok(r)) => l == r,
(Err(_), Err(_)) => true,
_ => false,
};
for test in test_vec {
assert!(eq(
parse_bucket_key(test, None, None),
parse_bucket_key(test, Some("bucket.garage.tld"), None)
));
assert!(eq(
parse_bucket_key(test, None, None),
parse_bucket_key(test, None, Some("garage.tld"))
));
assert!(eq(
parse_bucket_key(test, None, None),
parse_bucket_key(test, Some("not-garage.tld"), Some("garage.tld"))
));
}
}
} }