More permissive OPTIONS on S3 API
This commit is contained in:
parent
97f245f218
commit
8a5bbc3b0b
3 changed files with 50 additions and 20 deletions
|
@ -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?;
|
||||||
|
|
|
@ -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)?;
|
|
||||||
garage
|
|
||||||
.bucket_table
|
.bucket_table
|
||||||
.get(&EmptyKey, &bucket_id)
|
.get(&EmptyKey, &id)
|
||||||
.await?
|
.await?
|
||||||
.filter(|b| !b.state.is_deleted())
|
.filter(|b| !b.state.is_deleted())
|
||||||
.ok_or(Error::NoSuchBucket)?
|
.ok_or(Error::NoSuchBucket)?;
|
||||||
|
handle_options_for_bucket(req, &bucket)
|
||||||
} else {
|
} else {
|
||||||
// The only supported API call that doesn't use a bucket name is ListBuckets,
|
// If there is a bucket name in the request, but that name
|
||||||
// which we want to allow in all cases
|
// does not correspond to a global alias for a bucket,
|
||||||
return Ok(Response::builder()
|
// 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_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")
|
||||||
|
|
|
@ -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())),
|
||||||
|
|
Loading…
Reference in a new issue