From fc2f73ddb5ecaca250daa7b034fe59fb8c47f570 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 12 May 2022 11:19:41 +0200 Subject: [PATCH] BucketAllowKey and BucketDenyKey --- doc/drafts/admin-api.md | 45 +++++++++++++++++++++ src/api/admin/api_server.rs | 3 ++ src/api/admin/bucket.rs | 79 +++++++++++++++++++++++++++++++++++-- src/api/admin/key.rs | 11 ++++-- src/api/admin/router.rs | 6 +++ 5 files changed, 137 insertions(+), 7 deletions(-) diff --git a/doc/drafts/admin-api.md b/doc/drafts/admin-api.md index 048b77fb..5dc3f127 100644 --- a/doc/drafts/admin-api.md +++ b/doc/drafts/admin-api.md @@ -456,3 +456,48 @@ or no alias at all. Deletes a storage bucket. A bucket cannot be deleted if it is not empty. Warning: this will delete all aliases associated with the bucket! + + +## Operations on permissions for keys on buckets + +### BucketAllowKey `POST /bucket/allow` + +Allows a key to do read/write/owner operations on a bucket. + +Request body format: + +```json +{ + "bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b", + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "permissions": { + "read": true, + "write": true, + "owner": true + }, +} +``` + +Flags in `permissions` which have the value `true` will be activated. +Other flags will remain unchanged. + +### BucketDenyKey `POST /bucket/deny` + +Denies a key from doing read/write/owner operations on a bucket. + +Request body format: + +```json +{ + "bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b", + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "permissions": { + "read": false, + "write": false, + "owner": true + }, +} +``` + +Flags in `permissions` which have the value `true` will be deactivated. +Other flags will remain unchanged. diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs index 4366cbd6..6bdef56c 100644 --- a/src/api/admin/api_server.rs +++ b/src/api/admin/api_server.rs @@ -147,6 +147,9 @@ impl ApiHandler for AdminApiServer { } Endpoint::CreateBucket => handle_create_bucket(&self.garage, req).await, Endpoint::DeleteBucket { id } => handle_delete_bucket(&self.garage, id).await, + // Bucket-key permissions + Endpoint::BucketAllowKey => handle_bucket_allow_key(&self.garage, req).await, + Endpoint::BucketDenyKey => handle_bucket_deny_key(&self.garage, req).await, _ => Err(Error::NotImplemented(format!( "Admin endpoint {} not implemented yet", endpoint.name() diff --git a/src/api/admin/bucket.rs b/src/api/admin/bucket.rs index 8e6cc067..16e9c174 100644 --- a/src/api/admin/bucket.rs +++ b/src/api/admin/bucket.rs @@ -16,7 +16,7 @@ use garage_model::garage::Garage; use garage_model::permission::*; use garage_model::s3::object_table::ObjectFilter; -use crate::admin::key::KeyBucketPermResult; +use crate::admin::key::ApiBucketKeyPerm; use crate::error::*; use crate::helpers::*; @@ -174,7 +174,7 @@ async fn bucket_info_results( permissions: p .authorized_buckets .get(&bucket.id) - .map(|p| KeyBucketPermResult { + .map(|p| ApiBucketKeyPerm { read: p.allow_read, write: p.allow_write, owner: p.allow_owner, @@ -212,7 +212,7 @@ struct GetBucketInfoKey { access_key_id: String, #[serde(rename = "name")] name: String, - permissions: KeyBucketPermResult, + permissions: ApiBucketKeyPerm, #[serde(rename = "bucketLocalAliases")] bucket_local_aliases: Vec, } @@ -368,3 +368,76 @@ pub async fn handle_delete_bucket( .status(StatusCode::NO_CONTENT) .body(Body::empty())?) } + +pub async fn handle_bucket_allow_key( + garage: &Arc, + req: Request, +) -> Result, Error> { + handle_bucket_change_key_perm(garage, req, true).await +} + +pub async fn handle_bucket_deny_key( + garage: &Arc, + req: Request, +) -> Result, Error> { + handle_bucket_change_key_perm(garage, req, false).await +} + +pub async fn handle_bucket_change_key_perm( + garage: &Arc, + req: Request, + new_perm_flag: bool, +) -> Result, Error> { + let req = parse_json_body::(req).await?; + + let id_hex = hex::decode(&req.bucket_id).ok_or_bad_request("Invalid bucket id")?; + let bucket_id = Uuid::try_from(&id_hex).ok_or_bad_request("Invalid bucket id")?; + + let bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + let state = bucket.state.as_option().unwrap(); + + let key = garage + .key_helper() + .get_existing_key(&req.access_key_id) + .await?; + + let mut perm = state + .authorized_keys + .get(&key.key_id) + .cloned() + .unwrap_or(BucketKeyPerm::NO_PERMISSIONS); + + if req.permissions.read { + perm.allow_read = new_perm_flag; + } + if req.permissions.write { + perm.allow_write = new_perm_flag; + } + if req.permissions.owner { + perm.allow_owner = new_perm_flag; + } + + garage + .bucket_helper() + .set_bucket_key_permissions(bucket.id, &key.key_id, perm) + .await?; + + let bucket = garage + .bucket_table + .get(&EmptyKey, &bucket.id) + .await? + .ok_or_internal_error("Bucket should now exist but doesn't")?; + bucket_info_results(garage, bucket).await +} + +#[derive(Deserialize)] +struct BucketKeyPermChangeRequest { + #[serde(rename = "bucketId")] + bucket_id: String, + #[serde(rename = "accessKeyId")] + access_key_id: String, + permissions: ApiBucketKeyPerm, +} diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index 1252d2c8..19ad5160 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -198,7 +198,7 @@ async fn key_info_results(garage: &Arc, key: Key) -> Result, #[serde(rename = "localAliases")] local_aliases: Vec, - permissions: KeyBucketPermResult, + permissions: ApiBucketKeyPerm, } -#[derive(Serialize, Default)] -pub(crate) struct KeyBucketPermResult { +#[derive(Serialize, Deserialize, Default)] +pub(crate) struct ApiBucketKeyPerm { + #[serde(default)] pub(crate) read: bool, + #[serde(default)] pub(crate) write: bool, + #[serde(default)] pub(crate) owner: bool, } diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs index a6e1c848..6f787fe9 100644 --- a/src/api/admin/router.rs +++ b/src/api/admin/router.rs @@ -46,6 +46,9 @@ pub enum Endpoint { DeleteBucket { id: String, }, + // Bucket-Key Permissions + BucketAllowKey, + BucketDenyKey, }} impl Endpoint { @@ -81,6 +84,9 @@ impl Endpoint { GET "/bucket" => ListBuckets, POST "/bucket" => CreateBucket, DELETE "/bucket" if id => DeleteBucket (query::id), + // Bucket-key permissions + POST "/bucket/allow" => BucketAllowKey, + POST "/bucket/deny" => BucketDenyKey, ]); if let Some(message) = query.nonempty_message() {