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

[PAM] Implement token persistence in /run, and configurable persistence

parent 1ddf8c62
No related branches found
No related tags found
No related merge requests found
...@@ -19,6 +19,15 @@ token_url = "https://ticdesk-dev.teckids.org/oauth/token/" ...@@ -19,6 +19,15 @@ token_url = "https://ticdesk-dev.teckids.org/oauth/token/"
client_id = "Df1cpPEBsbG64oZ1Q1L8NetH1UKNBUyA5qhxg1Zh" client_id = "Df1cpPEBsbG64oZ1Q1L8NetH1UKNBUyA5qhxg1Zh"
client_secret = "" client_secret = ""
# Persist the user token in the filesystem
# Possible values:
# run - Only persist to runtime directory (probably /run)
# home - Also persist to home directory
# If NSS is in use, the token MUST be persisted to at least /run, or user-only
# mode will not work.
# Defaults to only /run
persist_token = { run = true, home = true }
[nss] [nss]
# Client ID and secret for acquiring OAuth tokens # Client ID and secret for acquiring OAuth tokens
# You might want to put these into a separate file nss_pam_webapi.secret.toml! # You might want to put these into a separate file nss_pam_webapi.secret.toml!
......
...@@ -283,14 +283,25 @@ impl UserInfo { ...@@ -283,14 +283,25 @@ impl UserInfo {
} }
} }
/// Get the full path to a cache file under our prefix in this user's XDG runtime directory
fn place_user_runtime_file(&mut self, filename: String) -> Result<PathBuf, io::Error> {
match self.get_user_xdg_base_directories() {
Ok(b) => b.place_runtime_file(filename),
Err(e) => {
error!("Error placing runtime file {}: {}", filename, e);
Err(e)
}
}
}
/// Get a known access token for this user /// Get a known access token for this user
/// ///
/// This will use the in-memory token from the `access_token` slot if it is filled, /// This will use the in-memory token from the `access_token` slot if it is filled,
/// or attempt to load a token from disk if not /// or attempt to load a token from disk if not
pub fn get_access_token(&mut self) -> Option<BasicTokenResponse> { pub fn get_access_token(&mut self) -> Option<BasicTokenResponse> {
// Try to load our acess token if none is known // Try to load our acess token from home directory if none is known
if self.access_token.is_none() { if self.access_token.is_none() {
debug!("No token in memory, trying to load from file"); debug!("No token in memory, trying to load from cache file");
self.drop_privileges().ok(); self.drop_privileges().ok();
// Trying to read even after failed privilege dropping is safe // Trying to read even after failed privilege dropping is safe
self.access_token = match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) { self.access_token = match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {
...@@ -303,6 +314,21 @@ impl UserInfo { ...@@ -303,6 +314,21 @@ impl UserInfo {
restore_privileges(); restore_privileges();
} }
// Try to load our acess token from runtime directory if none is known
if self.access_token.is_none() {
debug!("No token in memory, trying to load from runtime file");
self.drop_privileges().ok();
// Trying to read even after failed privilege dropping is safe
self.access_token = match self.place_user_runtime_file(USER_TOKEN_FILENAME.to_string()) {
Ok(path) => match load_json(path) {
Ok(read_token) => Some(read_token),
Err(_) => None
},
Err(_) => None
};
restore_privileges();
}
match &self.access_token { match &self.access_token {
Some(t) => Some(t.clone()), Some(t) => Some(t.clone()),
None => None None => None
...@@ -312,20 +338,53 @@ impl UserInfo { ...@@ -312,20 +338,53 @@ impl UserInfo {
/// Set the known access token for this user /// Set the known access token for this user
/// ///
/// This will store the token in memory in the `access_token` slot, and attempt to /// This will store the token in memory in the `access_token` slot, and attempt to
/// write the token to disk afterwards /// write the token to disk afterwards if requested.
pub fn set_access_token(&mut self, token: BasicTokenResponse, persist: bool) -> Result<(), io::Error> { ///
/// Arguments
/// ---------
///
/// * `token` - the OAuth token to store
/// * `persist_run` - whether to store token in XDG_RUNTIME_DIR (probably /run/<uid>)
/// * `persist_home` - whether to store token in XDG_CACHE_DIR (probably ~/.cache)
pub fn set_access_token(&mut self, token: BasicTokenResponse, persist_run: bool, persist_home: bool) -> Result<(), io::Error> {
self.access_token = Some(token.clone()); self.access_token = Some(token.clone());
debug!("Saved token in memory"); debug!("Saved token in memory");
if persist { if persist_run {
// Try to write user's token cache file // Try to write user's token cache file to XDG_RUNTIME_DIR
// We need to ensure privileges were dropped successfully to avoid symlink attacks
// cf. https://capec.mitre.org/data/definitions/132.html
let res = match self.drop_privileges() {
Ok(_) => match self.place_user_runtime_file(USER_TOKEN_FILENAME.to_string()) {
Ok(path) => {
debug!("Storing token in runtime file");
save_json(path, &token)
},
Err(e) => {
error!("Error getting cache path in runtime directory: {}", e);
Err(e)
}
},
Err(e) => {
error!("Error dropping privileges to store token in runtime directory: {}", e);
Err(e)
}
};
restore_privileges();
if res.is_err() {
return res;
}
}
if persist_home {
// Try to write user's token cache file to XDG_CACHE_DIR
// We need to ensure privileges were dropped successfully to avoid symlink attacks // We need to ensure privileges were dropped successfully to avoid symlink attacks
// cf. https://capec.mitre.org/data/definitions/132.html // cf. https://capec.mitre.org/data/definitions/132.html
let res = match self.drop_privileges() { let res = match self.drop_privileges() {
Ok(_) => match self.place_user_cache_file(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"); debug!("Storing token in cache file");
save_json(path, token) save_json(path, &token)
}, },
Err(e) => { Err(e) => {
error!("Error getting cache path in user home: {}", e); error!("Error getting cache path in user home: {}", e);
...@@ -338,11 +397,13 @@ impl UserInfo { ...@@ -338,11 +397,13 @@ impl UserInfo {
} }
}; };
restore_privileges(); restore_privileges();
if res.is_err() {
res return res;
} else { }
Ok(())
} }
// If we got here, no error was ever thrown
Ok(())
} }
} }
......
...@@ -25,6 +25,8 @@ pub fn get_config(conf_args: Option<config::Config>) -> config::Config { ...@@ -25,6 +25,8 @@ pub fn get_config(conf_args: Option<config::Config>) -> config::Config {
// Preset default configuration // Preset default configuration
let mut conf = config::Config::default(); let mut conf = config::Config::default();
conf.set("pam.flow", "password").ok(); conf.set("pam.flow", "password").ok();
conf.set("pam.persist_token.run", true).ok();
conf.set("pam.persist_token.home", false).ok();
// Unwrap passed arguments or use empty fallback // Unwrap passed arguments or use empty fallback
let conf_args = conf_args.unwrap_or_default(); let conf_args = conf_args.unwrap_or_default();
......
...@@ -105,14 +105,16 @@ impl PamServiceModule for PamOidc { ...@@ -105,14 +105,16 @@ impl PamServiceModule for PamOidc {
// 1. ...mark getpwnam unsafe (prevent cache code from calling it) // 1. ...mark getpwnam unsafe (prevent cache code from calling it)
set_is_getpwnam_safe(false); set_is_getpwnam_safe(false);
// 2. ...store the access token in memory // 2. ...store the access token in memory
get_context_user().set_access_token(t.clone(), false).ok(); get_context_user().set_access_token(t.clone(), false, false).ok();
// 3. ...call getpwnam ourselves without having the cache object locked // 3. ...call getpwnam ourselves without having the cache object locked
let passwd = getpwnam_safe(username.to_string()); let passwd = getpwnam_safe(username.to_string());
if passwd.is_ok() { if passwd.is_ok() {
// 4. ...if getpwnam was successful, store the token again (this time, // 4. ...if getpwnam was successful, store the token again (this time,
// modulo other errors, it will go through to $HOME) // modulo other errors, it will go through to $HOME)
get_context_user().set_passwd(passwd.unwrap()); get_context_user().set_passwd(passwd.unwrap());
get_context_user().set_access_token(t.clone(), true).ok(); let persist_run = conf.get_bool("pam.persist_token.run").unwrap();
let persist_home = conf.get_bool("pam.persist_token.home").unwrap();
get_context_user().set_access_token(t.clone(), persist_run, persist_home).ok();
} }
// 5. ...unlock getpwnam again (somewhat unnecessary) // 5. ...unlock getpwnam again (somewhat unnecessary)
set_is_getpwnam_safe(true); set_is_getpwnam_safe(true);
......
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