more complete admin API #298

Merged
lx merged 48 commits from admin-api into main 2022-05-24 10:16:40 +00:00
5 changed files with 137 additions and 7 deletions
Showing only changes of commit fc2f73ddb5 - Show all commits

View file

@ -456,3 +456,48 @@ or no alias at all.
Deletes a storage bucket. A bucket cannot be deleted if it is not empty. Deletes a storage bucket. A bucket cannot be deleted if it is not empty.
Warning: this will delete all aliases associated with the bucket! 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.

View file

@ -147,6 +147,9 @@ impl ApiHandler for AdminApiServer {
} }
Endpoint::CreateBucket => handle_create_bucket(&self.garage, req).await, Endpoint::CreateBucket => handle_create_bucket(&self.garage, req).await,
Endpoint::DeleteBucket { id } => handle_delete_bucket(&self.garage, id).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!( _ => Err(Error::NotImplemented(format!(
"Admin endpoint {} not implemented yet", "Admin endpoint {} not implemented yet",
endpoint.name() endpoint.name()

View file

@ -16,7 +16,7 @@ use garage_model::garage::Garage;
use garage_model::permission::*; use garage_model::permission::*;
use garage_model::s3::object_table::ObjectFilter; use garage_model::s3::object_table::ObjectFilter;
use crate::admin::key::KeyBucketPermResult; use crate::admin::key::ApiBucketKeyPerm;
use crate::error::*; use crate::error::*;
use crate::helpers::*; use crate::helpers::*;
@ -174,7 +174,7 @@ async fn bucket_info_results(
permissions: p permissions: p
.authorized_buckets .authorized_buckets
.get(&bucket.id) .get(&bucket.id)
.map(|p| KeyBucketPermResult { .map(|p| ApiBucketKeyPerm {
read: p.allow_read, read: p.allow_read,
write: p.allow_write, write: p.allow_write,
owner: p.allow_owner, owner: p.allow_owner,
@ -212,7 +212,7 @@ struct GetBucketInfoKey {
access_key_id: String, access_key_id: String,
#[serde(rename = "name")] #[serde(rename = "name")]
name: String, name: String,
permissions: KeyBucketPermResult, permissions: ApiBucketKeyPerm,
#[serde(rename = "bucketLocalAliases")] #[serde(rename = "bucketLocalAliases")]
bucket_local_aliases: Vec<String>, bucket_local_aliases: Vec<String>,
} }
@ -368,3 +368,76 @@ pub async fn handle_delete_bucket(
.status(StatusCode::NO_CONTENT) .status(StatusCode::NO_CONTENT)
.body(Body::empty())?) .body(Body::empty())?)
} }
pub async fn handle_bucket_allow_key(
garage: &Arc<Garage>,
req: Request<Body>,
) -> Result<Response<Body>, Error> {
handle_bucket_change_key_perm(garage, req, true).await
}
pub async fn handle_bucket_deny_key(
garage: &Arc<Garage>,
req: Request<Body>,
) -> Result<Response<Body>, Error> {
handle_bucket_change_key_perm(garage, req, false).await
}
pub async fn handle_bucket_change_key_perm(
garage: &Arc<Garage>,
req: Request<Body>,
new_perm_flag: bool,
) -> Result<Response<Body>, Error> {
let req = parse_json_body::<BucketKeyPermChangeRequest>(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,
}

View file

@ -198,7 +198,7 @@ async fn key_info_results(garage: &Arc<Garage>, key: Key) -> Result<Response<Bod
permissions: key_state permissions: key_state
.authorized_buckets .authorized_buckets
.get(&bucket.id) .get(&bucket.id)
.map(|p| KeyBucketPermResult { .map(|p| ApiBucketKeyPerm {
read: p.allow_read, read: p.allow_read,
write: p.allow_write, write: p.allow_write,
owner: p.allow_owner, owner: p.allow_owner,
@ -239,12 +239,15 @@ struct KeyInfoBucketResult {
global_aliases: Vec<String>, global_aliases: Vec<String>,
#[serde(rename = "localAliases")] #[serde(rename = "localAliases")]
local_aliases: Vec<String>, local_aliases: Vec<String>,
permissions: KeyBucketPermResult, permissions: ApiBucketKeyPerm,
} }
#[derive(Serialize, Default)] #[derive(Serialize, Deserialize, Default)]
pub(crate) struct KeyBucketPermResult { pub(crate) struct ApiBucketKeyPerm {
#[serde(default)]
pub(crate) read: bool, pub(crate) read: bool,
#[serde(default)]
pub(crate) write: bool, pub(crate) write: bool,
#[serde(default)]
pub(crate) owner: bool, pub(crate) owner: bool,
} }

View file

@ -46,6 +46,9 @@ pub enum Endpoint {
DeleteBucket { DeleteBucket {
id: String, id: String,
}, },
// Bucket-Key Permissions
BucketAllowKey,
BucketDenyKey,
}} }}
impl Endpoint { impl Endpoint {
@ -81,6 +84,9 @@ impl Endpoint {
GET "/bucket" => ListBuckets, GET "/bucket" => ListBuckets,
POST "/bucket" => CreateBucket, POST "/bucket" => CreateBucket,
DELETE "/bucket" if id => DeleteBucket (query::id), 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() { if let Some(message) = query.nonempty_message() {