Skip to main content

signstar_configure_build/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    fs::File,
5    io::Write,
6    path::{Path, PathBuf},
7    process::{Command, ExitStatus, id},
8    str::FromStr,
9};
10
11use log::{debug, info};
12use nix::unistd::User;
13use signstar_common::{
14    ssh::{get_ssh_authorized_key_base_dir, get_sshd_config_dropin_dir},
15    system_user::get_home_base_dir_path,
16};
17use signstar_config::config::{
18    AuthorizedKeyEntry,
19    Config,
20    MappingAuthorizedKeyEntry,
21    MappingSystemUserId,
22    SystemUserId,
23    SystemUserMapping,
24};
25#[cfg(feature = "nethsm")]
26use signstar_config::nethsm::NetHsmUserMapping;
27#[cfg(feature = "yubihsm2")]
28use signstar_config::yubihsm2::YubiHsm2UserMapping;
29use sysinfo::{Pid, System};
30
31/// Specific implementations for when any of the HSM backends are compiled in.
32#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
33mod impl_any {
34    use signstar_config::config::{UserBackendConnection, UserBackendConnectionFilter};
35
36    use super::*;
37
38    /// Creates system users and their integration.
39    ///
40    /// Uses the mappings found in a [`Config`] and creates relevant Unix users, if they don't exist
41    /// on the system yet.
42    /// System users are created unlocked, without passphrase, with their homes located in the
43    /// directory returned by [`get_home_base_dir_path`].
44    /// The home directories of users are not created upon user creation, but instead a [tmpfiles.d]
45    /// configuration is added for them to automate their creation upon system boot.
46    ///
47    /// Additionally, if an [`SshForceCommand`] can be derived from a particular mapping in the
48    /// [`Config`] and one or more SSH [authorized_keys] are defined for it, a dedicated SSH
49    /// integration is created for the system user.
50    /// This entails the creation of a dedicated [authorized_keys] file as well as an [sshd_config]
51    /// drop-in in a system-wide location.
52    /// Depending on the mapping in the [`Config`], a specific [ForceCommand] is set for the system
53    /// user, reflecting its role in the system.
54    ///
55    /// # Errors
56    ///
57    /// Returns an error if
58    /// - a system user name ([`SystemUserId`]) in the configuration can not be transformed into a
59    ///   valid system user name [`User`]
60    /// - a new user can not be created
61    /// - a newly created user can not be modified
62    /// - the tmpfiles.d integration for a newly created user can not be created
63    /// - the sshd_config drop-in file for a newly created user can not be created
64    ///
65    /// [tmpfiles.d]: https://man.archlinux.org/man/tmpfiles.d.5
66    /// [authorized_keys]: https://man.archlinux.org/man/sshd.8#AUTHORIZED_KEYS_FILE_FORMAT
67    /// [sshd_config]: https://man.archlinux.org/man/sshd_config.5
68    /// [ForceCommand]: https://man.archlinux.org/man/sshd_config.5#ForceCommand
69    pub fn create_system_users(config: &Config) -> Result<(), Error> {
70        // Only operate on non-administrative users.
71        for user_backend_connection in config
72            .user_backend_connections(UserBackendConnectionFilter::NonAdmin)
73            .iter()
74        {
75            let user = {
76                let user = match user_backend_connection {
77                    #[cfg(feature = "nethsm")]
78                    UserBackendConnection::NetHsm {
79                        admin_secret_handling: _,
80                        non_admin_secret_handling: _,
81                        connections: _,
82                        mapping,
83                    } => mapping.system_user_id(),
84                    #[cfg(feature = "yubihsm2")]
85                    UserBackendConnection::YubiHsm2 {
86                        admin_secret_handling: _,
87                        non_admin_secret_handling: _,
88                        connections: _,
89                        mapping,
90                    } => mapping.system_user_id(),
91                };
92
93                // if there is no system user, there is nothing to do
94                let Some(user) = user else {
95                    continue;
96                };
97                user
98            };
99
100            add_user_and_home(user)?;
101            add_tmpfilesd_integration(user)?;
102
103            let (ssh_force_command, authorized_key_entry) = {
104                match user_backend_connection {
105                    #[cfg(feature = "nethsm")]
106                    UserBackendConnection::NetHsm { mapping, .. } => (
107                        SshForceCommand::try_from(mapping),
108                        mapping.authorized_key_entry(),
109                    ),
110                    #[cfg(feature = "yubihsm2")]
111                    UserBackendConnection::YubiHsm2 { mapping, .. } => (
112                        SshForceCommand::try_from(mapping),
113                        mapping.authorized_key_entry(),
114                    ),
115                }
116            };
117
118            if let Ok(force_command) = ssh_force_command
119                && let Some(authorized_key) = authorized_key_entry
120            {
121                add_ssh_integration(user, authorized_key, &force_command)?;
122            }
123        }
124
125        for mapping in config.system().mappings() {
126            // if there is no system user, there is nothing to do
127            let Some(user) = mapping.system_user_id() else {
128                continue;
129            };
130            add_user_and_home(user)?;
131            add_tmpfilesd_integration(user)?;
132
133            let Some(authorized_key) = mapping.authorized_key_entry() else {
134                continue;
135            };
136            let force_command = SshForceCommand::from(mapping);
137            add_ssh_integration(user, authorized_key, &force_command)?;
138        }
139
140        Ok(())
141    }
142}
143
144/// Specific implementations for when none of the HSM backends are compiled in.
145#[cfg(not(any(feature = "nethsm", feature = "yubihsm2")))]
146mod impl_none {
147    use super::*;
148
149    /// Creates system users and their integration.
150    ///
151    /// Works on the [`UserMapping`]s of the provided `config` and creates system users for all
152    /// mappings, that define system users, if they don't exist on the system yet.
153    /// System users are created unlocked, without passphrase, with their homes located in the
154    /// directory returned by [`get_home_base_dir_path`].
155    /// The home directories of users are not created upon user creation, but instead a [tmpfiles.d]
156    /// configuration is added for them to automate their creation upon system boot.
157    ///
158    /// Additionally, if an [`SshForceCommand`] can be derived from the particular [`UserMapping`]
159    /// and one or more SSH [authorized_keys] are defined for it, a dedicated SSH integration is
160    /// created for the system user.
161    /// This entails the creation of a dedicated [authorized_keys] file as well as an [sshd_config]
162    /// drop-in in a system-wide location.
163    /// Depending on [`UserMapping`], a specific [ForceCommand] is set for the system user,
164    /// reflecting its role in the system.
165    ///
166    /// # Errors
167    ///
168    /// Returns an error if
169    /// - a system user name ([`SystemUserId`]) in the configuration can not be transformed into a
170    ///   valid system user name [`User`]
171    /// - a new user can not be created
172    /// - a newly created user can not be modified
173    /// - the tmpfiles.d integration for a newly created user can not be created
174    /// - the sshd_config drop-in file for a newly created user can not be created
175    ///
176    /// [tmpfiles.d]: https://man.archlinux.org/man/tmpfiles.d.5
177    /// [authorized_keys]: https://man.archlinux.org/man/sshd.8#AUTHORIZED_KEYS_FILE_FORMAT
178    /// [sshd_config]: https://man.archlinux.org/man/sshd_config.5
179    /// [ForceCommand]: https://man.archlinux.org/man/sshd_config.5#ForceCommand
180    pub fn create_system_users(config: &Config) -> Result<(), Error> {
181        for mapping in config.system().mappings() {
182            // if there is no system user, there is nothing to do
183            let Some(user) = mapping.system_user_id() else {
184                continue;
185            };
186            add_user_and_home(user)?;
187            add_tmpfilesd_integration(user)?;
188
189            let Some(authorized_key) = mapping.authorized_key_entry() else {
190                continue;
191            };
192            let force_command = SshForceCommand::from(mapping);
193            add_ssh_integration(user, authorized_key, &force_command)?;
194        }
195
196        Ok(())
197    }
198}
199
200#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
201pub use impl_any::create_system_users;
202#[cfg(not(any(feature = "nethsm", feature = "yubihsm2")))]
203pub use impl_none::create_system_users;
204
205pub mod cli;
206
207/// The error that may occur when using the "signstar-configure-build" executable.
208#[derive(Debug, thiserror::Error)]
209pub enum Error {
210    /// A config error
211    #[error("Configuration issue: {0}")]
212    Config(#[from] signstar_config::Error),
213
214    /// A [`Command`] exited unsuccessfully
215    #[error(
216        "The command exited with non-zero status code (\"{exit_status}\") and produced the following output on stderr:\n{stderr}"
217    )]
218    CommandNonZero {
219        /// The exit status of the failed command.
220        exit_status: ExitStatus,
221        /// The stderr of the failed command.
222        stderr: String,
223    },
224
225    /// A `u32` value can not be converted to `usize` on the current platform
226    #[error("Unable to convert u32 to usize on this platform.")]
227    FailedU32ToUsizeConversion,
228
229    /// There is no SSH ForceCommand defined for a mapping implementation.
230    #[error(
231        "No SSH ForceCommand defined for user mapping (HSM users: {}{})",
232        backend_users.join(", "),
233        if let Some(system_user) = system_user {
234            format!(", system user: {}", system_user)
235        } else {
236            "".to_string()
237        }
238    )]
239    NoForceCommandForMapping {
240        /// The list of HSM backend users for which no SSH `ForceCommand` is defined.
241        backend_users: Vec<String>,
242        /// The optional system user mapped to `backend_users`.
243        system_user: Option<String>,
244    },
245
246    /// No process information could be retrieved from the current PID
247    #[error("The information on the current process could not be retrieved")]
248    NoProcess,
249
250    /// The application is not run as root
251    #[error("This application must be run as root!")]
252    NotRoot,
253
254    /// No process information could be retrieved from the current PID
255    #[error("No user ID could be retrieved for the current process with PID {0}")]
256    NoUidForProcess(usize),
257
258    /// A string could not be converted to a sysinfo::Uid
259    #[error("The string {0} could not be converted to a \"sysinfo::Uid\"")]
260    SysUidFromStr(String),
261
262    /// A `Path` value for a tmpfiles.d integration is not valid.
263    #[error(
264        "The Path value {path} for the tmpfiles.d integration for {user} is not valid:\n{reason}"
265    )]
266    TmpfilesDPath {
267        /// The path that is not valid.
268        path: String,
269        /// The system user for which a `path` is invalid.
270        user: SystemUserId,
271        /// The reason why a path is not valid.
272        ///
273        /// # Note
274        ///
275        /// This is meant to complete the sentence "The Path value {path} for the tmpfiles.d
276        /// integration for {user} is not valid: "
277        reason: &'static str,
278    },
279
280    /// Adding a user failed
281    #[error("Adding user {user} failed:\n{source}")]
282    UserAdd {
283        /// The system user which cannot be added.
284        user: SystemUserId,
285        /// The source error.
286        source: std::io::Error,
287    },
288
289    /// Modifying a user failed
290    #[error("Modifying the user {user} failed:\n{source}")]
291    UserMod {
292        /// The system user which cannot be modified.
293        user: SystemUserId,
294        /// The source error.
295        source: std::io::Error,
296    },
297
298    /// A system user name can not be derived from a configuration user name
299    #[error("Getting a system user for the username {user} failed:\n{source}")]
300    UserNameConversion {
301        /// The system user that only exists in the configuration file.
302        user: SystemUserId,
303        /// The source error.
304        source: nix::Error,
305    },
306
307    /// Writing authorized_keys file for user failed
308    #[error("Writing authorized_keys file for {user} failed:\n{source}")]
309    WriteAuthorizedKeys {
310        /// The system user for which no "authorized_keys" file can be written.
311        user: SystemUserId,
312        /// The source error.
313        source: std::io::Error,
314    },
315
316    /// Writing sshd_config drop-in file for user failed
317    #[error("Writing sshd_config drop-in for {user} failed:\n{source}")]
318    WriteSshdConfig {
319        /// The system user for which an sshd_config drop-in cannot be written.
320        user: SystemUserId,
321        /// The source error.
322        source: std::io::Error,
323    },
324
325    /// Writing tmpfiles.d integration for user failed
326    #[error("Writing tmpfiles.d integration for {user} failed:\n{source}")]
327    WriteTmpfilesD {
328        /// The system user for which a tmpfiles.d file cannot be written.
329        user: SystemUserId,
330        /// The source error.
331        source: std::io::Error,
332    },
333}
334
335/// Adds a specific Unix user and its home, if it does not exist yet.
336///
337/// In addition, the system record for `user` is modified to be unlocked.
338///
339/// # Note
340///
341/// Requires the commands [useradd] and [usermod] to be present on the system.
342///
343/// # Errors
344///
345/// Returns an error, if
346///
347/// - retrieving user information on the system fails
348/// - creation of the user and its home fails
349/// - unlocking of the user fails
350///
351/// [useradd]: https://man.archlinux.org/man/useradd.8
352/// [usermod]: https://man.archlinux.org/man/usermod.8
353fn add_user_and_home(user: &SystemUserId) -> Result<(), Error> {
354    // If the Unix user exists already, we don't have to create it.
355    if User::from_name(user.as_ref())
356        .map_err(|source| Error::UserNameConversion {
357            user: user.clone(),
358            source,
359        })?
360        .is_none()
361    {
362        let home_base_dir = get_home_base_dir_path();
363
364        // add user, but do not create its home
365        info!("Creating user \"{user}\"...");
366        let user_add = Command::new("useradd")
367            .arg("--base-dir")
368            .arg(home_base_dir.as_path())
369            .arg("--user-group")
370            .arg("--shell")
371            .arg("/usr/bin/bash")
372            .arg(user.as_ref())
373            .output()
374            .map_err(|error| Error::UserAdd {
375                user: user.clone(),
376                source: error,
377            })?;
378
379        if !user_add.status.success() {
380            return Err(Error::CommandNonZero {
381                exit_status: user_add.status,
382                stderr: String::from_utf8_lossy(&user_add.stderr).into_owned(),
383            });
384        }
385        debug!("{}", String::from_utf8_lossy(&user_add.stdout));
386    } else {
387        debug!("Skipping existing user \"{user}\"...");
388    }
389
390    // Modify user to unlock it.
391    info!("Unlocking user \"{user}\"...");
392    let user_mod = Command::new("usermod")
393        .args(["--unlock", user.as_ref()])
394        .output()
395        .map_err(|source| Error::UserMod {
396            user: user.clone(),
397            source,
398        })?;
399
400    if !user_mod.status.success() {
401        return Err(Error::CommandNonZero {
402            exit_status: user_mod.status,
403            stderr: String::from_utf8_lossy(&user_mod.stderr).into_owned(),
404        });
405    }
406    debug!("{}", String::from_utf8_lossy(&user_mod.stdout));
407
408    Ok(())
409}
410
411/// Adds [tmpfiles.d] integration for a `user`.
412///
413/// # Errors
414///
415/// Returns an error, if
416///
417/// - creating the [tmpfiles.d] file for `user` fails
418/// - writing the [tmpfiles.d] file for `user` fails
419///
420/// [tmpfiles.d]: https://man.archlinux.org/man/tmpfiles.d.5
421fn add_tmpfilesd_integration(user: &SystemUserId) -> Result<(), Error> {
422    // add tmpfiles.d integration for the user to create its home directory
423    info!("Adding tmpfiles.d integration for user \"{user}\"...");
424
425    let mut buffer = File::create(format!("/usr/lib/tmpfiles.d/signstar-user-{user}.conf"))
426        .map_err(|source| Error::WriteTmpfilesD {
427            user: user.clone(),
428            source,
429        })?;
430    let home_base_dir = get_home_base_dir_path();
431
432    // ensure that the `Path` component in the tmpfiles.d file
433    // - has whitespace replaced with a c-style escape
434    // - does not contain specifiers
435    let home_dir = {
436        let home_dir = format!("{}/{user}", home_base_dir.to_string_lossy()).replace(" ", "\\x20");
437        if home_dir.contains("%") {
438            return Err(Error::TmpfilesDPath {
439                path: home_dir.clone(),
440                user: user.clone(),
441                reason: "Specifiers (%) are not supported at this point.",
442            });
443        }
444        home_dir
445    };
446
447    buffer
448        .write_all(format!("d {home_dir} 700 {user} {user}\n",).as_bytes())
449        .map_err(|source| Error::WriteTmpfilesD {
450            user: user.clone(),
451            source,
452        })?;
453
454    Ok(())
455}
456
457/// Adds the SSH integration for a specific Unix user.
458///
459/// Sets a single `authorized_key` entry for `user` in the system-wide SSH configuration location.
460/// Sets up a system-wide SSH configuration for `user` in which its `authorized_key` configuration
461/// as well as a specific `force_command` is enforced.
462///
463/// # Errors
464///
465/// Returns an error if
466///
467/// - the `authorized_key` entry for `user` cannot be created
468/// - the sshd configuration file for `user` cannot be created
469fn add_ssh_integration(
470    user: &SystemUserId,
471    authorized_key: &AuthorizedKeyEntry,
472    force_command: &SshForceCommand,
473) -> Result<(), Error> {
474    info!("Adding SSH authorized_keys file for user \"{user}\"...");
475    {
476        let mut buffer = File::create(
477            get_ssh_authorized_key_base_dir().join(format!("signstar-user-{user}.authorized_keys")),
478        )
479        .map_err(|source| Error::WriteAuthorizedKeys {
480            user: user.clone(),
481            source,
482        })?;
483        buffer
484            .write_all(authorized_key.to_string().as_bytes())
485            .map_err(|source| Error::WriteAuthorizedKeys {
486                user: user.clone(),
487                source,
488            })?;
489    }
490
491    // add sshd_config drop-in configuration for user
492    info!("Adding sshd_config drop-in configuration for user \"{user}\"...");
493    {
494        let mut buffer = File::create(
495            get_sshd_config_dropin_dir().join(format!("10-signstar-user-{user}.conf")),
496        )
497        .map_err(|source| Error::WriteSshdConfig {
498            user: user.clone(),
499            source,
500        })?;
501        buffer
502            .write_all(
503                format!(
504                    r#"Match user {user}
505    AuthorizedKeysFile /etc/ssh/signstar-user-{user}.authorized_keys
506    ForceCommand /usr/bin/{force_command}
507"#
508                )
509                .as_bytes(),
510            )
511            .map_err(|source| Error::WriteSshdConfig {
512                user: user.clone(),
513                source,
514            })?;
515    }
516
517    Ok(())
518}
519
520/// The configuration file path for the application.
521#[derive(Clone, Debug)]
522pub struct ConfigPath(PathBuf);
523
524impl ConfigPath {
525    /// Creates a new [`ConfigPath`] from a path.
526    pub fn new(path: PathBuf) -> Self {
527        Self(path)
528    }
529}
530
531impl AsRef<Path> for ConfigPath {
532    fn as_ref(&self) -> &Path {
533        self.0.as_path()
534    }
535}
536
537impl Default for ConfigPath {
538    /// Returns the default [`ConfigPath`].
539    ///
540    /// Uses [`Config::first_existing_system_path`] to find the first usable configuration file
541    /// path, or [`Config::default_system_path`] if none is found.
542    fn default() -> Self {
543        Self(Config::first_existing_system_path().unwrap_or(Config::default_system_path()))
544    }
545}
546
547impl From<PathBuf> for ConfigPath {
548    fn from(value: PathBuf) -> Self {
549        Self(value)
550    }
551}
552
553impl FromStr for ConfigPath {
554    type Err = Error;
555    fn from_str(s: &str) -> Result<Self, Self::Err> {
556        Ok(Self::new(PathBuf::from(s)))
557    }
558}
559
560/// A command enforced for a user connecting over SSH.
561///
562/// Tracks specific executables that are set using [ForceCommand] in an [sshd_config] drop-in
563/// configuration.
564///
565/// [sshd_config]: https://man.archlinux.org/man/sshd_config.5
566/// [ForceCommand]: https://man.archlinux.org/man/sshd_config.5#ForceCommand
567#[derive(strum::AsRefStr, Debug, strum::Display, strum::EnumString, strum::VariantNames)]
568pub enum SshForceCommand {
569    /// Enforce calling signstar-download-backup
570    #[strum(serialize = "signstar-download-backup")]
571    DownloadBackup,
572
573    /// Enforce calling signstar-download-key-certificate
574    #[strum(serialize = "signstar-download-key-certificate")]
575    DownloadKeyCertificate,
576
577    /// Enforce calling signstar-download-metrics
578    #[strum(serialize = "signstar-download-metrics")]
579    DownloadMetrics,
580
581    /// Enforce calling `signstar-shareholder` for handling SSS shares.
582    #[strum(serialize = "signstar-shareholder")]
583    Shareholder,
584
585    /// Enforce calling signstar-download-wireguard
586    #[strum(serialize = "signstar-download-wireguard")]
587    DownloadWireGuard,
588
589    /// Enforce calling `signstar-sign`.
590    #[strum(serialize = "signstar-sign")]
591    Sign,
592
593    /// Enforce calling signstar-upload-backup
594    #[strum(serialize = "signstar-upload-backup")]
595    UploadBackup,
596
597    /// Enforce calling signstar-upload-update
598    #[strum(serialize = "signstar-upload-update")]
599    UploadUpdate,
600}
601
602impl From<&SystemUserMapping> for SshForceCommand {
603    fn from(value: &SystemUserMapping) -> Self {
604        match value {
605            SystemUserMapping::ShareHolder { .. } => SshForceCommand::Shareholder,
606            SystemUserMapping::WireGuardDownload { .. } => SshForceCommand::DownloadWireGuard,
607        }
608    }
609}
610
611#[cfg(feature = "nethsm")]
612impl TryFrom<&NetHsmUserMapping> for SshForceCommand {
613    type Error = Error;
614
615    fn try_from(value: &NetHsmUserMapping) -> Result<Self, Self::Error> {
616        match value {
617            NetHsmUserMapping::Admin(admin) => Err(Error::NoForceCommandForMapping {
618                backend_users: vec![admin.to_string()],
619                system_user: None,
620            }),
621            NetHsmUserMapping::Backup { .. } => Ok(Self::DownloadBackup),
622            NetHsmUserMapping::HermeticMetrics {
623                backend_users,
624                system_user,
625            } => Err(Error::NoForceCommandForMapping {
626                backend_users: backend_users
627                    .get_users()
628                    .iter()
629                    .map(|user| user.to_string())
630                    .collect(),
631                system_user: Some(system_user.to_string()),
632            }),
633            NetHsmUserMapping::Metrics { .. } => Ok(Self::DownloadMetrics),
634            NetHsmUserMapping::Signing { .. } => Ok(SshForceCommand::Sign),
635        }
636    }
637}
638
639#[cfg(feature = "yubihsm2")]
640impl TryFrom<&YubiHsm2UserMapping> for SshForceCommand {
641    type Error = Error;
642
643    fn try_from(value: &YubiHsm2UserMapping) -> Result<Self, Self::Error> {
644        match value {
645            YubiHsm2UserMapping::Admin {
646                authentication_key_id,
647            } => Err(Error::NoForceCommandForMapping {
648                backend_users: vec![authentication_key_id.to_string()],
649                system_user: None,
650            }),
651            YubiHsm2UserMapping::AuditLog { .. } => Ok(SshForceCommand::DownloadMetrics),
652            YubiHsm2UserMapping::Backup { .. } => Ok(SshForceCommand::DownloadBackup),
653            YubiHsm2UserMapping::HermeticAuditLog {
654                authentication_key_id,
655                system_user,
656            } => Err(Error::NoForceCommandForMapping {
657                backend_users: vec![authentication_key_id.to_string()],
658                system_user: Some(system_user.to_string()),
659            }),
660            YubiHsm2UserMapping::Signing { .. } => Ok(SshForceCommand::Sign),
661        }
662    }
663}
664
665/// Checks whether the current process is run by root.
666///
667/// Gets the effective user ID of the current process and checks whether it is `0`.
668///
669/// # Errors
670///
671/// Returns an error if
672/// - conversion of PID to usize `fails`
673/// - the root user ID can not be converted from `"0"`
674/// - no user ID can be retrieved from the current process
675/// - the process is not run by root
676pub fn ensure_root() -> Result<(), Error> {
677    let pid: usize = id()
678        .try_into()
679        .map_err(|_| Error::FailedU32ToUsizeConversion)?;
680
681    let system = System::new_all();
682    let Some(process) = system.process(Pid::from(pid)) else {
683        return Err(Error::NoProcess);
684    };
685
686    let Some(uid) = process.effective_user_id() else {
687        return Err(Error::NoUidForProcess(pid));
688    };
689
690    let root_uid_str = "0";
691    let root_uid = sysinfo::Uid::from_str(root_uid_str)
692        .map_err(|_| Error::SysUidFromStr(root_uid_str.to_string()))?;
693
694    if uid.ne(&root_uid) {
695        return Err(Error::NotRoot);
696    }
697
698    Ok(())
699}