Skip to main content

signstar_host/configure_build/
mod.rs

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