summaryrefslogtreecommitdiffstats
path: root/crates/common/tedge_users/src/unix.rs
blob: a37e8784183b4be2a2d220238a0969e1e4cb5cbe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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();
    }
}