Skip to content
Snippets Groups Projects
Verified Commit 7583deeb authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Cache all user information as early as possible and re-use without lookup if needed

parent 4aebf539
No related branches found
No related tags found
No related merge requests found
...@@ -15,11 +15,10 @@ ...@@ -15,11 +15,10 @@
*/ */
use crate::BASE_NAME; use crate::BASE_NAME;
use crate::unix::getpwnam_safe; use crate::unix::{Passwd, getpwnam_safe, getpwuid_safe};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::collections::HashMap; use std::sync::{Mutex, MutexGuard};
use std::sync::{RwLock, RwLockReadGuard};
use libc::{geteuid, seteuid, uid_t}; use libc::{geteuid, seteuid, uid_t};
...@@ -37,119 +36,211 @@ use serde_json; ...@@ -37,119 +36,211 @@ use serde_json;
const USER_TOKEN_FILENAME: &str = "user_token.json"; const USER_TOKEN_FILENAME: &str = "user_token.json";
struct UserInfo {
uid: Option<uid_t>,
username: Option<String>,
passwd: Option<Passwd>,
access_token: Option<BasicTokenResponse>
}
pub struct Cache { pub struct Cache {
user_tokens: HashMap<String, BasicTokenResponse>, pub context_user: UserInfo
original_euid: uid_t,
} }
impl Cache { impl Cache {
pub fn new() -> Cache { pub fn new() -> Cache {
let euid; let euid = unsafe {
unsafe { geteuid()
euid = geteuid();
}; };
Cache { Cache {
user_tokens: HashMap::new(), context_user: UserInfo {
original_euid: euid uid: None,
username: None,
passwd: None,
access_token: None
}
} }
} }
}
impl UserInfo {
pub fn set_current_user(&mut self) {
self.set_uid(original_euid);
}
pub fn is_initialized(&self) -> bool {
self.uid.is_some() || self.username.is_some()
}
fn drop_privileges(&self, username: &String) -> Result<uid_t, io::Error> { fn try_resolve(&mut self) -> Result<&Passwd, io::Error> {
let current_euid; // If we already have a full passwd struct, return it as without resolving
unsafe { if self.passwd.is_some() {
current_euid = geteuid(); debug!("passwd entry for context user already resolved");
return Ok(self.passwd.as_ref().unwrap());
}
// If we cannot call getpwnam safely, return error (see `is_get_pwnam_safe`)
if !is_getpwnam_safe() {
let msg = "Context user cannot be resolved safely right now";
warn!("{}", msg);
return Err(io::Error::new(io::ErrorKind::WouldBlock, msg));
}
// Try one of the partial information to resolve
let res = match self.uid {
Some(uid) => getpwuid_safe(uid),
None => match &self.username {
Some(username) => getpwnam_safe(username.to_string()),
None => {
let msg = "No partial information set to use for getpwnam / getpwuid";
warn!("{}", msg);
Err(io::Error::new(io::ErrorKind::InvalidInput, msg))
}
}
}; };
match res {
Ok(passwd) => {
debug!("Successfully resolved context user's passwd entry");
self.passwd = Some(passwd);
Ok(self.passwd.as_ref().unwrap())
},
Err(e) => Err(e)
}
}
pub fn get_uid(&mut self) -> Result<uid_t, io::Error> {
match self.try_resolve() {
Ok(passwd) => Ok(passwd.pw_uid),
Err(e) => match self.uid {
Some(uid) => Ok(uid),
None => Err(e)
}
}
}
pub fn set_uid(&mut self, uid: uid_t) {
self.uid = Some(uid);
self.username = None;
self.passwd = None;
self.try_resolve();
}
let pw = match getpwnam_safe(username.to_string()) { pub fn get_username(&mut self) -> Result<String, io::Error> {
Ok(p) => p, match self.try_resolve() {
Ok(passwd) => Ok(&passwd.pw_name),
Err(e) => match self.username {
Some(username) => Ok(username),
None => Err(e)
}
}
}
pub fn set_username(&mut self, username: String) {
self.username = Some(username);
self.uid = None;
self.passwd = None;
self.try_resolve();
}
pub fn get_home_directory(&mut self) -> Result<String, io::Error> {
match self.try_resolve() {
Ok(passwd) => Ok(passwd.pw_dir),
Err(e) => Err(e)
}
}
fn drop_privileges(&self) -> Result<uid_t, io::Error> {
let current_euid = unsafe {
geteuid()
};
let target_euid = match self.get_uid() {
Ok(uid) => uid,
Err(e) => { Err(e) => {
error!("Failed to lookup user {}: {}", username, e); error!("Could not drop privileges because target UID is not resolved");
return Err(e); return Err(e);
} }
}; };
debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
let target_euid = pw.pw_uid;
if target_euid == current_euid { if target_euid == current_euid {
debug!("No need to drop privileges, already running as {}", username); debug!("No need to drop privileges, already running as {}", current_euid);
return Ok(current_euid); return Ok(current_euid);
} }
let res; let res = unsafe {
unsafe { seteuid(target_euid)
res = seteuid(target_euid);
}; };
if res == 0 { if res == 0 {
debug!("Successfully dropped privileges to {}", username); debug!("Successfully dropped privileges to {}", target_euid);
return Ok(target_euid); return Ok(target_euid);
} else { } else {
let e = io::Error::last_os_error(); let e = io::Error::last_os_error();
error!("Could not drop privileges to {}", username); error!("Could not drop privileges to {}", target_euid);
return Err(e); return Err(e);
} }
} }
fn restore_privileges(&self) { fn restore_privileges(&self) {
let current_euid; let current_euid = unsafe {
unsafe { geteuid()
current_euid = geteuid();
}; };
if current_euid != self.original_euid { if current_euid != original_euid {
debug!("Restoring privileges"); debug!("Restoring privileges");
let res; let res = unsafe {
unsafe { seteuid(original_euid)
res = seteuid(self.original_euid);
}; };
if res != 0 { if res != 0 {
panic!("Could not restore privileges to {}", self.original_euid); panic!("Could not restore privileges to {}", original_euid);
} }
} else { } else {
debug!("No need to restore privileges, already running as original user"); debug!("No need to restore privileges, already running as original user");
} }
} }
fn get_user_xdg_base_directories(&self, username: &String) -> Result<BaseDirectories, io::Error> { fn get_user_xdg_base_directories(&mut self) -> Result<BaseDirectories, io::Error> {
// Save original $HOME for later restore
let saved_home = env::var_os("HOME"); let saved_home = env::var_os("HOME");
let pw = match getpwnam_safe(username.to_string()) { // Determine user ID to find out whether we should override $HOME
Ok(p) => p, let uid = self.get_uid()?;
Err(e) => { if uid != original_euid {
error!("Failed to lookup user {}: {}", username, e); let user_home = self.get_home_directory()?;
return Err(e); env::set_var("HOME", user_home);
} debug!("Home directory for UID {} is {}", uid, user_home);
}; }
debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
let user_home = pw.pw_dir;
env::set_var("HOME", &user_home);
debug!("Home directory for {} is {}", username, user_home);
// Determine XDG directories now
let base_dirs = match BaseDirectories::with_prefix(BASE_NAME) { let base_dirs = match BaseDirectories::with_prefix(BASE_NAME) {
Ok(b) => b, Ok(b) => b,
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)) Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e))
}; };
if saved_home != None {
env::set_var("HOME", saved_home.unwrap()); // Restore $HOME to original if we changed it earlier
} else { if uid != original_euid {
env::remove_var("HOME"); if saved_home != None {
env::set_var("HOME", saved_home.unwrap());
} else {
env::remove_var("HOME");
}
} }
return Ok(base_dirs); return Ok(base_dirs);
} }
fn place_user_cache_file(&self, username: &String, filename: String) -> Result<PathBuf, io::Error> { fn place_user_cache_file(&mut self, filename: String) -> Result<PathBuf, io::Error> {
match self.get_user_xdg_base_directories(username) { match self.get_user_xdg_base_directories() {
Ok(b) => b.place_cache_file(filename), Ok(b) => b.place_cache_file(filename),
Err(e) => Err(io::Error::new(io::ErrorKind::NotFound, e)) Err(e) => Err(e)
} }
} }
pub fn load_user_token(&self, owner: &String) -> Option<&BasicTokenResponse> { pub fn get_access_token(&mut self) -> Option<BasicTokenResponse> {
if !self.user_tokens.contains_key(owner) { // Try to load our acess token if none is known
debug!("No token for {} in memory, trying to load from file", owner); if self.access_token.is_none() {
debug!("No token in memory, trying to load from file");
self.drop_privileges(owner).ok(); self.drop_privileges().ok();
let new_token = match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) { self.access_token = match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {
Ok(path) => match load_json(path) { Ok(path) => match load_json(path) {
Ok(read_token) => Some(read_token), Ok(read_token) => Some(read_token),
Err(_) => None Err(_) => None
...@@ -157,55 +248,29 @@ impl Cache { ...@@ -157,55 +248,29 @@ impl Cache {
Err(_) => None Err(_) => None
}; };
self.restore_privileges(); self.restore_privileges();
match new_token {
Some(t) => {
CACHE.write().unwrap().user_tokens.insert(owner.to_string(), t);
self.user_tokens.get(owner)
},
None => None
}
} else {
debug!("Found token for {} in memory", owner);
self.user_tokens.get(owner)
} }
return self.access_token;
} }
pub fn save_user_token(&self, owner: &String, token: BasicTokenResponse) -> Result<(), io::Error> { pub fn set_access_token(&mut self, token: BasicTokenResponse) -> Result<(), io::Error> {
CACHE.write().unwrap().user_tokens.insert(owner.to_string(), token.clone()); self.access_token = Some(token.clone());
debug!("Saved token for {} in memory", owner); debug!("Saved token for in memory");
// Try to write user's token cache file // Try to write user's token cache file
let res = match self.drop_privileges(owner) { let res = match self.drop_privileges() {
Ok(_) => match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) { Ok(_) => match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {
Ok(path) => { Ok(path) => {
debug!("Storing token for {} in cache file", owner); debug!("Storing token for in cache file");
save_json(path, token) save_json(path, token)
}, },
Err(e) => Err(e) Err(e) => Err(e)
}, },
Err(e) => Err(io::Error::new(io::ErrorKind::PermissionDenied, e)) Err(e) => Err(e)
}; };
self.restore_privileges(); self.restore_privileges();
return res; return res;
} }
pub fn delete_user_token(&self, owner: &String) {
CACHE.write().unwrap().user_tokens.remove(owner);
debug!("Token for {} removed from memory", owner);
// Try to remove user's token cache file
self.drop_privileges(owner).ok();
match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) {
Ok(path) => {
debug!("Deleting cache file for {}", owner);
fs::remove_file(path).ok();
()
},
Err(e) => ()
};
self.restore_privileges();
}
} }
fn load_json<O: DeserializeOwned>(path: PathBuf) -> Result<O, io::Error> { fn load_json<O: DeserializeOwned>(path: PathBuf) -> Result<O, io::Error> {
...@@ -226,10 +291,18 @@ fn save_json<O: Serialize>(path: PathBuf, obj: O) -> Result<(), io::Error> { ...@@ -226,10 +291,18 @@ fn save_json<O: Serialize>(path: PathBuf, obj: O) -> Result<(), io::Error> {
fs::write(path, json) fs::write(path, json)
} }
lazy_static! { fn is_getpwnam_safe() -> bool {
static ref CACHE: RwLock<Cache> = RwLock::new(Cache::new()); // FIXME Implement real logic
return true;
} }
pub fn get_cache() -> RwLockReadGuard<'static, Cache> { static original_euid: uid_t = unsafe {
CACHE.read().unwrap() geteuid()
};
lazy_static! {
static ref CACHE: Mutex<Cache> = Mutex::new(Cache::new());
}
pub fn get_cache() -> MutexGuard<'static, Cache> {
CACHE.lock().unwrap()
} }
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
use crate::config::{ use crate::config::{
get_config, get_config,
get_optional, get_optional,
get_or_error
}; };
use config::Config; use config::Config;
use crate::cache::get_cache; use crate::cache::get_cache;
...@@ -26,10 +25,6 @@ use crate::logging::setup_log; ...@@ -26,10 +25,6 @@ use crate::logging::setup_log;
use crate::oauth::{get_access_token_client, get_data_jq}; use crate::oauth::{get_access_token_client, get_data_jq};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use std::ffi::CStr;
use libc::geteuid;
use crate::unix::getpwuid_safe;
use libnss::interop::Response; use libnss::interop::Response;
use libnss::passwd::{PasswdHooks, Passwd}; use libnss::passwd::{PasswdHooks, Passwd};
...@@ -58,40 +53,25 @@ fn nss_hook_prepare() -> Config { ...@@ -58,40 +53,25 @@ fn nss_hook_prepare() -> Config {
return conf; return conf;
} }
fn get_current_user() -> String {
let euid;
unsafe {
euid = geteuid();
};
match getpwuid_safe(euid) {
Ok(p) => p.pw_name,
Err(e) => {
error!("Failed to lookup username for UID {}: {}", euid, e);
/* FIXME: return something (empty string?) to make the caller bail out early */
"nobody".to_string()
}
}
}
struct OidcPasswd; struct OidcPasswd;
impl PasswdHooks for OidcPasswd { impl PasswdHooks for OidcPasswd {
fn get_all_entries() -> Response<Vec<Passwd>> { fn get_all_entries() -> Response<Vec<Passwd>> {
let conf = nss_hook_prepare(); let conf = nss_hook_prepare();
let mut cache = get_cache();
let user = get_current_user(); // Set the context user to the current user, but only if not already set
let ctc; // When doing PAM, we might get called back into by libc to do some NSS
let token = match cache.load_user_token(&user) { // lookup, and we want to keep the PAM login user context in that case
if !get_cache().context_user.is_initialized() {
get_cache().context_user.set_current_user();
}
let token = match get_cache().context_user.get_access_token() {
Some(t) => t, Some(t) => t,
None => { None => {
// FIXME Implement caching of system token // FIXME Implement caching of system token
debug!("Could not find a user token for {} to request NSS data; trying client credentials", user); debug!("Could not find a user token to request NSS data; trying client credentials");
match get_access_token_client(&conf, "nss", "", "") { match get_access_token_client(&conf, "nss", "", "") {
Ok(ct) => { Ok(ct) => ct,
ctc = ct.clone();
&ctc
},
Err(e) => { Err(e) => {
error!("Failed to get access token with client credentials: {}", e); error!("Failed to get access token with client credentials: {}", e);
return Response::Unavail; return Response::Unavail;
...@@ -112,20 +92,17 @@ impl PasswdHooks for OidcPasswd { ...@@ -112,20 +92,17 @@ impl PasswdHooks for OidcPasswd {
fn get_entry_by_uid(uid: libc::uid_t) -> Response<Passwd> { fn get_entry_by_uid(uid: libc::uid_t) -> Response<Passwd> {
let conf = nss_hook_prepare(); let conf = nss_hook_prepare();
let mut cache = get_cache();
let user = get_current_user(); if !get_cache().context_user.is_initialized() {
let ctc; get_cache().context_user.set_current_user();
let token = match cache.load_user_token(&user) { }
let token = match get_cache().context_user.get_access_token() {
Some(t) => t, Some(t) => t,
None => { None => {
// FIXME Implement caching of system token // FIXME Implement caching of system token
debug!("Could not find a user token for {} to request NSS data; trying client credentials", user); debug!("Could not find a user token to request NSS data; trying client credentials");
match get_access_token_client(&conf, "nss", "", "") { match get_access_token_client(&conf, "nss", "", "") {
Ok(ct) => { Ok(ct) => ct,
ctc = ct.clone();
&ctc
},
Err(e) => { Err(e) => {
error!("Failed to get access token with client credentials: {}", e); error!("Failed to get access token with client credentials: {}", e);
return Response::Unavail; return Response::Unavail;
...@@ -146,20 +123,16 @@ impl PasswdHooks for OidcPasswd { ...@@ -146,20 +123,16 @@ impl PasswdHooks for OidcPasswd {
fn get_entry_by_name(name: String) -> Response<Passwd> { fn get_entry_by_name(name: String) -> Response<Passwd> {
let conf = nss_hook_prepare(); let conf = nss_hook_prepare();
let mut cache = get_cache(); if !get_cache().context_user.is_initialized() {
get_cache().context_user.set_current_user();
let user = get_current_user(); }
let ctc; let token = match get_cache().context_user.get_access_token() {
let token = match cache.load_user_token(&user) {
Some(t) => t, Some(t) => t,
None => { None => {
// FIXME Implement caching of system token // FIXME Implement caching of system token
debug!("Could not find a user token for {} to request NSS data; trying client credentials", user); debug!("Could not find a user token for to request NSS data; trying client credentials");
match get_access_token_client(&conf, "nss", "", "") { match get_access_token_client(&conf, "nss", "", "") {
Ok(ct) => { Ok(ct) => ct,
ctc = ct.clone();
&ctc
},
Err(e) => { Err(e) => {
error!("Failed to get access token with client credentials: {}", e); error!("Failed to get access token with client credentials: {}", e);
return Response::Unavail; return Response::Unavail;
......
...@@ -90,7 +90,8 @@ impl PamServiceModule for PamOidc { ...@@ -90,7 +90,8 @@ impl PamServiceModule for PamOidc {
match get_access_token_password(&conf, "pam", username.to_string(), password.to_string(), PamError::SERVICE_ERR, PamError::AUTH_ERR) { match get_access_token_password(&conf, "pam", username.to_string(), password.to_string(), PamError::SERVICE_ERR, PamError::AUTH_ERR) {
Ok(t) => { Ok(t) => {
info!("Authenticated {} using Resource Owner Password Grant", username); info!("Authenticated {} using Resource Owner Password Grant", username);
get_cache().save_user_token(&username.to_string(), t.into()); get_cache().context_user.set_username(username.to_string());
get_cache().context_user.set_access_token(t);
return PamError::SUCCESS; return PamError::SUCCESS;
}, },
Err(e) => { Err(e) => {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment