diff options
Diffstat (limited to 'crates/common/tedge_users/src/unix.rs')
-rw-r--r-- | crates/common/tedge_users/src/unix.rs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/crates/common/tedge_users/src/unix.rs b/crates/common/tedge_users/src/unix.rs new file mode 100644 index 00000000..a37e8784 --- /dev/null +++ b/crates/common/tedge_users/src/unix.rs @@ -0,0 +1,225 @@ +use std::fmt; +use std::rc::Rc; +use std::sync::Mutex; + +/// The `UserManager` allows the process to switch from one unix user to another. +/// +/// * If the process is running as root, then the method `UserManager::become_user()` +/// is effective and the process can switch back and forth to different users. +/// * If the process is not running as root, then the method `UserManager::become_user()` +/// has no effect. Note that no error is raised. +/// +/// The rational is that a `tedge` command running as root (i.e. using `sudo tedge`) +/// has a fine grained control over the different operations and files, +/// while the unprivileged `tedge` command never switches to a different user +/// and has to manipulate all the system resources with the initial user. +/// +#[derive(Clone)] +pub struct UserManager { + // This implementation can never be thread-safe because the current user is a global concept for the process. + // If one thread changes the user, it affects another thread that might have wanted a different user. + // So, let's use Rc rather than Arc to force !Send. + inner: Rc<Mutex<InnerUserManager>>, +} + +impl fmt::Debug for UserManager { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UserManager").finish() + } +} + +struct InnerUserManager { + users: Vec<String>, + guard: Option<users::switch::SwitchUserGuard>, +} + +impl UserManager { + /// Create a `UserManager`. + /// + /// This function MUST be called only once. + /// But be warned, the compiler will not prevent you to call it twice. + /// If you do so, one thread might be switched by another thread to some un-expected user. + /// + /// This struct is not `Send` and cannot be shared between thread. + pub fn new() -> UserManager { + UserManager { + inner: Rc::new(Mutex::new(InnerUserManager { + users: vec![], + guard: None, + })), + } + } + + /// Check if the process has been launched using `sudo` or not. + /// + /// # Example + /// + /// ``` + /// # use tedge_users::UserManager; + /// let path = if UserManager::running_as_root() { + /// "/etc/mosquitto/mosquitto.conf" + /// } else { + /// ".tedge/mosquitto.conf" + /// }; + /// ``` + pub fn running_as_root() -> bool { + users::get_current_uid() == 0 + } + + /// Check if the process has been launched using a desired user or not. + /// + /// # Example + /// + /// ``` + /// # use tedge_users::UserManager; + /// let path = if UserManager::running_as("tedge-mapper") { + /// "/etc/tedge/tedge.toml" + /// } else { + /// ".tedge/tedge.toml" + /// }; + /// ``` + pub fn running_as(desired_user: &str) -> bool { + users::get_current_username() == Some(desired_user.into()) + } + + /// Switch the effective user of the running process. + /// + /// This method returns a guard. As long as the guard is owned by the caller, + /// the process is running under the requested user. When the guard is dropped, + /// then the process switches back to the former user. These calls can be stacked. + /// + /// # Example + /// + /// ``` + /// # use tedge_users::UserManager; + /// let user_manager = UserManager::new(); + /// let _user_guard_1 = user_manager.become_user("user_1").expect("Fail to become user_1"); + /// // Running as user1 + /// { + /// let _user_guard_2 = user_manager.become_user("user_2").expect("Fail to become user_2"); + /// // Running as user2 + /// } + /// // Running as user1 + /// ``` + /// + /// If the process is not running as root, the user is unchanged, + /// no error is raised and a dummy guard is returned. + /// In other words, a process running as root can have a fine control of the different permission modes, + /// while the same program running under a non-privileged user will perform the same operations + /// but all using the same permission mode. + /// For that to work, appropriate user-accessible resources will have to be used. + /// + /// For example, running as root, the process can read the configuration file as the tedge user, + /// then create a private key as mosquitto and restart mosquitto using systemd as root. + /// The same process, running as the a regular user, operates as this initial user for all the operations, + /// reading its own configuration file, creating its own private certificate and running its own mosquitto instance. + /// + /// The function returns a `UserSwitchError` if the given user is unknown. + /// + pub fn become_user(&self, username: &str) -> Result<UserGuard, super::UserSwitchError> { + if UserManager::running_as_root() { + self.inner.lock().unwrap().become_user(username)?; + } + + Ok(UserGuard { + user_manager: self.clone(), + }) + } + + fn drop_guard(&self) { + let mut lock_guard = self.inner.lock().unwrap(); + lock_guard.drop_guard() + } +} + +impl InnerUserManager { + fn become_user(&mut self, username: &str) -> Result<(), super::UserSwitchError> { + self.guard.take(); + + match InnerUserManager::inner_become_user(username) { + Ok(guard) => { + self.guard = Some(guard); + self.users.push(username.to_owned()); + Ok(()) + } + Err(err) => { + self.inner_restore_previous_user(); + Err(err) + } + } + } + + fn drop_guard(&mut self) { + self.guard.take(); + + if self.users.pop().is_none() { + return; + } + + self.inner_restore_previous_user(); + } + + fn inner_restore_previous_user(&mut self) { + if let Some(username) = self.users.last() { + let guard = InnerUserManager::inner_become_user(username).unwrap_or_else(|_| { + panic!( + r#"Fail to switch back to the former user: {}. + Has this user been removed from the system? + Aborting to avoid any security issue."#, + username + ) + }); + self.guard = Some(guard); + } + } + + fn inner_become_user( + username: &str, + ) -> Result<users::switch::SwitchUserGuard, super::UserSwitchError> { + let user = users::get_user_by_name(username).ok_or_else(|| { + super::UserSwitchError::UnknownUser { + name: username.to_owned(), + } + })?; + + let group = users::get_group_by_name(username).ok_or_else(|| { + super::UserSwitchError::UnknownGroup { + name: username.to_owned(), + } + })?; + + let uid = user.uid(); + let gid = group.gid(); + + Ok(users::switch::switch_user_group(uid, gid)?) + } +} + +/// Materialize the fact that the process is running under a user different from the former one. +/// On drop the process switches back to the former user. +/// +/// Such a guard implements the RAII pattern and provides no methods beyond `drop`. +/// +/// # Example +/// +/// ``` +/// # use tedge_users::UserManager; +/// # use tedge_users::UserSwitchError; +/// fn create_certificate(user_manager: &UserManager) -> Result<(), UserSwitchError> { +/// let _user_guard = user_manager.become_user("mosquitto")?; +/// // As long as the _user_guard is owned, the process run as mosquitto. +/// +/// // Create the certificate on behalf of mosquitto. +/// +/// Ok(()) +/// } // Here, the _user_guard is dropped and the process switches back to the former user. +/// ``` +pub struct UserGuard { + user_manager: UserManager, +} + +impl Drop for UserGuard { + fn drop(&mut self) { + self.user_manager.drop_guard(); + } +} |