cli: add functions to manage admin api tokens
This commit is contained in:
parent
ec0da3b644
commit
1bd7689301
4 changed files with 356 additions and 0 deletions
|
@ -38,6 +38,7 @@ garage_web.workspace = true
|
||||||
backtrace.workspace = true
|
backtrace.workspace = true
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
bytesize.workspace = true
|
bytesize.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
timeago.workspace = true
|
timeago.workspace = true
|
||||||
parse_duration.workspace = true
|
parse_duration.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
|
|
227
src/garage/cli/remote/admin_token.rs
Normal file
227
src/garage/cli/remote/admin_token.rs
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
use format_table::format_table;
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
use garage_util::error::*;
|
||||||
|
|
||||||
|
use garage_api_admin::api::*;
|
||||||
|
|
||||||
|
use crate::cli::remote::*;
|
||||||
|
use crate::cli::structs::*;
|
||||||
|
|
||||||
|
impl Cli {
|
||||||
|
pub async fn cmd_admin_token(&self, cmd: AdminTokenOperation) -> Result<(), Error> {
|
||||||
|
match cmd {
|
||||||
|
AdminTokenOperation::List => self.cmd_list_admin_tokens().await,
|
||||||
|
AdminTokenOperation::Info { api_token } => self.cmd_admin_token_info(api_token).await,
|
||||||
|
AdminTokenOperation::Create(opt) => self.cmd_create_admin_token(opt).await,
|
||||||
|
AdminTokenOperation::Rename {
|
||||||
|
api_token,
|
||||||
|
new_name,
|
||||||
|
} => self.cmd_rename_admin_token(api_token, new_name).await,
|
||||||
|
AdminTokenOperation::Set(opt) => self.cmd_update_admin_token(opt).await,
|
||||||
|
AdminTokenOperation::Delete { api_token, yes } => {
|
||||||
|
self.cmd_delete_admin_token(api_token, yes).await
|
||||||
|
}
|
||||||
|
AdminTokenOperation::DeleteExpired { yes } => {
|
||||||
|
self.cmd_delete_expired_admin_tokens(yes).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd_list_admin_tokens(&self) -> Result<(), Error> {
|
||||||
|
let list = self.api_request(ListAdminTokensRequest).await?;
|
||||||
|
|
||||||
|
let mut table = vec!["ID\tNAME\tEXPIRATION\tSCOPE".to_string()];
|
||||||
|
for tok in list.0.iter() {
|
||||||
|
let scope = if tok.scope.len() > 1 {
|
||||||
|
format!("[{}]", tok.scope.len())
|
||||||
|
} else {
|
||||||
|
tok.scope.get(0).cloned().unwrap_or_default()
|
||||||
|
};
|
||||||
|
let exp = if tok.expired {
|
||||||
|
"expired".to_string()
|
||||||
|
} else {
|
||||||
|
tok.expiration
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.unwrap_or("never".into())
|
||||||
|
};
|
||||||
|
table.push(format!(
|
||||||
|
"{}\t{}\t{}\t{}\t",
|
||||||
|
tok.id.as_deref().unwrap_or("-"),
|
||||||
|
tok.name,
|
||||||
|
exp,
|
||||||
|
scope,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
format_table(table);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd_admin_token_info(&self, search: String) -> Result<(), Error> {
|
||||||
|
let info = self
|
||||||
|
.api_request(GetAdminTokenInfoRequest {
|
||||||
|
id: None,
|
||||||
|
search: Some(search),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
print_token_info(&info);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd_create_admin_token(&self, opt: AdminTokenCreateOp) -> Result<(), Error> {
|
||||||
|
// TODO
|
||||||
|
let res = self
|
||||||
|
.api_request(CreateAdminTokenRequest(UpdateAdminTokenRequestBody {
|
||||||
|
name: opt.name,
|
||||||
|
expiration: opt
|
||||||
|
.expires_in
|
||||||
|
.map(|x| parse_duration::parse::parse(&x))
|
||||||
|
.transpose()
|
||||||
|
.ok_or_message("Invalid duration passed for --expires-in parameter")?
|
||||||
|
.map(|dur| Utc::now() + dur),
|
||||||
|
scope: opt.scope.map(|s| {
|
||||||
|
s.split(",")
|
||||||
|
.map(|x| x.trim().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if opt.quiet {
|
||||||
|
println!("{}", res.secret_token);
|
||||||
|
} else {
|
||||||
|
println!("This is your secret bearer token, it will not be shown again by Garage:");
|
||||||
|
println!("\n {}\n", res.secret_token);
|
||||||
|
print_token_info(&res.info);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd_rename_admin_token(&self, old: String, new: String) -> Result<(), Error> {
|
||||||
|
let token = self
|
||||||
|
.api_request(GetAdminTokenInfoRequest {
|
||||||
|
id: None,
|
||||||
|
search: Some(old),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let info = self
|
||||||
|
.api_request(UpdateAdminTokenRequest {
|
||||||
|
id: token.id.unwrap(),
|
||||||
|
body: UpdateAdminTokenRequestBody {
|
||||||
|
name: Some(new),
|
||||||
|
expiration: None,
|
||||||
|
scope: None,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
print_token_info(&info.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd_update_admin_token(&self, opt: AdminTokenSetOp) -> Result<(), Error> {
|
||||||
|
let token = self
|
||||||
|
.api_request(GetAdminTokenInfoRequest {
|
||||||
|
id: None,
|
||||||
|
search: Some(opt.api_token),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let info = self
|
||||||
|
.api_request(UpdateAdminTokenRequest {
|
||||||
|
id: token.id.unwrap(),
|
||||||
|
body: UpdateAdminTokenRequestBody {
|
||||||
|
name: None,
|
||||||
|
expiration: opt
|
||||||
|
.expires_in
|
||||||
|
.map(|x| parse_duration::parse::parse(&x))
|
||||||
|
.transpose()
|
||||||
|
.ok_or_message("Invalid duration passed for --expires-in parameter")?
|
||||||
|
.map(|dur| Utc::now() + dur),
|
||||||
|
scope: opt.scope.map(|s| {
|
||||||
|
s.split(",")
|
||||||
|
.map(|x| x.trim().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
print_token_info(&info.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd_delete_admin_token(&self, token: String, yes: bool) -> Result<(), Error> {
|
||||||
|
let token = self
|
||||||
|
.api_request(GetAdminTokenInfoRequest {
|
||||||
|
id: None,
|
||||||
|
search: Some(token),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let id = token.id.unwrap();
|
||||||
|
|
||||||
|
if !yes {
|
||||||
|
return Err(Error::Message(format!(
|
||||||
|
"Add the --yes flag to delete API token `{}` ({})",
|
||||||
|
token.name, id
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.api_request(DeleteAdminTokenRequest { id }).await?;
|
||||||
|
|
||||||
|
println!("Admin API token has been deleted.");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd_delete_expired_admin_tokens(&self, yes: bool) -> Result<(), Error> {
|
||||||
|
let mut list = self.api_request(ListAdminTokensRequest).await?.0;
|
||||||
|
|
||||||
|
list.retain(|tok| tok.expired);
|
||||||
|
|
||||||
|
if !yes {
|
||||||
|
return Err(Error::Message(format!(
|
||||||
|
"This would delete {} admin API tokens, add the --yes flag to proceed.",
|
||||||
|
list.len(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for token in list.iter() {
|
||||||
|
let id = token.id.clone().unwrap();
|
||||||
|
println!("Deleting token `{}` ({})", token.name, id);
|
||||||
|
self.api_request(DeleteAdminTokenRequest { id }).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{} admin API tokens have been deleted.", list.len());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_token_info(token: &GetAdminTokenInfoResponse) {
|
||||||
|
format_table(vec![
|
||||||
|
format!("ID:\t{}", token.id.as_deref().unwrap_or("-")),
|
||||||
|
format!("Name:\t{}", token.name),
|
||||||
|
format!(
|
||||||
|
"Validity:\t{}",
|
||||||
|
token.expired.then_some("EXPIRED").unwrap_or("valid")
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"Expiration:\t{}",
|
||||||
|
token
|
||||||
|
.expiration
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.unwrap_or("never".into())
|
||||||
|
),
|
||||||
|
format!("Scope:\t{}", token.scope.to_vec().join(", ")),
|
||||||
|
]);
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod admin_token;
|
||||||
pub mod bucket;
|
pub mod bucket;
|
||||||
pub mod cluster;
|
pub mod cluster;
|
||||||
pub mod key;
|
pub mod key;
|
||||||
|
@ -35,6 +36,7 @@ impl Cli {
|
||||||
}
|
}
|
||||||
Command::Layout(layout_opt) => self.layout_command_dispatch(layout_opt).await,
|
Command::Layout(layout_opt) => self.layout_command_dispatch(layout_opt).await,
|
||||||
Command::Bucket(bo) => self.cmd_bucket(bo).await,
|
Command::Bucket(bo) => self.cmd_bucket(bo).await,
|
||||||
|
Command::AdminToken(to) => self.cmd_admin_token(to).await,
|
||||||
Command::Key(ko) => self.cmd_key(ko).await,
|
Command::Key(ko) => self.cmd_key(ko).await,
|
||||||
Command::Worker(wo) => self.cmd_worker(wo).await,
|
Command::Worker(wo) => self.cmd_worker(wo).await,
|
||||||
Command::Block(bo) => self.cmd_block(bo).await,
|
Command::Block(bo) => self.cmd_block(bo).await,
|
||||||
|
|
|
@ -30,6 +30,10 @@ pub enum Command {
|
||||||
#[structopt(name = "key", version = garage_version())]
|
#[structopt(name = "key", version = garage_version())]
|
||||||
Key(KeyOperation),
|
Key(KeyOperation),
|
||||||
|
|
||||||
|
/// Operations on admin API tokens
|
||||||
|
#[structopt(name = "admin-token", version = garage_version())]
|
||||||
|
AdminToken(AdminTokenOperation),
|
||||||
|
|
||||||
/// Start repair of node data on remote node
|
/// Start repair of node data on remote node
|
||||||
#[structopt(name = "repair", version = garage_version())]
|
#[structopt(name = "repair", version = garage_version())]
|
||||||
Repair(RepairOpt),
|
Repair(RepairOpt),
|
||||||
|
@ -64,6 +68,10 @@ pub enum Command {
|
||||||
AdminApiSchema,
|
AdminApiSchema,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// ---- garage node ... ----
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
pub enum NodeOperation {
|
pub enum NodeOperation {
|
||||||
/// Print the full node ID (public key) of this Garage node, and its publicly reachable IP
|
/// Print the full node ID (public key) of this Garage node, and its publicly reachable IP
|
||||||
|
@ -91,6 +99,10 @@ pub struct ConnectNodeOpt {
|
||||||
pub(crate) node: String,
|
pub(crate) node: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// ---- garage layout ... ----
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
pub enum LayoutOperation {
|
pub enum LayoutOperation {
|
||||||
/// Assign role to Garage node
|
/// Assign role to Garage node
|
||||||
|
@ -193,6 +205,10 @@ pub struct SkipDeadNodesOpt {
|
||||||
pub(crate) allow_missing_data: bool,
|
pub(crate) allow_missing_data: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// ---- garage bucket ... ----
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
pub enum BucketOperation {
|
pub enum BucketOperation {
|
||||||
/// List buckets
|
/// List buckets
|
||||||
|
@ -350,6 +366,10 @@ pub struct CleanupIncompleteUploadsOpt {
|
||||||
pub buckets: Vec<String>,
|
pub buckets: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// ---- garage key ... ----
|
||||||
|
// ------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
pub enum KeyOperation {
|
pub enum KeyOperation {
|
||||||
/// List keys
|
/// List keys
|
||||||
|
@ -447,6 +467,92 @@ pub struct KeyImportOpt {
|
||||||
pub yes: bool,
|
pub yes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------
|
||||||
|
// ---- garage admin-token ... ----
|
||||||
|
// --------------------------------
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
pub enum AdminTokenOperation {
|
||||||
|
/// List all admin API tokens
|
||||||
|
#[structopt(name = "list", version = garage_version())]
|
||||||
|
List,
|
||||||
|
|
||||||
|
/// Fetch info about a specific admin API token
|
||||||
|
#[structopt(name = "info", version = garage_version())]
|
||||||
|
Info {
|
||||||
|
/// Name or prefix of the ID of the token to look up
|
||||||
|
api_token: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Create new admin API token
|
||||||
|
#[structopt(name = "create", version = garage_version())]
|
||||||
|
Create(AdminTokenCreateOp),
|
||||||
|
|
||||||
|
/// Rename an admin API token
|
||||||
|
#[structopt(name = "rename", version = garage_version())]
|
||||||
|
Rename {
|
||||||
|
/// Name or prefix of the ID of the token to rename
|
||||||
|
api_token: String,
|
||||||
|
/// New name of the admintoken
|
||||||
|
new_name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Set parameters for an admin API token
|
||||||
|
#[structopt(name = "set", version = garage_version())]
|
||||||
|
Set(AdminTokenSetOp),
|
||||||
|
|
||||||
|
/// Delete an admin API token
|
||||||
|
#[structopt(name = "delete", version = garage_version())]
|
||||||
|
Delete {
|
||||||
|
/// Name or prefix of the ID of the token to delete
|
||||||
|
api_token: String,
|
||||||
|
/// Confirm deletion
|
||||||
|
#[structopt(long = "yes")]
|
||||||
|
yes: bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Delete all expired admin API tokens
|
||||||
|
#[structopt(name = "delete-expired", version = garage_version())]
|
||||||
|
DeleteExpired {
|
||||||
|
/// Confirm deletion
|
||||||
|
#[structopt(long = "yes")]
|
||||||
|
yes: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug, Clone)]
|
||||||
|
pub struct AdminTokenCreateOp {
|
||||||
|
/// Set a name for the token
|
||||||
|
pub name: Option<String>,
|
||||||
|
/// Set an expiration time for the token (see docs.rs/parse_duration for date
|
||||||
|
/// format)
|
||||||
|
#[structopt(long = "expires-in")]
|
||||||
|
pub expires_in: Option<String>,
|
||||||
|
/// Set a limited scope for the token (by default, `*`)
|
||||||
|
#[structopt(long = "scope")]
|
||||||
|
pub scope: Option<String>,
|
||||||
|
/// Print only the newly generated API token to stdout
|
||||||
|
#[structopt(short = "q", long = "quiet")]
|
||||||
|
pub quiet: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug, Clone)]
|
||||||
|
pub struct AdminTokenSetOp {
|
||||||
|
/// Name or prefix of the ID of the token to modify
|
||||||
|
pub api_token: String,
|
||||||
|
/// Set an expiration time for the token (see docs.rs/parse_duration for date
|
||||||
|
/// format)
|
||||||
|
#[structopt(long = "expires-in")]
|
||||||
|
pub expires_in: Option<String>,
|
||||||
|
/// Set a limited scope for the token
|
||||||
|
#[structopt(long = "scope")]
|
||||||
|
pub scope: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// ---- garage repair ... ----
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Clone)]
|
#[derive(StructOpt, Debug, Clone)]
|
||||||
pub struct RepairOpt {
|
pub struct RepairOpt {
|
||||||
/// Launch repair operation on all nodes
|
/// Launch repair operation on all nodes
|
||||||
|
@ -508,6 +614,10 @@ pub enum ScrubCmd {
|
||||||
Cancel,
|
Cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------
|
||||||
|
// ---- garage offline-repair ... ----
|
||||||
|
// -----------------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Clone)]
|
#[derive(StructOpt, Debug, Clone)]
|
||||||
pub struct OfflineRepairOpt {
|
pub struct OfflineRepairOpt {
|
||||||
/// Confirm the launch of the repair operation
|
/// Confirm the launch of the repair operation
|
||||||
|
@ -529,6 +639,10 @@ pub enum OfflineRepairWhat {
|
||||||
ObjectCounters,
|
ObjectCounters,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// ---- garage stats ... ----
|
||||||
|
// --------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Clone)]
|
#[derive(StructOpt, Debug, Clone)]
|
||||||
pub struct StatsOpt {
|
pub struct StatsOpt {
|
||||||
/// Gather statistics from all nodes
|
/// Gather statistics from all nodes
|
||||||
|
@ -536,6 +650,10 @@ pub struct StatsOpt {
|
||||||
pub all_nodes: bool,
|
pub all_nodes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// ---- garage worker ... ----
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
|
#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
|
||||||
pub enum WorkerOperation {
|
pub enum WorkerOperation {
|
||||||
/// List all workers on Garage node
|
/// List all workers on Garage node
|
||||||
|
@ -579,6 +697,10 @@ pub struct WorkerListOpt {
|
||||||
pub errors: bool,
|
pub errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// ---- garage block ... ----
|
||||||
|
// --------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
|
#[derive(StructOpt, Debug, Eq, PartialEq, Clone)]
|
||||||
pub enum BlockOperation {
|
pub enum BlockOperation {
|
||||||
/// List all blocks that currently have a resync error
|
/// List all blocks that currently have a resync error
|
||||||
|
@ -611,6 +733,10 @@ pub enum BlockOperation {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// ---- garage meta ... ----
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(StructOpt, Debug, Eq, PartialEq, Clone, Copy)]
|
||||||
pub enum MetaOperation {
|
pub enum MetaOperation {
|
||||||
/// Save a snapshot of the metadata db file
|
/// Save a snapshot of the metadata db file
|
||||||
|
|
Loading…
Add table
Reference in a new issue