mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-11 14:56:53 +00:00
Merge branch 'main' into next-v2
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
[package]
|
||||
name = "garage_api_admin"
|
||||
version = "1.0.1"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3.0"
|
||||
description = "Admin API server crate for the Garage object store"
|
||||
repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage"
|
||||
readme = "../../README.md"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
garage_model.workspace = true
|
||||
garage_table.workspace = true
|
||||
garage_util.workspace = true
|
||||
garage_rpc.workspace = true
|
||||
garage_api_common.workspace = true
|
||||
|
||||
argon2.workspace = true
|
||||
async-trait.workspace = true
|
||||
err-derive.workspace = true
|
||||
hex.workspace = true
|
||||
paste.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
futures.workspace = true
|
||||
tokio.workspace = true
|
||||
http.workspace = true
|
||||
hyper = { workspace = true, default-features = false, features = ["server", "http1"] }
|
||||
url.workspace = true
|
||||
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
opentelemetry.workspace = true
|
||||
opentelemetry-prometheus = { workspace = true, optional = true }
|
||||
prometheus = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
metrics = [ "opentelemetry-prometheus", "prometheus" ]
|
||||
@@ -8,10 +8,11 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use garage_model::garage::Garage;
|
||||
|
||||
use crate::admin::error::Error;
|
||||
use crate::admin::macros::*;
|
||||
use crate::admin::EndpointHandler;
|
||||
use crate::helpers::is_default;
|
||||
use garage_api_common::helpers::is_default;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::macros::*;
|
||||
use crate::EndpointHandler;
|
||||
|
||||
// This generates the following:
|
||||
//
|
||||
|
||||
@@ -19,15 +19,15 @@ use garage_model::garage::Garage;
|
||||
use garage_util::error::Error as GarageError;
|
||||
use garage_util::socket_address::UnixOrTCPSocketAddress;
|
||||
|
||||
use crate::generic_server::*;
|
||||
use garage_api_common::generic_server::*;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::admin::api::*;
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::router_v0;
|
||||
use crate::admin::router_v1;
|
||||
use crate::admin::Authorization;
|
||||
use crate::admin::EndpointHandler;
|
||||
use crate::helpers::*;
|
||||
use crate::api::*;
|
||||
use crate::error::*;
|
||||
use crate::router_v0;
|
||||
use crate::router_v1;
|
||||
use crate::Authorization;
|
||||
use crate::EndpointHandler;
|
||||
|
||||
pub type ResBody = BoxBody<Error>;
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ use garage_model::permission::*;
|
||||
use garage_model::s3::mpu_table;
|
||||
use garage_model::s3::object_table::*;
|
||||
|
||||
use crate::admin::api::*;
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::EndpointHandler;
|
||||
use crate::common_error::CommonError;
|
||||
use garage_api_common::common_error::CommonError;
|
||||
|
||||
use crate::api::*;
|
||||
use crate::error::*;
|
||||
use crate::EndpointHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl EndpointHandler for ListBucketsRequest {
|
||||
|
||||
@@ -10,9 +10,9 @@ use garage_rpc::layout;
|
||||
|
||||
use garage_model::garage::Garage;
|
||||
|
||||
use crate::admin::api::*;
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::EndpointHandler;
|
||||
use crate::api::*;
|
||||
use crate::error::*;
|
||||
use crate::EndpointHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl EndpointHandler for GetClusterStatusRequest {
|
||||
|
||||
+8
-15
@@ -6,17 +6,19 @@ use hyper::{HeaderMap, StatusCode};
|
||||
|
||||
pub use garage_model::helper::error::Error as HelperError;
|
||||
|
||||
use crate::common_error::CommonError;
|
||||
pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError};
|
||||
use crate::generic_server::ApiError;
|
||||
use crate::helpers::*;
|
||||
use garage_api_common::common_error::{commonErrorDerivative, CommonError};
|
||||
pub use garage_api_common::common_error::{
|
||||
CommonErrorDerivative, OkOrBadRequest, OkOrInternalError,
|
||||
};
|
||||
use garage_api_common::generic_server::ApiError;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
/// Errors of this crate
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(display = "{}", _0)]
|
||||
/// Error from common error
|
||||
Common(CommonError),
|
||||
Common(#[error(source)] CommonError),
|
||||
|
||||
// Category: cannot process
|
||||
/// The API access key does not exist
|
||||
@@ -31,14 +33,7 @@ pub enum Error {
|
||||
KeyAlreadyExists(String),
|
||||
}
|
||||
|
||||
impl<T> From<T> for Error
|
||||
where
|
||||
CommonError: From<T>,
|
||||
{
|
||||
fn from(err: T) -> Self {
|
||||
Error::Common(CommonError::from(err))
|
||||
}
|
||||
}
|
||||
commonErrorDerivative!(Error);
|
||||
|
||||
/// FIXME: helper errors are transformed into their corresponding variants
|
||||
/// in the Error struct, but in many case a helper error should be considered
|
||||
@@ -53,8 +48,6 @@ impl From<HelperError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonErrorDerivative for Error {}
|
||||
|
||||
impl Error {
|
||||
pub fn code(&self) -> &'static str {
|
||||
match self {
|
||||
|
||||
@@ -8,9 +8,9 @@ use garage_table::*;
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::key_table::*;
|
||||
|
||||
use crate::admin::api::*;
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::EndpointHandler;
|
||||
use crate::api::*;
|
||||
use crate::error::*;
|
||||
use crate::EndpointHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl EndpointHandler for ListKeysRequest {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod api_server;
|
||||
mod error;
|
||||
mod macros;
|
||||
@@ -2,8 +2,9 @@ use std::borrow::Cow;
|
||||
|
||||
use hyper::{Method, Request};
|
||||
|
||||
use crate::admin::error::*;
|
||||
use crate::router_macros::*;
|
||||
use garage_api_common::router_macros::*;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
router_match! {@func
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ use std::borrow::Cow;
|
||||
|
||||
use hyper::{Method, Request};
|
||||
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::router_v0;
|
||||
use crate::router_macros::*;
|
||||
use garage_api_common::router_macros::*;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::router_v0;
|
||||
|
||||
router_match! {@func
|
||||
|
||||
|
||||
@@ -4,12 +4,13 @@ use hyper::body::Incoming as IncomingBody;
|
||||
use hyper::{Method, Request};
|
||||
use paste::paste;
|
||||
|
||||
use crate::admin::api::*;
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::router_v1;
|
||||
use crate::admin::Authorization;
|
||||
use crate::helpers::*;
|
||||
use crate::router_macros::*;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::router_macros::*;
|
||||
|
||||
use crate::api::*;
|
||||
use crate::error::*;
|
||||
use crate::router_v1;
|
||||
use crate::Authorization;
|
||||
|
||||
impl AdminApiRequest {
|
||||
/// Determine which S3 endpoint a request is for using the request, and a bucket which was
|
||||
|
||||
@@ -10,11 +10,12 @@ use hyper::{Response, StatusCode};
|
||||
use garage_model::garage::Garage;
|
||||
use garage_rpc::system::ClusterHealthStatus;
|
||||
|
||||
use crate::admin::api::{CheckDomainRequest, HealthRequest, OptionsRequest};
|
||||
use crate::admin::api_server::ResBody;
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::EndpointHandler;
|
||||
use crate::helpers::*;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api::{CheckDomainRequest, HealthRequest, OptionsRequest};
|
||||
use crate::api_server::ResBody;
|
||||
use crate::error::*;
|
||||
use crate::EndpointHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl EndpointHandler for OptionsRequest {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "garage_api_common"
|
||||
version = "1.0.1"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3.0"
|
||||
description = "Common functions for the API server crates for the Garage object store"
|
||||
repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage"
|
||||
readme = "../../README.md"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
garage_model.workspace = true
|
||||
garage_table.workspace = true
|
||||
garage_util.workspace = true
|
||||
|
||||
async-trait.workspace = true
|
||||
bytes.workspace = true
|
||||
chrono.workspace = true
|
||||
crypto-common.workspace = true
|
||||
err-derive.workspace = true
|
||||
hex.workspace = true
|
||||
hmac.workspace = true
|
||||
idna.workspace = true
|
||||
tracing.workspace = true
|
||||
nom.workspace = true
|
||||
pin-project.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
futures.workspace = true
|
||||
tokio.workspace = true
|
||||
http.workspace = true
|
||||
http-body-util.workspace = true
|
||||
hyper = { workspace = true, default-features = false, features = ["server", "http1"] }
|
||||
hyper-util.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
opentelemetry.workspace = true
|
||||
@@ -57,6 +57,35 @@ pub enum CommonError {
|
||||
InvalidBucketName(String),
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! commonErrorDerivative {
|
||||
( $error_struct: ident ) => {
|
||||
impl From<garage_util::error::Error> for $error_struct {
|
||||
fn from(err: garage_util::error::Error) -> Self {
|
||||
Self::Common(CommonError::InternalError(err))
|
||||
}
|
||||
}
|
||||
impl From<http::Error> for $error_struct {
|
||||
fn from(err: http::Error) -> Self {
|
||||
Self::Common(CommonError::Http(err))
|
||||
}
|
||||
}
|
||||
impl From<hyper::Error> for $error_struct {
|
||||
fn from(err: hyper::Error) -> Self {
|
||||
Self::Common(CommonError::Hyper(err))
|
||||
}
|
||||
}
|
||||
impl From<hyper::header::ToStrError> for $error_struct {
|
||||
fn from(err: hyper::header::ToStrError) -> Self {
|
||||
Self::Common(CommonError::InvalidHeader(err))
|
||||
}
|
||||
}
|
||||
impl CommonErrorDerivative for $error_struct {}
|
||||
};
|
||||
}
|
||||
|
||||
pub use commonErrorDerivative;
|
||||
|
||||
impl CommonError {
|
||||
pub fn http_status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
@@ -118,14 +147,14 @@ impl TryFrom<HelperError> for CommonError {
|
||||
/// This is used for helper functions that might return InvalidBucketName
|
||||
/// or NoSuchBucket for instance, and we want to pass that error
|
||||
/// up to our caller.
|
||||
pub(crate) fn pass_helper_error(err: HelperError) -> CommonError {
|
||||
pub fn pass_helper_error(err: HelperError) -> CommonError {
|
||||
match CommonError::try_from(err) {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("Helper error `{}` should hot have happenned here", e),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn helper_error_as_internal(err: HelperError) -> CommonError {
|
||||
pub fn helper_error_as_internal(err: HelperError) -> CommonError {
|
||||
match err {
|
||||
HelperError::Internal(e) => CommonError::InternalError(e),
|
||||
e => CommonError::InternalError(GarageError::Message(e.to_string())),
|
||||
@@ -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(),
|
||||
))
|
||||
}
|
||||
@@ -37,7 +37,7 @@ use garage_util::socket_address::UnixOrTCPSocketAddress;
|
||||
|
||||
use crate::helpers::{BoxBody, ErrorBody};
|
||||
|
||||
pub(crate) trait ApiEndpoint: Send + Sync + 'static {
|
||||
pub trait ApiEndpoint: Send + Sync + 'static {
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
fn add_span_attributes(&self, span: SpanRef<'_>);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ pub trait ApiError: std::error::Error + Send + Sync + 'static {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub(crate) trait ApiHandler: Send + Sync + 'static {
|
||||
pub trait ApiHandler: Send + Sync + 'static {
|
||||
const API_NAME: &'static str;
|
||||
const API_NAME_DISPLAY: &'static str;
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) trait ApiHandler: Send + Sync + 'static {
|
||||
) -> Result<Response<BoxBody<Self::Error>>, Self::Error>;
|
||||
}
|
||||
|
||||
pub(crate) struct ApiServer<A: ApiHandler> {
|
||||
pub struct ApiServer<A: ApiHandler> {
|
||||
region: String,
|
||||
api_handler: A,
|
||||
|
||||
@@ -363,9 +363,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CustomApiErrorBody {
|
||||
pub(crate) code: String,
|
||||
pub(crate) message: String,
|
||||
pub(crate) region: String,
|
||||
pub(crate) path: String,
|
||||
pub struct CustomApiErrorBody {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
pub region: String,
|
||||
pub path: String,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Crate for serving a S3 compatible API
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod common_error;
|
||||
|
||||
pub mod cors;
|
||||
pub mod encoding;
|
||||
pub mod generic_server;
|
||||
pub mod helpers;
|
||||
pub mod router_macros;
|
||||
pub mod signature;
|
||||
@@ -1,5 +1,6 @@
|
||||
/// This macro is used to generate very repetitive match {} blocks in this module
|
||||
/// It is _not_ made to be used anywhere else
|
||||
#[macro_export]
|
||||
macro_rules! router_match {
|
||||
(@match $enum:expr , [ $($endpoint:ident,)* ]) => {{
|
||||
// usage: router_match {@match my_enum, [ VariantWithField1, VariantWithField2 ..] }
|
||||
@@ -210,6 +211,7 @@ macro_rules! router_match {
|
||||
|
||||
/// This macro is used to generate part of the code in this module. It must be called only one, and
|
||||
/// is useless outside of this module.
|
||||
#[macro_export]
|
||||
macro_rules! generateQueryParameters {
|
||||
(
|
||||
keywords: [ $($kw_param:expr => $kw_name: ident),* ],
|
||||
@@ -298,5 +300,5 @@ macro_rules! generateQueryParameters {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use generateQueryParameters;
|
||||
pub(crate) use router_match;
|
||||
pub use generateQueryParameters;
|
||||
pub use router_match;
|
||||
@@ -518,7 +518,7 @@ impl Authorization {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parse_form(params: &HeaderMap) -> Result<Self, Error> {
|
||||
pub fn parse_form(params: &HeaderMap) -> Result<Self, Error> {
|
||||
let algorithm = params
|
||||
.get(X_AMZ_ALGORITHM)
|
||||
.ok_or_bad_request("Missing X-Amz-Algorithm header")?
|
||||
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "garage_api_k2v"
|
||||
version = "1.0.1"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3.0"
|
||||
description = "K2V API server crate for the Garage object store"
|
||||
repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage"
|
||||
readme = "../../README.md"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
garage_model = { workspace = true, features = [ "k2v" ] }
|
||||
garage_table.workspace = true
|
||||
garage_util = { workspace = true, features = [ "k2v" ] }
|
||||
garage_api_common.workspace = true
|
||||
|
||||
async-trait.workspace = true
|
||||
base64.workspace = true
|
||||
err-derive.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
futures.workspace = true
|
||||
tokio.workspace = true
|
||||
http.workspace = true
|
||||
http-body-util.workspace = true
|
||||
hyper = { workspace = true, default-features = false, features = ["server", "http1"] }
|
||||
percent-encoding.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
opentelemetry.workspace = true
|
||||
+11
-12
@@ -13,26 +13,25 @@ use garage_util::socket_address::UnixOrTCPSocketAddress;
|
||||
|
||||
use garage_model::garage::Garage;
|
||||
|
||||
use crate::generic_server::*;
|
||||
use crate::k2v::error::*;
|
||||
use garage_api_common::cors::*;
|
||||
use garage_api_common::generic_server::*;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_request;
|
||||
|
||||
use crate::signature::verify_request;
|
||||
use crate::batch::*;
|
||||
use crate::error::*;
|
||||
use crate::index::*;
|
||||
use crate::item::*;
|
||||
use crate::router::Endpoint;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::k2v::batch::*;
|
||||
use crate::k2v::index::*;
|
||||
use crate::k2v::item::*;
|
||||
use crate::k2v::router::Endpoint;
|
||||
use crate::s3::cors::*;
|
||||
|
||||
pub use crate::signature::streaming::ReqBody;
|
||||
pub use garage_api_common::signature::streaming::ReqBody;
|
||||
pub type ResBody = BoxBody<Error>;
|
||||
|
||||
pub struct K2VApiServer {
|
||||
garage: Arc<Garage>,
|
||||
}
|
||||
|
||||
pub(crate) struct K2VApiEndpoint {
|
||||
pub struct K2VApiEndpoint {
|
||||
bucket_name: String,
|
||||
endpoint: Endpoint,
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ use garage_table::{EnumerationOrder, TableSchema};
|
||||
|
||||
use garage_model::k2v::item_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::k2v::api_server::{ReqBody, ResBody};
|
||||
use crate::k2v::error::*;
|
||||
use crate::k2v::item::parse_causality_token;
|
||||
use crate::k2v::range::read_range;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::error::*;
|
||||
use crate::item::parse_causality_token;
|
||||
use crate::range::read_range;
|
||||
|
||||
pub async fn handle_insert_batch(
|
||||
ctx: ReqCtx,
|
||||
|
||||
+10
-17
@@ -2,19 +2,21 @@ use err_derive::Error;
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::{HeaderMap, StatusCode};
|
||||
|
||||
use crate::common_error::CommonError;
|
||||
pub(crate) use crate::common_error::{helper_error_as_internal, pass_helper_error};
|
||||
pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError};
|
||||
use crate::generic_server::ApiError;
|
||||
use crate::helpers::*;
|
||||
use crate::signature::error::Error as SignatureError;
|
||||
use garage_api_common::common_error::{commonErrorDerivative, CommonError};
|
||||
pub(crate) use garage_api_common::common_error::{helper_error_as_internal, pass_helper_error};
|
||||
pub use garage_api_common::common_error::{
|
||||
CommonErrorDerivative, OkOrBadRequest, OkOrInternalError,
|
||||
};
|
||||
use garage_api_common::generic_server::ApiError;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::error::Error as SignatureError;
|
||||
|
||||
/// Errors of this crate
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(display = "{}", _0)]
|
||||
/// Error from common error
|
||||
Common(CommonError),
|
||||
Common(#[error(source)] CommonError),
|
||||
|
||||
// Category: cannot process
|
||||
/// Authorization Header Malformed
|
||||
@@ -42,16 +44,7 @@ pub enum Error {
|
||||
InvalidUtf8Str(#[error(source)] std::str::Utf8Error),
|
||||
}
|
||||
|
||||
impl<T> From<T> for Error
|
||||
where
|
||||
CommonError: From<T>,
|
||||
{
|
||||
fn from(err: T) -> Self {
|
||||
Error::Common(CommonError::from(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonErrorDerivative for Error {}
|
||||
commonErrorDerivative!(Error);
|
||||
|
||||
impl From<SignatureError> for Error {
|
||||
fn from(err: SignatureError) -> Self {
|
||||
|
||||
@@ -5,10 +5,11 @@ use garage_table::util::*;
|
||||
|
||||
use garage_model::k2v::item_table::{BYTES, CONFLICTS, ENTRIES, VALUES};
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::k2v::api_server::ResBody;
|
||||
use crate::k2v::error::*;
|
||||
use crate::k2v::range::read_range;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api_server::ResBody;
|
||||
use crate::error::*;
|
||||
use crate::range::read_range;
|
||||
|
||||
pub async fn handle_read_index(
|
||||
ctx: ReqCtx,
|
||||
|
||||
+4
-3
@@ -6,9 +6,10 @@ use hyper::{Request, Response, StatusCode};
|
||||
use garage_model::k2v::causality::*;
|
||||
use garage_model::k2v::item_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::k2v::api_server::{ReqBody, ResBody};
|
||||
use crate::k2v::error::*;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::error::*;
|
||||
|
||||
pub const X_GARAGE_CAUSALITY_TOKEN: &str = "X-Garage-Causality-Token";
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod api_server;
|
||||
mod error;
|
||||
mod router;
|
||||
@@ -7,8 +7,9 @@ use std::sync::Arc;
|
||||
use garage_table::replication::TableShardedReplication;
|
||||
use garage_table::*;
|
||||
|
||||
use crate::helpers::key_after_prefix;
|
||||
use crate::k2v::error::*;
|
||||
use garage_api_common::helpers::key_after_prefix;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
/// Read range in a Garage table.
|
||||
/// Returns (entries, more?, nextStart)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::k2v::error::*;
|
||||
use crate::error::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use hyper::{Method, Request};
|
||||
|
||||
use crate::helpers::Authorization;
|
||||
use crate::router_macros::{generateQueryParameters, router_match};
|
||||
use garage_api_common::helpers::Authorization;
|
||||
use garage_api_common::router_macros::{generateQueryParameters, router_match};
|
||||
|
||||
router_match! {@func
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//! Crate for serving a S3 compatible API
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod common_error;
|
||||
|
||||
mod encoding;
|
||||
pub mod generic_server;
|
||||
pub mod helpers;
|
||||
mod router_macros;
|
||||
/// This mode is public only to help testing. Don't expect stability here
|
||||
pub mod signature;
|
||||
|
||||
pub mod admin;
|
||||
#[cfg(feature = "k2v")]
|
||||
pub mod k2v;
|
||||
pub mod s3;
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "garage_api"
|
||||
name = "garage_api_s3"
|
||||
version = "1.0.1"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||
edition = "2018"
|
||||
@@ -20,9 +20,9 @@ garage_block.workspace = true
|
||||
garage_net.workspace = true
|
||||
garage_util.workspace = true
|
||||
garage_rpc.workspace = true
|
||||
garage_api_common.workspace = true
|
||||
|
||||
aes-gcm.workspace = true
|
||||
argon2.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-trait.workspace = true
|
||||
base64.workspace = true
|
||||
@@ -30,21 +30,15 @@ bytes.workspace = true
|
||||
chrono.workspace = true
|
||||
crc32fast.workspace = true
|
||||
crc32c.workspace = true
|
||||
crypto-common.workspace = true
|
||||
err-derive.workspace = true
|
||||
hex.workspace = true
|
||||
hmac.workspace = true
|
||||
idna.workspace = true
|
||||
tracing.workspace = true
|
||||
md-5.workspace = true
|
||||
nom.workspace = true
|
||||
paste.workspace = true
|
||||
pin-project.workspace = true
|
||||
sha1.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
futures.workspace = true
|
||||
futures-util.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
tokio-util.workspace = true
|
||||
@@ -55,21 +49,13 @@ httpdate.workspace = true
|
||||
http-range.workspace = true
|
||||
http-body-util.workspace = true
|
||||
hyper = { workspace = true, default-features = false, features = ["server", "http1"] }
|
||||
hyper-util.workspace = true
|
||||
multer.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
roxmltree.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
serde.workspace = true
|
||||
serde_bytes.workspace = true
|
||||
serde_json.workspace = true
|
||||
quick-xml.workspace = true
|
||||
|
||||
opentelemetry.workspace = true
|
||||
opentelemetry-prometheus = { workspace = true, optional = true }
|
||||
prometheus = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
k2v = [ "garage_util/k2v", "garage_model/k2v" ]
|
||||
metrics = [ "opentelemetry-prometheus", "prometheus" ]
|
||||
+19
-19
@@ -15,33 +15,33 @@ use garage_util::socket_address::UnixOrTCPSocketAddress;
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::key_table::Key;
|
||||
|
||||
use crate::generic_server::*;
|
||||
use crate::s3::error::*;
|
||||
use garage_api_common::cors::*;
|
||||
use garage_api_common::generic_server::*;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_request;
|
||||
|
||||
use crate::signature::verify_request;
|
||||
use crate::bucket::*;
|
||||
use crate::copy::*;
|
||||
use crate::cors::*;
|
||||
use crate::delete::*;
|
||||
use crate::error::*;
|
||||
use crate::get::*;
|
||||
use crate::lifecycle::*;
|
||||
use crate::list::*;
|
||||
use crate::multipart::*;
|
||||
use crate::post_object::handle_post_object;
|
||||
use crate::put::*;
|
||||
use crate::router::Endpoint;
|
||||
use crate::website::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::bucket::*;
|
||||
use crate::s3::copy::*;
|
||||
use crate::s3::cors::*;
|
||||
use crate::s3::delete::*;
|
||||
use crate::s3::get::*;
|
||||
use crate::s3::lifecycle::*;
|
||||
use crate::s3::list::*;
|
||||
use crate::s3::multipart::*;
|
||||
use crate::s3::post_object::handle_post_object;
|
||||
use crate::s3::put::*;
|
||||
use crate::s3::router::Endpoint;
|
||||
use crate::s3::website::*;
|
||||
|
||||
pub use crate::signature::streaming::ReqBody;
|
||||
pub use garage_api_common::signature::streaming::ReqBody;
|
||||
pub type ResBody = BoxBody<Error>;
|
||||
|
||||
pub struct S3ApiServer {
|
||||
garage: Arc<Garage>,
|
||||
}
|
||||
|
||||
pub(crate) struct S3ApiEndpoint {
|
||||
pub struct S3ApiEndpoint {
|
||||
bucket_name: Option<String>,
|
||||
endpoint: Endpoint,
|
||||
}
|
||||
|
||||
@@ -13,12 +13,13 @@ use garage_util::crdt::*;
|
||||
use garage_util::data::*;
|
||||
use garage_util::time::*;
|
||||
|
||||
use crate::common_error::CommonError;
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::xml as s3_xml;
|
||||
use crate::signature::verify_signed_content;
|
||||
use garage_api_common::common_error::CommonError;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_signed_content;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::error::*;
|
||||
use crate::xml as s3_xml;
|
||||
|
||||
pub fn handle_get_bucket_location(ctx: ReqCtx) -> Result<Response<ResBody>, Error> {
|
||||
let ReqCtx { garage, .. } = ctx;
|
||||
|
||||
@@ -15,7 +15,7 @@ use garage_util::error::OkOrMessage;
|
||||
|
||||
use garage_model::s3::object_table::*;
|
||||
|
||||
use crate::s3::error::*;
|
||||
use crate::error::*;
|
||||
|
||||
pub const X_AMZ_CHECKSUM_ALGORITHM: HeaderName =
|
||||
HeaderName::from_static("x-amz-checksum-algorithm");
|
||||
|
||||
+11
-10
@@ -20,15 +20,16 @@ use garage_model::s3::mpu_table::*;
|
||||
use garage_model::s3::object_table::*;
|
||||
use garage_model::s3::version_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::checksum::*;
|
||||
use crate::s3::encryption::EncryptionParams;
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::get::full_object_byte_stream;
|
||||
use crate::s3::multipart;
|
||||
use crate::s3::put::{get_headers, save_stream, ChecksumMode, SaveStreamResult};
|
||||
use crate::s3::xml::{self as s3_xml, xmlns_tag};
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::checksum::*;
|
||||
use crate::encryption::EncryptionParams;
|
||||
use crate::error::*;
|
||||
use crate::get::full_object_byte_stream;
|
||||
use crate::multipart;
|
||||
use crate::put::{get_headers, save_stream, ChecksumMode, SaveStreamResult};
|
||||
use crate::xml::{self as s3_xml, xmlns_tag};
|
||||
|
||||
// -------- CopyObject ---------
|
||||
|
||||
@@ -862,7 +863,7 @@ pub struct CopyPartResult {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::s3::xml::to_xml_with_header;
|
||||
use crate::xml::to_xml_with_header;
|
||||
|
||||
#[test]
|
||||
fn copy_object_result() -> Result<(), Error> {
|
||||
|
||||
+9
-174
@@ -1,31 +1,21 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use quick_xml::de::from_reader;
|
||||
|
||||
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, header::HeaderName, Method, Request, Response,
|
||||
StatusCode,
|
||||
};
|
||||
use hyper::{header::HeaderName, Method, Request, Response, StatusCode};
|
||||
|
||||
use http_body_util::BodyExt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::common_error::{helper_error_as_internal, CommonError};
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::xml::{to_xml_with_header, xmlns_tag, IntValue, Value};
|
||||
use crate::signature::verify_signed_content;
|
||||
|
||||
use garage_model::bucket_table::{Bucket, BucketParams, CorsRule as GarageCorsRule};
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::bucket_table::{Bucket, CorsRule as GarageCorsRule};
|
||||
use garage_util::data::*;
|
||||
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_signed_content;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::error::*;
|
||||
use crate::xml::{to_xml_with_header, xmlns_tag, IntValue, Value};
|
||||
|
||||
pub async fn handle_get_cors(ctx: ReqCtx) -> Result<Response<ResBody>, Error> {
|
||||
let ReqCtx { bucket_params, .. } = ctx;
|
||||
if let Some(cors) = bucket_params.cors_config.get() {
|
||||
@@ -100,161 +90,6 @@ pub async fn handle_put_cors(
|
||||
.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 ----
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
||||
@@ -5,12 +5,13 @@ use garage_util::data::*;
|
||||
|
||||
use garage_model::s3::object_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::put::next_timestamp;
|
||||
use crate::s3::xml as s3_xml;
|
||||
use crate::signature::verify_signed_content;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_signed_content;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::error::*;
|
||||
use crate::put::next_timestamp;
|
||||
use crate::xml as s3_xml;
|
||||
|
||||
async fn handle_delete_internal(ctx: &ReqCtx, key: &str) -> Result<(Uuid, Uuid), Error> {
|
||||
let ReqCtx {
|
||||
|
||||
@@ -28,9 +28,10 @@ use garage_util::migrate::Migrate;
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::s3::object_table::{ObjectVersionEncryption, ObjectVersionMetaInner};
|
||||
|
||||
use crate::common_error::*;
|
||||
use crate::s3::checksum::Md5Checksum;
|
||||
use crate::s3::error::Error;
|
||||
use garage_api_common::common_error::*;
|
||||
|
||||
use crate::checksum::Md5Checksum;
|
||||
use crate::error::Error;
|
||||
|
||||
const X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM: HeaderName =
|
||||
HeaderName::from_static("x-amz-server-side-encryption-customer-algorithm");
|
||||
|
||||
+17
-18
@@ -6,20 +6,28 @@ use hyper::{HeaderMap, StatusCode};
|
||||
|
||||
use garage_model::helper::error::Error as HelperError;
|
||||
|
||||
pub(crate) use crate::common_error::pass_helper_error;
|
||||
use crate::common_error::{helper_error_as_internal, CommonError};
|
||||
pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError};
|
||||
use crate::generic_server::ApiError;
|
||||
use crate::helpers::*;
|
||||
use crate::s3::xml as s3_xml;
|
||||
use crate::signature::error::Error as SignatureError;
|
||||
pub(crate) use garage_api_common::common_error::pass_helper_error;
|
||||
|
||||
use garage_api_common::common_error::{
|
||||
commonErrorDerivative, helper_error_as_internal, CommonError,
|
||||
};
|
||||
|
||||
pub use garage_api_common::common_error::{
|
||||
CommonErrorDerivative, OkOrBadRequest, OkOrInternalError,
|
||||
};
|
||||
|
||||
use garage_api_common::generic_server::ApiError;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::error::Error as SignatureError;
|
||||
|
||||
use crate::xml as s3_xml;
|
||||
|
||||
/// Errors of this crate
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(display = "{}", _0)]
|
||||
/// Error from common error
|
||||
Common(CommonError),
|
||||
Common(#[error(source)] CommonError),
|
||||
|
||||
// Category: cannot process
|
||||
/// Authorization Header Malformed
|
||||
@@ -81,14 +89,7 @@ pub enum Error {
|
||||
NotImplemented(String),
|
||||
}
|
||||
|
||||
impl<T> From<T> for Error
|
||||
where
|
||||
CommonError: From<T>,
|
||||
{
|
||||
fn from(err: T) -> Self {
|
||||
Error::Common(CommonError::from(err))
|
||||
}
|
||||
}
|
||||
commonErrorDerivative!(Error);
|
||||
|
||||
// Helper errors are always passed as internal errors by default.
|
||||
// To pass the specific error code back to the client, use `pass_helper_error`.
|
||||
@@ -98,8 +99,6 @@ impl From<HelperError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonErrorDerivative for Error {}
|
||||
|
||||
impl From<roxmltree::Error> for Error {
|
||||
fn from(err: roxmltree::Error) -> Self {
|
||||
Self::InvalidXml(format!("{}", err))
|
||||
|
||||
+6
-5
@@ -25,11 +25,12 @@ use garage_model::garage::Garage;
|
||||
use garage_model::s3::object_table::*;
|
||||
use garage_model::s3::version_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::ResBody;
|
||||
use crate::s3::checksum::{add_checksum_response_headers, X_AMZ_CHECKSUM_MODE};
|
||||
use crate::s3::encryption::EncryptionParams;
|
||||
use crate::s3::error::*;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api_server::ResBody;
|
||||
use crate::checksum::{add_checksum_response_headers, X_AMZ_CHECKSUM_MODE};
|
||||
use crate::encryption::EncryptionParams;
|
||||
use crate::error::*;
|
||||
|
||||
const X_AMZ_MP_PARTS_COUNT: &str = "x-amz-mp-parts-count";
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod api_server;
|
||||
pub mod error;
|
||||
|
||||
@@ -5,11 +5,12 @@ use hyper::{Request, Response, StatusCode};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::xml::{to_xml_with_header, xmlns_tag, IntValue, Value};
|
||||
use crate::signature::verify_signed_content;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_signed_content;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::error::*;
|
||||
use crate::xml::{to_xml_with_header, xmlns_tag, IntValue, Value};
|
||||
|
||||
use garage_model::bucket_table::{
|
||||
parse_lifecycle_date, Bucket, LifecycleExpiration as GarageLifecycleExpiration,
|
||||
|
||||
+8
-7
@@ -13,13 +13,14 @@ use garage_model::s3::object_table::*;
|
||||
|
||||
use garage_table::EnumerationOrder;
|
||||
|
||||
use crate::encoding::*;
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::encryption::EncryptionParams;
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::multipart as s3_multipart;
|
||||
use crate::s3::xml as s3_xml;
|
||||
use garage_api_common::encoding::*;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::encryption::EncryptionParams;
|
||||
use crate::error::*;
|
||||
use crate::multipart as s3_multipart;
|
||||
use crate::xml as s3_xml;
|
||||
|
||||
const DUMMY_NAME: &str = "Dummy Key";
|
||||
const DUMMY_KEY: &str = "GKDummyKey";
|
||||
|
||||
@@ -15,14 +15,15 @@ use garage_model::s3::mpu_table::*;
|
||||
use garage_model::s3::object_table::*;
|
||||
use garage_model::s3::version_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::checksum::*;
|
||||
use crate::s3::encryption::EncryptionParams;
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::put::*;
|
||||
use crate::s3::xml as s3_xml;
|
||||
use crate::signature::verify_signed_content;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_signed_content;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::checksum::*;
|
||||
use crate::encryption::EncryptionParams;
|
||||
use crate::error::*;
|
||||
use crate::put::*;
|
||||
use crate::xml as s3_xml;
|
||||
|
||||
// ----
|
||||
|
||||
|
||||
@@ -16,15 +16,16 @@ use serde::Deserialize;
|
||||
use garage_model::garage::Garage;
|
||||
use garage_model::s3::object_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::ResBody;
|
||||
use crate::s3::checksum::*;
|
||||
use crate::s3::cors::*;
|
||||
use crate::s3::encryption::EncryptionParams;
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::put::{get_headers, save_stream, ChecksumMode};
|
||||
use crate::s3::xml as s3_xml;
|
||||
use crate::signature::payload::{verify_v4, Authorization};
|
||||
use garage_api_common::cors::*;
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::payload::{verify_v4, Authorization};
|
||||
|
||||
use crate::api_server::ResBody;
|
||||
use crate::checksum::*;
|
||||
use crate::encryption::EncryptionParams;
|
||||
use crate::error::*;
|
||||
use crate::put::{get_headers, save_stream, ChecksumMode};
|
||||
use crate::xml as s3_xml;
|
||||
|
||||
pub async fn handle_post_object(
|
||||
garage: Arc<Garage>,
|
||||
|
||||
+6
-5
@@ -30,11 +30,12 @@ use garage_model::s3::block_ref_table::*;
|
||||
use garage_model::s3::object_table::*;
|
||||
use garage_model::s3::version_table::*;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::checksum::*;
|
||||
use crate::s3::encryption::EncryptionParams;
|
||||
use crate::s3::error::*;
|
||||
use garage_api_common::helpers::*;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::checksum::*;
|
||||
use crate::encryption::EncryptionParams;
|
||||
use crate::error::*;
|
||||
|
||||
const PUT_BLOCKS_MAX_PARALLEL: usize = 3;
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ use std::borrow::Cow;
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::{HeaderMap, Method, Request};
|
||||
|
||||
use crate::helpers::Authorization;
|
||||
use crate::router_macros::{generateQueryParameters, router_match};
|
||||
use crate::s3::error::*;
|
||||
use garage_api_common::helpers::Authorization;
|
||||
use garage_api_common::router_macros::{generateQueryParameters, router_match};
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
router_match! {@func
|
||||
|
||||
|
||||
@@ -4,15 +4,16 @@ use http_body_util::BodyExt;
|
||||
use hyper::{Request, Response, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::s3::api_server::{ReqBody, ResBody};
|
||||
use crate::s3::error::*;
|
||||
use crate::s3::xml::{to_xml_with_header, xmlns_tag, IntValue, Value};
|
||||
use crate::signature::verify_signed_content;
|
||||
|
||||
use garage_model::bucket_table::*;
|
||||
use garage_util::data::*;
|
||||
|
||||
use garage_api_common::helpers::*;
|
||||
use garage_api_common::signature::verify_signed_content;
|
||||
|
||||
use crate::api_server::{ReqBody, ResBody};
|
||||
use crate::error::*;
|
||||
use crate::xml::{to_xml_with_header, xmlns_tag, IntValue, Value};
|
||||
|
||||
pub async fn handle_get_website(ctx: ReqCtx) -> Result<Response<ResBody>, Error> {
|
||||
let ReqCtx { bucket_params, .. } = ctx;
|
||||
if let Some(website) = bucket_params.website_config.get() {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
use quick_xml::se::to_string;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::s3::error::Error as ApiError;
|
||||
use crate::error::Error as ApiError;
|
||||
|
||||
pub fn to_xml_with_header<T: Serialize>(x: &T) -> Result<String, ApiError> {
|
||||
let mut xml = r#"<?xml version="1.0" encoding="UTF-8"?>"#.to_string();
|
||||
|
||||
Reference in New Issue
Block a user