more complete admin API #298
7 changed files with 227 additions and 105 deletions
|
@ -136,10 +136,15 @@ impl ApiHandler for AdminApiServer {
|
||||||
Endpoint::GetKeyInfo { id, search } => {
|
Endpoint::GetKeyInfo { id, search } => {
|
||||||
handle_get_key_info(&self.garage, id, search).await
|
handle_get_key_info(&self.garage, id, search).await
|
||||||
}
|
}
|
||||||
|
Endpoint::CreateKey => handle_create_key(&self.garage, req).await,
|
||||||
|
Endpoint::UpdateKey { id } => handle_update_key(&self.garage, id, req).await,
|
||||||
|
Endpoint::DeleteKey { id } => handle_delete_key(&self.garage, id).await,
|
||||||
|
/*
|
||||||
_ => Err(Error::NotImplemented(format!(
|
_ => Err(Error::NotImplemented(format!(
|
||||||
"Admin endpoint {} not implemented yet",
|
"Admin endpoint {} not implemented yet",
|
||||||
endpoint.name()
|
endpoint.name()
|
||||||
))),
|
))),
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use hyper::{Body, Request, Response, StatusCode};
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use garage_util::crdt::*;
|
|
||||||
use garage_util::data::*;
|
|
||||||
use garage_util::error::Error as GarageError;
|
use garage_util::error::Error as GarageError;
|
||||||
|
|
||||||
use garage_rpc::layout::*;
|
|
||||||
|
|
||||||
use garage_table::*;
|
use garage_table::*;
|
||||||
|
|
||||||
use garage_model::garage::Garage;
|
use garage_model::garage::Garage;
|
||||||
|
@ -62,7 +57,7 @@ pub async fn handle_get_key_info(
|
||||||
.ok_or(Error::NoSuchKey)?
|
.ok_or(Error::NoSuchKey)?
|
||||||
} else if let Some(search) = search {
|
} else if let Some(search) = search {
|
||||||
garage
|
garage
|
||||||
.bucket_helper()
|
.key_helper()
|
||||||
.get_existing_matching_key(&search)
|
.get_existing_matching_key(&search)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::NoSuchKey)?
|
.map_err(|_| Error::NoSuchKey)?
|
||||||
|
@ -70,6 +65,84 @@ pub async fn handle_get_key_info(
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
key_info_results(garage, key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_create_key(
|
||||||
|
garage: &Arc<Garage>,
|
||||||
|
req: Request<Body>,
|
||||||
|
) -> Result<Response<Body>, Error> {
|
||||||
|
let req = parse_json_body::<CreateKeyRequest>(req).await?;
|
||||||
|
|
||||||
|
let key = Key::new(&req.name);
|
||||||
|
garage.key_table.insert(&key).await?;
|
||||||
|
|
||||||
|
key_info_results(garage, key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CreateKeyRequest {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_update_key(
|
||||||
|
garage: &Arc<Garage>,
|
||||||
|
id: String,
|
||||||
|
req: Request<Body>,
|
||||||
|
) -> Result<Response<Body>, Error> {
|
||||||
|
let req = parse_json_body::<UpdateKeyRequest>(req).await?;
|
||||||
|
|
||||||
|
let mut key = garage
|
||||||
|
.key_table
|
||||||
|
.get(&EmptyKey, &id)
|
||||||
|
.await?
|
||||||
|
.ok_or(Error::NoSuchKey)?;
|
||||||
|
|
||||||
|
let key_state = key.state.as_option_mut().ok_or(Error::NoSuchKey)?;
|
||||||
|
|
||||||
|
if let Some(new_name) = req.name {
|
||||||
|
key_state.name.update(new_name);
|
||||||
|
}
|
||||||
|
if let Some(allow) = req.allow {
|
||||||
|
if allow.create_bucket {
|
||||||
|
key_state.allow_create_bucket.update(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(deny) = req.deny {
|
||||||
|
if deny.create_bucket {
|
||||||
|
key_state.allow_create_bucket.update(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
garage.key_table.insert(&key).await?;
|
||||||
|
|
||||||
|
key_info_results(garage, key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UpdateKeyRequest {
|
||||||
|
name: Option<String>,
|
||||||
|
allow: Option<KeyPerm>,
|
||||||
|
deny: Option<KeyPerm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_delete_key(garage: &Arc<Garage>, id: String) -> Result<Response<Body>, Error> {
|
||||||
|
let mut key = garage
|
||||||
|
.key_table
|
||||||
|
.get(&EmptyKey, &id)
|
||||||
|
.await?
|
||||||
|
.ok_or(Error::NoSuchKey)?;
|
||||||
|
|
||||||
|
key.state.as_option().ok_or(Error::NoSuchKey)?;
|
||||||
|
|
||||||
|
garage.key_helper().delete_key(&mut key).await?;
|
||||||
|
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(StatusCode::NO_CONTENT)
|
||||||
|
.body(Body::empty())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn key_info_results(garage: &Arc<Garage>, key: Key) -> Result<Response<Body>, Error> {
|
||||||
let mut relevant_buckets = HashMap::new();
|
let mut relevant_buckets = HashMap::new();
|
||||||
|
|
||||||
let key_state = key.state.as_option().unwrap();
|
let key_state = key.state.as_option().unwrap();
|
||||||
|
@ -99,7 +172,7 @@ pub async fn handle_get_key_info(
|
||||||
name: key_state.name.get().clone(),
|
name: key_state.name.get().clone(),
|
||||||
access_key_id: key.key_id.clone(),
|
access_key_id: key.key_id.clone(),
|
||||||
secret_access_key: key_state.secret_key.clone(),
|
secret_access_key: key_state.secret_key.clone(),
|
||||||
permissions: KeyPermResult {
|
permissions: KeyPerm {
|
||||||
create_bucket: *key_state.allow_create_bucket.get(),
|
create_bucket: *key_state.allow_create_bucket.get(),
|
||||||
},
|
},
|
||||||
buckets: relevant_buckets
|
buckets: relevant_buckets
|
||||||
|
@ -153,13 +226,13 @@ struct GetKeyInfoResult {
|
||||||
access_key_id: String,
|
access_key_id: String,
|
||||||
#[serde(rename = "secretAccessKey")]
|
#[serde(rename = "secretAccessKey")]
|
||||||
secret_access_key: String,
|
secret_access_key: String,
|
||||||
permissions: KeyPermResult,
|
permissions: KeyPerm,
|
||||||
buckets: Vec<KeyInfoBucketResult>,
|
buckets: Vec<KeyInfoBucketResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct KeyPermResult {
|
struct KeyPerm {
|
||||||
#[serde(rename = "createBucket")]
|
#[serde(rename = "createBucket", default)]
|
||||||
create_bucket: bool,
|
create_bucket: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -261,6 +261,7 @@ impl AdminRpcHandler {
|
||||||
|
|
||||||
async fn handle_alias_bucket(&self, query: &AliasBucketOpt) -> Result<AdminRpc, Error> {
|
async fn handle_alias_bucket(&self, query: &AliasBucketOpt) -> Result<AdminRpc, Error> {
|
||||||
let helper = self.garage.bucket_helper();
|
let helper = self.garage.bucket_helper();
|
||||||
|
let key_helper = self.garage.key_helper();
|
||||||
|
|
||||||
let bucket_id = helper
|
let bucket_id = helper
|
||||||
.resolve_global_bucket_name(&query.existing_bucket)
|
.resolve_global_bucket_name(&query.existing_bucket)
|
||||||
|
@ -268,7 +269,7 @@ impl AdminRpcHandler {
|
||||||
.ok_or_bad_request("Bucket not found")?;
|
.ok_or_bad_request("Bucket not found")?;
|
||||||
|
|
||||||
if let Some(key_pattern) = &query.local {
|
if let Some(key_pattern) = &query.local {
|
||||||
let key = helper.get_existing_matching_key(key_pattern).await?;
|
let key = key_helper.get_existing_matching_key(key_pattern).await?;
|
||||||
|
|
||||||
helper
|
helper
|
||||||
.set_local_bucket_alias(bucket_id, &key.key_id, &query.new_name)
|
.set_local_bucket_alias(bucket_id, &key.key_id, &query.new_name)
|
||||||
|
@ -290,9 +291,10 @@ impl AdminRpcHandler {
|
||||||
|
|
||||||
async fn handle_unalias_bucket(&self, query: &UnaliasBucketOpt) -> Result<AdminRpc, Error> {
|
async fn handle_unalias_bucket(&self, query: &UnaliasBucketOpt) -> Result<AdminRpc, Error> {
|
||||||
let helper = self.garage.bucket_helper();
|
let helper = self.garage.bucket_helper();
|
||||||
|
let key_helper = self.garage.key_helper();
|
||||||
|
|
||||||
if let Some(key_pattern) = &query.local {
|
if let Some(key_pattern) = &query.local {
|
||||||
let key = helper.get_existing_matching_key(key_pattern).await?;
|
let key = key_helper.get_existing_matching_key(key_pattern).await?;
|
||||||
|
|
||||||
let bucket_id = key
|
let bucket_id = key
|
||||||
.state
|
.state
|
||||||
|
@ -331,12 +333,15 @@ impl AdminRpcHandler {
|
||||||
|
|
||||||
async fn handle_bucket_allow(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> {
|
async fn handle_bucket_allow(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> {
|
||||||
let helper = self.garage.bucket_helper();
|
let helper = self.garage.bucket_helper();
|
||||||
|
let key_helper = self.garage.key_helper();
|
||||||
|
|
||||||
let bucket_id = helper
|
let bucket_id = helper
|
||||||
.resolve_global_bucket_name(&query.bucket)
|
.resolve_global_bucket_name(&query.bucket)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_bad_request("Bucket not found")?;
|
.ok_or_bad_request("Bucket not found")?;
|
||||||
let key = helper.get_existing_matching_key(&query.key_pattern).await?;
|
let key = key_helper
|
||||||
|
.get_existing_matching_key(&query.key_pattern)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let allow_read = query.read || key.allow_read(&bucket_id);
|
let allow_read = query.read || key.allow_read(&bucket_id);
|
||||||
let allow_write = query.write || key.allow_write(&bucket_id);
|
let allow_write = query.write || key.allow_write(&bucket_id);
|
||||||
|
@ -363,12 +368,15 @@ impl AdminRpcHandler {
|
||||||
|
|
||||||
async fn handle_bucket_deny(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> {
|
async fn handle_bucket_deny(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> {
|
||||||
let helper = self.garage.bucket_helper();
|
let helper = self.garage.bucket_helper();
|
||||||
|
let key_helper = self.garage.key_helper();
|
||||||
|
|
||||||
let bucket_id = helper
|
let bucket_id = helper
|
||||||
.resolve_global_bucket_name(&query.bucket)
|
.resolve_global_bucket_name(&query.bucket)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_bad_request("Bucket not found")?;
|
.ok_or_bad_request("Bucket not found")?;
|
||||||
let key = helper.get_existing_matching_key(&query.key_pattern).await?;
|
let key = key_helper
|
||||||
|
.get_existing_matching_key(&query.key_pattern)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let allow_read = !query.read && key.allow_read(&bucket_id);
|
let allow_read = !query.read && key.allow_read(&bucket_id);
|
||||||
let allow_write = !query.write && key.allow_write(&bucket_id);
|
let allow_write = !query.write && key.allow_write(&bucket_id);
|
||||||
|
@ -469,7 +477,7 @@ impl AdminRpcHandler {
|
||||||
async fn handle_key_info(&self, query: &KeyOpt) -> Result<AdminRpc, Error> {
|
async fn handle_key_info(&self, query: &KeyOpt) -> Result<AdminRpc, Error> {
|
||||||
let key = self
|
let key = self
|
||||||
.garage
|
.garage
|
||||||
.bucket_helper()
|
.key_helper()
|
||||||
.get_existing_matching_key(&query.key_pattern)
|
.get_existing_matching_key(&query.key_pattern)
|
||||||
.await?;
|
.await?;
|
||||||
self.key_info_result(key).await
|
self.key_info_result(key).await
|
||||||
|
@ -484,7 +492,7 @@ impl AdminRpcHandler {
|
||||||
async fn handle_rename_key(&self, query: &KeyRenameOpt) -> Result<AdminRpc, Error> {
|
async fn handle_rename_key(&self, query: &KeyRenameOpt) -> Result<AdminRpc, Error> {
|
||||||
let mut key = self
|
let mut key = self
|
||||||
.garage
|
.garage
|
||||||
.bucket_helper()
|
.key_helper()
|
||||||
.get_existing_matching_key(&query.key_pattern)
|
.get_existing_matching_key(&query.key_pattern)
|
||||||
.await?;
|
.await?;
|
||||||
key.params_mut()
|
key.params_mut()
|
||||||
|
@ -496,9 +504,11 @@ impl AdminRpcHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_delete_key(&self, query: &KeyDeleteOpt) -> Result<AdminRpc, Error> {
|
async fn handle_delete_key(&self, query: &KeyDeleteOpt) -> Result<AdminRpc, Error> {
|
||||||
let helper = self.garage.bucket_helper();
|
let key_helper = self.garage.key_helper();
|
||||||
|
|
||||||
let mut key = helper.get_existing_matching_key(&query.key_pattern).await?;
|
let mut key = key_helper
|
||||||
|
.get_existing_matching_key(&query.key_pattern)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if !query.yes {
|
if !query.yes {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
|
@ -506,32 +516,7 @@ impl AdminRpcHandler {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = key.state.as_option_mut().unwrap();
|
key_helper.delete_key(&mut key).await?;
|
||||||
|
|
||||||
// --- done checking, now commit ---
|
|
||||||
// (the step at unset_local_bucket_alias will fail if a bucket
|
|
||||||
// does not have another alias, the deletion will be
|
|
||||||
// interrupted in the middle if that happens)
|
|
||||||
|
|
||||||
// 1. Delete local aliases
|
|
||||||
for (alias, _, to) in state.local_aliases.items().iter() {
|
|
||||||
if let Some(bucket_id) = to {
|
|
||||||
helper
|
|
||||||
.unset_local_bucket_alias(*bucket_id, &key.key_id, alias)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Remove permissions on all authorized buckets
|
|
||||||
for (ab_id, _auth) in state.authorized_buckets.items().iter() {
|
|
||||||
helper
|
|
||||||
.set_bucket_key_permissions(*ab_id, &key.key_id, BucketKeyPerm::NO_PERMISSIONS)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Actually delete key
|
|
||||||
key.state = Deletable::delete();
|
|
||||||
self.garage.key_table.insert(&key).await?;
|
|
||||||
|
|
||||||
Ok(AdminRpc::Ok(format!(
|
Ok(AdminRpc::Ok(format!(
|
||||||
"Key {} was deleted successfully.",
|
"Key {} was deleted successfully.",
|
||||||
|
@ -542,7 +527,7 @@ impl AdminRpcHandler {
|
||||||
async fn handle_allow_key(&self, query: &KeyPermOpt) -> Result<AdminRpc, Error> {
|
async fn handle_allow_key(&self, query: &KeyPermOpt) -> Result<AdminRpc, Error> {
|
||||||
let mut key = self
|
let mut key = self
|
||||||
.garage
|
.garage
|
||||||
.bucket_helper()
|
.key_helper()
|
||||||
.get_existing_matching_key(&query.key_pattern)
|
.get_existing_matching_key(&query.key_pattern)
|
||||||
.await?;
|
.await?;
|
||||||
if query.create_bucket {
|
if query.create_bucket {
|
||||||
|
@ -555,7 +540,7 @@ impl AdminRpcHandler {
|
||||||
async fn handle_deny_key(&self, query: &KeyPermOpt) -> Result<AdminRpc, Error> {
|
async fn handle_deny_key(&self, query: &KeyPermOpt) -> Result<AdminRpc, Error> {
|
||||||
let mut key = self
|
let mut key = self
|
||||||
.garage
|
.garage
|
||||||
.bucket_helper()
|
.key_helper()
|
||||||
.get_existing_matching_key(&query.key_pattern)
|
.get_existing_matching_key(&query.key_pattern)
|
||||||
.await?;
|
.await?;
|
||||||
if query.create_bucket {
|
if query.create_bucket {
|
||||||
|
|
|
@ -191,6 +191,10 @@ impl Garage {
|
||||||
pub fn bucket_helper(&self) -> helper::bucket::BucketHelper {
|
pub fn bucket_helper(&self) -> helper::bucket::BucketHelper {
|
||||||
helper::bucket::BucketHelper(self)
|
helper::bucket::BucketHelper(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn key_helper(&self) -> helper::key::KeyHelper {
|
||||||
|
helper::key::KeyHelper(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "k2v")]
|
#[cfg(feature = "k2v")]
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::bucket_alias_table::*;
|
||||||
use crate::bucket_table::*;
|
use crate::bucket_table::*;
|
||||||
use crate::garage::Garage;
|
use crate::garage::Garage;
|
||||||
use crate::helper::error::*;
|
use crate::helper::error::*;
|
||||||
use crate::key_table::{Key, KeyFilter};
|
use crate::helper::key::KeyHelper;
|
||||||
use crate::permission::BucketKeyPerm;
|
use crate::permission::BucketKeyPerm;
|
||||||
|
|
||||||
pub struct BucketHelper<'a>(pub(crate) &'a Garage);
|
pub struct BucketHelper<'a>(pub(crate) &'a Garage);
|
||||||
|
@ -77,60 +77,6 @@ impl<'a> BucketHelper<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Key if it is present in key table,
|
|
||||||
/// even if it is in deleted state. Querying a non-existing
|
|
||||||
/// key ID returns an internal error.
|
|
||||||
pub async fn get_internal_key(&self, key_id: &String) -> Result<Key, Error> {
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.key_table
|
|
||||||
.get(&EmptyKey, key_id)
|
|
||||||
.await?
|
|
||||||
.ok_or_message(format!("Key {} does not exist", key_id))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a Key if it is present in key table,
|
|
||||||
/// only if it is in non-deleted state.
|
|
||||||
/// Querying a non-existing key ID or a deleted key
|
|
||||||
/// returns a bad request error.
|
|
||||||
pub async fn get_existing_key(&self, key_id: &String) -> Result<Key, Error> {
|
|
||||||
self.0
|
|
||||||
.key_table
|
|
||||||
.get(&EmptyKey, key_id)
|
|
||||||
.await?
|
|
||||||
.filter(|b| !b.state.is_deleted())
|
|
||||||
.ok_or_bad_request(format!("Key {} does not exist or has been deleted", key_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a Key if it is present in key table,
|
|
||||||
/// looking it up by key ID or by a match on its name,
|
|
||||||
/// only if it is in non-deleted state.
|
|
||||||
/// Querying a non-existing key ID or a deleted key
|
|
||||||
/// returns a bad request error.
|
|
||||||
pub async fn get_existing_matching_key(&self, pattern: &str) -> Result<Key, Error> {
|
|
||||||
let candidates = self
|
|
||||||
.0
|
|
||||||
.key_table
|
|
||||||
.get_range(
|
|
||||||
&EmptyKey,
|
|
||||||
None,
|
|
||||||
Some(KeyFilter::MatchesAndNotDeleted(pattern.to_string())),
|
|
||||||
10,
|
|
||||||
EnumerationOrder::Forward,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if candidates.len() != 1 {
|
|
||||||
Err(Error::BadRequest(format!(
|
|
||||||
"{} matching keys",
|
|
||||||
candidates.len()
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Ok(candidates.into_iter().next().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets a new alias for a bucket in global namespace.
|
/// Sets a new alias for a bucket in global namespace.
|
||||||
/// This function fails if:
|
/// This function fails if:
|
||||||
/// - alias name is not valid according to S3 spec
|
/// - alias name is not valid according to S3 spec
|
||||||
|
@ -303,6 +249,8 @@ impl<'a> BucketHelper<'a> {
|
||||||
key_id: &String,
|
key_id: &String,
|
||||||
alias_name: &String,
|
alias_name: &String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let key_helper = KeyHelper(self.0);
|
||||||
|
|
||||||
if !is_valid_bucket_name(alias_name) {
|
if !is_valid_bucket_name(alias_name) {
|
||||||
return Err(Error::BadRequest(format!(
|
return Err(Error::BadRequest(format!(
|
||||||
"{}: {}",
|
"{}: {}",
|
||||||
|
@ -311,7 +259,7 @@ impl<'a> BucketHelper<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bucket = self.get_existing_bucket(bucket_id).await?;
|
let mut bucket = self.get_existing_bucket(bucket_id).await?;
|
||||||
let mut key = self.get_existing_key(key_id).await?;
|
let mut key = key_helper.get_existing_key(key_id).await?;
|
||||||
|
|
||||||
let mut key_param = key.state.as_option_mut().unwrap();
|
let mut key_param = key.state.as_option_mut().unwrap();
|
||||||
|
|
||||||
|
@ -360,8 +308,10 @@ impl<'a> BucketHelper<'a> {
|
||||||
key_id: &String,
|
key_id: &String,
|
||||||
alias_name: &String,
|
alias_name: &String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let key_helper = KeyHelper(self.0);
|
||||||
|
|
||||||
let mut bucket = self.get_existing_bucket(bucket_id).await?;
|
let mut bucket = self.get_existing_bucket(bucket_id).await?;
|
||||||
let mut key = self.get_existing_key(key_id).await?;
|
let mut key = key_helper.get_existing_key(key_id).await?;
|
||||||
|
|
||||||
let mut bucket_p = bucket.state.as_option_mut().unwrap();
|
let mut bucket_p = bucket.state.as_option_mut().unwrap();
|
||||||
|
|
||||||
|
@ -429,8 +379,10 @@ impl<'a> BucketHelper<'a> {
|
||||||
key_id: &String,
|
key_id: &String,
|
||||||
mut perm: BucketKeyPerm,
|
mut perm: BucketKeyPerm,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let key_helper = KeyHelper(self.0);
|
||||||
|
|
||||||
let mut bucket = self.get_internal_bucket(bucket_id).await?;
|
let mut bucket = self.get_internal_bucket(bucket_id).await?;
|
||||||
let mut key = self.get_internal_key(key_id).await?;
|
let mut key = key_helper.get_internal_key(key_id).await?;
|
||||||
|
|
||||||
if let Some(bstate) = bucket.state.as_option() {
|
if let Some(bstate) = bucket.state.as_option() {
|
||||||
if let Some(kp) = bstate.authorized_keys.get(key_id) {
|
if let Some(kp) = bstate.authorized_keys.get(key_id) {
|
||||||
|
|
102
src/model/helper/key.rs
Normal file
102
src/model/helper/key.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use garage_table::util::*;
|
||||||
|
use garage_util::crdt::*;
|
||||||
|
use garage_util::error::OkOrMessage;
|
||||||
|
|
||||||
|
use crate::garage::Garage;
|
||||||
|
use crate::helper::bucket::BucketHelper;
|
||||||
|
use crate::helper::error::*;
|
||||||
|
use crate::key_table::{Key, KeyFilter};
|
||||||
|
use crate::permission::BucketKeyPerm;
|
||||||
|
|
||||||
|
pub struct KeyHelper<'a>(pub(crate) &'a Garage);
|
||||||
|
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
|
impl<'a> KeyHelper<'a> {
|
||||||
|
/// Returns a Key if it is present in key table,
|
||||||
|
/// even if it is in deleted state. Querying a non-existing
|
||||||
|
/// key ID returns an internal error.
|
||||||
|
pub async fn get_internal_key(&self, key_id: &String) -> Result<Key, Error> {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.key_table
|
||||||
|
.get(&EmptyKey, key_id)
|
||||||
|
.await?
|
||||||
|
.ok_or_message(format!("Key {} does not exist", key_id))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a Key if it is present in key table,
|
||||||
|
/// only if it is in non-deleted state.
|
||||||
|
/// Querying a non-existing key ID or a deleted key
|
||||||
|
/// returns a bad request error.
|
||||||
|
pub async fn get_existing_key(&self, key_id: &String) -> Result<Key, Error> {
|
||||||
|
self.0
|
||||||
|
.key_table
|
||||||
|
.get(&EmptyKey, key_id)
|
||||||
|
.await?
|
||||||
|
.filter(|b| !b.state.is_deleted())
|
||||||
|
.ok_or_bad_request(format!("Key {} does not exist or has been deleted", key_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a Key if it is present in key table,
|
||||||
|
/// looking it up by key ID or by a match on its name,
|
||||||
|
/// only if it is in non-deleted state.
|
||||||
|
/// Querying a non-existing key ID or a deleted key
|
||||||
|
/// returns a bad request error.
|
||||||
|
pub async fn get_existing_matching_key(&self, pattern: &str) -> Result<Key, Error> {
|
||||||
|
let candidates = self
|
||||||
|
.0
|
||||||
|
.key_table
|
||||||
|
.get_range(
|
||||||
|
&EmptyKey,
|
||||||
|
None,
|
||||||
|
Some(KeyFilter::MatchesAndNotDeleted(pattern.to_string())),
|
||||||
|
10,
|
||||||
|
EnumerationOrder::Forward,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if candidates.len() != 1 {
|
||||||
|
Err(Error::BadRequest(format!(
|
||||||
|
"{} matching keys",
|
||||||
|
candidates.len()
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(candidates.into_iter().next().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes an API access key
|
||||||
|
pub async fn delete_key(&self, key: &mut Key) -> Result<(), Error> {
|
||||||
|
let bucket_helper = BucketHelper(self.0);
|
||||||
|
|
||||||
|
let state = key.state.as_option_mut().unwrap();
|
||||||
|
|
||||||
|
// --- done checking, now commit ---
|
||||||
|
// (the step at unset_local_bucket_alias will fail if a bucket
|
||||||
|
// does not have another alias, the deletion will be
|
||||||
|
// interrupted in the middle if that happens)
|
||||||
|
|
||||||
|
// 1. Delete local aliases
|
||||||
|
for (alias, _, to) in state.local_aliases.items().iter() {
|
||||||
|
if let Some(bucket_id) = to {
|
||||||
|
bucket_helper
|
||||||
|
.unset_local_bucket_alias(*bucket_id, &key.key_id, alias)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Remove permissions on all authorized buckets
|
||||||
|
for (ab_id, _auth) in state.authorized_buckets.items().iter() {
|
||||||
|
bucket_helper
|
||||||
|
.set_bucket_key_permissions(*ab_id, &key.key_id, BucketKeyPerm::NO_PERMISSIONS)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Actually delete key
|
||||||
|
key.state = Deletable::delete();
|
||||||
|
self.0.key_table.insert(key).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod bucket;
|
pub mod bucket;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod key;
|
||||||
|
|
Loading…
Reference in a new issue