diff --git a/src/api/api_server.rs b/src/api/api_server.rs index 77587de8..322ea298 100644 --- a/src/api/api_server.rs +++ b/src/api/api_server.rs @@ -111,9 +111,13 @@ async fn handler_inner(garage: Arc, req: Request) -> Result, req: Request) -> Result handle_options(&req, &bucket).await, Endpoint::HeadObject { key, part_number, .. } => handle_head(garage, &req, bucket_id, &key, part_number).await, diff --git a/src/api/s3_cors.rs b/src/api/s3_cors.rs index cde66079..e3deaeda 100644 --- a/src/api/s3_cors.rs +++ b/src/api/s3_cors.rs @@ -100,7 +100,63 @@ pub async fn handle_put_cors( .body(Body::empty())?) } -pub async fn handle_options(req: &Request, bucket: &Bucket) -> Result, Error> { +pub async fn handle_options_s3api( + garage: Arc, + req: &Request, + bucket_name: Option, +) -> Result, Error> { + // FIXME: CORS rules of buckets with local aliases are + // not taken into account. + + // If the bucket name is a global bucket name, + // we try to apply the CORS rules of that bucket. + // If a user has a local bucket name that has + // the same name, its CORS rules won't be applied + // and will be shadowed by the rules of the globally + // existing bucket (but this is inevitable because + // OPTIONS calls are not auhtenticated). + if let Some(bn) = bucket_name { + let helper = garage.bucket_helper(); + let bucket_id = helper.resolve_global_bucket_name(&bn).await?; + if let Some(id) = bucket_id { + let bucket = garage + .bucket_table + .get(&EmptyKey, &id) + .await? + .filter(|b| !b.state.is_deleted()) + .ok_or(Error::NoSuchBucket)?; + handle_options_for_bucket(req, &bucket) + } else { + // If there is a bucket name in the request, but that name + // does not correspond to a global alias for a bucket, + // then it's either a non-existing bucket or a local bucket. + // We have no way of knowing, because the request is not + // authenticated and thus we can't resolve local aliases. + // We take the permissive approach of allowing everything, + // because we don't want to prevent web apps that use + // local bucket names from making API calls. + Ok(Response::builder() + .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header(ACCESS_CONTROL_ALLOW_METHODS, "*") + .status(StatusCode::OK) + .body(Body::empty())?) + } + } else { + // If there is no bucket name in the request, + // we are doing a ListBuckets call, which we want to allow + // for all origins. + Ok(Response::builder() + .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header(ACCESS_CONTROL_ALLOW_METHODS, "GET") + .status(StatusCode::OK) + .body(Body::empty())?) + } +} + +pub fn handle_options_for_bucket( + req: &Request, + bucket: &Bucket, +) -> Result, Error> { let origin = req .headers() .get("Origin") diff --git a/src/api/s3_router.rs b/src/api/s3_router.rs index 2a68d79e..95a7eceb 100644 --- a/src/api/s3_router.rs +++ b/src/api/s3_router.rs @@ -414,8 +414,7 @@ pub enum Endpoint { // It's intended to be used with HTML forms, using a multipart/form-data body. // It works a lot like presigned requests, but everything is in the form instead // of being query parameters of the URL, so authenticating it is a bit different. - PostObject { - }, + PostObject, }} impl Endpoint { @@ -430,7 +429,11 @@ impl Endpoint { let path = uri.path().trim_start_matches('/'); let query = uri.query(); if bucket.is_none() && path.is_empty() { - return Ok((Self::ListBuckets, None)); + if *req.method() == Method::OPTIONS { + return Ok((Self::Options, None)); + } else { + return Ok((Self::ListBuckets, None)); + } } let (bucket, key) = if let Some(bucket) = bucket { diff --git a/src/api/signature/payload.rs b/src/api/signature/payload.rs index a6c32e41..85687a23 100644 --- a/src/api/signature/payload.rs +++ b/src/api/signature/payload.rs @@ -60,6 +60,9 @@ pub async fn check_payload_signature( let (_, scope) = parse_credential(&authorization.credential)?; let string_to_sign = string_to_sign(&authorization.date, &scope, &canonical_request); + trace!("canonical request:\n{}", canonical_request); + trace!("string to sign:\n{}", string_to_sign); + let key = verify_v4( garage, &authorization.credential, diff --git a/src/web/web_server.rs b/src/web/web_server.rs index 6c7d7c35..80d2feb9 100644 --- a/src/web/web_server.rs +++ b/src/web/web_server.rs @@ -13,7 +13,7 @@ use crate::error::*; use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError}; use garage_api::helpers::{authority_to_host, host_to_bucket}; -use garage_api::s3_cors::{add_cors_headers, find_matching_cors_rule, handle_options}; +use garage_api::s3_cors::{add_cors_headers, find_matching_cors_rule, handle_options_for_bucket}; use garage_api::s3_get::{handle_get, handle_head}; use garage_model::garage::Garage; @@ -133,7 +133,7 @@ async fn serve_file(garage: Arc, req: &Request) -> Result handle_options(req, &bucket).await, + Method::OPTIONS => handle_options_for_bucket(req, &bucket), Method::HEAD => handle_head(garage.clone(), req, bucket_id, &key, None).await, Method::GET => handle_get(garage.clone(), req, bucket_id, &key, None).await, _ => Err(ApiError::BadRequest("HTTP method not supported".into())),