[pbs-devel] [PATCH proxmox 3/3] proxmox-access-control: add TTL window to token secret cache

Samuel Rufinatscha s.rufinatscha at proxmox.com
Fri Dec 5 14:25:59 CET 2025


Verify_secret() currently calls refresh_cache_if_file_changed() on every
request, which performs a metadata() call on token.shadow each time.
Under load this adds unnecessary overhead, considering also the file
should rarely change.

This patch introduces a TTL boundary, controlled by
TOKEN_SECRET_CACHE_TTL_SECS. File metadata is only re-loaded once the
TTL has expired.

Signed-off-by: Samuel Rufinatscha <s.rufinatscha at proxmox.com>
---
 proxmox-access-control/src/token_shadow.rs | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/proxmox-access-control/src/token_shadow.rs b/proxmox-access-control/src/token_shadow.rs
index d08fb06a..885e629d 100644
--- a/proxmox-access-control/src/token_shadow.rs
+++ b/proxmox-access-control/src/token_shadow.rs
@@ -9,6 +9,7 @@ use serde_json::{from_value, Value};
 
 use proxmox_auth_api::types::Authid;
 use proxmox_product_config::{open_api_lockfile, replace_config, ApiLockGuard};
+use proxmox_time::epoch_i64;
 
 use crate::init::impl_feature::{token_shadow, token_shadow_lock};
 
@@ -18,6 +19,8 @@ use crate::init::impl_feature::{token_shadow, token_shadow_lock};
 /// subsequent authentications for the same token+secret combination, avoiding
 /// recomputing the password hash on every request.
 static TOKEN_SECRET_CACHE: OnceLock<RwLock<ApiTokenSecretCache>> = OnceLock::new();
+/// Max age in seconds of the token secret cache before checking for file changes.
+const TOKEN_SECRET_CACHE_TTL_SECS: i64 = 60;
 
 // Get exclusive lock
 fn lock_config() -> Result<ApiLockGuard, Error> {
@@ -44,6 +47,15 @@ fn write_file(data: HashMap<Authid, String>) -> Result<(), Error> {
 fn refresh_cache_if_file_changed() -> Result<(), Error> {
     let mut cache = token_secret_cache().write().unwrap();
 
+    let now = epoch_i64();
+
+    // Fast path: Within TTL boundary
+    if let Some(last) = cache.last_checked {
+        if now - last < TOKEN_SECRET_CACHE_TTL_SECS {
+            return Ok(());
+        }
+    }
+
     // Fetch the current token.shadow metadata
     let (new_mtime, new_len) = match fs::metadata(token_shadow().as_path()) {
         Ok(meta) => (meta.modified().ok(), Some(meta.len())),
@@ -60,6 +72,7 @@ fn refresh_cache_if_file_changed() -> Result<(), Error> {
     cache.secrets.clear();
     cache.file_mtime = new_mtime;
     cache.file_len = new_len;
+    cache.last_checked = Some(now);
 
     Ok(())
 }
@@ -150,6 +163,8 @@ struct ApiTokenSecretCache {
     file_mtime: Option<SystemTime>,
     // shadow file length to detect changes
     file_len: Option<u64>,
+    // last time the file metadata was checked
+    last_checked: Option<i64>,
 }
 
 fn token_secret_cache() -> &'static RwLock<ApiTokenSecretCache> {
@@ -158,6 +173,7 @@ fn token_secret_cache() -> &'static RwLock<ApiTokenSecretCache> {
             secrets: HashMap::new(),
             file_mtime: None,
             file_len: None,
+            last_checked: None,
         })
     })
 }
-- 
2.47.3





More information about the pbs-devel mailing list