signstar_config/
non_admin_credentials.rs

1//! Non-administrative credentials handling for a NetHSM backend.
2use std::{
3    fmt::{Debug, Display},
4    fs::{File, Permissions, create_dir_all, read_to_string, set_permissions},
5    io::Write,
6    os::unix::fs::{PermissionsExt, chown},
7    path::{Path, PathBuf},
8    process::{Command, Stdio},
9};
10
11#[cfg(doc)]
12use nethsm::NetHsm;
13use nethsm::{FullCredentials, Passphrase, UserId};
14#[cfg(doc)]
15use nethsm_config::HermeticParallelConfig;
16use nethsm_config::{ExtendedUserMapping, NonAdministrativeSecretHandling, SystemUserId};
17use rand::{Rng, distributions::Alphanumeric, thread_rng};
18use signstar_common::{
19    common::SECRET_FILE_MODE,
20    system_user::{
21        get_home_base_dir_path,
22        get_plaintext_secret_file,
23        get_systemd_creds_secret_file,
24        get_user_secrets_dir,
25    },
26};
27
28use crate::{
29    config::load_config,
30    utils::{
31        fail_if_not_root,
32        fail_if_root,
33        get_command,
34        get_current_system_user,
35        get_system_user_pair,
36        match_current_system_user,
37    },
38};
39
40/// An error that may occur when handling non-administrative credentials for a NetHSM backend.
41#[derive(Debug, thiserror::Error)]
42pub enum Error {
43    /// There are one or more errors when loading credentials for a specific system user.
44    #[error("Errors occurred when loading credentials for system user {system_user}:\n{errors}")]
45    CredentialsLoading {
46        /// The system user for which loading of backend user credentials led to errors.
47        system_user: SystemUserId,
48        /// The errors that occurred during loading of backend user credentials for `system_user`.
49        errors: CredentialsLoadingErrors,
50    },
51
52    /// There are no credentials for a specific system user.
53    #[error("There are no credentials for system user {system_user}")]
54    CredentialsMissing {
55        /// The system user for which credentials are missing.
56        system_user: SystemUserId,
57    },
58
59    /// A mapping does not offer a system user.
60    #[error("There is no system user in the mapping.")]
61    NoSystemUser,
62
63    /// A user is not a signing user for the NetHSM backend.
64    #[error("The user is not an operator user in the NetHSM backend used for signing.")]
65    NotSigningUser,
66
67    /// A passphrase directory can not be created.
68    #[error("Passphrase directory {path} for user {system_user} can not be created:\n{source}")]
69    SecretsDirCreate {
70        /// The path to a secrets directory that could not be created.
71        path: PathBuf,
72        /// The system user in whose home directory `path` could not be created.
73        system_user: SystemUserId,
74        /// The source error.
75        source: std::io::Error,
76    },
77
78    /// A secrets file can not be created.
79    #[error("The secrets file {path} can not be created for user {system_user}:\n{source}")]
80    SecretsFileCreate {
81        /// The path to a secrets file that could not be created.
82        path: PathBuf,
83        /// The system user in whose home directory `path` could not be created.
84        system_user: SystemUserId,
85        /// The source error.
86        source: std::io::Error,
87    },
88
89    /// The file metadata of a secrets file cannot be retrieved.
90    #[error("File metadata of secrets file {path} cannot be retrieved")]
91    SecretsFileMetadata {
92        /// The path to a secrets file for which metadata could not be retrieved.
93        path: PathBuf,
94        /// The source error.
95        source: std::io::Error,
96    },
97
98    /// A secrets file does not exist.
99    #[error("Secrets file not found: {path}")]
100    SecretsFileMissing {
101        /// The path to a secrets file that is missing.
102        path: PathBuf,
103    },
104
105    /// A secrets file is not a file.
106    #[error("Secrets file is not a file: {path}")]
107    SecretsFileNotAFile {
108        /// The path to a secrets file that is not a file.
109        path: PathBuf,
110    },
111
112    /// A secrets file does not have the correct permissions.
113    #[error("Secrets file {path} has permissions {mode}, but {SECRET_FILE_MODE} is required")]
114    SecretsFilePermissions {
115        /// The path to a secrets file for which permissions could not be set.
116        path: PathBuf,
117        /// The file mode that should be applied to the file at `path`.
118        mode: u32,
119    },
120
121    /// A secrets file cannot be read.
122    #[error("Failed reading secrets file {path}:\n{source}")]
123    SecretsFileRead {
124        /// The path to a secrets file that could not be read.
125        path: PathBuf,
126        /// The source error.
127        source: std::io::Error,
128    },
129
130    /// A secrets file can not be written to.
131    #[error("The secrets file {path} can not be written to for user {system_user}: {source}")]
132    SecretsFileWrite {
133        /// The path to a secrets file that could not be written to.
134        path: PathBuf,
135        /// The system user in whose home directory `path` resides.
136        system_user: SystemUserId,
137        /// The source error.
138        source: std::io::Error,
139    },
140}
141
142/// An error that may occur when loading credentials for a [`SystemUserId`].
143///
144/// Alongside an [`Error`][`crate::Error`] contains a target [`UserId`] for which the error
145/// occurred.
146#[derive(Debug)]
147pub struct CredentialsLoadingError {
148    user_id: UserId,
149    error: crate::Error,
150}
151
152impl CredentialsLoadingError {
153    /// Creates a new [`CredentialsLoadingError`].
154    pub fn new(user_id: UserId, error: crate::Error) -> Self {
155        Self { user_id, error }
156    }
157
158    /// Returns a reference to the [`UserId`].
159    pub fn get_user_id(&self) -> &UserId {
160        &self.user_id
161    }
162
163    /// Returns a reference to the [`Error`][crate::Error].
164    pub fn get_error(&self) -> &crate::Error {
165        &self.error
166    }
167}
168
169impl Display for CredentialsLoadingError {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        write!(f, "{}: {}", self.user_id, self.error)
172    }
173}
174
175/// A wrapper for a list of [`CredentialsLoadingError`]s.
176#[derive(Debug)]
177pub struct CredentialsLoadingErrors {
178    errors: Vec<CredentialsLoadingError>,
179}
180
181impl CredentialsLoadingErrors {
182    /// Creates a new [`CredentialsLoadingError`].
183    pub fn new(errors: Vec<CredentialsLoadingError>) -> Self {
184        Self { errors }
185    }
186
187    /// Returns a reference to the list of [`CredentialsLoadingError`]s.
188    pub fn get_errors(&self) -> &[CredentialsLoadingError] {
189        &self.errors
190    }
191}
192
193impl Display for CredentialsLoadingErrors {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        write!(
196            f,
197            "{}",
198            self.errors
199                .iter()
200                .map(|error| error.to_string())
201                .collect::<Vec<String>>()
202                .join("\n")
203        )
204    }
205}
206
207/// A collection of credentials and credential loading errors for a system user.
208///
209/// Tracks a [`SystemUserId`], zero or more [`FullCredentials`] mapped to it, as well as zero or
210/// more errors related to loading the passphrase for a [`UserId`].
211#[derive(Debug)]
212pub struct CredentialsLoading {
213    mapping: ExtendedUserMapping,
214    credentials: Vec<FullCredentials>,
215    errors: CredentialsLoadingErrors,
216}
217
218impl CredentialsLoading {
219    /// Creates a new [`CredentialsLoading`].
220    pub fn new(
221        mapping: ExtendedUserMapping,
222        credentials: Vec<FullCredentials>,
223        errors: CredentialsLoadingErrors,
224    ) -> Self {
225        Self {
226            mapping,
227            credentials,
228            errors,
229        }
230    }
231
232    /// Creates a [`CredentialsLoading`] for the calling system user.
233    ///
234    /// Uses the data of the calling system user to derive the specific mapping for it from the
235    /// Signstar configuration (a [`HermeticParallelConfig`]).
236    /// Then continues to retrieve the credentials for all associated [`NetHsm`] users of the
237    /// mapping.
238    ///
239    /// # Errors
240    ///
241    /// Returns an error if
242    /// - it is not possible to derive user data from the calling process,
243    /// - if there is no user data for the calling process,
244    /// - the Signstar configuration file does not exist,
245    /// - it is not possible to load the Signstar configuration,
246    /// - not exactly one user mapping exists for the calling system user,
247    /// - or if credentials loading fails due to a severe error.
248    pub fn from_system_user() -> Result<Self, crate::Error> {
249        let user = get_current_system_user()?;
250
251        let system_config = load_config()?;
252
253        let mapping = system_config
254            .get_extended_mapping_for_user(&user.name)
255            .map_err(|source| crate::Error::Config(crate::config::Error::NetHsmConfig(source)))?;
256
257        // get all credentials for the mapping
258        let credentials_loading = mapping.load_credentials()?;
259
260        Ok(credentials_loading)
261    }
262
263    /// Returns the [`ExtendedUserMapping`].
264    pub fn get_mapping(&self) -> &ExtendedUserMapping {
265        &self.mapping
266    }
267
268    /// Returns all [`FullCredentials`].
269    pub fn get_credentials(&self) -> &[FullCredentials] {
270        &self.credentials
271    }
272
273    /// Returns a reference to a [`SystemUserId`].
274    ///
275    /// # Errors
276    ///
277    /// Returns an error if there is no system user in the tracked mapping.
278    pub fn get_system_user_id(&self) -> Result<&SystemUserId, crate::Error> {
279        match self.mapping.get_user_mapping().get_system_user() {
280            Some(system_user) => Ok(system_user),
281            None => Err(crate::Error::NonAdminSecretHandling(Error::NoSystemUser)),
282        }
283    }
284
285    /// Indicates whether there are any errors with [`UserId`]s.
286    ///
287    /// Returns `true` if there are errors, `false` otherwise.
288    pub fn has_userid_errors(&self) -> bool {
289        !self.errors.get_errors().is_empty()
290    }
291
292    /// Returns the collected errors for [`UserId`]s.
293    pub fn get_userid_errors(self) -> CredentialsLoadingErrors {
294        self.errors
295    }
296
297    /// Indicates whether the contained [`ExtendedUserMapping`] is that of a signing user.
298    pub fn has_signing_user(&self) -> bool {
299        matches!(
300            self.mapping.get_user_mapping(),
301            nethsm_config::UserMapping::SystemNetHsmOperatorSigning {
302                nethsm_user: _,
303                nethsm_key_setup: _,
304                ssh_authorized_key: _,
305                system_user: _,
306                tag: _,
307            }
308        )
309    }
310
311    /// Returns the credentials for a signing user.
312    ///
313    /// # Errors
314    ///
315    /// Returns an error if
316    /// - the tracked user is not a signing user
317    /// - errors occurred when loading the system user's credentials
318    /// - or there are no credentials for the system user.
319    pub fn credentials_for_signing_user(self) -> Result<FullCredentials, crate::Error> {
320        if !self.has_signing_user() {
321            return Err(crate::Error::NonAdminSecretHandling(Error::NotSigningUser));
322        }
323
324        if !self.errors.get_errors().is_empty() {
325            return Err(crate::Error::NonAdminSecretHandling(
326                Error::CredentialsLoading {
327                    system_user: self.get_system_user_id()?.clone(),
328                    errors: self.errors,
329                },
330            ));
331        }
332
333        if let Some(credentials) = self.credentials.first() {
334            Ok(credentials.clone())
335        } else {
336            return Err(crate::Error::NonAdminSecretHandling(
337                Error::CredentialsMissing {
338                    system_user: self.get_system_user_id()?.clone(),
339                },
340            ));
341        }
342    }
343}
344
345/// A trait to implement loading of credentials, which includes reading of secrets.
346pub trait SecretsReader {
347    /// Loads credentials.
348    fn load_credentials(self) -> Result<CredentialsLoading, crate::Error>;
349}
350
351/// Checks the accessibility of a secrets file.
352///
353/// Checks whether file at `path`
354/// - exists,
355/// - is a file,
356/// - has accessible metadata,
357/// - and has the file mode [`SECRET_FILE_MODE`].
358///
359/// # Errors
360///
361/// Returns an error, if the file at `path`
362/// - does not exist,
363/// - is not a file,
364/// - does not have accessible metadata,
365/// - or has a file mode other than [`SECRET_FILE_MODE`].
366fn check_secrets_file(path: &Path) -> Result<(), crate::Error> {
367    // check if a path exists
368    if !path.exists() {
369        return Err(crate::Error::NonAdminSecretHandling(
370            Error::SecretsFileMissing {
371                path: path.to_path_buf(),
372            },
373        ));
374    }
375
376    // check if this is a file
377    if !path.is_file() {
378        return Err(crate::Error::NonAdminSecretHandling(
379            Error::SecretsFileNotAFile {
380                path: path.to_path_buf(),
381            },
382        ));
383    }
384
385    // check for correct permissions
386    match path.metadata() {
387        Ok(metadata) => {
388            let mode = metadata.permissions().mode();
389            if mode != SECRET_FILE_MODE {
390                return Err(crate::Error::NonAdminSecretHandling(
391                    Error::SecretsFilePermissions {
392                        path: path.to_path_buf(),
393                        mode,
394                    },
395                ));
396            }
397        }
398        Err(source) => {
399            return Err(crate::Error::NonAdminSecretHandling(
400                Error::SecretsFileMetadata {
401                    path: path.to_path_buf(),
402                    source,
403                },
404            ));
405        }
406    }
407
408    Ok(())
409}
410
411impl SecretsReader for ExtendedUserMapping {
412    /// Loads credentials for each [`UserId`] associated with a [`SystemUserId`].
413    ///
414    /// The [`SystemUserId`] of the mapping must be equal to the current system user calling this
415    /// function.
416    /// Relies on [`get_plaintext_secret_file`] and [`get_systemd_creds_secret_file`] to retrieve
417    /// the specific path to a secret file for each [`UserId`] mapped to a [`SystemUserId`].
418    ///
419    /// Returns a [`CredentialsLoading`], which may contain critical errors related to loading a
420    /// passphrase for each available [`UserId`].
421    /// The caller is expected to handle any errors tracked in the returned object based on context.
422    ///
423    /// # Errors
424    ///
425    /// Returns an error if
426    /// - the [`ExtendedUserMapping`] provides no [`SystemUserId`],
427    /// - no system user equal to the [`SystemUserId`] exists,
428    /// - the [`SystemUserId`] is not equal to the currently calling system user,
429    /// - or the [systemd-creds] command is not available when trying to decrypt secrets.
430    ///
431    /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
432    fn load_credentials(self) -> Result<CredentialsLoading, crate::Error> {
433        // Retrieve required SystemUserId and User and compare with current User.
434        let (system_user, user) = get_system_user_pair(&self)?;
435        let current_system_user = get_current_system_user()?;
436
437        // fail if running as root
438        fail_if_root(&current_system_user)?;
439        match_current_system_user(&current_system_user, &user)?;
440
441        let secret_handling = self.get_non_admin_secret_handling();
442        let mut credentials = Vec::new();
443        let mut errors = Vec::new();
444
445        for user_id in self.get_user_mapping().get_nethsm_users() {
446            let secrets_file = match secret_handling {
447                NonAdministrativeSecretHandling::Plaintext => {
448                    get_plaintext_secret_file(system_user.as_ref(), &user_id.to_string())
449                }
450                NonAdministrativeSecretHandling::SystemdCreds => {
451                    get_systemd_creds_secret_file(system_user.as_ref(), &user_id.to_string())
452                }
453            };
454            // Ensure the secrets file has correct ownership and permissions.
455            if let Err(error) = check_secrets_file(secrets_file.as_path()) {
456                errors.push(CredentialsLoadingError::new(user_id, error));
457                continue;
458            };
459
460            match secret_handling {
461                // Read from plaintext secrets file.
462                NonAdministrativeSecretHandling::Plaintext => {
463                    // get passphrase or error
464                    match read_to_string(&secrets_file)
465                        .map_err(|source| Error::SecretsFileRead {
466                            path: secrets_file,
467                            source,
468                        })
469                        .map_err(crate::Error::NonAdminSecretHandling)
470                    {
471                        Ok(passphrase) => credentials
472                            .push(FullCredentials::new(user_id, Passphrase::new(passphrase))),
473                        Err(error) => {
474                            errors.push(CredentialsLoadingError::new(user_id, error));
475                            continue;
476                        }
477                    }
478                }
479                // Read from systemd-creds encrypted secrets file.
480                NonAdministrativeSecretHandling::SystemdCreds => {
481                    // Decrypt secret using systemd-creds.
482                    let creds_command = get_command("systemd-creds")?;
483                    let mut command = Command::new(creds_command);
484                    let command = command
485                        .arg("--user")
486                        .arg("decrypt")
487                        .arg(&secrets_file)
488                        .arg("-");
489                    match command
490                        .output()
491                        .map_err(|source| crate::Error::CommandExec {
492                            command: format!("{command:?}"),
493                            source,
494                        }) {
495                        Ok(command_output) => {
496                            // fail if decryption did not result in a successful status code
497                            if !command_output.status.success() {
498                                errors.push(CredentialsLoadingError::new(
499                                    user_id,
500                                    crate::Error::CommandNonZero {
501                                        command: format!("{command:?}"),
502                                        exit_status: command_output.status,
503                                        stderr: String::from_utf8_lossy(&command_output.stderr)
504                                            .into_owned(),
505                                    },
506                                ));
507                                continue;
508                            }
509
510                            let creds = match String::from_utf8(command_output.stdout) {
511                                Ok(creds) => creds,
512                                Err(source) => {
513                                    errors.push(CredentialsLoadingError::new(
514                                        user_id.clone(),
515                                        crate::Error::Utf8String {
516                                            path: secrets_file,
517                                            context: format!(
518                                                "converting stdout of {command:?} to string"
519                                            ),
520                                            source,
521                                        },
522                                    ));
523                                    continue;
524                                }
525                            };
526
527                            credentials.push(FullCredentials::new(user_id, Passphrase::new(creds)));
528                        }
529                        Err(error) => {
530                            errors.push(CredentialsLoadingError::new(user_id, error));
531                            continue;
532                        }
533                    }
534                }
535            }
536        }
537
538        Ok(CredentialsLoading::new(
539            self,
540            credentials,
541            CredentialsLoadingErrors { errors },
542        ))
543    }
544}
545
546/// A trait to create non-administrative secrets and accompanying directories.
547pub trait SecretsWriter {
548    /// Creates secrets directories for all non-administrative mappings.
549    fn create_secrets_dir(&self) -> Result<(), crate::Error>;
550
551    /// Creates non-administrative secrets for all mappings of system users to backend users.
552    fn create_non_administrative_secrets(&self) -> Result<(), crate::Error>;
553}
554
555impl SecretsWriter for ExtendedUserMapping {
556    /// Creates secrets directories for all non-administrative mappings.
557    ///
558    /// Matches the [`SystemUserId`] in a mapping with an actual user on the system.
559    /// Creates the passphrase directory for the user and ensures correct ownership of it and all
560    /// parent directories up until the user's home directory.
561    ///
562    /// # Errors
563    ///
564    /// Returns an error if
565    /// - no system user is available in the mapping,
566    /// - the system user of the mapping is not available on the system,
567    /// - the directory could not be created,
568    /// - the ownership of any directory between the user's home and the passphrase directory can
569    ///   not be changed.
570    fn create_secrets_dir(&self) -> Result<(), crate::Error> {
571        // Retrieve required SystemUserId and User and compare with current User.
572        let (system_user, user) = get_system_user_pair(self)?;
573
574        // fail if not running as root
575        fail_if_not_root(&get_current_system_user()?)?;
576
577        // get and create the user's passphrase directory
578        let secrets_dir = get_user_secrets_dir(system_user.as_ref());
579        create_dir_all(&secrets_dir).map_err(|source| Error::SecretsDirCreate {
580            path: secrets_dir.clone(),
581            system_user: system_user.clone(),
582            source,
583        })?;
584
585        // Recursively chown all directories to the user and group, until `HOME_BASE_DIR` is
586        // reached.
587        let home_dir = get_home_base_dir_path().join(PathBuf::from(system_user.as_ref()));
588        let mut chown_dir = secrets_dir.clone();
589        while chown_dir != home_dir {
590            chown(&chown_dir, Some(user.uid.as_raw()), Some(user.gid.as_raw())).map_err(
591                |source| crate::Error::Chown {
592                    path: chown_dir.to_path_buf(),
593                    user: system_user.to_string(),
594                    source,
595                },
596            )?;
597            if let Some(parent) = &chown_dir.parent() {
598                chown_dir = parent.to_path_buf()
599            } else {
600                break;
601            }
602        }
603
604        Ok(())
605    }
606
607    /// Creates passphrases for all non-administrative mappings.
608    ///
609    /// Creates a random alphanumeric, 30-char long passphrase for each backend user of each
610    /// non-administrative user mapping.
611    ///
612    /// - If `self` is configured to use [`NonAdministrativeSecretHandling::Plaintext`], the
613    ///   passphrase is stored in a secrets file, defined by [`get_plaintext_secret_file`].
614    /// - If `self` is configured to use [`NonAdministrativeSecretHandling::SystemdCreds`], the
615    ///   passphrase is encrypted using [systemd-creds] and stored in a secrets file, defined by
616    ///   [`get_systemd_creds_secret_file`].
617    ///
618    /// # Errors
619    ///
620    /// Returns an error if
621    /// - the targeted system user does not exist in the mapping or on the system,
622    /// - the function is called using a non-root user,
623    /// - the [systemd-creds] command is not available when trying to encrypt the passphrase,
624    /// - the encryption of the passphrase using [systemd-creds] fails,
625    /// - the secrets file can not be created,
626    /// - the secrets file can not be written to,
627    /// - or the ownership and permissions of the secrets file can not be changed.
628    ///
629    /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
630    fn create_non_administrative_secrets(&self) -> Result<(), crate::Error> {
631        // Retrieve required SystemUserId and User.
632        let (system_user, user) = get_system_user_pair(self)?;
633
634        // fail if not running as root
635        fail_if_not_root(&get_current_system_user()?)?;
636
637        let secret_handling = self.get_non_admin_secret_handling();
638
639        // add a secret for each NetHSM user
640        for user_id in self.get_user_mapping().get_nethsm_users() {
641            let secrets_file = match secret_handling {
642                NonAdministrativeSecretHandling::Plaintext => {
643                    get_plaintext_secret_file(system_user.as_ref(), &user_id.to_string())
644                }
645                NonAdministrativeSecretHandling::SystemdCreds => {
646                    get_systemd_creds_secret_file(system_user.as_ref(), &user_id.to_string())
647                }
648            };
649            println!(
650                "Create secret for system user {system_user} and backend user {user_id} in file: {secrets_file:?}"
651            );
652            let secret = {
653                // create initial (unencrypted) secret
654                let initial_secret: String = thread_rng()
655                    .sample_iter(&Alphanumeric)
656                    .take(30)
657                    .map(char::from)
658                    .collect();
659                // Create credentials files depending on secret handling
660                match secret_handling {
661                    NonAdministrativeSecretHandling::Plaintext => {
662                        initial_secret.as_bytes().to_vec()
663                    }
664                    NonAdministrativeSecretHandling::SystemdCreds => {
665                        // Create systemd-creds encrypted secret.
666                        let creds_command = get_command("systemd-creds")?;
667                        let mut command = Command::new(creds_command);
668                        let command = command
669                            .arg("--user")
670                            .arg("--name=")
671                            .arg("--uid")
672                            .arg(system_user.as_ref())
673                            .arg("encrypt")
674                            .arg("-")
675                            .arg("-");
676                        let mut command_child = command
677                            .stdin(Stdio::piped())
678                            .stdout(Stdio::piped())
679                            .spawn()
680                            .map_err(|source| crate::Error::CommandBackground {
681                                command: format!("{command:?}"),
682                                source,
683                            })?;
684                        let Some(mut stdin) = command_child.stdin.take() else {
685                            return Err(crate::Error::CommandAttachToStdin {
686                                command: format!("{command:?}"),
687                            })?;
688                        };
689
690                        let system_user_thread = system_user.clone();
691                        let handle = std::thread::spawn(move || {
692                            stdin
693                                .write_all(initial_secret.as_bytes())
694                                .map_err(|source| crate::Error::CommandWriteToStdin {
695                                    command:
696                                        format!("systemd-creds --user --name= --uid {system_user_thread} encrypt - -"),
697                                    source,
698                                })
699                        });
700
701                        let _handle_result = handle.join().map_err(|source| crate::Error::Thread {
702                            context: format!(
703                                "storing systemd-creds encrypted non-administrative secrets: {source:?}"
704                            ),
705                        })?;
706
707                        let command_output =
708                            command_child.wait_with_output().map_err(|source| {
709                                crate::Error::CommandExec {
710                                    command: format!("{command:?}"),
711                                    source,
712                                }
713                            })?;
714
715                        if !command_output.status.success() {
716                            return Err(crate::Error::CommandNonZero {
717                                command: format!("{command:?}"),
718                                exit_status: command_output.status,
719                                stderr: String::from_utf8_lossy(&command_output.stderr)
720                                    .into_owned(),
721                            });
722                        }
723                        command_output.stdout
724                    }
725                }
726            };
727
728            // Write secret to file and adjust permission and ownership of file.
729            let mut file = File::create(secrets_file.as_path()).map_err(|source| {
730                Error::SecretsFileCreate {
731                    path: secrets_file.clone(),
732                    system_user: system_user.clone(),
733                    source,
734                }
735            })?;
736            file.write_all(&secret)
737                .map_err(|source| Error::SecretsFileWrite {
738                    path: secrets_file.clone(),
739                    system_user: system_user.clone(),
740                    source,
741                })?;
742            chown(
743                &secrets_file,
744                Some(user.uid.as_raw()),
745                Some(user.gid.as_raw()),
746            )
747            .map_err(|source| crate::Error::Chown {
748                path: secrets_file.clone(),
749                user: system_user.to_string(),
750                source,
751            })?;
752            set_permissions(
753                secrets_file.as_path(),
754                Permissions::from_mode(SECRET_FILE_MODE),
755            )
756            .map_err(|source| crate::Error::ApplyPermissions {
757                path: secrets_file.clone(),
758                mode: SECRET_FILE_MODE,
759                source,
760            })?;
761        }
762        Ok(())
763    }
764}