diff --git a/doc/api/garage-admin-v2.json b/doc/api/garage-admin-v2.json index 4cc907d1..4e07ed68 100644 --- a/doc/api/garage-admin-v2.json +++ b/doc/api/garage-admin-v2.json @@ -4006,6 +4006,10 @@ ], "description": "Name of the admin API token" }, + "neverExpires": { + "type": "boolean", + "description": "Set the admin token to never expire" + }, "scope": { "type": [ "array", @@ -4137,6 +4141,10 @@ "null" ], "description": "Name of the API key" + }, + "neverExpires": { + "type": "boolean", + "description": "Set the access key to never expire" } } }, diff --git a/src/api/admin/admin_token.rs b/src/api/admin/admin_token.rs index b010dcf9..082d942a 100644 --- a/src/api/admin/admin_token.rs +++ b/src/api/admin/admin_token.rs @@ -124,7 +124,7 @@ impl RequestHandler for CreateAdminTokenRequest { AdminApiToken::new(&format!("token_{}", Utc::now().format("%Y%m%d_%H%M"))) }; - apply_token_updates(&mut token, self.0); + apply_token_updates(&mut token, self.0)?; garage.admin_token_table.insert(&token).await?; @@ -145,7 +145,7 @@ impl RequestHandler for UpdateAdminTokenRequest { ) -> Result { let mut token = get_existing_admin_token(&garage, &self.id).await?; - apply_token_updates(&mut token, self.body); + apply_token_updates(&mut token, self.body)?; garage.admin_token_table.insert(&token).await?; @@ -204,7 +204,16 @@ async fn get_existing_admin_token(garage: &Garage, id: &String) -> Result Result<(), Error> { + if updates.never_expires && updates.expiration.is_some() { + return Err(Error::bad_request( + "cannot specify `expiration` and `never_expires`", + )); + } + let params = token.params_mut().unwrap(); if let Some(name) = updates.name { @@ -215,7 +224,12 @@ fn apply_token_updates(token: &mut AdminApiToken, updates: UpdateAdminTokenReque .expiration .update(Some(expiration.timestamp_millis() as u64)); } + if updates.never_expires { + params.expiration.update(None); + } if let Some(scope) = updates.scope { params.scope.update(AdminApiTokenScope(scope)); } + + Ok(()) } diff --git a/src/api/admin/api.rs b/src/api/admin/api.rs index fa6c6b2d..1766ae28 100644 --- a/src/api/admin/api.rs +++ b/src/api/admin/api.rs @@ -366,6 +366,9 @@ pub struct UpdateAdminTokenRequestBody { pub name: Option, /// Expiration time and date, formatted according to RFC 3339 pub expiration: Option>, + /// Set the admin token to never expire + #[serde(default)] + pub never_expires: bool, /// Scope of the admin API token, a list of admin endpoint names (such as /// `GetClusterStatus`, etc), or the special value `*` to allow all /// admin endpoints. **WARNING:** Granting a scope of `CreateAdminToken` or @@ -737,6 +740,9 @@ pub struct UpdateKeyRequestBody { pub name: Option, /// Expiration time and date, formatted according to RFC 3339 pub expiration: Option>, + /// Set the access key to never expire + #[serde(default)] + pub never_expires: bool, /// Permissions to allow for the key pub allow: Option, /// Permissions to deny for the key diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index 07373e76..7f0d819f 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -105,7 +105,7 @@ impl RequestHandler for CreateKeyRequest { ) -> Result { let mut key = Key::new("Unnamed key"); - apply_key_updates(&mut key, self.0); + apply_key_updates(&mut key, self.0)?; garage.key_table.insert(&key).await?; @@ -152,7 +152,7 @@ impl RequestHandler for UpdateKeyRequest { ) -> Result { let mut key = garage.key_helper().get_existing_key(&self.id).await?; - apply_key_updates(&mut key, self.body); + apply_key_updates(&mut key, self.body)?; garage.key_table.insert(&key).await?; @@ -265,7 +265,13 @@ async fn key_info_results( Ok(res) } -fn apply_key_updates(key: &mut Key, updates: UpdateKeyRequestBody) { +fn apply_key_updates(key: &mut Key, updates: UpdateKeyRequestBody) -> Result<(), Error> { + if updates.never_expires && updates.expiration.is_some() { + return Err(Error::bad_request( + "cannot specify `expiration` and `never_expires`", + )); + } + let key_state = key.state.as_option_mut().unwrap(); if let Some(new_name) = updates.name { @@ -276,6 +282,9 @@ fn apply_key_updates(key: &mut Key, updates: UpdateKeyRequestBody) { .expiration .update(Some(expiration.timestamp_millis() as u64)); } + if updates.never_expires { + key_state.expiration.update(None); + } if let Some(allow) = updates.allow { if allow.create_bucket { key_state.allow_create_bucket.update(true); @@ -286,4 +295,6 @@ fn apply_key_updates(key: &mut Key, updates: UpdateKeyRequestBody) { key_state.allow_create_bucket.update(false); } } + + Ok(()) } diff --git a/src/garage/cli/remote/admin_token.rs b/src/garage/cli/remote/admin_token.rs index cd7ff9b0..83050c92 100644 --- a/src/garage/cli/remote/admin_token.rs +++ b/src/garage/cli/remote/admin_token.rs @@ -88,6 +88,7 @@ impl Cli { .transpose() .ok_or_message("Invalid duration passed for --expires-in parameter")? .map(|dur| Utc::now() + dur), + never_expires: false, scope: opt.scope.map(|s| { s.split(",") .map(|x| x.trim().to_string()) @@ -121,6 +122,7 @@ impl Cli { body: UpdateAdminTokenRequestBody { name: Some(new), expiration: None, + never_expires: false, scope: None, }, }) @@ -150,6 +152,7 @@ impl Cli { .transpose() .ok_or_message("Invalid duration passed for --expires-in parameter")? .map(|dur| Utc::now() + dur), + never_expires: opt.never_expires, scope: opt.scope.map({ let mut new_scope = token.scope; |scope_str| { diff --git a/src/garage/cli/remote/key.rs b/src/garage/cli/remote/key.rs index d254f4e0..6faede01 100644 --- a/src/garage/cli/remote/key.rs +++ b/src/garage/cli/remote/key.rs @@ -77,6 +77,7 @@ impl Cli { .transpose() .ok_or_message("Invalid duration passed for --expires-in parameter")? .map(|dur| Utc::now() + dur), + never_expires: false, allow: None, deny: None, })) @@ -102,6 +103,7 @@ impl Cli { body: UpdateKeyRequestBody { name: Some(opt.new_name), expiration: None, + never_expires: false, allow: None, deny: None, }, @@ -133,6 +135,7 @@ impl Cli { .transpose() .ok_or_message("Invalid duration passed for --expires-in parameter")? .map(|dur| Utc::now() + dur), + never_expires: opt.never_expires, allow: None, deny: None, }, @@ -185,6 +188,7 @@ impl Cli { body: UpdateKeyRequestBody { name: None, expiration: None, + never_expires: false, allow: Some(KeyPerm { create_bucket: opt.create_bucket, }), @@ -213,6 +217,7 @@ impl Cli { body: UpdateKeyRequestBody { name: None, expiration: None, + never_expires: false, allow: None, deny: Some(KeyPerm { create_bucket: opt.create_bucket, diff --git a/src/garage/cli/structs.rs b/src/garage/cli/structs.rs index 01a5d77f..7c00aefc 100644 --- a/src/garage/cli/structs.rs +++ b/src/garage/cli/structs.rs @@ -461,6 +461,9 @@ pub struct KeySetOpt { /// (see docs.rs/parse_duration for date format) #[structopt(long = "expires-in")] pub expires_in: Option, + /// Set the access key to never expire + #[structopt(long = "never-expires")] + pub never_expires: bool, } #[derive(StructOpt, Debug)] @@ -587,10 +590,15 @@ pub struct AdminTokenCreateOp { 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, + /// Set the token to never expire + #[structopt(long = "never-expires")] + pub never_expires: bool, + /// 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.