Key management admin commands

This commit is contained in:
Alex Auvolat 2020-04-23 20:25:45 +00:00
parent 4ef84a0558
commit 51fb3799a1
5 changed files with 293 additions and 54 deletions

View file

@ -12,6 +12,7 @@ use crate::rpc::rpc_client::*;
use crate::rpc::rpc_server::*; use crate::rpc::rpc_server::*;
use crate::store::bucket_table::*; use crate::store::bucket_table::*;
use crate::store::key_table::*;
use crate::store::repair::Repair; use crate::store::repair::Repair;
use crate::*; use crate::*;
@ -29,6 +30,8 @@ pub enum AdminRPC {
Ok(String), Ok(String),
BucketList(Vec<String>), BucketList(Vec<String>),
BucketInfo(Bucket), BucketInfo(Bucket),
KeyList(Vec<(String, String)>),
KeyInfo(Key),
} }
impl RpcMessage for AdminRPC {} impl RpcMessage for AdminRPC {}
@ -72,19 +75,8 @@ impl AdminRpcHandler {
Ok(AdminRPC::BucketList(bucket_names)) Ok(AdminRPC::BucketList(bucket_names))
} }
BucketOperation::Info(query) => { BucketOperation::Info(query) => {
let bucket = self let bucket = self.get_existing_bucket(&query.name).await?;
.garage Ok(AdminRPC::BucketInfo(bucket))
.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
))),
}
} }
BucketOperation::Create(query) => { BucketOperation::Create(query) => {
let bucket = self.garage.bucket_table.get(&EmptyKey, &query.name).await?; 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))) Ok(AdminRPC::Ok(format!("Bucket {} was created.", query.name)))
} }
BucketOperation::Delete(query) => { BucketOperation::Delete(query) => {
let bucket = match self let bucket = self.get_existing_bucket(&query.name).await?;
.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 objects = self let objects = self
.garage .garage
.object_table .object_table
@ -136,6 +114,17 @@ impl AdminRpcHandler {
"Add --yes flag to really perform this operation" "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 self.garage
.bucket_table .bucket_table
.insert(&Bucket::new( .insert(&Bucket::new(
@ -147,15 +136,172 @@ impl AdminRpcHandler {
.await?; .await?;
Ok(AdminRPC::Ok(format!("Bucket {} was deleted.", query.name))) Ok(AdminRPC::Ok(format!("Bucket {} was deleted.", query.name)))
} }
_ => { BucketOperation::Allow(query) => {
// TODO let key = self.get_existing_key(&query.key_id).await?;
Err(Error::Message(format!("Not implemented"))) 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<AdminRPC, Error> { async fn handle_key_cmd(&self, cmd: KeyOperation) -> Result<AdminRPC, Error> {
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::<Vec<_>>();
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<Bucket, Error> {
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<Key, Error> {
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<Self>, opt: RepairOpt) -> Result<AdminRPC, Error> { async fn handle_launch_repair(self: &Arc<Self>, opt: RepairOpt) -> Result<AdminRPC, Error> {

View file

@ -172,7 +172,7 @@ pub struct DeleteBucketOpt {
pub struct PermBucketOpt { pub struct PermBucketOpt {
/// Access key ID /// Access key ID
#[structopt(long = "key")] #[structopt(long = "key")]
pub key: String, pub key_id: String,
/// Allow/deny read operations /// Allow/deny read operations
#[structopt(long = "read")] #[structopt(long = "read")]
@ -192,19 +192,53 @@ pub enum KeyOperation {
#[structopt(name = "list")] #[structopt(name = "list")]
List, List,
/// Get key info
#[structopt(name = "info")]
Info(KeyOpt),
/// Create new key /// Create new key
#[structopt(name = "new")] #[structopt(name = "new")]
New, New(KeyNewOpt),
/// Rename key
#[structopt(name = "rename")]
Rename(KeyRenameOpt),
/// Delete key /// Delete key
#[structopt(name = "delete")] #[structopt(name = "delete")]
Delete(KeyDeleteOpt), 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)] #[derive(Serialize, Deserialize, StructOpt, Debug)]
pub struct KeyDeleteOpt { pub struct KeyDeleteOpt {
/// Name of the bucket to delete /// ID of the key
bucket: String, key_id: String,
/// Confirm deletion
#[structopt(long = "yes")]
yes: bool,
} }
#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] #[derive(Serialize, Deserialize, StructOpt, Debug, Clone)]
@ -489,6 +523,15 @@ async fn cmd_admin(
AdminRPC::BucketInfo(bucket) => { AdminRPC::BucketInfo(bucket) => {
println!("{:?}", 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 => { r => {
error!("Unexpected response: {:?}", r); error!("Unexpected response: {:?}", r);
} }

View file

@ -19,6 +19,7 @@ use crate::table::*;
use crate::store::block::*; use crate::store::block::*;
use crate::store::block_ref_table::*; use crate::store::block_ref_table::*;
use crate::store::bucket_table::*; use crate::store::bucket_table::*;
use crate::store::key_table::*;
use crate::store::object_table::*; use crate::store::object_table::*;
use crate::store::version_table::*; use crate::store::version_table::*;
@ -35,6 +36,8 @@ pub struct Garage {
pub block_manager: Arc<BlockManager>, pub block_manager: Arc<BlockManager>,
pub bucket_table: Arc<Table<BucketTable, TableFullReplication>>, pub bucket_table: Arc<Table<BucketTable, TableFullReplication>>,
pub key_table: Arc<Table<KeyTable, TableFullReplication>>,
pub object_table: Arc<Table<ObjectTable, TableShardedReplication>>, pub object_table: Arc<Table<ObjectTable, TableShardedReplication>>,
pub version_table: Arc<Table<VersionTable, TableShardedReplication>>, pub version_table: Arc<Table<VersionTable, TableShardedReplication>>,
pub block_ref_table: Arc<Table<BlockRefTable, TableShardedReplication>>, pub block_ref_table: Arc<Table<BlockRefTable, TableShardedReplication>>,
@ -138,6 +141,17 @@ impl Garage {
) )
.await; .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..."); info!("Initialize Garage...");
let garage = Arc::new(Self { let garage = Arc::new(Self {
config, config,
@ -146,6 +160,7 @@ impl Garage {
block_manager, block_manager,
background, background,
bucket_table, bucket_table,
key_table,
object_table, object_table,
version_table, version_table,
block_ref_table, block_ref_table,

View file

@ -41,7 +41,7 @@ impl Bucket {
pub fn add_key(&mut self, key: AllowedKey) -> Result<(), ()> { pub fn add_key(&mut self, key: AllowedKey) -> Result<(), ()> {
match self match self
.authorized_keys .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) => { Err(i) => {
self.authorized_keys.insert(i, key); self.authorized_keys.insert(i, key);
@ -53,14 +53,17 @@ impl Bucket {
pub fn authorized_keys(&self) -> &[AllowedKey] { pub fn authorized_keys(&self) -> &[AllowedKey] {
&self.authorized_keys[..] &self.authorized_keys[..]
} }
pub fn clear_keys(&mut self) {
self.authorized_keys.clear();
}
} }
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct AllowedKey { pub struct AllowedKey {
pub access_key_id: String, pub key_id: String,
pub timestamp: u64, pub timestamp: u64,
pub allowed_read: bool, pub allow_read: bool,
pub allowed_write: bool, pub allow_write: bool,
} }
impl Entry<EmptyKey, String> for Bucket { impl Entry<EmptyKey, String> for Bucket {
@ -83,7 +86,7 @@ impl Entry<EmptyKey, String> for Bucket {
for ak in other.authorized_keys.iter() { for ak in other.authorized_keys.iter() {
match self match self
.authorized_keys .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) => { Ok(i) => {
let our_ak = &mut self.authorized_keys[i]; let our_ak = &mut self.authorized_keys[i];

View file

@ -1,16 +1,21 @@
use async_trait::async_trait; use async_trait::async_trait;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::data::*;
use crate::error::Error; use crate::error::Error;
use crate::table::*; use crate::table::*;
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct Key { pub struct Key {
// Primary key // Primary key
pub access_key_id: String, pub key_id: String,
// Associated secret key (immutable) // Associated secret key (immutable)
pub secret_access_key: String, pub secret_key: String,
// Name
pub name: String,
pub name_timestamp: u64,
// Deletion // Deletion
pub deleted: bool, pub deleted: bool,
@ -20,12 +25,14 @@ pub struct Key {
} }
impl Key { impl Key {
pub fn new(buckets: Vec<AllowedBucket>) -> Self { pub fn new(name: String, buckets: Vec<AllowedBucket>) -> Self {
let access_key_id = format!("GK{}", hex::encode(&rand::random::<[u8; 12]>()[..])); let key_id = format!("GK{}", hex::encode(&rand::random::<[u8; 12]>()[..]));
let secret_access_key = hex::encode(&rand::random::<[u8; 32]>()[..]); let secret_key = hex::encode(&rand::random::<[u8; 32]>()[..]);
let mut ret = Self { let mut ret = Self {
access_key_id, key_id,
secret_access_key, secret_key,
name,
name_timestamp: now_msec(),
deleted: false, deleted: false,
authorized_buckets: vec![], authorized_buckets: vec![],
}; };
@ -35,10 +42,12 @@ impl Key {
} }
ret ret
} }
pub fn delete(access_key_id: String, secret_access_key: String) -> Self { pub fn delete(key_id: String) -> Self {
Self { Self {
access_key_id, key_id,
secret_access_key, secret_key: "".into(),
name: "".into(),
name_timestamp: now_msec(),
deleted: true, deleted: true,
authorized_buckets: vec![], authorized_buckets: vec![],
} }
@ -59,14 +68,31 @@ impl Key {
pub fn authorized_buckets(&self) -> &[AllowedBucket] { pub fn authorized_buckets(&self) -> &[AllowedBucket] {
&self.authorized_buckets[..] &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)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct AllowedBucket { pub struct AllowedBucket {
pub bucket: String, pub bucket: String,
pub timestamp: u64, pub timestamp: u64,
pub allowed_read: bool, pub allow_read: bool,
pub allowed_write: bool, pub allow_write: bool,
} }
impl Entry<EmptyKey, String> for Key { impl Entry<EmptyKey, String> for Key {
@ -74,15 +100,21 @@ impl Entry<EmptyKey, String> for Key {
&EmptyKey &EmptyKey
} }
fn sort_key(&self) -> &String { fn sort_key(&self) -> &String {
&self.access_key_id &self.key_id
} }
fn merge(&mut self, other: &Self) { fn merge(&mut self, other: &Self) {
if other.deleted { if other.deleted {
self.deleted = true; self.deleted = true;
}
if self.deleted {
self.authorized_buckets.clear(); self.authorized_buckets.clear();
return; 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() { for ab in other.authorized_buckets.iter() {
match self match self