mirror of
https://github.com/bytedance/g3.git
synced 2026-04-26 10:41:37 +00:00
g3proxy: introduce a cache layer for remote user passwords
This commit is contained in:
parent
7dcf3974a8
commit
4a97072987
6 changed files with 150 additions and 2 deletions
91
g3proxy/src/auth/cache.rs
Normal file
91
g3proxy/src/auth/cache.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::time::Duration;
|
||||
|
||||
use foldhash::fast::FixedState;
|
||||
use lru::LruCache;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use g3_types::metrics::NodeName;
|
||||
|
||||
thread_local! {
|
||||
static CACHE: RefCell<HashMap<NodeName, GroupLocalCache, FixedState>> = const {
|
||||
RefCell::new(HashMap::with_hasher(FixedState::with_seed(0)))
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct UserLocalCache {
|
||||
password_map: BTreeMap<String, Instant>,
|
||||
}
|
||||
|
||||
struct GroupLocalCache {
|
||||
user_map: LruCache<String, UserLocalCache, FixedState>,
|
||||
}
|
||||
|
||||
impl Default for GroupLocalCache {
|
||||
fn default() -> Self {
|
||||
GroupLocalCache::new(crate::config::auth::group::DEFAULT_CACHE_USER_COUNT)
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupLocalCache {
|
||||
fn new(user_count: NonZeroUsize) -> Self {
|
||||
GroupLocalCache {
|
||||
user_map: LruCache::with_hasher(user_count, FixedState::with_seed(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn has_valid_password(group: &NodeName, username: &str, password: &str) -> bool {
|
||||
CACHE.with(|cache| {
|
||||
let mut cache = cache.borrow_mut();
|
||||
let group = cache
|
||||
.entry(group.clone())
|
||||
.or_insert_with(GroupLocalCache::default);
|
||||
|
||||
let Some(user) = group.user_map.get_mut(username) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some((p, t)) = user.password_map.remove_entry(password) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if t > Instant::now() {
|
||||
user.password_map.insert(p, t);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn save_user_password(
|
||||
group: &NodeName,
|
||||
user_count: NonZeroUsize,
|
||||
username: String,
|
||||
password: String,
|
||||
expire_time: Duration,
|
||||
) {
|
||||
CACHE.with(|cache| {
|
||||
let mut cache = cache.borrow_mut();
|
||||
let group = cache
|
||||
.entry(group.clone())
|
||||
.and_modify(|g| g.user_map.resize(user_count))
|
||||
.or_insert_with(|| GroupLocalCache::new(user_count));
|
||||
|
||||
let user = group
|
||||
.user_map
|
||||
.get_or_insert_mut(username, UserLocalCache::default);
|
||||
|
||||
user.password_map
|
||||
.insert(password, Instant::now() + expire_time);
|
||||
})
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ use tokio::sync::{mpsc, oneshot};
|
|||
|
||||
use g3_types::auth::UserAuthError;
|
||||
|
||||
use crate::config::auth::LdapUserGroupConfig;
|
||||
use crate::config::auth::{LdapUserGroupConfig, UserGroupConfig};
|
||||
|
||||
mod connect;
|
||||
use connect::LdapConnector;
|
||||
|
|
@ -51,6 +51,14 @@ impl LdapAuthPoolHandle {
|
|||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), UserAuthError> {
|
||||
if crate::auth::cache::has_valid_password(
|
||||
self.config.basic_config().name(),
|
||||
username,
|
||||
password,
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let req = LdapAuthRequest {
|
||||
username: username.to_string(),
|
||||
|
|
@ -65,7 +73,16 @@ impl LdapAuthPoolHandle {
|
|||
let _ = self.req_sender.send(req).await;
|
||||
|
||||
match tokio::time::timeout(self.config.queue_wait_timeout, receiver).await {
|
||||
Ok(Ok(Some(_))) => Ok(()),
|
||||
Ok(Ok(Some((username, password)))) => {
|
||||
crate::auth::cache::save_user_password(
|
||||
self.config.basic_config().name(),
|
||||
self.config.cache_user_count,
|
||||
username,
|
||||
password,
|
||||
self.config.cache_expire_time,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Ok(Ok(None)) => Err(UserAuthError::TokenNotMatch),
|
||||
Ok(Err(_)) => Err(UserAuthError::RemoteError),
|
||||
Err(_) => Err(UserAuthError::RemoteTimeout),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ pub(crate) use ops::reload;
|
|||
mod registry;
|
||||
pub(crate) use registry::{get_all_groups, get_names, get_or_insert_default};
|
||||
|
||||
mod cache;
|
||||
|
||||
mod site;
|
||||
pub(crate) use site::UserSite;
|
||||
use site::UserSites;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -34,6 +35,8 @@ pub(crate) struct LdapUserGroupConfig {
|
|||
pub(crate) connection_pool: ConnectionPoolConfig,
|
||||
pub(crate) queue_channel_size: usize,
|
||||
pub(crate) queue_wait_timeout: Duration,
|
||||
pub(crate) cache_user_count: NonZeroUsize,
|
||||
pub(crate) cache_expire_time: Duration,
|
||||
}
|
||||
|
||||
impl LdapUserGroupConfig {
|
||||
|
|
@ -53,6 +56,8 @@ impl LdapUserGroupConfig {
|
|||
connection_pool: ConnectionPoolConfig::new(1024, 8),
|
||||
queue_channel_size: 64,
|
||||
queue_wait_timeout: Duration::from_secs(4),
|
||||
cache_user_count: super::DEFAULT_CACHE_USER_COUNT,
|
||||
cache_expire_time: super::DEFAULT_CACHE_EXPIRE_TIME,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +174,15 @@ impl LdapUserGroupConfig {
|
|||
.context(format!("invalid humanize duration value for key {k}"))?;
|
||||
Ok(())
|
||||
}
|
||||
"cache_user_count" => {
|
||||
self.cache_user_count = g3_yaml::value::as_nonzero_usize(v)?;
|
||||
Ok(())
|
||||
}
|
||||
"cache_expire_time" => {
|
||||
self.cache_expire_time = g3_yaml::humanize::as_duration(v)
|
||||
.context(format!("invalid humanize duration value for key {k}"))?;
|
||||
Ok(())
|
||||
}
|
||||
_ => self.basic.set(k, v),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
use std::time::Duration;
|
||||
|
||||
use g3_macros::AnyConfig;
|
||||
use g3_types::metrics::NodeName;
|
||||
use g3_yaml::YamlDocPosition;
|
||||
|
|
@ -16,6 +19,9 @@ pub(crate) use facts::FactsUserGroupConfig;
|
|||
mod ldap;
|
||||
pub(crate) use ldap::LdapUserGroupConfig;
|
||||
|
||||
pub(crate) const DEFAULT_CACHE_USER_COUNT: NonZeroUsize = NonZeroUsize::new(128).unwrap();
|
||||
const DEFAULT_CACHE_EXPIRE_TIME: Duration = Duration::from_secs(300);
|
||||
|
||||
pub(crate) trait UserGroupConfig {
|
||||
fn basic_config(&self) -> &BasicUserGroupConfig;
|
||||
|
||||
|
|
|
|||
|
|
@ -126,3 +126,21 @@ queue_wait_timeout
|
|||
Set the timeout value when auth with the LDAP server for a client request.
|
||||
|
||||
**default**: 4s
|
||||
|
||||
cache_user_count
|
||||
----------------
|
||||
|
||||
**optional**, **type**: usize
|
||||
|
||||
Set how many users will be LRU cached in thread local storage.
|
||||
|
||||
**default**: 128
|
||||
|
||||
cache_expire_time
|
||||
-----------------
|
||||
|
||||
**optional**, **type**: :ref:`humanize duration <conf_value_humanize_duration>`
|
||||
|
||||
Set the expire time for valid passwords in the thread local LRU cache.
|
||||
|
||||
**default**: 5min
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue