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 @@
*/
use crate::BASE_NAME;
use crate::unix::getpwnam_safe;
use crate::unix::{Passwd, getpwnam_safe, getpwuid_safe};
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::{RwLock, RwLockReadGuard};
use std::sync::{Mutex, MutexGuard};
use libc::{geteuid, seteuid, uid_t};
......@@ -37,119 +36,211 @@ use serde_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 {
user_tokens: HashMap<String, BasicTokenResponse>,
original_euid: uid_t,
pub context_user: UserInfo
}
impl Cache {
pub fn new() -> Cache {
let euid;
unsafe {
euid = geteuid();
let euid = unsafe {
geteuid()
};
Cache {
user_tokens: HashMap::new(),
original_euid: euid
context_user: UserInfo {
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> {
let current_euid;
unsafe {
current_euid = geteuid();
fn try_resolve(&mut self) -> Result<&Passwd, io::Error> {
// If we already have a full passwd struct, return it as without resolving
if self.passwd.is_some() {
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()) {
Ok(p) => p,
pub fn get_username(&mut self) -> Result<String, io::Error> {
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) => {
error!("Failed to lookup user {}: {}", username, e);
error!("Could not drop privileges because target UID is not resolved");
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 {
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);
}
let res;
unsafe {
res = seteuid(target_euid);
let res = unsafe {
seteuid(target_euid)
};
if res == 0 {
debug!("Successfully dropped privileges to {}", username);
debug!("Successfully dropped privileges to {}", target_euid);
return Ok(target_euid);
} else {
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);
}
}
fn restore_privileges(&self) {
let current_euid;
unsafe {
current_euid = geteuid();
let current_euid = unsafe {
geteuid()
};
if current_euid != self.original_euid {
if current_euid != original_euid {
debug!("Restoring privileges");
let res;
unsafe {
res = seteuid(self.original_euid);
let res = unsafe {
seteuid(original_euid)
};
if res != 0 {
panic!("Could not restore privileges to {}", self.original_euid);
panic!("Could not restore privileges to {}", original_euid);
}
} else {
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 pw = match getpwnam_safe(username.to_string()) {
Ok(p) => p,
Err(e) => {
error!("Failed to lookup user {}: {}", username, e);
return Err(e);
}
};
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 user ID to find out whether we should override $HOME
let uid = self.get_uid()?;
if uid != original_euid {
let user_home = self.get_home_directory()?;
env::set_var("HOME", user_home);
debug!("Home directory for UID {} is {}", uid, user_home);
}
// Determine XDG directories now
let base_dirs = match BaseDirectories::with_prefix(BASE_NAME) {
Ok(b) => b,
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e))
};
if saved_home != None {
env::set_var("HOME", saved_home.unwrap());
} else {
env::remove_var("HOME");
// Restore $HOME to original if we changed it earlier
if uid != original_euid {
if saved_home != None {
env::set_var("HOME", saved_home.unwrap());
} else {
env::remove_var("HOME");
}
}
return Ok(base_dirs);
}
fn place_user_cache_file(&self, username: &String, filename: String) -> Result<PathBuf, io::Error> {
match self.get_user_xdg_base_directories(username) {
fn place_user_cache_file(&mut self, filename: String) -> Result<PathBuf, io::Error> {
match self.get_user_xdg_base_directories() {
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> {
if !self.user_tokens.contains_key(owner) {
debug!("No token for {} in memory, trying to load from file", owner);
self.drop_privileges(owner).ok();
let new_token = match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) {
pub fn get_access_token(&mut self) -> Option<BasicTokenResponse> {
// Try to load our acess token if none is known
if self.access_token.is_none() {
debug!("No token in memory, trying to load from file");
self.drop_privileges().ok();
self.access_token = match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {
Ok(path) => match load_json(path) {
Ok(read_token) => Some(read_token),
Err(_) => None
......@@ -157,55 +248,29 @@ impl Cache {
Err(_) => None
};
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> {
CACHE.write().unwrap().user_tokens.insert(owner.to_string(), token.clone());
debug!("Saved token for {} in memory", owner);
pub fn set_access_token(&mut self, token: BasicTokenResponse) -> Result<(), io::Error> {
self.access_token = Some(token.clone());
debug!("Saved token for in memory");
// Try to write user's token cache file
let res = match self.drop_privileges(owner) {
Ok(_) => match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) {
let res = match self.drop_privileges() {
Ok(_) => match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {
Ok(path) => {
debug!("Storing token for {} in cache file", owner);
debug!("Storing token for in cache file");
save_json(path, token)
},
Err(e) => Err(e)
},
Err(e) => Err(io::Error::new(io::ErrorKind::PermissionDenied, e))
Err(e) => Err(e)
};
self.restore_privileges();
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> {
......@@ -226,10 +291,18 @@ fn save_json<O: Serialize>(path: PathBuf, obj: O) -> Result<(), io::Error> {
fs::write(path, json)
}
lazy_static! {
static ref CACHE: RwLock<Cache> = RwLock::new(Cache::new());
fn is_getpwnam_safe() -> bool {
// FIXME Implement real logic
return true;
}
pub fn get_cache() -> RwLockReadGuard<'static, Cache> {
CACHE.read().unwrap()
static original_euid: uid_t = unsafe {
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 @@
use crate::config::{
get_config,
get_optional,
get_or_error
};
use config::Config;
use crate::cache::get_cache;
......@@ -26,10 +25,6 @@ use crate::logging::setup_log;
use crate::oauth::{get_access_token_client, get_data_jq};
use serde::{Serialize, Deserialize};
use std::ffi::CStr;
use libc::geteuid;
use crate::unix::getpwuid_safe;
use libnss::interop::Response;
use libnss::passwd::{PasswdHooks, Passwd};
......@@ -58,40 +53,25 @@ fn nss_hook_prepare() -> Config {
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;
impl PasswdHooks for OidcPasswd {
fn get_all_entries() -> Response<Vec<Passwd>> {
let conf = nss_hook_prepare();
let mut cache = get_cache();
let user = get_current_user();
let ctc;
let token = match cache.load_user_token(&user) {
// Set the context user to the current user, but only if not already set
// When doing PAM, we might get called back into by libc to do some NSS
// 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,
None => {
// 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", "", "") {
Ok(ct) => {
ctc = ct.clone();
&ctc
},
Ok(ct) => ct,
Err(e) => {
error!("Failed to get access token with client credentials: {}", e);
return Response::Unavail;
......@@ -112,20 +92,17 @@ impl PasswdHooks for OidcPasswd {
fn get_entry_by_uid(uid: libc::uid_t) -> Response<Passwd> {
let conf = nss_hook_prepare();
let mut cache = get_cache();
let user = get_current_user();
let ctc;
let token = match cache.load_user_token(&user) {
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,
None => {
// 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", "", "") {
Ok(ct) => {
ctc = ct.clone();
&ctc
},
Ok(ct) => ct,
Err(e) => {
error!("Failed to get access token with client credentials: {}", e);
return Response::Unavail;
......@@ -146,20 +123,16 @@ impl PasswdHooks for OidcPasswd {
fn get_entry_by_name(name: String) -> Response<Passwd> {
let conf = nss_hook_prepare();
let mut cache = get_cache();
let user = get_current_user();
let ctc;
let token = match cache.load_user_token(&user) {
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,
None => {
// 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", "", "") {
Ok(ct) => {
ctc = ct.clone();
&ctc
},
Ok(ct) => ct,
Err(e) => {
error!("Failed to get access token with client credentials: {}", e);
return Response::Unavail;
......
......@@ -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) {
Ok(t) => {
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;
},
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