aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-01-05 16:23:09 +0100
committerAlex Auvolat <alex@adnab.me>2022-01-05 16:28:19 +0100
commit135858d067e21e5207d275f4a56a5b721a685a07 (patch)
tree907d4aa3fd9b766e81aa0568689cb2d92a32a697
parent8395030e4891ae48ca30428318e8d435c157f74f (diff)
downloadgarage-135858d067e21e5207d275f4a56a5b721a685a07.tar.gz
garage-135858d067e21e5207d275f4a56a5b721a685a07.zip
Implement DeleteBucket
-rw-r--r--src/api/api_server.rs7
-rw-r--r--src/api/s3_bucket.rs98
-rw-r--r--src/api/s3_router.rs17
3 files changed, 116 insertions, 6 deletions
diff --git a/src/api/api_server.rs b/src/api/api_server.rs
index db5da8cc..41ae6857 100644
--- a/src/api/api_server.rs
+++ b/src/api/api_server.rs
@@ -123,6 +123,7 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
let allowed = match endpoint.authorization_type() {
Authorization::Read(_) => api_key.allow_read(&bucket_id),
Authorization::Write(_) => api_key.allow_write(&bucket_id),
+ Authorization::Owner(_) => api_key.allow_owner(&bucket_id),
_ => unreachable!(),
};
@@ -199,9 +200,9 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
let response = Response::builder().body(empty_body).unwrap();
Ok(response)
}
- Endpoint::DeleteBucket { .. } => Err(Error::Forbidden(
- "Cannot delete buckets using S3 api, please talk to Garage directly".into(),
- )),
+ Endpoint::DeleteBucket { .. } => {
+ handle_delete_bucket(&garage, bucket_id, bucket_name, api_key).await
+ }
Endpoint::GetBucketLocation { .. } => handle_get_bucket_location(garage),
Endpoint::GetBucketVersioning { .. } => handle_get_bucket_versioning(),
Endpoint::ListObjects {
diff --git a/src/api/s3_bucket.rs b/src/api/s3_bucket.rs
index 23f43317..ea7e0b8c 100644
--- a/src/api/s3_bucket.rs
+++ b/src/api/s3_bucket.rs
@@ -1,14 +1,14 @@
use std::collections::HashMap;
use std::sync::Arc;
-use hyper::{Body, Request, Response};
+use hyper::{Body, Request, Response, StatusCode};
use garage_model::bucket_alias_table::*;
use garage_model::bucket_table::Bucket;
use garage_model::garage::Garage;
use garage_model::key_table::Key;
use garage_model::permission::BucketKeyPerm;
-use garage_table::util::EmptyKey;
+use garage_table::util::*;
use garage_util::crdt::*;
use garage_util::data::*;
use garage_util::time::*;
@@ -187,6 +187,100 @@ pub async fn handle_create_bucket(
.unwrap())
}
+pub async fn handle_delete_bucket(
+ garage: &Garage,
+ bucket_id: Uuid,
+ bucket_name: String,
+ api_key: Key,
+) -> Result<Response<Body>, Error> {
+ let key_params = api_key
+ .params()
+ .ok_or_internal_error("Key should not be deleted at this point")?;
+
+ let is_local_alias = matches!(key_params.local_aliases.get(&bucket_name), Some(Some(_)));
+
+ let mut bucket = garage
+ .bucket_helper()
+ .get_existing_bucket(bucket_id)
+ .await?;
+ let bucket_state = bucket.state.as_option().unwrap();
+
+ // If the bucket has no other aliases, this is a true deletion.
+ // Otherwise, it is just an alias removal.
+
+ let has_other_global_aliases = bucket_state
+ .aliases
+ .items()
+ .iter()
+ .filter(|(_, _, active)| *active)
+ .any(|(n, _, _)| is_local_alias || (*n != bucket_name));
+
+ let has_other_local_aliases = bucket_state
+ .local_aliases
+ .items()
+ .iter()
+ .filter(|(_, _, active)| *active)
+ .any(|((k, n), _, _)| !is_local_alias || *n != bucket_name || *k != api_key.key_id);
+
+ if !has_other_global_aliases && !has_other_local_aliases {
+ // Delete bucket
+
+ // Check bucket is empty
+ let objects = garage
+ .object_table
+ .get_range(&bucket_id, None, Some(DeletedFilter::NotDeleted), 10)
+ .await?;
+ if !objects.is_empty() {
+ return Err(Error::BadRequest(format!(
+ "Bucket {} is not empty",
+ bucket_name
+ )));
+ }
+
+ // --- done checking, now commit ---
+ // 1. delete bucket alias
+ if is_local_alias {
+ garage
+ .bucket_helper()
+ .unset_local_bucket_alias(bucket_id, &api_key.key_id, &bucket_name)
+ .await?;
+ } else {
+ garage
+ .bucket_helper()
+ .unset_global_bucket_alias(bucket_id, &bucket_name)
+ .await?;
+ }
+
+ // 2. delete authorization from keys that had access
+ for (key_id, _) in bucket.authorized_keys() {
+ garage
+ .bucket_helper()
+ .set_bucket_key_permissions(bucket.id, key_id, BucketKeyPerm::NO_PERMISSIONS)
+ .await?;
+ }
+
+ // 3. delete bucket
+ bucket.state = Deletable::delete();
+ garage.bucket_table.insert(&bucket).await?;
+ } else if is_local_alias {
+ // Just unalias
+ garage
+ .bucket_helper()
+ .unset_local_bucket_alias(bucket_id, &api_key.key_id, &bucket_name)
+ .await?;
+ } else {
+ // Just unalias (but from global namespace)
+ garage
+ .bucket_helper()
+ .unset_global_bucket_alias(bucket_id, &bucket_name)
+ .await?;
+ }
+
+ Ok(Response::builder()
+ .status(StatusCode::NO_CONTENT)
+ .body(Body::empty())?)
+}
+
fn parse_create_bucket_xml(xml_bytes: &[u8]) -> Option<Option<String>> {
// Returns None if invalid data
// Returns Some(None) if no location constraint is given
diff --git a/src/api/s3_router.rs b/src/api/s3_router.rs
index 4ce1d238..f32aed30 100644
--- a/src/api/s3_router.rs
+++ b/src/api/s3_router.rs
@@ -789,7 +789,6 @@ impl Endpoint {
GetBucketRequestPayment,
GetBucketTagging,
GetBucketVersioning,
- GetBucketWebsite,
GetObject,
GetObjectAcl,
GetObjectLegalHold,
@@ -813,8 +812,22 @@ impl Endpoint {
]
}
.is_some();
+ let owner = s3_match! {
+ @extract
+ self,
+ bucket,
+ [
+ DeleteBucket,
+ GetBucketWebsite,
+ PutBucketWebsite,
+ DeleteBucketWebsite,
+ ]
+ }
+ .is_some();
if readonly {
Authorization::Read(bucket)
+ } else if owner {
+ Authorization::Owner(bucket)
} else {
Authorization::Write(bucket)
}
@@ -830,6 +843,8 @@ pub enum Authorization<'a> {
Read(&'a str),
/// Having Write permission on bucket .0 is required
Write(&'a str),
+ /// Having Owner permission on bucket .0 is required
+ Owner(&'a str),
}
/// This macro is used to generate part of the code in this module. It must be called only one, and