forked from Deuxfleurs/garage
split s3/cors.rs into also common/cors.rs
This commit is contained in:
parent
84f1db91c4
commit
afa28706e5
11 changed files with 179 additions and 186 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -1409,8 +1409,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"quick-xml",
|
|
||||||
"roxmltree",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1464,8 +1462,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"quick-xml",
|
|
||||||
"roxmltree",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1497,7 +1493,6 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"garage_api_common",
|
"garage_api_common",
|
||||||
"garage_api_s3",
|
|
||||||
"garage_block",
|
"garage_block",
|
||||||
"garage_model",
|
"garage_model",
|
||||||
"garage_net",
|
"garage_net",
|
||||||
|
@ -1521,8 +1516,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"quick-xml",
|
|
||||||
"roxmltree",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -58,13 +58,11 @@ hyper = { workspace = true, default-features = false, features = ["server", "htt
|
||||||
hyper-util.workspace = true
|
hyper-util.workspace = true
|
||||||
multer.workspace = true
|
multer.workspace = true
|
||||||
percent-encoding.workspace = true
|
percent-encoding.workspace = true
|
||||||
roxmltree.workspace = true
|
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_bytes.workspace = true
|
serde_bytes.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
quick-xml.workspace = true
|
|
||||||
|
|
||||||
opentelemetry.workspace = true
|
opentelemetry.workspace = true
|
||||||
opentelemetry-prometheus = { workspace = true, optional = true }
|
opentelemetry-prometheus = { workspace = true, optional = true }
|
||||||
|
|
|
@ -57,13 +57,11 @@ hyper = { workspace = true, default-features = false, features = ["server", "htt
|
||||||
hyper-util.workspace = true
|
hyper-util.workspace = true
|
||||||
multer.workspace = true
|
multer.workspace = true
|
||||||
percent-encoding.workspace = true
|
percent-encoding.workspace = true
|
||||||
roxmltree.workspace = true
|
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_bytes.workspace = true
|
serde_bytes.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
quick-xml.workspace = true
|
|
||||||
|
|
||||||
opentelemetry.workspace = true
|
opentelemetry.workspace = true
|
||||||
opentelemetry-prometheus = { workspace = true, optional = true }
|
opentelemetry-prometheus = { workspace = true, optional = true }
|
||||||
|
|
170
src/api/common/cors.rs
Normal file
170
src/api/common/cors.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use http::header::{
|
||||||
|
ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD,
|
||||||
|
};
|
||||||
|
use hyper::{body::Body, body::Incoming as IncomingBody, Request, Response, StatusCode};
|
||||||
|
|
||||||
|
use garage_model::bucket_table::{BucketParams, CorsRule as GarageCorsRule};
|
||||||
|
use garage_model::garage::Garage;
|
||||||
|
|
||||||
|
use crate::common_error::{
|
||||||
|
helper_error_as_internal, CommonError, OkOrBadRequest, OkOrInternalError,
|
||||||
|
};
|
||||||
|
use crate::helpers::*;
|
||||||
|
|
||||||
|
pub fn find_matching_cors_rule<'a>(
|
||||||
|
bucket_params: &'a BucketParams,
|
||||||
|
req: &Request<impl Body>,
|
||||||
|
) -> Result<Option<&'a GarageCorsRule>, CommonError> {
|
||||||
|
if let Some(cors_config) = bucket_params.cors_config.get() {
|
||||||
|
if let Some(origin) = req.headers().get("Origin") {
|
||||||
|
let origin = origin.to_str()?;
|
||||||
|
let request_headers = match req.headers().get(ACCESS_CONTROL_REQUEST_HEADERS) {
|
||||||
|
Some(h) => h.to_str()?.split(',').map(|h| h.trim()).collect::<Vec<_>>(),
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
return Ok(cors_config.iter().find(|rule| {
|
||||||
|
cors_rule_matches(rule, origin, req.method().as_ref(), request_headers.iter())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cors_rule_matches<'a, HI, S>(
|
||||||
|
rule: &GarageCorsRule,
|
||||||
|
origin: &'a str,
|
||||||
|
method: &'a str,
|
||||||
|
mut request_headers: HI,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
HI: Iterator<Item = S>,
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
rule.allow_origins.iter().any(|x| x == "*" || x == origin)
|
||||||
|
&& rule.allow_methods.iter().any(|x| x == "*" || x == method)
|
||||||
|
&& request_headers.all(|h| {
|
||||||
|
rule.allow_headers
|
||||||
|
.iter()
|
||||||
|
.any(|x| x == "*" || x == h.as_ref())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_cors_headers(
|
||||||
|
resp: &mut Response<impl Body>,
|
||||||
|
rule: &GarageCorsRule,
|
||||||
|
) -> Result<(), http::header::InvalidHeaderValue> {
|
||||||
|
let h = resp.headers_mut();
|
||||||
|
h.insert(
|
||||||
|
ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
rule.allow_origins.join(", ").parse()?,
|
||||||
|
);
|
||||||
|
h.insert(
|
||||||
|
ACCESS_CONTROL_ALLOW_METHODS,
|
||||||
|
rule.allow_methods.join(", ").parse()?,
|
||||||
|
);
|
||||||
|
h.insert(
|
||||||
|
ACCESS_CONTROL_ALLOW_HEADERS,
|
||||||
|
rule.allow_headers.join(", ").parse()?,
|
||||||
|
);
|
||||||
|
h.insert(
|
||||||
|
ACCESS_CONTROL_EXPOSE_HEADERS,
|
||||||
|
rule.expose_headers.join(", ").parse()?,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_options_api(
|
||||||
|
garage: Arc<Garage>,
|
||||||
|
req: &Request<IncomingBody>,
|
||||||
|
bucket_name: Option<String>,
|
||||||
|
) -> Result<Response<EmptyBody>, CommonError> {
|
||||||
|
// 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
|
||||||
|
.map_err(helper_error_as_internal)?;
|
||||||
|
if let Some(id) = bucket_id {
|
||||||
|
let bucket = garage
|
||||||
|
.bucket_helper()
|
||||||
|
.get_existing_bucket(id)
|
||||||
|
.await
|
||||||
|
.map_err(helper_error_as_internal)?;
|
||||||
|
let bucket_params = bucket.state.into_option().unwrap();
|
||||||
|
handle_options_for_bucket(req, &bucket_params)
|
||||||
|
} 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(EmptyBody::new())?)
|
||||||
|
}
|
||||||
|
} 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(EmptyBody::new())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_options_for_bucket(
|
||||||
|
req: &Request<IncomingBody>,
|
||||||
|
bucket_params: &BucketParams,
|
||||||
|
) -> Result<Response<EmptyBody>, CommonError> {
|
||||||
|
let origin = req
|
||||||
|
.headers()
|
||||||
|
.get("Origin")
|
||||||
|
.ok_or_bad_request("Missing Origin header")?
|
||||||
|
.to_str()?;
|
||||||
|
let request_method = req
|
||||||
|
.headers()
|
||||||
|
.get(ACCESS_CONTROL_REQUEST_METHOD)
|
||||||
|
.ok_or_bad_request("Missing Access-Control-Request-Method header")?
|
||||||
|
.to_str()?;
|
||||||
|
let request_headers = match req.headers().get(ACCESS_CONTROL_REQUEST_HEADERS) {
|
||||||
|
Some(h) => h.to_str()?.split(',').map(|h| h.trim()).collect::<Vec<_>>(),
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cors_config) = bucket_params.cors_config.get() {
|
||||||
|
let matching_rule = cors_config
|
||||||
|
.iter()
|
||||||
|
.find(|rule| cors_rule_matches(rule, origin, request_method, request_headers.iter()));
|
||||||
|
if let Some(rule) = matching_rule {
|
||||||
|
let mut resp = Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.body(EmptyBody::new())?;
|
||||||
|
add_cors_headers(&mut resp, rule).ok_or_internal_error("Invalid CORS configuration")?;
|
||||||
|
return Ok(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(CommonError::Forbidden(
|
||||||
|
"This CORS request is not allowed.".into(),
|
||||||
|
))
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ extern crate tracing;
|
||||||
|
|
||||||
pub mod common_error;
|
pub mod common_error;
|
||||||
|
|
||||||
|
pub mod cors;
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
pub mod generic_server;
|
pub mod generic_server;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod router_macros;
|
pub mod router_macros;
|
||||||
/// This mode is public only to help testing. Don't expect stability here
|
|
||||||
pub mod signature;
|
pub mod signature;
|
||||||
|
|
|
@ -21,7 +21,6 @@ garage_net.workspace = true
|
||||||
garage_util.workspace = true
|
garage_util.workspace = true
|
||||||
garage_rpc.workspace = true
|
garage_rpc.workspace = true
|
||||||
garage_api_common.workspace = true
|
garage_api_common.workspace = true
|
||||||
garage_api_s3.workspace = true
|
|
||||||
|
|
||||||
aes-gcm.workspace = true
|
aes-gcm.workspace = true
|
||||||
argon2.workspace = true
|
argon2.workspace = true
|
||||||
|
@ -59,13 +58,11 @@ hyper = { workspace = true, default-features = false, features = ["server", "htt
|
||||||
hyper-util.workspace = true
|
hyper-util.workspace = true
|
||||||
multer.workspace = true
|
multer.workspace = true
|
||||||
percent-encoding.workspace = true
|
percent-encoding.workspace = true
|
||||||
roxmltree.workspace = true
|
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_bytes.workspace = true
|
serde_bytes.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
quick-xml.workspace = true
|
|
||||||
|
|
||||||
opentelemetry.workspace = true
|
opentelemetry.workspace = true
|
||||||
opentelemetry-prometheus = { workspace = true, optional = true }
|
opentelemetry-prometheus = { workspace = true, optional = true }
|
||||||
|
|
|
@ -12,10 +12,10 @@ use garage_util::socket_address::UnixOrTCPSocketAddress;
|
||||||
|
|
||||||
use garage_model::garage::Garage;
|
use garage_model::garage::Garage;
|
||||||
|
|
||||||
|
use garage_api_common::cors::*;
|
||||||
use garage_api_common::generic_server::*;
|
use garage_api_common::generic_server::*;
|
||||||
use garage_api_common::helpers::*;
|
use garage_api_common::helpers::*;
|
||||||
use garage_api_common::signature::verify_request;
|
use garage_api_common::signature::verify_request;
|
||||||
use garage_api_s3::cors::*;
|
|
||||||
|
|
||||||
use crate::batch::*;
|
use crate::batch::*;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
|
|
@ -14,6 +14,7 @@ use garage_util::socket_address::UnixOrTCPSocketAddress;
|
||||||
use garage_model::garage::Garage;
|
use garage_model::garage::Garage;
|
||||||
use garage_model::key_table::Key;
|
use garage_model::key_table::Key;
|
||||||
|
|
||||||
|
use garage_api_common::cors::*;
|
||||||
use garage_api_common::generic_server::*;
|
use garage_api_common::generic_server::*;
|
||||||
use garage_api_common::helpers::*;
|
use garage_api_common::helpers::*;
|
||||||
use garage_api_common::signature::verify_request;
|
use garage_api_common::signature::verify_request;
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use quick_xml::de::from_reader;
|
use quick_xml::de::from_reader;
|
||||||
|
|
||||||
use http::header::{
|
use hyper::{header::HeaderName, Method, Request, Response, StatusCode};
|
||||||
ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
||||||
ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD,
|
|
||||||
};
|
|
||||||
use hyper::{
|
|
||||||
body::Body, body::Incoming as IncomingBody, header::HeaderName, Method, Request, Response,
|
|
||||||
StatusCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use garage_model::bucket_table::{Bucket, BucketParams, CorsRule as GarageCorsRule};
|
use garage_model::bucket_table::{Bucket, CorsRule as GarageCorsRule};
|
||||||
use garage_model::garage::Garage;
|
|
||||||
use garage_util::data::*;
|
use garage_util::data::*;
|
||||||
|
|
||||||
use garage_api_common::common_error::{helper_error_as_internal, CommonError};
|
|
||||||
use garage_api_common::helpers::*;
|
use garage_api_common::helpers::*;
|
||||||
use garage_api_common::signature::verify_signed_content;
|
use garage_api_common::signature::verify_signed_content;
|
||||||
|
|
||||||
|
@ -101,161 +90,6 @@ pub async fn handle_put_cors(
|
||||||
.body(empty_body())?)
|
.body(empty_body())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_options_api(
|
|
||||||
garage: Arc<Garage>,
|
|
||||||
req: &Request<IncomingBody>,
|
|
||||||
bucket_name: Option<String>,
|
|
||||||
) -> Result<Response<EmptyBody>, CommonError> {
|
|
||||||
// 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
|
|
||||||
.map_err(helper_error_as_internal)?;
|
|
||||||
if let Some(id) = bucket_id {
|
|
||||||
let bucket = garage
|
|
||||||
.bucket_helper()
|
|
||||||
.get_existing_bucket(id)
|
|
||||||
.await
|
|
||||||
.map_err(helper_error_as_internal)?;
|
|
||||||
let bucket_params = bucket.state.into_option().unwrap();
|
|
||||||
handle_options_for_bucket(req, &bucket_params)
|
|
||||||
} 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(EmptyBody::new())?)
|
|
||||||
}
|
|
||||||
} 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(EmptyBody::new())?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_options_for_bucket(
|
|
||||||
req: &Request<IncomingBody>,
|
|
||||||
bucket_params: &BucketParams,
|
|
||||||
) -> Result<Response<EmptyBody>, CommonError> {
|
|
||||||
let origin = req
|
|
||||||
.headers()
|
|
||||||
.get("Origin")
|
|
||||||
.ok_or_bad_request("Missing Origin header")?
|
|
||||||
.to_str()?;
|
|
||||||
let request_method = req
|
|
||||||
.headers()
|
|
||||||
.get(ACCESS_CONTROL_REQUEST_METHOD)
|
|
||||||
.ok_or_bad_request("Missing Access-Control-Request-Method header")?
|
|
||||||
.to_str()?;
|
|
||||||
let request_headers = match req.headers().get(ACCESS_CONTROL_REQUEST_HEADERS) {
|
|
||||||
Some(h) => h.to_str()?.split(',').map(|h| h.trim()).collect::<Vec<_>>(),
|
|
||||||
None => vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(cors_config) = bucket_params.cors_config.get() {
|
|
||||||
let matching_rule = cors_config
|
|
||||||
.iter()
|
|
||||||
.find(|rule| cors_rule_matches(rule, origin, request_method, request_headers.iter()));
|
|
||||||
if let Some(rule) = matching_rule {
|
|
||||||
let mut resp = Response::builder()
|
|
||||||
.status(StatusCode::OK)
|
|
||||||
.body(EmptyBody::new())?;
|
|
||||||
add_cors_headers(&mut resp, rule).ok_or_internal_error("Invalid CORS configuration")?;
|
|
||||||
return Ok(resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(CommonError::Forbidden(
|
|
||||||
"This CORS request is not allowed.".into(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_matching_cors_rule<'a>(
|
|
||||||
bucket_params: &'a BucketParams,
|
|
||||||
req: &Request<impl Body>,
|
|
||||||
) -> Result<Option<&'a GarageCorsRule>, Error> {
|
|
||||||
if let Some(cors_config) = bucket_params.cors_config.get() {
|
|
||||||
if let Some(origin) = req.headers().get("Origin") {
|
|
||||||
let origin = origin.to_str()?;
|
|
||||||
let request_headers = match req.headers().get(ACCESS_CONTROL_REQUEST_HEADERS) {
|
|
||||||
Some(h) => h.to_str()?.split(',').map(|h| h.trim()).collect::<Vec<_>>(),
|
|
||||||
None => vec![],
|
|
||||||
};
|
|
||||||
return Ok(cors_config.iter().find(|rule| {
|
|
||||||
cors_rule_matches(rule, origin, req.method().as_ref(), request_headers.iter())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cors_rule_matches<'a, HI, S>(
|
|
||||||
rule: &GarageCorsRule,
|
|
||||||
origin: &'a str,
|
|
||||||
method: &'a str,
|
|
||||||
mut request_headers: HI,
|
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
HI: Iterator<Item = S>,
|
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
rule.allow_origins.iter().any(|x| x == "*" || x == origin)
|
|
||||||
&& rule.allow_methods.iter().any(|x| x == "*" || x == method)
|
|
||||||
&& request_headers.all(|h| {
|
|
||||||
rule.allow_headers
|
|
||||||
.iter()
|
|
||||||
.any(|x| x == "*" || x == h.as_ref())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_cors_headers(
|
|
||||||
resp: &mut Response<impl Body>,
|
|
||||||
rule: &GarageCorsRule,
|
|
||||||
) -> Result<(), http::header::InvalidHeaderValue> {
|
|
||||||
let h = resp.headers_mut();
|
|
||||||
h.insert(
|
|
||||||
ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
||||||
rule.allow_origins.join(", ").parse()?,
|
|
||||||
);
|
|
||||||
h.insert(
|
|
||||||
ACCESS_CONTROL_ALLOW_METHODS,
|
|
||||||
rule.allow_methods.join(", ").parse()?,
|
|
||||||
);
|
|
||||||
h.insert(
|
|
||||||
ACCESS_CONTROL_ALLOW_HEADERS,
|
|
||||||
rule.allow_headers.join(", ").parse()?,
|
|
||||||
);
|
|
||||||
h.insert(
|
|
||||||
ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
||||||
rule.expose_headers.join(", ").parse()?,
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- SERIALIZATION AND DESERIALIZATION TO/FROM S3 XML ----
|
// ---- SERIALIZATION AND DESERIALIZATION TO/FROM S3 XML ----
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
|
|
@ -16,12 +16,12 @@ use serde::Deserialize;
|
||||||
use garage_model::garage::Garage;
|
use garage_model::garage::Garage;
|
||||||
use garage_model::s3::object_table::*;
|
use garage_model::s3::object_table::*;
|
||||||
|
|
||||||
|
use garage_api_common::cors::*;
|
||||||
use garage_api_common::helpers::*;
|
use garage_api_common::helpers::*;
|
||||||
use garage_api_common::signature::payload::{verify_v4, Authorization};
|
use garage_api_common::signature::payload::{verify_v4, Authorization};
|
||||||
|
|
||||||
use crate::api_server::ResBody;
|
use crate::api_server::ResBody;
|
||||||
use crate::checksum::*;
|
use crate::checksum::*;
|
||||||
use crate::cors::*;
|
|
||||||
use crate::encryption::EncryptionParams;
|
use crate::encryption::EncryptionParams;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::put::{get_headers, save_stream, ChecksumMode};
|
use crate::put::{get_headers, save_stream, ChecksumMode};
|
||||||
|
|
|
@ -20,9 +20,11 @@ use opentelemetry::{
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
|
||||||
|
use garage_api_common::cors::{
|
||||||
|
add_cors_headers, find_matching_cors_rule, handle_options_for_bucket,
|
||||||
|
};
|
||||||
use garage_api_common::generic_server::{server_loop, UnixListenerOn};
|
use garage_api_common::generic_server::{server_loop, UnixListenerOn};
|
||||||
use garage_api_common::helpers::*;
|
use garage_api_common::helpers::*;
|
||||||
use garage_api_s3::cors::{add_cors_headers, find_matching_cors_rule, handle_options_for_bucket};
|
|
||||||
use garage_api_s3::error::{
|
use garage_api_s3::error::{
|
||||||
CommonErrorDerivative, Error as ApiError, OkOrBadRequest, OkOrInternalError,
|
CommonErrorDerivative, Error as ApiError, OkOrBadRequest, OkOrInternalError,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue