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