possibility to update access key expiration date

This commit is contained in:
Alex Auvolat 2025-04-17 11:30:58 +02:00
parent c56b7e20c3
commit 590c9bb4db
5 changed files with 114 additions and 34 deletions

View File

@ -2162,15 +2162,7 @@
"$ref": "#/components/schemas/GetBucketInfoResponse"
},
"CreateKeyRequest": {
"type": "object",
"properties": {
"name": {
"type": [
"string",
"null"
]
}
}
"$ref": "#/components/schemas/UpdateKeyRequestBody"
},
"CreateKeyResponse": {
"$ref": "#/components/schemas/GetKeyInfoResponse"
@ -4115,7 +4107,8 @@
"type": "null"
},
{
"$ref": "#/components/schemas/KeyPerm"
"$ref": "#/components/schemas/KeyPerm",
"description": "Permissions to allow for the key"
}
]
},
@ -4125,15 +4118,25 @@
"type": "null"
},
{
"$ref": "#/components/schemas/KeyPerm"
"$ref": "#/components/schemas/KeyPerm",
"description": "Permissions to deny for the key"
}
]
},
"expiration": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "Expiration time and date, formatted according to RFC 3339"
},
"name": {
"type": [
"string",
"null"
]
],
"description": "Name of the API key"
}
}
},

View File

@ -701,9 +701,7 @@ pub struct ApiBucketKeyPerm {
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateKeyRequest {
pub name: Option<String>,
}
pub struct CreateKeyRequest(pub UpdateKeyRequestBody);
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct CreateKeyResponse(pub GetKeyInfoResponse);
@ -735,8 +733,13 @@ pub struct UpdateKeyResponse(pub GetKeyInfoResponse);
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateKeyRequestBody {
/// Name of the API key
pub name: Option<String>,
/// Expiration time and date, formatted according to RFC 3339
pub expiration: Option<DateTime<Utc>>,
/// Permissions to allow for the key
pub allow: Option<KeyPerm>,
/// Permissions to deny for the key
pub deny: Option<KeyPerm>,
}

View File

@ -103,7 +103,10 @@ impl RequestHandler for CreateKeyRequest {
garage: &Arc<Garage>,
_admin: &Admin,
) -> Result<CreateKeyResponse, Error> {
let key = Key::new(self.name.as_deref().unwrap_or("Unnamed key"));
let mut key = Key::new("Unnamed key");
apply_key_updates(&mut key, self.0);
garage.key_table.insert(&key).await?;
Ok(CreateKeyResponse(
@ -149,21 +152,7 @@ impl RequestHandler for UpdateKeyRequest {
) -> Result<UpdateKeyResponse, Error> {
let mut key = garage.key_helper().get_existing_key(&self.id).await?;
let key_state = key.state.as_option_mut().unwrap();
if let Some(new_name) = self.body.name {
key_state.name.update(new_name);
}
if let Some(allow) = self.body.allow {
if allow.create_bucket {
key_state.allow_create_bucket.update(true);
}
}
if let Some(deny) = self.body.deny {
if deny.create_bucket {
key_state.allow_create_bucket.update(false);
}
}
apply_key_updates(&mut key, self.body);
garage.key_table.insert(&key).await?;
@ -275,3 +264,26 @@ async fn key_info_results(
Ok(res)
}
fn apply_key_updates(key: &mut Key, updates: UpdateKeyRequestBody) {
let key_state = key.state.as_option_mut().unwrap();
if let Some(new_name) = updates.name {
key_state.name.update(new_name);
}
if let Some(expiration) = updates.expiration {
key_state
.expiration
.update(Some(expiration.timestamp_millis() as u64));
}
if let Some(allow) = updates.allow {
if allow.create_bucket {
key_state.allow_create_bucket.update(true);
}
}
if let Some(deny) = updates.deny {
if deny.create_bucket {
key_state.allow_create_bucket.update(false);
}
}
}

View File

@ -1,6 +1,6 @@
use format_table::format_table;
use chrono::Local;
use chrono::{Local, Utc};
use garage_util::error::*;
@ -16,6 +16,7 @@ impl Cli {
KeyOperation::Info(query) => self.cmd_key_info(query).await,
KeyOperation::Create(query) => self.cmd_create_key(query).await,
KeyOperation::Rename(query) => self.cmd_rename_key(query).await,
KeyOperation::Set(opt) => self.cmd_update_key(opt).await,
KeyOperation::Delete(query) => self.cmd_delete_key(query).await,
KeyOperation::Allow(query) => self.cmd_allow_key(query).await,
KeyOperation::Deny(query) => self.cmd_deny_key(query).await,
@ -68,9 +69,17 @@ impl Cli {
pub async fn cmd_create_key(&self, opt: KeyNewOpt) -> Result<(), Error> {
let key = self
.api_request(CreateKeyRequest {
.api_request(CreateKeyRequest(UpdateKeyRequestBody {
name: Some(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),
allow: None,
deny: None,
}))
.await?;
print_key_info(&key.0);
@ -92,6 +101,38 @@ impl Cli {
id: key.access_key_id,
body: UpdateKeyRequestBody {
name: Some(opt.new_name),
expiration: None,
allow: None,
deny: None,
},
})
.await?;
print_key_info(&new_key.0);
Ok(())
}
pub async fn cmd_update_key(&self, opt: KeySetOpt) -> Result<(), Error> {
let key = self
.api_request(GetKeyInfoRequest {
id: None,
search: Some(opt.key_pattern),
show_secret_key: false,
})
.await?;
let new_key = self
.api_request(UpdateKeyRequest {
id: key.access_key_id,
body: UpdateKeyRequestBody {
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),
allow: None,
deny: None,
},
@ -143,6 +184,7 @@ impl Cli {
id: key.access_key_id,
body: UpdateKeyRequestBody {
name: None,
expiration: None,
allow: Some(KeyPerm {
create_bucket: opt.create_bucket,
}),
@ -170,6 +212,7 @@ impl Cli {
id: key.access_key_id,
body: UpdateKeyRequestBody {
name: None,
expiration: None,
allow: None,
deny: Some(KeyPerm {
create_bucket: opt.create_bucket,

View File

@ -426,6 +426,10 @@ pub enum KeyOperation {
/// Import key
#[structopt(name = "import", version = garage_version())]
Import(KeyImportOpt),
/// Set parameters for an access key
#[structopt(name = "set", version = garage_version())]
Set(KeySetOpt),
}
#[derive(StructOpt, Debug)]
@ -442,6 +446,21 @@ pub struct KeyNewOpt {
/// Name of the key
#[structopt(default_value = "Unnamed key")]
pub name: String,
/// Set an expiration time for the access key
/// (see docs.rs/parse_duration for date format)
#[structopt(long = "expires-in")]
pub expires_in: Option<String>,
}
#[derive(StructOpt, Debug)]
pub struct KeySetOpt {
/// ID or name of the key
pub key_pattern: String,
/// Set an expiration time for the access key
/// (see docs.rs/parse_duration for date format)
#[structopt(long = "expires-in")]
pub expires_in: Option<String>,
}
#[derive(StructOpt, Debug)]