[pbs-devel] [PATCH proxmox-backup 2/3] pbs-config: invalidate token-secret cache on token.shadow changes
Samuel Rufinatscha
s.rufinatscha at proxmox.com
Fri Dec 5 14:25:55 CET 2025
Previously the in-memory token-secret cache was only updated via
set_secret() and delete_secret(), so manual edits to token.shadow were
not reflected.
This patch adds file change detection to the cache. It tracks the mtime
and length of token.shadow and clears the in-memory token secret cache
whenever these values change.
Note, this patch fetches file stats on every request. An TTL-based
optimization will be covered in a subsequent patch of the series.
This patch is a partly-fix.
[1] https://bugzilla.proxmox.com/show_bug.cgi?id=7017
Signed-off-by: Samuel Rufinatscha <s.rufinatscha at proxmox.com>
---
pbs-config/src/token_shadow.rs | 35 ++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/pbs-config/src/token_shadow.rs b/pbs-config/src/token_shadow.rs
index 47aa2fc2..ed54cdfa 100644
--- a/pbs-config/src/token_shadow.rs
+++ b/pbs-config/src/token_shadow.rs
@@ -1,5 +1,8 @@
use std::collections::HashMap;
+use std::fs;
+use std::io::ErrorKind;
use std::sync::RwLock;
+use std::time::SystemTime;
use anyhow::{bail, format_err, Error};
use once_cell::sync::OnceCell;
@@ -57,12 +60,38 @@ fn write_file(data: HashMap<Authid, String>) -> Result<(), Error> {
proxmox_sys::fs::replace_file(CONF_FILE, &json, options, true)
}
+fn refresh_cache_if_file_changed() -> Result<(), Error> {
+ let mut cache = token_secret_cache().write().unwrap();
+
+ // Fetch the current token.shadow metadata
+ let (new_mtime, new_len) = match fs::metadata(CONF_FILE) {
+ Ok(meta) => (meta.modified().ok(), Some(meta.len())),
+ Err(e) if e.kind() == ErrorKind::NotFound => (None, None),
+ Err(e) => return Err(e.into()),
+ };
+
+ // Fast path: file did not change, keep the cache
+ if cache.file_mtime == new_mtime && cache.file_len == new_len {
+ return Ok(());
+ }
+
+ // File changed, drop all cached secrets
+ cache.secrets.clear();
+ cache.file_mtime = new_mtime;
+ cache.file_len = new_len;
+
+ Ok(())
+}
+
/// Verifies that an entry for given tokenid / API token secret exists
pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> {
if !tokenid.is_token() {
bail!("not an API token ID");
}
+ // Ensure cache is in sync with on-disk token.shadow file
+ refresh_cache_if_file_changed()?;
+
// Fast path
if let Some(cached) = token_secret_cache().read().unwrap().secrets.get(tokenid) {
// Compare cached secret with provided one using constant time comparison
@@ -136,12 +165,18 @@ struct ApiTokenSecretCache {
/// `generate_and_set_secret`. Used to avoid repeated
/// password-hash computation on subsequent authentications.
secrets: HashMap<Authid, String>,
+ // shadow file mtime to detect changes
+ file_mtime: Option<SystemTime>,
+ // shadow file length to detect changes
+ file_len: Option<u64>,
}
fn token_secret_cache() -> &'static RwLock<ApiTokenSecretCache> {
TOKEN_SECRET_CACHE.get_or_init(|| {
RwLock::new(ApiTokenSecretCache {
secrets: HashMap::new(),
+ file_mtime: None,
+ file_len: None,
})
})
}
--
2.47.3
More information about the pbs-devel
mailing list