From 51fb3799a153a0db990fc74a37563ec612e20fc2 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 23 Apr 2020 20:25:45 +0000 Subject: [PATCH] Key management admin commands --- src/admin_rpc.rs | 210 ++++++++++++++++++++++++++++++++------ src/main.rs | 51 ++++++++- src/server.rs | 15 +++ src/store/bucket_table.rs | 13 ++- src/store/key_table.rs | 58 ++++++++--- 5 files changed, 293 insertions(+), 54 deletions(-) diff --git a/src/admin_rpc.rs b/src/admin_rpc.rs index e4afc68..4e5c56d 100644 --- a/src/admin_rpc.rs +++ b/src/admin_rpc.rs @@ -12,6 +12,7 @@ use crate::rpc::rpc_client::*; use crate::rpc::rpc_server::*; use crate::store::bucket_table::*; +use crate::store::key_table::*; use crate::store::repair::Repair; use crate::*; @@ -29,6 +30,8 @@ pub enum AdminRPC { Ok(String), BucketList(Vec), BucketInfo(Bucket), + KeyList(Vec<(String, String)>), + KeyInfo(Key), } impl RpcMessage for AdminRPC {} @@ -72,19 +75,8 @@ impl AdminRpcHandler { Ok(AdminRPC::BucketList(bucket_names)) } BucketOperation::Info(query) => { - let bucket = self - .garage - .bucket_table - .get(&EmptyKey, &query.name) - .await? - .filter(|b| !b.deleted); - match bucket { - Some(b) => Ok(AdminRPC::BucketInfo(b)), - None => Err(Error::BadRequest(format!( - "Bucket {} not found", - query.name - ))), - } + let bucket = self.get_existing_bucket(&query.name).await?; + Ok(AdminRPC::BucketInfo(bucket)) } BucketOperation::Create(query) => { let bucket = self.garage.bucket_table.get(&EmptyKey, &query.name).await?; @@ -105,21 +97,7 @@ impl AdminRpcHandler { Ok(AdminRPC::Ok(format!("Bucket {} was created.", query.name))) } BucketOperation::Delete(query) => { - let bucket = match self - .garage - .bucket_table - .get(&EmptyKey, &query.name) - .await? - .filter(|b| !b.deleted) - { - None => { - return Err(Error::BadRequest(format!( - "Bucket {} does not exist", - query.name - ))); - } - Some(b) => b, - }; + let bucket = self.get_existing_bucket(&query.name).await?; let objects = self .garage .object_table @@ -136,6 +114,17 @@ impl AdminRpcHandler { "Add --yes flag to really perform this operation" ))); } + // --- done checking, now commit --- + for ak in bucket.authorized_keys() { + if let Some(key) = self.garage.key_table.get(&EmptyKey, &ak.key_id).await? { + if !key.deleted { + self.update_key_bucket(key, &bucket.name, false, false) + .await?; + } + } else { + return Err(Error::Message(format!("Key not found: {}", ak.key_id))); + } + } self.garage .bucket_table .insert(&Bucket::new( @@ -147,15 +136,172 @@ impl AdminRpcHandler { .await?; Ok(AdminRPC::Ok(format!("Bucket {} was deleted.", query.name))) } - _ => { - // TODO - Err(Error::Message(format!("Not implemented"))) + BucketOperation::Allow(query) => { + let key = self.get_existing_key(&query.key_id).await?; + let bucket = self.get_existing_bucket(&query.bucket).await?; + let allow_read = query.read || key.allow_read(&query.bucket); + let allow_write = query.write || key.allow_write(&query.bucket); + self.update_key_bucket(key, &query.bucket, allow_read, allow_write) + .await?; + self.update_bucket_key(bucket, &query.key_id, allow_read, allow_write) + .await?; + Ok(AdminRPC::Ok(format!( + "New permissions for {} on {}: read {}, write {}.", + &query.key_id, &query.bucket, allow_read, allow_write + ))) + } + BucketOperation::Deny(query) => { + let key = self.get_existing_key(&query.key_id).await?; + let bucket = self.get_existing_bucket(&query.bucket).await?; + let allow_read = !query.read && key.allow_read(&query.bucket); + let allow_write = !query.write && key.allow_write(&query.bucket); + self.update_key_bucket(key, &query.bucket, allow_read, allow_write) + .await?; + self.update_bucket_key(bucket, &query.key_id, allow_read, allow_write) + .await?; + Ok(AdminRPC::Ok(format!( + "New permissions for {} on {}: read {}, write {}.", + &query.key_id, &query.bucket, allow_read, allow_write + ))) } } } async fn handle_key_cmd(&self, cmd: KeyOperation) -> Result { - Err(Error::Message(format!("Not implemented"))) + match cmd { + KeyOperation::List => { + let key_ids = self + .garage + .key_table + .get_range(&EmptyKey, None, Some(()), 10000) + .await? + .iter() + .map(|k| (k.key_id.to_string(), k.name.to_string())) + .collect::>(); + Ok(AdminRPC::KeyList(key_ids)) + } + KeyOperation::Info(query) => { + let key = self.get_existing_key(&query.key_id).await?; + Ok(AdminRPC::KeyInfo(key)) + } + KeyOperation::New(query) => { + let key = Key::new(query.name, vec![]); + self.garage.key_table.insert(&key).await?; + Ok(AdminRPC::KeyInfo(key)) + } + KeyOperation::Rename(query) => { + let mut key = self.get_existing_key(&query.key_id).await?; + key.name_timestamp = std::cmp::max(key.name_timestamp + 1, now_msec()); + key.name = query.new_name; + self.garage.key_table.insert(&key).await?; + Ok(AdminRPC::KeyInfo(key)) + } + KeyOperation::Delete(query) => { + let key = self.get_existing_key(&query.key_id).await?; + if !query.yes { + return Err(Error::BadRequest(format!( + "Add --yes flag to really perform this operation" + ))); + } + // --- done checking, now commit --- + for ab in key.authorized_buckets().iter() { + if let Some(bucket) = + self.garage.bucket_table.get(&EmptyKey, &ab.bucket).await? + { + if !bucket.deleted { + self.update_bucket_key(bucket, &key.key_id, false, false) + .await?; + } + } else { + return Err(Error::Message(format!("Bucket not found: {}", ab.bucket))); + } + } + let del_key = Key::delete(key.key_id); + self.garage.key_table.insert(&del_key).await?; + Ok(AdminRPC::Ok(format!( + "Key {} was deleted successfully.", + query.key_id + ))) + } + } + } + + async fn get_existing_bucket(&self, bucket: &String) -> Result { + self.garage + .bucket_table + .get(&EmptyKey, bucket) + .await? + .filter(|b| !b.deleted) + .map(Ok) + .unwrap_or(Err(Error::BadRequest(format!( + "Bucket {} does not exist", + bucket + )))) + } + + async fn get_existing_key(&self, id: &String) -> Result { + self.garage + .key_table + .get(&EmptyKey, id) + .await? + .filter(|k| !k.deleted) + .map(Ok) + .unwrap_or(Err(Error::BadRequest(format!("Key {} does not exist", id)))) + } + + async fn update_bucket_key( + &self, + mut bucket: Bucket, + key_id: &String, + allow_read: bool, + allow_write: bool, + ) -> Result<(), Error> { + let timestamp = match bucket + .authorized_keys() + .iter() + .find(|x| x.key_id == *key_id) + { + None => now_msec(), + Some(ab) => std::cmp::max(ab.timestamp + 1, now_msec()), + }; + bucket.clear_keys(); + bucket + .add_key(AllowedKey { + key_id: key_id.clone(), + timestamp, + allow_read, + allow_write, + }) + .unwrap(); + self.garage.bucket_table.insert(&bucket).await?; + Ok(()) + } + + async fn update_key_bucket( + &self, + mut key: Key, + bucket: &String, + allow_read: bool, + allow_write: bool, + ) -> Result<(), Error> { + let timestamp = match key + .authorized_buckets() + .iter() + .find(|x| x.bucket == *bucket) + { + None => now_msec(), + Some(ab) => std::cmp::max(ab.timestamp + 1, now_msec()), + }; + key.clear_buckets(); + key.add_bucket(AllowedBucket { + bucket: bucket.clone(), + timestamp, + allow_read, + allow_write, + }) + .unwrap(); + self.garage.key_table.insert(&key).await?; + Ok(()) } async fn handle_launch_repair(self: &Arc, opt: RepairOpt) -> Result { diff --git a/src/main.rs b/src/main.rs index cf3a413..2c25aad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -172,7 +172,7 @@ pub struct DeleteBucketOpt { pub struct PermBucketOpt { /// Access key ID #[structopt(long = "key")] - pub key: String, + pub key_id: String, /// Allow/deny read operations #[structopt(long = "read")] @@ -192,19 +192,53 @@ pub enum KeyOperation { #[structopt(name = "list")] List, + /// Get key info + #[structopt(name = "info")] + Info(KeyOpt), + /// Create new key #[structopt(name = "new")] - New, + New(KeyNewOpt), + + /// Rename key + #[structopt(name = "rename")] + Rename(KeyRenameOpt), /// Delete key #[structopt(name = "delete")] Delete(KeyDeleteOpt), } +#[derive(Serialize, Deserialize, StructOpt, Debug)] +pub struct KeyOpt { + /// ID of the key + key_id: String, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug)] +pub struct KeyNewOpt { + /// Name of the key + #[structopt(long = "name", default_value = "Unnamed key")] + name: String, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug)] +pub struct KeyRenameOpt { + /// ID of the key + key_id: String, + + /// New name of the key + new_name: String, +} + #[derive(Serialize, Deserialize, StructOpt, Debug)] pub struct KeyDeleteOpt { - /// Name of the bucket to delete - bucket: String, + /// ID of the key + key_id: String, + + /// Confirm deletion + #[structopt(long = "yes")] + yes: bool, } #[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] @@ -489,6 +523,15 @@ async fn cmd_admin( AdminRPC::BucketInfo(bucket) => { println!("{:?}", bucket); } + AdminRPC::KeyList(kl) => { + println!("List of keys:"); + for key in kl { + println!("{}\t{}", key.0, key.1); + } + } + AdminRPC::KeyInfo(key) => { + println!("{:?}", key); + } r => { error!("Unexpected response: {:?}", r); } diff --git a/src/server.rs b/src/server.rs index de04615..0724630 100644 --- a/src/server.rs +++ b/src/server.rs @@ -19,6 +19,7 @@ use crate::table::*; use crate::store::block::*; use crate::store::block_ref_table::*; use crate::store::bucket_table::*; +use crate::store::key_table::*; use crate::store::object_table::*; use crate::store::version_table::*; @@ -35,6 +36,8 @@ pub struct Garage { pub block_manager: Arc, pub bucket_table: Arc>, + pub key_table: Arc>, + pub object_table: Arc>, pub version_table: Arc>, pub block_ref_table: Arc>, @@ -138,6 +141,17 @@ impl Garage { ) .await; + info!("Initialize key_table_table..."); + let key_table = Table::new( + KeyTable, + control_rep_param.clone(), + system.clone(), + &db, + "key".to_string(), + rpc_server, + ) + .await; + info!("Initialize Garage..."); let garage = Arc::new(Self { config, @@ -146,6 +160,7 @@ impl Garage { block_manager, background, bucket_table, + key_table, object_table, version_table, block_ref_table, diff --git a/src/store/bucket_table.rs b/src/store/bucket_table.rs index 7778b8f..a9bdaa7 100644 --- a/src/store/bucket_table.rs +++ b/src/store/bucket_table.rs @@ -41,7 +41,7 @@ impl Bucket { pub fn add_key(&mut self, key: AllowedKey) -> Result<(), ()> { match self .authorized_keys - .binary_search_by(|k| k.access_key_id.cmp(&key.access_key_id)) + .binary_search_by(|k| k.key_id.cmp(&key.key_id)) { Err(i) => { self.authorized_keys.insert(i, key); @@ -53,14 +53,17 @@ impl Bucket { pub fn authorized_keys(&self) -> &[AllowedKey] { &self.authorized_keys[..] } + pub fn clear_keys(&mut self) { + self.authorized_keys.clear(); + } } #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub struct AllowedKey { - pub access_key_id: String, + pub key_id: String, pub timestamp: u64, - pub allowed_read: bool, - pub allowed_write: bool, + pub allow_read: bool, + pub allow_write: bool, } impl Entry for Bucket { @@ -83,7 +86,7 @@ impl Entry for Bucket { for ak in other.authorized_keys.iter() { match self .authorized_keys - .binary_search_by(|our_ak| our_ak.access_key_id.cmp(&ak.access_key_id)) + .binary_search_by(|our_ak| our_ak.key_id.cmp(&ak.key_id)) { Ok(i) => { let our_ak = &mut self.authorized_keys[i]; diff --git a/src/store/key_table.rs b/src/store/key_table.rs index 6c3f96d..add6ab0 100644 --- a/src/store/key_table.rs +++ b/src/store/key_table.rs @@ -1,16 +1,21 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use crate::data::*; use crate::error::Error; use crate::table::*; #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub struct Key { // Primary key - pub access_key_id: String, + pub key_id: String, // Associated secret key (immutable) - pub secret_access_key: String, + pub secret_key: String, + + // Name + pub name: String, + pub name_timestamp: u64, // Deletion pub deleted: bool, @@ -20,12 +25,14 @@ pub struct Key { } impl Key { - pub fn new(buckets: Vec) -> Self { - let access_key_id = format!("GK{}", hex::encode(&rand::random::<[u8; 12]>()[..])); - let secret_access_key = hex::encode(&rand::random::<[u8; 32]>()[..]); + pub fn new(name: String, buckets: Vec) -> Self { + let key_id = format!("GK{}", hex::encode(&rand::random::<[u8; 12]>()[..])); + let secret_key = hex::encode(&rand::random::<[u8; 32]>()[..]); let mut ret = Self { - access_key_id, - secret_access_key, + key_id, + secret_key, + name, + name_timestamp: now_msec(), deleted: false, authorized_buckets: vec![], }; @@ -35,10 +42,12 @@ impl Key { } ret } - pub fn delete(access_key_id: String, secret_access_key: String) -> Self { + pub fn delete(key_id: String) -> Self { Self { - access_key_id, - secret_access_key, + key_id, + secret_key: "".into(), + name: "".into(), + name_timestamp: now_msec(), deleted: true, authorized_buckets: vec![], } @@ -59,14 +68,31 @@ impl Key { pub fn authorized_buckets(&self) -> &[AllowedBucket] { &self.authorized_buckets[..] } + pub fn clear_buckets(&mut self) { + self.authorized_buckets.clear(); + } + pub fn allow_read(&self, bucket: &str) -> bool { + self.authorized_buckets + .iter() + .find(|x| x.bucket.as_str() == bucket) + .map(|x| x.allow_read) + .unwrap_or(false) + } + pub fn allow_write(&self, bucket: &str) -> bool { + self.authorized_buckets + .iter() + .find(|x| x.bucket.as_str() == bucket) + .map(|x| x.allow_write) + .unwrap_or(false) + } } #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub struct AllowedBucket { pub bucket: String, pub timestamp: u64, - pub allowed_read: bool, - pub allowed_write: bool, + pub allow_read: bool, + pub allow_write: bool, } impl Entry for Key { @@ -74,15 +100,21 @@ impl Entry for Key { &EmptyKey } fn sort_key(&self) -> &String { - &self.access_key_id + &self.key_id } fn merge(&mut self, other: &Self) { if other.deleted { self.deleted = true; + } + if self.deleted { self.authorized_buckets.clear(); return; } + if other.name_timestamp > self.name_timestamp { + self.name_timestamp = other.name_timestamp; + self.name = other.name.clone(); + } for ab in other.authorized_buckets.iter() { match self