mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 08:38:58 +00:00
fix(security): add IAM authorization to FTP read/metadata/cwd handlers (GHSA-3g29-xff2-92vp) (#3347)
fix(security): add IAM authorization to FTP read/metadata/cwd handlers The FTP frontend's get() (RETR), metadata() (SIZE/MDTM), and cwd() (CWD) handlers dispatched directly to the storage backend without calling authorize_operation(). This allowed any authenticated FTP user, including those with explicit Deny policies, to read arbitrary objects and probe bucket existence regardless of IAM policy. Add authorize_operation() calls matching the pattern used by the write-path handlers (put, del, list, rmd) and the WebDAV driver: - get(): S3Action::GetObject - metadata() for files: S3Action::HeadObject - metadata() for directories: S3Action::HeadBucket - cwd(): S3Action::HeadBucket Fixes: GHSA-3g29-xff2-92vp Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -233,6 +233,11 @@ where
|
||||
.map_err(|e| Error::new(ErrorKind::PermanentFileNotAvailable, format!("{}: {}", "Invalid path", e)))?;
|
||||
|
||||
if let Some(key) = key {
|
||||
// Authorize HeadObject
|
||||
authorize_operation(session_context, &S3Action::HeadObject, &bucket, Some(&key))
|
||||
.await
|
||||
.map_err(|_| Error::new(ErrorKind::PermanentFileNotAvailable, "Access denied"))?;
|
||||
|
||||
match self
|
||||
.storage
|
||||
.head_object(
|
||||
@@ -264,6 +269,11 @@ where
|
||||
}
|
||||
} else {
|
||||
// Directory metadata - use HeadBucket
|
||||
// Authorize HeadBucket
|
||||
authorize_operation(session_context, &S3Action::HeadBucket, &bucket, None)
|
||||
.await
|
||||
.map_err(|_| Error::new(ErrorKind::PermanentFileNotAvailable, "Access denied"))?;
|
||||
|
||||
let bucket_clone = bucket.clone();
|
||||
match self
|
||||
.storage
|
||||
@@ -434,6 +444,11 @@ where
|
||||
|
||||
let key = key.ok_or_else(|| Error::new(ErrorKind::PermanentFileNotAvailable, "Cannot get directory"))?;
|
||||
|
||||
// Authorize GetObject
|
||||
authorize_operation(session_context, &S3Action::GetObject, &bucket, Some(&key))
|
||||
.await
|
||||
.map_err(|_| Error::new(ErrorKind::PermanentFileNotAvailable, "Access denied"))?;
|
||||
|
||||
match self
|
||||
.storage
|
||||
.get_object(
|
||||
@@ -668,6 +683,11 @@ where
|
||||
.parse_s3_path(&path_str)
|
||||
.map_err(|e| Error::new(ErrorKind::PermanentFileNotAvailable, format!("{}: {}", "Invalid path", e)))?;
|
||||
|
||||
// Authorize HeadBucket (CWD probes bucket existence)
|
||||
authorize_operation(session_context, &S3Action::HeadBucket, &bucket, None)
|
||||
.await
|
||||
.map_err(|_| Error::new(ErrorKind::PermanentFileNotAvailable, "Access denied"))?;
|
||||
|
||||
// Check if bucket exists
|
||||
match self
|
||||
.storage
|
||||
|
||||
@@ -318,11 +318,9 @@ pub struct ListRemoteTargetHandler {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for ListRemoteTargetHandler {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
validate_replication_admin_request(&req, AdminAction::GetBucketTargetAction).await?;
|
||||
|
||||
let queries = extract_query_params(&req.uri);
|
||||
let Some(_cred) = req.credentials else {
|
||||
error!("credentials null");
|
||||
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||
};
|
||||
|
||||
if let Some(bucket) = queries.get("bucket") {
|
||||
if bucket.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user