More permissive OPTIONS on S3 API
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
continuous-integration/drone Build is passing

This commit is contained in:
Alex 2022-03-01 11:15:16 +01:00
parent 97f245f218
commit 8a5bbc3b0b
Signed by: lx
GPG key ID: 0E496D15096376BE
3 changed files with 50 additions and 20 deletions

View file

@ -116,7 +116,7 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
return handle_post_object(garage, req, bucket_name.unwrap()).await; return handle_post_object(garage, req, bucket_name.unwrap()).await;
} }
if let Endpoint::Options = endpoint { if let Endpoint::Options = endpoint {
return handle_options(garage, &req, bucket_name).await; return handle_options_s3api(garage, &req, bucket_name).await;
} }
let (api_key, content_sha256) = check_payload_signature(&garage, &req).await?; let (api_key, content_sha256) = check_payload_signature(&garage, &req).await?;

View file

@ -100,33 +100,63 @@ pub async fn handle_put_cors(
.body(Body::empty())?) .body(Body::empty())?)
} }
pub async fn handle_options( pub async fn handle_options_s3api(
garage: Arc<Garage>, garage: Arc<Garage>,
req: &Request<Body>, req: &Request<Body>,
bucket_name: Option<String>, bucket_name: Option<String>,
) -> Result<Response<Body>, Error> { ) -> Result<Response<Body>, Error> {
let bucket = if let Some(bn) = bucket_name { // 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 helper = garage.bucket_helper();
let bucket_id = helper let bucket_id = helper.resolve_global_bucket_name(&bn).await?;
.resolve_global_bucket_name(&bn) if let Some(id) = bucket_id {
.await? let bucket = garage
.ok_or(Error::NoSuchBucket)?; .bucket_table
garage .get(&EmptyKey, &id)
.bucket_table .await?
.get(&EmptyKey, &bucket_id) .filter(|b| !b.state.is_deleted())
.await? .ok_or(Error::NoSuchBucket)?;
.filter(|b| !b.state.is_deleted()) handle_options_for_bucket(req, &bucket)
.ok_or(Error::NoSuchBucket)? } 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 { } else {
// The only supported API call that doesn't use a bucket name is ListBuckets, // If there is no bucket name in the request,
// which we want to allow in all cases // we are doing a ListBuckets call, which we want to allow
return Ok(Response::builder() // for all origins.
Ok(Response::builder()
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
.header(ACCESS_CONTROL_ALLOW_METHODS, "GET") .header(ACCESS_CONTROL_ALLOW_METHODS, "GET")
.status(StatusCode::OK) .status(StatusCode::OK)
.body(Body::empty())?); .body(Body::empty())?)
}; }
}
pub fn handle_options_for_bucket(
req: &Request<Body>,
bucket: &Bucket,
) -> Result<Response<Body>, Error> {
let origin = req let origin = req
.headers() .headers()
.get("Origin") .get("Origin")

View file

@ -13,7 +13,7 @@ use crate::error::*;
use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError}; use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError};
use garage_api::helpers::{authority_to_host, host_to_bucket}; 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_api::s3_get::{handle_get, handle_head};
use garage_model::garage::Garage; use garage_model::garage::Garage;
@ -133,7 +133,7 @@ async fn serve_file(garage: Arc<Garage>, req: &Request<Body>) -> Result<Response
); );
let ret_doc = match *req.method() { let ret_doc = match *req.method() {
Method::OPTIONS => handle_options(garage.clone(), req, Some(bucket_name.to_string())).await, Method::OPTIONS => handle_options_for_bucket(req, &bucket),
Method::HEAD => handle_head(garage.clone(), req, bucket_id, &key, None).await, Method::HEAD => handle_head(garage.clone(), req, bucket_id, &key, None).await,
Method::GET => handle_get(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())), _ => Err(ApiError::BadRequest("HTTP method not supported".into())),