From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 56EA21FF13F for ; Thu, 09 Apr 2026 17:54:39 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 73007ACE3; Thu, 9 Apr 2026 17:55:20 +0200 (CEST) From: Samuel Rufinatscha To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox v8 6/6] token shadow: deduplicate more code into apply_api_mutation Date: Thu, 9 Apr 2026 17:54:26 +0200 Message-ID: <20260409155437.312760-7-s.rufinatscha@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260409155437.312760-1-s.rufinatscha@proxmox.com> References: <20260409155437.312760-1-s.rufinatscha@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1775750013875 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.231 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: DP2YJNI6OCUNZHTJFH66Z7HE5HJMSWGT X-Message-ID-Hash: DP2YJNI6OCUNZHTJFH66Z7HE5HJMSWGT X-MailFrom: s.rufinatscha@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Signed-off-by: Samuel Rufinatscha --- proxmox-access-control/src/token_shadow.rs | 71 +++++++++------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/proxmox-access-control/src/token_shadow.rs b/proxmox-access-control/src/token_shadow.rs index 270f3bfa..a8cd4209 100644 --- a/proxmox-access-control/src/token_shadow.rs +++ b/proxmox-access-control/src/token_shadow.rs @@ -164,43 +164,13 @@ pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { /// Generates a new secret for the given tokenid / API token, sets it then returns it. /// The secret is stored as salted hash. pub fn generate_and_set_secret(tokenid: &Authid) -> Result { - let secret = format!("{:x}", proxmox_uuid::Uuid::generate()); - - if !tokenid.is_token() { - bail!("not an API token ID"); - } - - let guard = lock_config()?; - - // Capture state before we write to detect external edits. - let pre_meta = shadow_mtime_len().unwrap_or((None, None)); - - let mut data = read_file()?; - let hashed_secret = proxmox_sys::crypt::encrypt_pw(&secret)?; - data.insert(tokenid.clone(), hashed_secret); - write_file(data)?; - - apply_api_mutation(guard, tokenid, Some(&secret), pre_meta); - - Ok(secret) + apply_api_mutation(tokenid, true)? + .ok_or_else(|| format_err!("Failed to generate API token secret")) } /// Deletes the entry for the given tokenid. pub fn delete_secret(tokenid: &Authid) -> Result<(), Error> { - if !tokenid.is_token() { - bail!("not an API token ID"); - } - - let guard = lock_config()?; - - // Capture state before we write to detect external edits. - let pre_meta = shadow_mtime_len().unwrap_or((None, None)); - - let mut data = read_file()?; - data.remove(tokenid); - write_file(data)?; - - apply_api_mutation(guard, tokenid, None, pre_meta); + apply_api_mutation(tokenid, false)?; Ok(()) } @@ -293,12 +263,28 @@ fn cache_try_insert_secret(tokenid: Authid, secret: String, gen_before: usize) { } } -fn apply_api_mutation( - _guard: ApiLockGuard, - tokenid: &Authid, - secret: Option<&str>, - pre_write_meta: (Option, Option), -) { +fn apply_api_mutation(tokenid: &Authid, generate: bool) -> Result, Error> { + if !tokenid.is_token() { + bail!("not an API token ID"); + } + + let _guard = lock_config()?; + + // Capture state before we write to detect external edits. + let pre_write_meta = shadow_mtime_len().unwrap_or((None, None)); + + let mut data = read_file()?; + let secret = if generate { + let secret = format!("{:x}", proxmox_uuid::Uuid::generate()); + let hashed_secret = proxmox_sys::crypt::encrypt_pw(&secret)?; + data.insert(tokenid.clone(), hashed_secret); + Some(secret) + } else { + data.remove(tokenid); + None + }; + write_file(data)?; + let now = epoch_i64(); // Signal cache invalidation to other processes (best-effort). @@ -308,14 +294,14 @@ fn apply_api_mutation( // If we cannot get the current generation, we cannot trust the cache let Some(current_gen) = token_shadow_generation() else { cache.reset_and_set_gen(0); - return; + return Ok(secret); }; // If we cannot bump the generation, or if it changed after // obtaining the cache write lock, we cannot trust the cache if bumped_gen != Some(current_gen) { cache.reset_and_set_gen(current_gen); - return; + return Ok(secret); } // If our cached file metadata does not match the on-disk state before our write, @@ -329,7 +315,7 @@ fn apply_api_mutation( } // Apply the new mutation. - match secret { + match &secret { Some(secret) => { let cached_secret = CachedSecret { secret: secret.to_owned(), @@ -354,6 +340,7 @@ fn apply_api_mutation( cache.reset_and_set_gen(current_gen); } } + Ok(secret) } /// Get the current generation. -- 2.47.3