1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Error types for the proxy.
use thiserror::Error;
/// Central error type for the proxy, mapping each variant to an S3-compatible HTTP response.
#[derive(Debug, Error)]
pub enum ProxyError {
/// The requested virtual bucket does not exist in the registry.
#[error("bucket not found: {0}")]
BucketNotFound(String),
/// The requested object key was not found in the backend store.
#[error("no such key: {0}")]
NoSuchKey(String),
/// The caller's identity lacks permission for the requested operation.
#[error("access denied")]
AccessDenied,
/// The SigV4 signature in the request does not match the expected value.
#[error("signature mismatch")]
SignatureDoesNotMatch,
/// The request is malformed or contains invalid parameters.
#[error("invalid request: {0}")]
InvalidRequest(String),
/// The request contains no authentication credentials.
#[error("missing authentication")]
MissingAuth,
/// The credentials used to sign the request have expired.
#[error("expired credentials")]
ExpiredCredentials,
/// The OIDC token provided for STS role assumption is invalid or untrusted.
#[error("invalid OIDC token: {0}")]
InvalidOidcToken(String),
/// The IAM role specified in an STS request does not exist.
#[error("role not found: {0}")]
RoleNotFound(String),
/// The upstream object store backend returned an error.
#[error("backend error: {0}")]
BackendError(String),
/// A conditional request header (e.g. `If-Match`) was not satisfied.
#[error("precondition failed")]
PreconditionFailed,
/// The object has not been modified since the time specified by `If-Modified-Since`.
#[error("not modified")]
NotModified,
/// The proxy configuration is invalid or incomplete.
#[error("config error: {0}")]
ConfigError(String),
/// An unexpected internal error occurred.
#[error("internal error: {0}")]
Internal(String),
}
impl ProxyError {
/// Return the S3-compatible XML error code.
pub fn s3_error_code(&self) -> &'static str {
match self {
Self::BucketNotFound(_) => "NoSuchBucket",
Self::NoSuchKey(_) => "NoSuchKey",
Self::AccessDenied => "AccessDenied",
Self::SignatureDoesNotMatch => "SignatureDoesNotMatch",
Self::InvalidRequest(_) => "InvalidRequest",
Self::MissingAuth => "AccessDenied",
Self::ExpiredCredentials => "ExpiredToken",
Self::InvalidOidcToken(_) => "InvalidIdentityToken",
Self::RoleNotFound(_) => "AccessDenied",
Self::BackendError(_) => "ServiceUnavailable",
Self::PreconditionFailed => "PreconditionFailed",
Self::NotModified => "NotModified",
Self::ConfigError(_) => "InternalError",
Self::Internal(_) => "InternalError",
}
}
/// HTTP status code for this error.
pub fn status_code(&self) -> u16 {
match self {
Self::BucketNotFound(_) | Self::NoSuchKey(_) => 404,
Self::AccessDenied | Self::MissingAuth | Self::ExpiredCredentials => 403,
Self::SignatureDoesNotMatch => 403,
Self::InvalidRequest(_) => 400,
Self::InvalidOidcToken(_) => 400,
Self::RoleNotFound(_) => 403,
Self::PreconditionFailed => 412,
Self::NotModified => 304,
Self::BackendError(_) => 503,
Self::ConfigError(_) | Self::Internal(_) => 500,
}
}
/// Return a message safe to show to external clients.
///
/// For server-side errors (5xx), returns a generic message to avoid
/// leaking backend infrastructure details. For client errors (4xx),
/// returns the full message (the client already knows the bucket name,
/// key, etc.).
pub fn safe_message(&self) -> String {
match self {
Self::BackendError(_) => "Service unavailable".to_string(),
Self::ConfigError(_) | Self::Internal(_) => "Internal server error".to_string(),
other => other.to_string(),
}
}
/// Convert an `object_store::Error` into a `ProxyError`.
pub fn from_object_store_error(e: object_store::Error) -> Self {
match e {
object_store::Error::NotFound { path, .. } => Self::NoSuchKey(path),
object_store::Error::Precondition { .. } => Self::PreconditionFailed,
object_store::Error::NotModified { .. } => Self::NotModified,
_ => Self::BackendError(e.to_string()),
}
}
}