From d2a064bb1b9ad01a20e9fba7842b343916da665a Mon Sep 17 00:00:00 2001 From: Alex Auvolat <lx@deuxfleurs.fr> Date: Wed, 12 Mar 2025 10:15:12 +0100 Subject: [PATCH] cli: add and remove scopes using --scope=+Scope or --scope=-Scope --- src/garage/cli/remote/admin_token.rs | 26 ++++++++++++++++++++++---- src/garage/cli/structs.rs | 16 ++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/garage/cli/remote/admin_token.rs b/src/garage/cli/remote/admin_token.rs index 4d765b92..78286dc4 100644 --- a/src/garage/cli/remote/admin_token.rs +++ b/src/garage/cli/remote/admin_token.rs @@ -152,10 +152,28 @@ impl Cli { .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<_>>() + scope: opt.scope.map({ + let mut new_scope = token.scope; + |scope_str| { + if let Some(add) = scope_str.strip_prefix("+") { + for a in add.split(",").map(|x| x.trim().to_string()) { + if !new_scope.contains(&a) { + new_scope.push(a); + } + } + new_scope + } else if let Some(sub) = scope_str.strip_prefix("-") { + for r in sub.split(",").map(|x| x.trim()) { + new_scope.retain(|x| x != r); + } + new_scope + } else { + scope_str + .split(",") + .map(|x| x.trim().to_string()) + .collect::<Vec<_>>() + } + } }), }, }) diff --git a/src/garage/cli/structs.rs b/src/garage/cli/structs.rs index 0b0a8b94..d4446a17 100644 --- a/src/garage/cli/structs.rs +++ b/src/garage/cli/structs.rs @@ -528,7 +528,12 @@ pub struct AdminTokenCreateOp { /// format) #[structopt(long = "expires-in")] pub expires_in: Option<String>, - /// Set a limited scope for the token (by default, `*`) + /// Set a limited scope for the token, as a comma-separated list of + /// admin API functions (e.g. GetClusterStatus, etc.). The default scope + /// is `*`, which allows access to all admin API functions. + /// Note that granting a scope that allows `CreateAdminToken` or + /// `UpdateAdminToken` allows for privilege escalation, and is therefore + /// equivalent to `*`. #[structopt(long = "scope")] pub scope: Option<String>, /// Print only the newly generated API token to stdout @@ -544,7 +549,14 @@ pub struct AdminTokenSetOp { /// format) #[structopt(long = "expires-in")] pub expires_in: Option<String>, - /// Set a limited scope for the token + /// Set a limited scope for the token, as a comma-separated list of + /// admin API functions (e.g. GetClusterStatus, etc.), or `*` to allow + /// all admin API functions. + /// Use `--scope=+Scope1,Scope2` to add scopes to the existing list, + /// and `--scope=-Scope1,Scope2` to remove scopes from the existing list. + /// Note that granting a scope that allows `CreateAdminToken` or + /// `UpdateAdminToken` allows for privilege escalation, and is therefore + /// equivalent to `*`. #[structopt(long = "scope")] pub scope: Option<String>, }