forked from Deuxfleurs/garage
Key management admin commands
This commit is contained in:
parent
4ef84a0558
commit
51fb3799a1
5 changed files with 293 additions and 54 deletions
210
src/admin_rpc.rs
210
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<String>),
|
||||
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<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> {
|
||||
|
|
51
src/main.rs
51
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);
|
||||
}
|
||||
|
|
|
@ -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<BlockManager>,
|
||||
|
||||
pub bucket_table: Arc<Table<BucketTable, TableFullReplication>>,
|
||||
pub key_table: Arc<Table<KeyTable, TableFullReplication>>,
|
||||
|
||||
pub object_table: Arc<Table<ObjectTable, TableShardedReplication>>,
|
||||
pub version_table: Arc<Table<VersionTable, TableShardedReplication>>,
|
||||
pub block_ref_table: Arc<Table<BlockRefTable, TableShardedReplication>>,
|
||||
|
@ -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,
|
||||
|
|
|
@ -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<EmptyKey, String> for Bucket {
|
||||
|
@ -83,7 +86,7 @@ impl Entry<EmptyKey, String> 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];
|
||||
|
|
|
@ -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<AllowedBucket>) -> 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<AllowedBucket>) -> 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<EmptyKey, String> for Key {
|
||||
|
@ -74,15 +100,21 @@ impl Entry<EmptyKey, String> 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
|
||||
|
|
Loading…
Reference in a new issue