more complete admin API #298

Merged
lx merged 48 commits from admin-api into main 2022-05-24 10:16:40 +00:00
14 changed files with 203 additions and 13 deletions
Showing only changes of commit ec16d166f9 - Show all commits

View file

@ -12,7 +12,7 @@ use garage_util::error::Error as GarageError;
use garage_model::garage::Garage; use garage_model::garage::Garage;
use crate::s3::error::*; use crate::k2v::error::*;
use crate::generic_server::*; use crate::generic_server::*;
use crate::signature::payload::check_payload_signature; use crate::signature::payload::check_payload_signature;
@ -84,7 +84,8 @@ impl ApiHandler for K2VApiServer {
// The OPTIONS method is procesed early, before we even check for an API key // The OPTIONS method is procesed early, before we even check for an API key
if let Endpoint::Options = endpoint { if let Endpoint::Options = endpoint {
return handle_options_s3api(garage, &req, Some(bucket_name)).await; return Ok(handle_options_s3api(garage, &req, Some(bucket_name)).await
.ok_or_bad_request("Error handling OPTIONS")?);
} }
let (api_key, mut content_sha256) = check_payload_signature(&garage, "k2v", &req).await?; let (api_key, mut content_sha256) = check_payload_signature(&garage, "k2v", &req).await?;
@ -126,7 +127,8 @@ impl ApiHandler for K2VApiServer {
// are always preflighted, i.e. the browser should make // are always preflighted, i.e. the browser should make
// an OPTIONS call before to check it is allowed // an OPTIONS call before to check it is allowed
let matching_cors_rule = match *req.method() { let matching_cors_rule = match *req.method() {
Method::GET | Method::HEAD | Method::POST => find_matching_cors_rule(&bucket, &req)?, Method::GET | Method::HEAD | Method::POST => find_matching_cors_rule(&bucket, &req)
.ok_or_internal_error("Error looking up CORS rule")?,
_ => None, _ => None,
}; };

View file

@ -12,7 +12,7 @@ use garage_model::garage::Garage;
use garage_model::k2v::causality::*; use garage_model::k2v::causality::*;
use garage_model::k2v::item_table::*; use garage_model::k2v::item_table::*;
use crate::s3::error::*; use crate::k2v::error::*;
use crate::helpers::*; use crate::helpers::*;
use crate::k2v::range::read_range; use crate::k2v::range::read_range;

118
src/api/k2v/error.rs Normal file
View file

@ -0,0 +1,118 @@
use err_derive::Error;
use hyper::header::HeaderValue;
use hyper::{Body, HeaderMap, StatusCode};
use garage_model::helper::error::Error as HelperError;
use crate::common_error::CommonError;
pub use crate::common_error::{OkOrBadRequest, OkOrInternalError};
use crate::generic_server::ApiError;
use crate::signature::error::Error as SignatureError;
/// Errors of this crate
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "{}", _0)]
/// Error from common error
CommonError(CommonError),
// Category: cannot process
/// No proper api key was used, or the signature was invalid
#[error(display = "Forbidden: {}", _0)]
Forbidden(String),
/// Authorization Header Malformed
#[error(display = "Authorization header malformed, expected scope: {}", _0)]
AuthorizationHeaderMalformed(String),
/// The object requested don't exists
#[error(display = "Key not found")]
NoSuchKey,
/// The bucket requested don't exists
#[error(display = "Bucket not found")]
NoSuchBucket,
/// Some base64 encoded data was badly encoded
#[error(display = "Invalid base64: {}", _0)]
InvalidBase64(#[error(source)] base64::DecodeError),
/// The client sent a header with invalid value
#[error(display = "Invalid header value: {}", _0)]
InvalidHeader(#[error(source)] hyper::header::ToStrError),
/// The client asked for an invalid return format (invalid Accept header)
#[error(display = "Not acceptable: {}", _0)]
NotAcceptable(String),
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
#[error(display = "Invalid UTF-8: {}", _0)]
InvalidUtf8Str(#[error(source)] std::str::Utf8Error),
}
impl<T> From<T> for Error
where
CommonError: From<T>,
{
fn from(err: T) -> Self {
Error::CommonError(CommonError::from(err))
}
}
impl From<HelperError> for Error {
fn from(err: HelperError) -> Self {
match err {
HelperError::Internal(i) => Self::CommonError(CommonError::InternalError(i)),
HelperError::BadRequest(b) => Self::CommonError(CommonError::BadRequest(b)),
e => Self::CommonError(CommonError::BadRequest(format!("{}", e))),
}
}
}
impl From<SignatureError> for Error {
fn from(err: SignatureError) -> Self {
match err {
SignatureError::CommonError(c) => Self::CommonError(c),
SignatureError::AuthorizationHeaderMalformed(c) => Self::AuthorizationHeaderMalformed(c),
SignatureError::Forbidden(f) => Self::Forbidden(f),
SignatureError::InvalidUtf8Str(i) => Self::InvalidUtf8Str(i),
SignatureError::InvalidHeader(h) => Self::InvalidHeader(h),
}
}
}
impl Error {
//pub fn internal_error<M: ToString>(msg: M) -> Self {
// Self::CommonError(CommonError::InternalError(GarageError::Message(
// msg.to_string(),
// )))
//}
pub fn bad_request<M: ToString>(msg: M) -> Self {
Self::CommonError(CommonError::BadRequest(msg.to_string()))
}
}
impl ApiError for Error {
/// Get the HTTP status code that best represents the meaning of the error for the client
fn http_status_code(&self) -> StatusCode {
match self {
Error::CommonError(c) => c.http_status_code(),
Error::NoSuchKey | Error::NoSuchBucket => StatusCode::NOT_FOUND,
Error::Forbidden(_) => StatusCode::FORBIDDEN,
Error::NotAcceptable(_) => StatusCode::NOT_ACCEPTABLE,
_ => StatusCode::BAD_REQUEST,
}
}
fn add_http_headers(&self, _header_map: &mut HeaderMap<HeaderValue>) {
// nothing
}
fn http_body(&self, garage_region: &str, path: &str) -> Body {
Body::from(format!(
"ERROR: {}\n\ngarage region: {}\npath: {}",
self, garage_region, path
))
}
}

View file

@ -12,7 +12,7 @@ use garage_table::util::*;
use garage_model::garage::Garage; use garage_model::garage::Garage;
use garage_model::k2v::counter_table::{BYTES, CONFLICTS, ENTRIES, VALUES}; use garage_model::k2v::counter_table::{BYTES, CONFLICTS, ENTRIES, VALUES};
use crate::s3::error::*; use crate::k2v::error::*;
use crate::k2v::range::read_range; use crate::k2v::range::read_range;
pub async fn handle_read_index( pub async fn handle_read_index(

View file

@ -10,7 +10,7 @@ use garage_model::garage::Garage;
use garage_model::k2v::causality::*; use garage_model::k2v::causality::*;
use garage_model::k2v::item_table::*; use garage_model::k2v::item_table::*;
use crate::s3::error::*; use crate::k2v::error::*;
pub const X_GARAGE_CAUSALITY_TOKEN: &str = "X-Garage-Causality-Token"; pub const X_GARAGE_CAUSALITY_TOKEN: &str = "X-Garage-Causality-Token";

View file

@ -1,4 +1,5 @@
pub mod api_server; pub mod api_server;
mod error;
mod router; mod router;
mod batch; mod batch;

View file

@ -7,7 +7,7 @@ use std::sync::Arc;
use garage_table::replication::TableShardedReplication; use garage_table::replication::TableShardedReplication;
use garage_table::*; use garage_table::*;
use crate::s3::error::*; use crate::k2v::error::*;
use crate::helpers::key_after_prefix; use crate::helpers::key_after_prefix;
/// Read range in a Garage table. /// Read range in a Garage table.

View file

@ -1,4 +1,4 @@
use crate::s3::error::*; use crate::k2v::error::*;
use std::borrow::Cow; use std::borrow::Cow;

View file

@ -119,7 +119,8 @@ impl ApiHandler for S3ApiServer {
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_s3api(garage, &req, bucket_name).await; return handle_options_s3api(garage, &req, bucket_name).await
.map_err(Error::from);
} }
let (api_key, mut content_sha256) = check_payload_signature(&garage, "s3", &req).await?; let (api_key, mut content_sha256) = check_payload_signature(&garage, "s3", &req).await?;

View file

@ -11,6 +11,7 @@ use crate::common_error::CommonError;
pub use crate::common_error::{OkOrBadRequest, OkOrInternalError}; pub use crate::common_error::{OkOrBadRequest, OkOrInternalError};
use crate::generic_server::ApiError; use crate::generic_server::ApiError;
use crate::s3::xml as s3_xml; use crate::s3::xml as s3_xml;
use crate::signature::error::Error as SignatureError;
/// Errors of this crate /// Errors of this crate
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -134,6 +135,18 @@ impl From<HelperError> for Error {
} }
} }
impl From<SignatureError> for Error {
fn from(err: SignatureError) -> Self {
match err {
SignatureError::CommonError(c) => Self::CommonError(c),
SignatureError::AuthorizationHeaderMalformed(c) => Self::AuthorizationHeaderMalformed(c),
SignatureError::Forbidden(f) => Self::Forbidden(f),
SignatureError::InvalidUtf8Str(i) => Self::InvalidUtf8Str(i),
SignatureError::InvalidHeader(h) => Self::InvalidHeader(h),
}
}
}
impl From<multer::Error> for Error { impl From<multer::Error> for Error {
fn from(err: multer::Error) -> Self { fn from(err: multer::Error) -> Self {
Self::bad_request(err) Self::bad_request(err)

View file

@ -0,0 +1,54 @@
use err_derive::Error;
use garage_util::error::Error as GarageError;
use crate::common_error::CommonError;
pub use crate::common_error::{OkOrBadRequest, OkOrInternalError};
/// Errors of this crate
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "{}", _0)]
/// Error from common error
CommonError(CommonError),
/// Authorization Header Malformed
#[error(display = "Authorization header malformed, expected scope: {}", _0)]
AuthorizationHeaderMalformed(String),
/// No proper api key was used, or the signature was invalid
#[error(display = "Forbidden: {}", _0)]
Forbidden(String),
// Category: bad request
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
#[error(display = "Invalid UTF-8: {}", _0)]
InvalidUtf8Str(#[error(source)] std::str::Utf8Error),
/// The client sent a header with invalid value
#[error(display = "Invalid header value: {}", _0)]
InvalidHeader(#[error(source)] hyper::header::ToStrError),
}
impl<T> From<T> for Error
where
CommonError: From<T>,
{
fn from(err: T) -> Self {
Error::CommonError(CommonError::from(err))
}
}
impl Error {
pub fn internal_error<M: ToString>(msg: M) -> Self {
Self::CommonError(CommonError::InternalError(GarageError::Message(
msg.to_string(),
)))
}
pub fn bad_request<M: ToString>(msg: M) -> Self {
Self::CommonError(CommonError::BadRequest(msg.to_string()))
}
}

View file

@ -4,11 +4,12 @@ use sha2::Sha256;
use garage_util::data::{sha256sum, Hash}; use garage_util::data::{sha256sum, Hash};
use crate::s3::error::*; pub mod error;
pub mod payload; pub mod payload;
pub mod streaming; pub mod streaming;
use error::*;
pub const SHORT_DATE: &str = "%Y%m%d"; pub const SHORT_DATE: &str = "%Y%m%d";
pub const LONG_DATETIME: &str = "%Y%m%dT%H%M%SZ"; pub const LONG_DATETIME: &str = "%Y%m%dT%H%M%SZ";

View file

@ -15,7 +15,7 @@ use super::LONG_DATETIME;
use super::{compute_scope, signing_hmac}; use super::{compute_scope, signing_hmac};
use crate::encoding::uri_encode; use crate::encoding::uri_encode;
use crate::s3::error::*; use crate::signature::error::*;
pub async fn check_payload_signature( pub async fn check_payload_signature(
garage: &Garage, garage: &Garage,

View file

@ -12,7 +12,7 @@ use garage_util::data::Hash;
use super::{compute_scope, sha256sum, HmacSha256, LONG_DATETIME}; use super::{compute_scope, sha256sum, HmacSha256, LONG_DATETIME};
use crate::s3::error::*; use crate::signature::error::*;
pub fn parse_streaming_body( pub fn parse_streaming_body(
api_key: &Key, api_key: &Key,