From ec16d166f940f59098ae5cc0c0b3d8298f1bcc78 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Fri, 13 May 2022 15:43:44 +0200 Subject: [PATCH] Separate error types for k2v and signature --- src/api/k2v/api_server.rs | 8 ++- src/api/k2v/batch.rs | 2 +- src/api/k2v/error.rs | 118 +++++++++++++++++++++++++++++++++ src/api/k2v/index.rs | 2 +- src/api/k2v/item.rs | 2 +- src/api/k2v/mod.rs | 1 + src/api/k2v/range.rs | 2 +- src/api/k2v/router.rs | 2 +- src/api/s3/api_server.rs | 3 +- src/api/s3/error.rs | 13 ++++ src/api/signature/error.rs | 54 +++++++++++++++ src/api/signature/mod.rs | 5 +- src/api/signature/payload.rs | 2 +- src/api/signature/streaming.rs | 2 +- 14 files changed, 203 insertions(+), 13 deletions(-) create mode 100644 src/api/k2v/error.rs create mode 100644 src/api/signature/error.rs diff --git a/src/api/k2v/api_server.rs b/src/api/k2v/api_server.rs index 38ef8d45..b70fcdff 100644 --- a/src/api/k2v/api_server.rs +++ b/src/api/k2v/api_server.rs @@ -12,7 +12,7 @@ use garage_util::error::Error as GarageError; use garage_model::garage::Garage; -use crate::s3::error::*; +use crate::k2v::error::*; use crate::generic_server::*; 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 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?; @@ -126,7 +127,8 @@ impl ApiHandler for K2VApiServer { // are always preflighted, i.e. the browser should make // an OPTIONS call before to check it is allowed 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, }; diff --git a/src/api/k2v/batch.rs b/src/api/k2v/batch.rs index dab3bfb2..8eae471c 100644 --- a/src/api/k2v/batch.rs +++ b/src/api/k2v/batch.rs @@ -12,7 +12,7 @@ use garage_model::garage::Garage; use garage_model::k2v::causality::*; use garage_model::k2v::item_table::*; -use crate::s3::error::*; +use crate::k2v::error::*; use crate::helpers::*; use crate::k2v::range::read_range; diff --git a/src/api/k2v/error.rs b/src/api/k2v/error.rs new file mode 100644 index 00000000..6b9e81e6 --- /dev/null +++ b/src/api/k2v/error.rs @@ -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 From for Error +where + CommonError: From, +{ + fn from(err: T) -> Self { + Error::CommonError(CommonError::from(err)) + } +} + +impl From 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 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(msg: M) -> Self { + // Self::CommonError(CommonError::InternalError(GarageError::Message( + // msg.to_string(), + // ))) + //} + + pub fn bad_request(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) { + // nothing + } + + fn http_body(&self, garage_region: &str, path: &str) -> Body { + Body::from(format!( + "ERROR: {}\n\ngarage region: {}\npath: {}", + self, garage_region, path + )) + } +} diff --git a/src/api/k2v/index.rs b/src/api/k2v/index.rs index e587841c..d5db906d 100644 --- a/src/api/k2v/index.rs +++ b/src/api/k2v/index.rs @@ -12,7 +12,7 @@ use garage_table::util::*; use garage_model::garage::Garage; use garage_model::k2v::counter_table::{BYTES, CONFLICTS, ENTRIES, VALUES}; -use crate::s3::error::*; +use crate::k2v::error::*; use crate::k2v::range::read_range; pub async fn handle_read_index( diff --git a/src/api/k2v/item.rs b/src/api/k2v/item.rs index 95624d57..836d386f 100644 --- a/src/api/k2v/item.rs +++ b/src/api/k2v/item.rs @@ -10,7 +10,7 @@ use garage_model::garage::Garage; use garage_model::k2v::causality::*; 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"; diff --git a/src/api/k2v/mod.rs b/src/api/k2v/mod.rs index ee210ad5..b6a8c5cf 100644 --- a/src/api/k2v/mod.rs +++ b/src/api/k2v/mod.rs @@ -1,4 +1,5 @@ pub mod api_server; +mod error; mod router; mod batch; diff --git a/src/api/k2v/range.rs b/src/api/k2v/range.rs index cf6034b9..6aa5c90c 100644 --- a/src/api/k2v/range.rs +++ b/src/api/k2v/range.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use garage_table::replication::TableShardedReplication; use garage_table::*; -use crate::s3::error::*; +use crate::k2v::error::*; use crate::helpers::key_after_prefix; /// Read range in a Garage table. diff --git a/src/api/k2v/router.rs b/src/api/k2v/router.rs index c509a4da..093fb9a7 100644 --- a/src/api/k2v/router.rs +++ b/src/api/k2v/router.rs @@ -1,4 +1,4 @@ -use crate::s3::error::*; +use crate::k2v::error::*; use std::borrow::Cow; diff --git a/src/api/s3/api_server.rs b/src/api/s3/api_server.rs index 6b565fd0..4df9ee6d 100644 --- a/src/api/s3/api_server.rs +++ b/src/api/s3/api_server.rs @@ -119,7 +119,8 @@ impl ApiHandler for S3ApiServer { return handle_post_object(garage, req, bucket_name.unwrap()).await; } 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?; diff --git a/src/api/s3/error.rs b/src/api/s3/error.rs index 3cb97019..a0c4703c 100644 --- a/src/api/s3/error.rs +++ b/src/api/s3/error.rs @@ -11,6 +11,7 @@ use crate::common_error::CommonError; pub use crate::common_error::{OkOrBadRequest, OkOrInternalError}; use crate::generic_server::ApiError; use crate::s3::xml as s3_xml; +use crate::signature::error::Error as SignatureError; /// Errors of this crate #[derive(Debug, Error)] @@ -134,6 +135,18 @@ impl From for Error { } } +impl From 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 for Error { fn from(err: multer::Error) -> Self { Self::bad_request(err) diff --git a/src/api/signature/error.rs b/src/api/signature/error.rs new file mode 100644 index 00000000..69f3c6c5 --- /dev/null +++ b/src/api/signature/error.rs @@ -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 From for Error +where + CommonError: From, +{ + fn from(err: T) -> Self { + Error::CommonError(CommonError::from(err)) + } +} + + +impl Error { + pub fn internal_error(msg: M) -> Self { + Self::CommonError(CommonError::InternalError(GarageError::Message( + msg.to_string(), + ))) + } + + pub fn bad_request(msg: M) -> Self { + Self::CommonError(CommonError::BadRequest(msg.to_string())) + } +} + diff --git a/src/api/signature/mod.rs b/src/api/signature/mod.rs index 4679747f..dd5b590c 100644 --- a/src/api/signature/mod.rs +++ b/src/api/signature/mod.rs @@ -4,11 +4,12 @@ use sha2::Sha256; use garage_util::data::{sha256sum, Hash}; -use crate::s3::error::*; - +pub mod error; pub mod payload; pub mod streaming; +use error::*; + pub const SHORT_DATE: &str = "%Y%m%d"; pub const LONG_DATETIME: &str = "%Y%m%dT%H%M%SZ"; diff --git a/src/api/signature/payload.rs b/src/api/signature/payload.rs index 47445bc7..155a6f94 100644 --- a/src/api/signature/payload.rs +++ b/src/api/signature/payload.rs @@ -15,7 +15,7 @@ use super::LONG_DATETIME; use super::{compute_scope, signing_hmac}; use crate::encoding::uri_encode; -use crate::s3::error::*; +use crate::signature::error::*; pub async fn check_payload_signature( garage: &Garage, diff --git a/src/api/signature/streaming.rs b/src/api/signature/streaming.rs index 06a0512e..c8358c4f 100644 --- a/src/api/signature/streaming.rs +++ b/src/api/signature/streaming.rs @@ -12,7 +12,7 @@ use garage_util::data::Hash; use super::{compute_scope, sha256sum, HmacSha256, LONG_DATETIME}; -use crate::s3::error::*; +use crate::signature::error::*; pub fn parse_streaming_body( api_key: &Key,