signstar_config/config/
mapping.rs

1//! User mapping for [`SignstarConfig`].
2
3use std::{
4    collections::HashSet,
5    fs::{File, Permissions, create_dir_all, read_to_string, set_permissions},
6    io::Write,
7    os::unix::fs::{PermissionsExt, chown},
8    path::{Path, PathBuf},
9    process::{Command, Stdio},
10};
11
12#[cfg(doc)]
13use nethsm::NetHsm;
14use nethsm::{FullCredentials, KeyId, NamespaceId, Passphrase, UserId, UserRole};
15use rand::{Rng, distributions::Alphanumeric, thread_rng};
16use serde::{Deserialize, Serialize};
17use signstar_common::{
18    common::SECRET_FILE_MODE,
19    system_user::{
20        get_home_base_dir_path,
21        get_plaintext_secret_file,
22        get_systemd_creds_secret_file,
23        get_user_secrets_dir,
24    },
25};
26use signstar_crypto::key::SigningKeySetup;
27
28use crate::{
29    AdministrativeSecretHandling,
30    AuthorizedKeyEntry,
31    CredentialsLoading,
32    CredentialsLoadingError,
33    CredentialsLoadingErrors,
34    Error,
35    NonAdministrativeSecretHandling,
36    SignstarConfig,
37    SystemUserId,
38    SystemWideUserId,
39    config::base::BackendConnection,
40    nethsm::config::{FilterUserKeys, NetHsmMetricsUsers},
41    utils::{
42        fail_if_not_root,
43        fail_if_root,
44        get_command,
45        get_current_system_user,
46        get_system_user_pair,
47        match_current_system_user,
48    },
49};
50
51/// User mapping between system users and [`NetHsm`] users
52#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
53pub enum UserMapping {
54    /// A NetHsm user in the Administrator role, without a system user mapped to it
55    #[serde(rename = "nethsm_only_admin")]
56    NetHsmOnlyAdmin(UserId),
57
58    /// A system user, with SSH access, mapped to a system-wide [`NetHsm`] user in the Backup role.
59    #[serde(rename = "system_nethsm_backup")]
60    SystemNetHsmBackup {
61        /// The name of the [`NetHsm`] user.
62        nethsm_user: SystemWideUserId,
63        /// The SSH public key used for connecting to the `system_user`.
64        ssh_authorized_key: AuthorizedKeyEntry,
65        /// The name of the system user.
66        system_user: SystemUserId,
67    },
68
69    /// A system user, with SSH access, mapped to a system-wide [`NetHsm`] user
70    /// in the Metrics role and `n` users in the Operator role with read-only access to zero or
71    /// more keys
72    #[serde(rename = "system_nethsm_metrics")]
73    SystemNetHsmMetrics {
74        /// The [`NetHsm`] users in the [`Metrics`][`UserRole::Metrics`] and
75        /// [`operator`][`UserRole::Operator`] role.
76        nethsm_users: NetHsmMetricsUsers,
77        /// The SSH public key used for connecting to the `system_user`.
78        ssh_authorized_key: AuthorizedKeyEntry,
79        /// The name of the system user.
80        system_user: SystemUserId,
81    },
82
83    /// A system user, with SSH access, mapped to a [`NetHsm`] user in the
84    /// Operator role with access to a single signing key.
85    ///
86    /// Signing key and NetHSM user are mapped using a tag.
87    #[serde(rename = "system_nethsm_operator_signing")]
88    SystemNetHsmOperatorSigning {
89        /// The name of the [`NetHsm`] user.
90        nethsm_user: UserId,
91        /// The ID of the [`NetHsm`] key.
92        key_id: KeyId,
93        /// The setup of a [`NetHsm`] key.
94        nethsm_key_setup: SigningKeySetup,
95        /// The SSH public key used for connecting to the `system_user`.
96        ssh_authorized_key: AuthorizedKeyEntry,
97        /// The name of the system user.
98        system_user: SystemUserId,
99        /// The tag used for the user and the signing key on the [`NetHsm`].
100        tag: String,
101    },
102
103    /// A system user, with SSH access, mapped to a YubiHSM2 user in the
104    /// Operator role with access to a single signing key.
105    ///
106    /// Signing key and YubiHSM user are mapped using a permission.
107    #[cfg(feature = "yubihsm2")]
108    #[serde(rename = "system_yubihsm_operator_signing")]
109    SystemYubiHsmOperatorSigning {
110        /// The identifier of the authentication key used to create a session with the YubiHSM2.
111        authentication_key_id: u16,
112        /// The setup of a YubiHSM2 key.
113        backend_key_setup: SigningKeySetup,
114        /// The identifier of the key in the YubiHSM2 backend.
115        backend_key_id: u16,
116        /// The domain the backend key belongs to.
117        backend_key_domain: usize,
118        /// The SSH public key used for connecting to the `system_user`.
119        ssh_authorized_key: AuthorizedKeyEntry,
120        /// The name of the system user.
121        system_user: SystemUserId,
122    },
123
124    /// A system user, without SSH access, mapped to a system-wide [`NetHsm`]
125    /// user in the Metrics role and one or more NetHsm users in the Operator role with
126    /// read-only access to zero or more keys
127    #[serde(rename = "hermetic_system_nethsm_metrics")]
128    HermeticSystemNetHsmMetrics {
129        /// The [`NetHsm`] users in the [`Metrics`][`UserRole::Metrics`] and
130        /// [`operator`][`UserRole::Operator`] role.
131        nethsm_users: NetHsmMetricsUsers,
132        /// The name of the system user.
133        system_user: SystemUserId,
134    },
135
136    /// A system user, with SSH access, not mapped to any backend user, that is used for downloading
137    /// shares of a shared secret.
138    #[serde(rename = "system_only_share_download")]
139    SystemOnlyShareDownload {
140        /// The name of the system user.
141        system_user: SystemUserId,
142        /// The list of SSH public keys used for connecting to the `system_user`.
143        ssh_authorized_key: AuthorizedKeyEntry,
144    },
145
146    /// A system user, with SSH access, not mapped to any backend user, that is used for uploading
147    /// shares of a shared secret.
148    #[serde(rename = "system_only_share_upload")]
149    SystemOnlyShareUpload {
150        /// The name of the system user.
151        system_user: SystemUserId,
152        /// The list of SSH public keys used for connecting to the `system_user`.
153        ssh_authorized_key: AuthorizedKeyEntry,
154    },
155
156    /// A system user, with SSH access, not mapped to any backend user, that is used for downloading
157    /// the WireGuard configuration of the host.
158    #[serde(rename = "system_only_wireguard_download")]
159    SystemOnlyWireGuardDownload {
160        /// The name of the system user.
161        system_user: SystemUserId,
162        /// The list of SSH public keys used for connecting to the `system_user`.
163        ssh_authorized_key: AuthorizedKeyEntry,
164    },
165}
166
167impl UserMapping {
168    /// Returns the optional system user of the mapping
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use signstar_config::{SystemUserId, UserMapping};
174    ///
175    /// # fn main() -> testresult::TestResult {
176    /// let mapping = UserMapping::SystemOnlyShareDownload {
177    ///     system_user: "user1".parse()?,
178    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
179    /// };
180    /// assert_eq!(mapping.get_system_user(), Some(&SystemUserId::new("user1".to_string())?));
181    ///
182    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
183    /// assert_eq!(mapping.get_system_user(), None);
184    /// # Ok(())
185    /// # }
186    /// ```
187    pub fn get_system_user(&self) -> Option<&SystemUserId> {
188        match self {
189            UserMapping::NetHsmOnlyAdmin(_) => None,
190            UserMapping::SystemNetHsmBackup {
191                nethsm_user: _,
192                ssh_authorized_key: _,
193                system_user,
194            }
195            | UserMapping::SystemNetHsmOperatorSigning {
196                nethsm_user: _,
197                key_id: _,
198                nethsm_key_setup: _,
199                ssh_authorized_key: _,
200                system_user,
201                tag: _,
202            }
203            | UserMapping::SystemNetHsmMetrics {
204                nethsm_users: _,
205                ssh_authorized_key: _,
206                system_user,
207            }
208            | UserMapping::HermeticSystemNetHsmMetrics {
209                nethsm_users: _,
210                system_user,
211            }
212            | UserMapping::SystemOnlyShareDownload {
213                system_user,
214                ssh_authorized_key: _,
215            }
216            | UserMapping::SystemOnlyShareUpload {
217                system_user,
218                ssh_authorized_key: _,
219            }
220            | UserMapping::SystemOnlyWireGuardDownload {
221                system_user,
222                ssh_authorized_key: _,
223            } => Some(system_user),
224            #[cfg(feature = "yubihsm2")]
225            UserMapping::SystemYubiHsmOperatorSigning {
226                authentication_key_id: _,
227                backend_key_setup: _,
228                backend_key_id: _,
229                backend_key_domain: _,
230                system_user,
231                ssh_authorized_key: _,
232            } => Some(system_user),
233        }
234    }
235
236    /// Returns the NetHsm users of the mapping
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// use nethsm::UserId;
242    /// use signstar_config::UserMapping;
243    ///
244    /// # fn main() -> testresult::TestResult {
245    /// let mapping = UserMapping::SystemOnlyShareDownload {
246    ///     system_user: "user1".parse()?,
247    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
248    /// };
249    /// assert!(mapping.get_nethsm_users().is_empty());
250    ///
251    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
252    /// assert_eq!(mapping.get_nethsm_users(), vec![UserId::new("user1".to_string())?]);
253    /// # Ok(())
254    /// # }
255    /// ```
256    pub fn get_nethsm_users(&self) -> Vec<UserId> {
257        match self {
258            UserMapping::SystemNetHsmBackup {
259                nethsm_user,
260                system_user: _,
261                ssh_authorized_key: _,
262            } => vec![nethsm_user.clone().into()],
263            UserMapping::NetHsmOnlyAdmin(nethsm_user)
264            | UserMapping::SystemNetHsmOperatorSigning {
265                nethsm_user,
266                key_id: _,
267                nethsm_key_setup: _,
268                system_user: _,
269                ssh_authorized_key: _,
270                tag: _,
271            } => vec![nethsm_user.clone()],
272            UserMapping::SystemNetHsmMetrics {
273                nethsm_users,
274                system_user: _,
275                ssh_authorized_key: _,
276            }
277            | UserMapping::HermeticSystemNetHsmMetrics {
278                nethsm_users,
279                system_user: _,
280            } => nethsm_users.get_users(),
281            UserMapping::SystemOnlyShareDownload {
282                system_user: _,
283                ssh_authorized_key: _,
284            }
285            | UserMapping::SystemOnlyShareUpload {
286                system_user: _,
287                ssh_authorized_key: _,
288            }
289            | UserMapping::SystemOnlyWireGuardDownload {
290                system_user: _,
291                ssh_authorized_key: _,
292            } => vec![],
293            #[cfg(feature = "yubihsm2")]
294            UserMapping::SystemYubiHsmOperatorSigning { .. } => Vec::new(),
295        }
296    }
297
298    /// Returns the list of all tracked [`UserId`]s and their respective [`UserRole`]s.
299    ///
300    /// # Examples
301    ///
302    /// ```
303    /// use nethsm::{UserId, UserRole};
304    /// use signstar_config::UserMapping;
305    ///
306    /// # fn main() -> testresult::TestResult {
307    /// let mapping = UserMapping::SystemOnlyShareDownload {
308    ///     system_user: "user1".parse()?,
309    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
310    /// };
311    /// assert!(mapping.get_nethsm_users_and_roles().is_empty());
312    ///
313    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
314    /// assert_eq!(mapping.get_nethsm_users_and_roles(), vec![(UserId::new("user1".to_string())?, UserRole::Administrator)]);
315    /// # Ok(())
316    /// # }
317    /// ```
318    pub fn get_nethsm_users_and_roles(&self) -> Vec<(UserId, UserRole)> {
319        match self {
320            UserMapping::SystemNetHsmBackup {
321                nethsm_user,
322                system_user: _,
323                ssh_authorized_key: _,
324            } => vec![(nethsm_user.clone().into(), UserRole::Backup)],
325            UserMapping::NetHsmOnlyAdmin(nethsm_user) => {
326                vec![(nethsm_user.clone(), UserRole::Administrator)]
327            }
328            UserMapping::SystemNetHsmOperatorSigning {
329                nethsm_user,
330                key_id: _,
331                nethsm_key_setup: _,
332                system_user: _,
333                ssh_authorized_key: _,
334                tag: _,
335            } => vec![(nethsm_user.clone(), UserRole::Operator)],
336            UserMapping::SystemNetHsmMetrics {
337                nethsm_users,
338                system_user: _,
339                ssh_authorized_key: _,
340            }
341            | UserMapping::HermeticSystemNetHsmMetrics {
342                nethsm_users,
343                system_user: _,
344            } => nethsm_users.get_users_and_roles(),
345            UserMapping::SystemOnlyShareDownload {
346                system_user: _,
347                ssh_authorized_key: _,
348            }
349            | UserMapping::SystemOnlyShareUpload {
350                system_user: _,
351                ssh_authorized_key: _,
352            }
353            | UserMapping::SystemOnlyWireGuardDownload {
354                system_user: _,
355                ssh_authorized_key: _,
356            } => vec![],
357            #[cfg(feature = "yubihsm2")]
358            UserMapping::SystemYubiHsmOperatorSigning { .. } => Vec::new(),
359        }
360    }
361
362    /// Returns a list of tuples containing [`UserId`], [`UserRole`] and a list of tags.
363    ///
364    /// # Note
365    ///
366    /// Certain variants of [`UserMapping`] such as [`UserMapping::SystemOnlyShareDownload`],
367    /// [`UserMapping::SystemOnlyShareUpload`] and [`UserMapping::SystemOnlyWireGuardDownload`]
368    /// always return an empty [`Vec`] because they do not track backend users.
369    ///
370    /// # Examples
371    ///
372    /// ```
373    /// use nethsm::{CryptographicKeyContext, OpenPgpUserIdList, UserId, UserRole};
374    /// use signstar_crypto::key::SigningKeySetup;
375    /// use signstar_config::{AuthorizedKeyEntry, UserMapping};
376    ///
377    /// # fn main() -> testresult::TestResult {
378    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
379    ///     nethsm_user: "user1".parse()?,
380    ///     key_id: "key1".parse()?,
381    ///     nethsm_key_setup: SigningKeySetup::new(
382    ///         "Curve25519".parse()?,
383    ///         vec!["EdDsaSignature".parse()?],
384    ///         None,
385    ///         "EdDsa".parse()?,
386    ///         CryptographicKeyContext::OpenPgp{
387    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
388    ///             version: "v4".parse()?,
389    ///         },
390    ///     )?,
391    ///     system_user: "ssh-user1".parse()?,
392    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
393    ///     tag: "tag1".to_string(),
394    /// };
395    /// assert_eq!(
396    ///     mapping.get_nethsm_user_role_and_tags(),
397    ///     vec![(UserId::new("user1".to_string())?, UserRole::Operator, vec!["tag1".to_string()])]);
398    /// # Ok(())
399    /// # }
400    /// ```
401    pub fn get_nethsm_user_role_and_tags(&self) -> Vec<(UserId, UserRole, Vec<String>)> {
402        match self {
403            UserMapping::SystemNetHsmOperatorSigning {
404                nethsm_user,
405                key_id: _,
406                nethsm_key_setup: _,
407                system_user: _,
408                ssh_authorized_key: _,
409                tag,
410            } => vec![(
411                nethsm_user.clone(),
412                UserRole::Operator,
413                vec![tag.to_string()],
414            )],
415            UserMapping::SystemNetHsmBackup {
416                nethsm_user,
417                ssh_authorized_key: _,
418                system_user: _,
419            } => vec![(nethsm_user.clone().into(), UserRole::Backup, Vec::new())],
420            UserMapping::NetHsmOnlyAdmin(user_id) => {
421                vec![(user_id.clone(), UserRole::Administrator, Vec::new())]
422            }
423            UserMapping::SystemNetHsmMetrics {
424                nethsm_users,
425                ssh_authorized_key: _,
426                system_user: _,
427            } => nethsm_users
428                .get_users_and_roles()
429                .iter()
430                .map(|(user, role)| (user.clone(), *role, Vec::new()))
431                .collect(),
432            UserMapping::HermeticSystemNetHsmMetrics {
433                nethsm_users,
434                system_user: _,
435            } => nethsm_users
436                .get_users_and_roles()
437                .iter()
438                .map(|(user, role)| (user.clone(), *role, Vec::new()))
439                .collect(),
440            UserMapping::SystemOnlyShareDownload { .. }
441            | UserMapping::SystemOnlyShareUpload { .. }
442            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
443            #[cfg(feature = "yubihsm2")]
444            UserMapping::SystemYubiHsmOperatorSigning { .. } => Vec::new(),
445        }
446    }
447
448    /// Returns the SSH authorized key of the mapping if it exists.
449    ///
450    /// Returns [`None`] if the mapping does not have an SSH authorized key.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use std::str::FromStr;
456    ///
457    /// use signstar_config::{AuthorizedKeyEntry, UserMapping};
458    ///
459    /// # fn main() -> testresult::TestResult {
460    /// let mapping = UserMapping::SystemOnlyShareDownload {
461    ///     system_user: "user1".parse()?,
462    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
463    /// };
464    /// assert_eq!(mapping.get_ssh_authorized_key(), Some(&AuthorizedKeyEntry::from_str("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host")?));
465    ///
466    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
467    /// assert_eq!(mapping.get_ssh_authorized_key(), None);
468    /// # Ok(())
469    /// # }
470    /// ```
471    pub fn get_ssh_authorized_key(&self) -> Option<&AuthorizedKeyEntry> {
472        match self {
473            UserMapping::NetHsmOnlyAdmin(_)
474            | UserMapping::HermeticSystemNetHsmMetrics {
475                nethsm_users: _,
476                system_user: _,
477            } => None,
478            UserMapping::SystemNetHsmBackup {
479                nethsm_user: _,
480                system_user: _,
481                ssh_authorized_key,
482            }
483            | UserMapping::SystemNetHsmMetrics {
484                nethsm_users: _,
485                system_user: _,
486                ssh_authorized_key,
487            }
488            | UserMapping::SystemOnlyShareDownload {
489                system_user: _,
490                ssh_authorized_key,
491            }
492            | UserMapping::SystemOnlyShareUpload {
493                system_user: _,
494                ssh_authorized_key,
495            }
496            | UserMapping::SystemOnlyWireGuardDownload {
497                system_user: _,
498                ssh_authorized_key,
499            }
500            | UserMapping::SystemNetHsmOperatorSigning {
501                nethsm_user: _,
502                key_id: _,
503                nethsm_key_setup: _,
504                system_user: _,
505                ssh_authorized_key,
506                tag: _,
507            } => Some(ssh_authorized_key),
508            #[cfg(feature = "yubihsm2")]
509            UserMapping::SystemYubiHsmOperatorSigning {
510                authentication_key_id: _,
511                backend_key_setup: _,
512                backend_key_id: _,
513                backend_key_domain: _,
514                system_user: _,
515                ssh_authorized_key,
516            } => Some(ssh_authorized_key),
517        }
518    }
519
520    /// Returns all used [`KeyId`]s of the mapping
521    ///
522    /// # Examples
523    ///
524    /// ```
525    /// use nethsm::{CryptographicKeyContext, KeyId, OpenPgpUserIdList};
526    /// use signstar_crypto::key::SigningKeySetup;
527    /// use signstar_config::{AuthorizedKeyEntry, UserMapping};
528    ///
529    /// # fn main() -> testresult::TestResult {
530    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
531    ///     nethsm_user: "user1".parse()?,
532    ///     key_id: "key1".parse()?,
533    ///     nethsm_key_setup: SigningKeySetup::new(
534    ///         "Curve25519".parse()?,
535    ///         vec!["EdDsaSignature".parse()?],
536    ///         None,
537    ///         "EdDsa".parse()?,
538    ///         CryptographicKeyContext::OpenPgp{
539    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
540    ///             version: "v4".parse()?,
541    ///         },
542    ///     )?,
543    ///     system_user: "ssh-user1".parse()?,
544    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
545    ///     tag: "tag1".to_string(),
546    /// };
547    /// assert_eq!(mapping.get_nethsm_key_ids(None), vec![KeyId::new("key1".to_string())?]);
548    ///
549    /// let mapping = UserMapping::SystemOnlyShareDownload {
550    ///     system_user: "user1".parse()?,
551    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
552    /// };
553    /// assert_eq!(mapping.get_nethsm_key_ids(None), vec![]);
554    /// # Ok(())
555    /// # }
556    /// ```
557    pub fn get_nethsm_key_ids(&self, namespace: Option<&NamespaceId>) -> Vec<KeyId> {
558        match self {
559            UserMapping::SystemNetHsmOperatorSigning {
560                nethsm_user,
561                key_id,
562                nethsm_key_setup: _,
563                system_user: _,
564                ssh_authorized_key: _,
565                tag: _,
566            } => {
567                if nethsm_user.namespace() == namespace {
568                    vec![key_id.clone()]
569                } else {
570                    vec![]
571                }
572            }
573            UserMapping::SystemNetHsmMetrics {
574                nethsm_users: _,
575                system_user: _,
576                ssh_authorized_key: _,
577            }
578            | UserMapping::NetHsmOnlyAdmin(_)
579            | UserMapping::HermeticSystemNetHsmMetrics {
580                nethsm_users: _,
581                system_user: _,
582            }
583            | UserMapping::SystemNetHsmBackup {
584                nethsm_user: _,
585                system_user: _,
586                ssh_authorized_key: _,
587            }
588            | UserMapping::SystemOnlyShareDownload {
589                system_user: _,
590                ssh_authorized_key: _,
591            }
592            | UserMapping::SystemOnlyShareUpload {
593                system_user: _,
594                ssh_authorized_key: _,
595            }
596            | UserMapping::SystemOnlyWireGuardDownload {
597                system_user: _,
598                ssh_authorized_key: _,
599            } => vec![],
600            #[cfg(feature = "yubihsm2")]
601            UserMapping::SystemYubiHsmOperatorSigning { .. } => Vec::new(),
602        }
603    }
604
605    /// Returns tags for keys and users
606    ///
607    /// Tags can be filtered by [namespace] by providing [`Some`] `namespace`.
608    /// Providing [`None`] implies that the context is system-wide.
609    ///
610    /// # Examples
611    ///
612    /// ```
613    /// use nethsm::{CryptographicKeyContext, OpenPgpUserIdList};
614    /// use signstar_crypto::key::SigningKeySetup;
615    /// use signstar_config::UserMapping;
616    ///
617    /// # fn main() -> testresult::TestResult {
618    /// let mapping = UserMapping::SystemOnlyShareDownload {
619    ///     system_user: "user1".parse()?,
620    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
621    /// };
622    /// assert!(mapping.get_nethsm_tags(None).is_empty());
623    ///
624    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
625    /// assert!(mapping.get_nethsm_tags(None).is_empty());
626    ///
627    /// let mapping = UserMapping::SystemNetHsmOperatorSigning{
628    ///     nethsm_user: "ns1~user1".parse()?,
629    ///     key_id: "key1".parse()?,
630    ///     nethsm_key_setup: SigningKeySetup::new(
631    ///         "Curve25519".parse()?,
632    ///         vec!["EdDsaSignature".parse()?],
633    ///         None,
634    ///         "EdDsa".parse()?,
635    ///         CryptographicKeyContext::OpenPgp{
636    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
637    ///             version: "4".parse()?,
638    ///     })?,
639    ///     system_user: "user1".parse()?,
640    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
641    ///     tag: "tag1".to_string(),
642    /// };
643    /// assert!(mapping.get_nethsm_tags(None).is_empty());
644    /// assert_eq!(mapping.get_nethsm_tags(Some(&"ns1".parse()?)), vec!["tag1"]);
645    /// # Ok(())
646    /// # }
647    /// ```
648    /// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
649    pub fn get_nethsm_tags(&self, namespace: Option<&NamespaceId>) -> Vec<&str> {
650        match self {
651            UserMapping::SystemNetHsmOperatorSigning {
652                nethsm_user,
653                key_id: _,
654                nethsm_key_setup: _,
655                system_user: _,
656                ssh_authorized_key: _,
657                tag,
658            } => {
659                if nethsm_user.namespace() == namespace {
660                    vec![tag.as_str()]
661                } else {
662                    vec![]
663                }
664            }
665            UserMapping::SystemNetHsmMetrics {
666                nethsm_users: _,
667                system_user: _,
668                ssh_authorized_key: _,
669            }
670            | UserMapping::NetHsmOnlyAdmin(_)
671            | UserMapping::HermeticSystemNetHsmMetrics {
672                nethsm_users: _,
673                system_user: _,
674            }
675            | UserMapping::SystemNetHsmBackup {
676                nethsm_user: _,
677                system_user: _,
678                ssh_authorized_key: _,
679            }
680            | UserMapping::SystemOnlyShareDownload {
681                system_user: _,
682                ssh_authorized_key: _,
683            }
684            | UserMapping::SystemOnlyShareUpload {
685                system_user: _,
686                ssh_authorized_key: _,
687            }
688            | UserMapping::SystemOnlyWireGuardDownload {
689                system_user: _,
690                ssh_authorized_key: _,
691            } => vec![],
692            #[cfg(feature = "yubihsm2")]
693            UserMapping::SystemYubiHsmOperatorSigning { .. } => Vec::new(),
694        }
695    }
696
697    /// Returns a list of tuples of [`UserId`], [`KeyId`], [`SigningKeySetup`] and tag for the
698    /// mapping.
699    ///
700    /// Using a `filter` (see [`FilterUserKeys`]) it is possible to have only a subset of the
701    /// available tuples be returned:
702    ///
703    /// - [`FilterUserKeys::All`]: Returns all available tuples.
704    /// - [`FilterUserKeys::Namespaced`]: Returns tuples that match [`UserId`]s with a namespace.
705    /// - [`FilterUserKeys::Namespace`]: Returns tuples that match [`UserId`]s with a specific
706    ///   namespace.
707    /// - [`FilterUserKeys::SystemWide`]: Returns tuples that match [`UserId`]s without a namespace.
708    /// - [`FilterUserKeys::Namespace`]: Returns tuples that match a specific tag.
709    ///
710    /// # Examples
711    ///
712    /// ```
713    /// use nethsm::{CryptographicKeyContext, KeyId, OpenPgpUserIdList, UserId};
714    /// use signstar_crypto::key::SigningKeySetup;
715    /// use signstar_config::{FilterUserKeys, UserMapping};
716    ///
717    /// # fn main() -> testresult::TestResult {
718    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
719    ///     nethsm_user: "user1".parse()?,
720    ///     key_id: "key1".parse()?,
721    ///     nethsm_key_setup: SigningKeySetup::new(
722    ///         "Curve25519".parse()?,
723    ///         vec!["EdDsaSignature".parse()?],
724    ///         None,
725    ///         "EdDsa".parse()?,
726    ///         CryptographicKeyContext::OpenPgp{
727    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
728    ///             version: "v4".parse()?,
729    ///         },
730    ///     )?,
731    ///     system_user: "ssh-user1".parse()?,
732    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
733    ///     tag: "tag1".to_string(),
734    /// };
735    /// assert_eq!(
736    ///     mapping.get_nethsm_user_key_and_tag(FilterUserKeys::All),
737    ///     vec![(
738    ///         "user1".parse()?,
739    ///         "key1".parse()?,
740    ///         SigningKeySetup::new(
741    ///             "Curve25519".parse()?,
742    ///             vec!["EdDsaSignature".parse()?],
743    ///             None,
744    ///             "EdDsa".parse()?,
745    ///             CryptographicKeyContext::OpenPgp{
746    ///                 user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
747    ///                 version: "v4".parse()?,
748    ///             },
749    ///         )?,
750    ///         "tag1".to_string(),
751    ///     )]
752    /// );
753    /// assert_eq!(mapping.get_nethsm_user_key_and_tag(FilterUserKeys::Namespace("test".parse()?)), vec![]);
754    /// assert_eq!(mapping.get_nethsm_user_key_and_tag(FilterUserKeys::Tag("tag2".parse()?)), vec![]);
755    ///
756    /// let mapping = UserMapping::SystemOnlyShareDownload {
757    ///     system_user: "user1".parse()?,
758    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
759    /// };
760    /// assert_eq!(mapping.get_nethsm_user_key_and_tag(FilterUserKeys::All), vec![]);
761    /// # Ok(())
762    /// # }
763    /// ```
764    pub fn get_nethsm_user_key_and_tag(
765        &self,
766        filter: FilterUserKeys,
767    ) -> Vec<(UserId, KeyId, SigningKeySetup, String)> {
768        match self {
769            UserMapping::SystemNetHsmOperatorSigning {
770                nethsm_user,
771                key_id,
772                nethsm_key_setup,
773                system_user: _,
774                ssh_authorized_key: _,
775                tag,
776            } => match filter {
777                FilterUserKeys::All => {
778                    vec![(
779                        nethsm_user.clone(),
780                        key_id.clone(),
781                        nethsm_key_setup.clone(),
782                        tag.clone(),
783                    )]
784                }
785                FilterUserKeys::Namespaced => {
786                    if nethsm_user.is_namespaced() {
787                        vec![(
788                            nethsm_user.clone(),
789                            key_id.clone(),
790                            nethsm_key_setup.clone(),
791                            tag.clone(),
792                        )]
793                    } else {
794                        Vec::new()
795                    }
796                }
797                FilterUserKeys::Namespace(namespace) => {
798                    if Some(&namespace) == nethsm_user.namespace() {
799                        vec![(
800                            nethsm_user.clone(),
801                            key_id.clone(),
802                            nethsm_key_setup.clone(),
803                            tag.clone(),
804                        )]
805                    } else {
806                        Vec::new()
807                    }
808                }
809                FilterUserKeys::SystemWide => {
810                    if !nethsm_user.is_namespaced() {
811                        vec![(
812                            nethsm_user.clone(),
813                            key_id.clone(),
814                            nethsm_key_setup.clone(),
815                            tag.clone(),
816                        )]
817                    } else {
818                        Vec::new()
819                    }
820                }
821                FilterUserKeys::Tag(filter_tag) => {
822                    if &filter_tag == tag {
823                        vec![(
824                            nethsm_user.clone(),
825                            key_id.clone(),
826                            nethsm_key_setup.clone(),
827                            tag.clone(),
828                        )]
829                    } else {
830                        Vec::new()
831                    }
832                }
833            },
834            UserMapping::SystemNetHsmMetrics {
835                nethsm_users: _,
836                system_user: _,
837                ssh_authorized_key: _,
838            }
839            | UserMapping::NetHsmOnlyAdmin(_)
840            | UserMapping::HermeticSystemNetHsmMetrics {
841                nethsm_users: _,
842                system_user: _,
843            }
844            | UserMapping::SystemNetHsmBackup {
845                nethsm_user: _,
846                system_user: _,
847                ssh_authorized_key: _,
848            }
849            | UserMapping::SystemOnlyShareDownload {
850                system_user: _,
851                ssh_authorized_key: _,
852            }
853            | UserMapping::SystemOnlyShareUpload {
854                system_user: _,
855                ssh_authorized_key: _,
856            }
857            | UserMapping::SystemOnlyWireGuardDownload {
858                system_user: _,
859                ssh_authorized_key: _,
860            } => vec![],
861            #[cfg(feature = "yubihsm2")]
862            UserMapping::SystemYubiHsmOperatorSigning { .. } => Vec::new(),
863        }
864    }
865
866    /// Returns all [`NetHsm`][`nethsm::NetHsm`] [namespaces] of the mapping
867    ///
868    /// # Examples
869    ///
870    /// ```
871    /// use nethsm::{CryptographicKeyContext, OpenPgpUserIdList};
872    /// use signstar_crypto::key::SigningKeySetup;
873    /// use signstar_config::UserMapping;
874    ///
875    /// # fn main() -> testresult::TestResult {
876    /// let mapping = UserMapping::SystemOnlyShareDownload {
877    ///     system_user: "user1".parse()?,
878    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
879    /// };
880    /// assert!(mapping.get_nethsm_namespaces().is_empty());
881    ///
882    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
883    /// assert!(mapping.get_nethsm_namespaces().is_empty());
884    ///
885    /// let mapping = UserMapping::SystemNetHsmOperatorSigning{
886    ///     nethsm_user: "ns1~user1".parse()?,
887    ///     key_id: "key1".parse()?,
888    ///     nethsm_key_setup: SigningKeySetup::new(
889    ///         "Curve25519".parse()?,
890    ///         vec!["EdDsaSignature".parse()?],
891    ///         None,
892    ///         "EdDsa".parse()?,
893    ///         CryptographicKeyContext::OpenPgp{
894    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
895    ///             version: "4".parse()?,
896    ///     })?,
897    ///     system_user: "user1".parse()?,
898    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
899    ///     tag: "tag1".to_string(),
900    /// };
901    /// assert_eq!(mapping.get_nethsm_namespaces(), vec!["ns1".parse()?]);
902    /// # Ok(())
903    /// # }
904    /// ```
905    /// [namespaces]: https://docs.nitrokey.com/nethsm/administration#namespaces
906    pub fn get_nethsm_namespaces(&self) -> Vec<NamespaceId> {
907        match self {
908            UserMapping::NetHsmOnlyAdmin(nethsm_user)
909            | UserMapping::SystemNetHsmOperatorSigning {
910                nethsm_user,
911                key_id: _,
912                nethsm_key_setup: _,
913                system_user: _,
914                ssh_authorized_key: _,
915                tag: _,
916            } => {
917                if let Some(namespace) = nethsm_user.namespace() {
918                    vec![namespace.clone()]
919                } else {
920                    vec![]
921                }
922            }
923            UserMapping::HermeticSystemNetHsmMetrics {
924                nethsm_users,
925                system_user: _,
926            }
927            | UserMapping::SystemNetHsmMetrics {
928                nethsm_users,
929                system_user: _,
930                ssh_authorized_key: _,
931            } => nethsm_users
932                .get_users()
933                .iter()
934                .filter_map(|user_id| user_id.namespace())
935                .cloned()
936                .collect(),
937            UserMapping::SystemOnlyShareDownload {
938                system_user: _,
939                ssh_authorized_key: _,
940            }
941            | UserMapping::SystemNetHsmBackup {
942                nethsm_user: _,
943                system_user: _,
944                ssh_authorized_key: _,
945            }
946            | UserMapping::SystemOnlyShareUpload {
947                system_user: _,
948                ssh_authorized_key: _,
949            }
950            | UserMapping::SystemOnlyWireGuardDownload {
951                system_user: _,
952                ssh_authorized_key: _,
953            } => vec![],
954            #[cfg(feature = "yubihsm2")]
955            UserMapping::SystemYubiHsmOperatorSigning { .. } => Vec::new(),
956        }
957    }
958
959    /// Returns whether the mapping has both system and HSM backend users.
960    ///
961    /// Returns `true` if the `self` has at least one system and one HSM backend user, and `false`
962    /// otherwise.
963    pub fn has_system_and_backend_user(&self) -> bool {
964        match self {
965            UserMapping::SystemNetHsmOperatorSigning {
966                nethsm_user: _,
967                key_id: _,
968                nethsm_key_setup: _,
969                system_user: _,
970                ssh_authorized_key: _,
971                tag: _,
972            }
973            | UserMapping::HermeticSystemNetHsmMetrics {
974                nethsm_users: _,
975                system_user: _,
976            }
977            | UserMapping::SystemNetHsmMetrics {
978                nethsm_users: _,
979                system_user: _,
980                ssh_authorized_key: _,
981            }
982            | UserMapping::SystemNetHsmBackup {
983                nethsm_user: _,
984                system_user: _,
985                ssh_authorized_key: _,
986            } => true,
987            #[cfg(feature = "yubihsm2")]
988            UserMapping::SystemYubiHsmOperatorSigning { .. } => true,
989            UserMapping::SystemOnlyShareDownload {
990                system_user: _,
991                ssh_authorized_key: _,
992            }
993            | UserMapping::SystemOnlyShareUpload {
994                system_user: _,
995                ssh_authorized_key: _,
996            }
997            | UserMapping::SystemOnlyWireGuardDownload {
998                system_user: _,
999                ssh_authorized_key: _,
1000            }
1001            | UserMapping::NetHsmOnlyAdmin(_) => false,
1002        }
1003    }
1004}
1005
1006/// Checks the accessibility of a secrets file.
1007///
1008/// Checks whether file at `path`
1009///
1010/// - exists,
1011/// - is a file,
1012/// - has accessible metadata,
1013/// - and has the file mode [`SECRET_FILE_MODE`].
1014///
1015/// # Errors
1016///
1017/// Returns an error, if the file at `path`
1018///
1019/// - does not exist,
1020/// - is not a file,
1021/// - does not have accessible metadata,
1022/// - or has a file mode other than [`SECRET_FILE_MODE`].
1023pub(crate) fn check_secrets_file(path: impl AsRef<Path>) -> Result<(), Error> {
1024    let path = path.as_ref();
1025
1026    // check if a path exists
1027    if !path.exists() {
1028        return Err(crate::non_admin_credentials::Error::SecretsFileMissing {
1029            path: path.to_path_buf(),
1030        }
1031        .into());
1032    }
1033
1034    // check if this is a file
1035    if !path.is_file() {
1036        return Err(Error::NonAdminSecretHandling(
1037            crate::non_admin_credentials::Error::SecretsFileNotAFile {
1038                path: path.to_path_buf(),
1039            },
1040        ));
1041    }
1042
1043    // check for correct permissions
1044    match path.metadata() {
1045        Ok(metadata) => {
1046            let mode = metadata.permissions().mode();
1047            if mode != SECRET_FILE_MODE {
1048                return Err(Error::NonAdminSecretHandling(
1049                    crate::non_admin_credentials::Error::SecretsFilePermissions {
1050                        path: path.to_path_buf(),
1051                        mode,
1052                    },
1053                ));
1054            }
1055        }
1056        Err(source) => {
1057            return Err(Error::NonAdminSecretHandling(
1058                crate::non_admin_credentials::Error::SecretsFileMetadata {
1059                    path: path.to_path_buf(),
1060                    source,
1061                },
1062            ));
1063        }
1064    }
1065
1066    Ok(())
1067}
1068
1069/// A [`UserMapping`] centric view of a [`SignstarConfig`].
1070///
1071/// Wraps a single [`UserMapping`], as well as the system-wide [`AdministrativeSecretHandling`],
1072/// [`NonAdministrativeSecretHandling`] and [`BackendConnection`]s.
1073#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1074pub struct ExtendedUserMapping {
1075    admin_secret_handling: AdministrativeSecretHandling,
1076    non_admin_secret_handling: NonAdministrativeSecretHandling,
1077    connections: HashSet<BackendConnection>,
1078    user_mapping: UserMapping,
1079}
1080
1081impl ExtendedUserMapping {
1082    /// Creates a new [`ExtendedUserMapping`].
1083    pub fn new(
1084        admin_secret_handling: AdministrativeSecretHandling,
1085        non_admin_secret_handling: NonAdministrativeSecretHandling,
1086        connections: HashSet<BackendConnection>,
1087        user_mapping: UserMapping,
1088    ) -> Self {
1089        Self {
1090            admin_secret_handling,
1091            non_admin_secret_handling,
1092            connections,
1093            user_mapping,
1094        }
1095    }
1096
1097    /// Returns the [`AdministrativeSecretHandling`].
1098    pub fn get_admin_secret_handling(&self) -> AdministrativeSecretHandling {
1099        self.admin_secret_handling
1100    }
1101
1102    /// Returns the [`BackendConnection`]s.
1103    pub fn get_connections(&self) -> HashSet<BackendConnection> {
1104        self.connections.clone()
1105    }
1106
1107    /// Returns the [`NonAdministrativeSecretHandling`].
1108    pub fn get_non_admin_secret_handling(&self) -> NonAdministrativeSecretHandling {
1109        self.non_admin_secret_handling
1110    }
1111
1112    /// Returns the [`UserMapping`].
1113    pub fn get_user_mapping(&self) -> &UserMapping {
1114        &self.user_mapping
1115    }
1116
1117    /// Loads credentials for each [`UserId`] associated with a [`SystemUserId`].
1118    ///
1119    /// The [`SystemUserId`] of the mapping must be equal to the current system user calling this
1120    /// function.
1121    /// Relies on [`get_plaintext_secret_file`] and [`get_systemd_creds_secret_file`] to retrieve
1122    /// the specific path to a secret file for each [`UserId`] mapped to a [`SystemUserId`].
1123    ///
1124    /// Returns a [`CredentialsLoading`], which may contain critical errors related to loading a
1125    /// passphrase for each available [`UserId`].
1126    /// The caller is expected to handle any errors tracked in the returned object based on context.
1127    ///
1128    /// # Errors
1129    ///
1130    /// Returns an error if
1131    /// - the [`ExtendedUserMapping`] provides no [`SystemUserId`],
1132    /// - no system user equal to the [`SystemUserId`] exists,
1133    /// - the [`SystemUserId`] is not equal to the currently calling system user,
1134    /// - or the [systemd-creds] command is not available when trying to decrypt secrets.
1135    ///
1136    /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
1137    pub fn load_credentials(&self) -> Result<CredentialsLoading, Error> {
1138        // Retrieve required SystemUserId and User and compare with current User.
1139        let (system_user, user) = get_system_user_pair(self)?;
1140        let current_system_user = get_current_system_user()?;
1141
1142        // fail if running as root
1143        fail_if_root(&current_system_user)?;
1144        match_current_system_user(&current_system_user, &user)?;
1145
1146        let secret_handling = self.get_non_admin_secret_handling();
1147        let mut credentials = Vec::new();
1148        let mut errors = Vec::new();
1149
1150        for user_id in self.get_user_mapping().get_nethsm_users() {
1151            let secrets_file = match secret_handling {
1152                NonAdministrativeSecretHandling::Plaintext => {
1153                    get_plaintext_secret_file(system_user.as_ref(), &user_id.to_string())
1154                }
1155                NonAdministrativeSecretHandling::SystemdCreds => {
1156                    get_systemd_creds_secret_file(system_user.as_ref(), &user_id.to_string())
1157                }
1158            };
1159            // Ensure the secrets file has correct ownership and permissions.
1160            if let Err(error) = check_secrets_file(secrets_file.as_path()) {
1161                errors.push(CredentialsLoadingError::new(user_id, error));
1162                continue;
1163            };
1164
1165            match secret_handling {
1166                // Read from plaintext secrets file.
1167                NonAdministrativeSecretHandling::Plaintext => {
1168                    // get passphrase or error
1169                    match read_to_string(&secrets_file).map_err(|source| {
1170                        Error::NonAdminSecretHandling(
1171                            crate::non_admin_credentials::Error::SecretsFileRead {
1172                                path: secrets_file,
1173                                source,
1174                            },
1175                        )
1176                    }) {
1177                        Ok(passphrase) => credentials
1178                            .push(FullCredentials::new(user_id, Passphrase::new(passphrase))),
1179                        Err(error) => {
1180                            errors.push(CredentialsLoadingError::new(user_id, error));
1181                            continue;
1182                        }
1183                    }
1184                }
1185                // Read from systemd-creds encrypted secrets file.
1186                NonAdministrativeSecretHandling::SystemdCreds => {
1187                    // Decrypt secret using systemd-creds.
1188                    let creds_command = get_command("systemd-creds")?;
1189                    let mut command = Command::new(creds_command);
1190                    let command = command
1191                        .arg("--user")
1192                        .arg("decrypt")
1193                        .arg(&secrets_file)
1194                        .arg("-");
1195                    match command.output().map_err(|source| Error::CommandExec {
1196                        command: format!("{command:?}"),
1197                        source,
1198                    }) {
1199                        Ok(command_output) => {
1200                            // fail if decryption did not result in a successful status code
1201                            if !command_output.status.success() {
1202                                errors.push(CredentialsLoadingError::new(
1203                                    user_id,
1204                                    Error::CommandNonZero {
1205                                        command: format!("{command:?}"),
1206                                        exit_status: command_output.status,
1207                                        stderr: String::from_utf8_lossy(&command_output.stderr)
1208                                            .into_owned(),
1209                                    },
1210                                ));
1211                                continue;
1212                            }
1213
1214                            let creds = match String::from_utf8(command_output.stdout) {
1215                                Ok(creds) => creds,
1216                                Err(source) => {
1217                                    errors.push(CredentialsLoadingError::new(
1218                                        user_id.clone(),
1219                                        Error::Utf8String {
1220                                            path: secrets_file,
1221                                            context: format!(
1222                                                "converting stdout of {command:?} to string"
1223                                            ),
1224                                            source,
1225                                        },
1226                                    ));
1227                                    continue;
1228                                }
1229                            };
1230
1231                            credentials.push(FullCredentials::new(user_id, Passphrase::new(creds)));
1232                        }
1233                        Err(error) => {
1234                            errors.push(CredentialsLoadingError::new(user_id, error));
1235                            continue;
1236                        }
1237                    }
1238                }
1239            }
1240        }
1241
1242        Ok(CredentialsLoading::new(
1243            self.clone(),
1244            credentials,
1245            CredentialsLoadingErrors::new(errors),
1246        ))
1247    }
1248
1249    /// Creates secrets directories for all non-administrative mappings.
1250    ///
1251    /// Matches the [`SystemUserId`] in a mapping with an actual user on the system.
1252    /// Creates the passphrase directory for the user and ensures correct ownership of it and all
1253    /// parent directories up until the user's home directory.
1254    ///
1255    /// # Errors
1256    ///
1257    /// Returns an error if
1258    /// - no system user is available in the mapping,
1259    /// - the system user of the mapping is not available on the system,
1260    /// - the directory could not be created,
1261    /// - the ownership of any directory between the user's home and the passphrase directory can
1262    ///   not be changed.
1263    pub fn create_secrets_dir(&self) -> Result<(), Error> {
1264        // Retrieve required SystemUserId and User and compare with current User.
1265        let (system_user, user) = get_system_user_pair(self)?;
1266
1267        // fail if not running as root
1268        fail_if_not_root(&get_current_system_user()?)?;
1269
1270        // get and create the user's passphrase directory
1271        let secrets_dir = get_user_secrets_dir(system_user.as_ref());
1272        create_dir_all(&secrets_dir).map_err(|source| {
1273            crate::non_admin_credentials::Error::SecretsDirCreate {
1274                path: secrets_dir.clone(),
1275                system_user: system_user.clone(),
1276                source,
1277            }
1278        })?;
1279
1280        // Recursively chown all directories to the user and group, until `HOME_BASE_DIR` is
1281        // reached.
1282        let home_dir = get_home_base_dir_path().join(PathBuf::from(system_user.as_ref()));
1283        let mut chown_dir = secrets_dir.clone();
1284        while chown_dir != home_dir {
1285            chown(&chown_dir, Some(user.uid.as_raw()), Some(user.gid.as_raw())).map_err(
1286                |source| Error::Chown {
1287                    path: chown_dir.to_path_buf(),
1288                    user: system_user.to_string(),
1289                    source,
1290                },
1291            )?;
1292            if let Some(parent) = &chown_dir.parent() {
1293                chown_dir = parent.to_path_buf()
1294            } else {
1295                break;
1296            }
1297        }
1298
1299        Ok(())
1300    }
1301
1302    /// Creates passphrases for all non-administrative mappings.
1303    ///
1304    /// Creates a random alphanumeric, 30-char long passphrase for each backend user of each
1305    /// non-administrative user mapping.
1306    ///
1307    /// - If `self` is configured to use [`NonAdministrativeSecretHandling::Plaintext`], the
1308    ///   passphrase is stored in a secrets file, defined by [`get_plaintext_secret_file`].
1309    /// - If `self` is configured to use [`NonAdministrativeSecretHandling::SystemdCreds`], the
1310    ///   passphrase is encrypted using [systemd-creds] and stored in a secrets file, defined by
1311    ///   [`get_systemd_creds_secret_file`].
1312    ///
1313    /// # Errors
1314    ///
1315    /// Returns an error if
1316    /// - the targeted system user does not exist in the mapping or on the system,
1317    /// - the function is called using a non-root user,
1318    /// - the [systemd-creds] command is not available when trying to encrypt the passphrase,
1319    /// - the encryption of the passphrase using [systemd-creds] fails,
1320    /// - the secrets file can not be created,
1321    /// - the secrets file can not be written to,
1322    /// - or the ownership and permissions of the secrets file can not be changed.
1323    ///
1324    /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
1325    pub fn create_non_administrative_secrets(&self) -> Result<(), Error> {
1326        // Retrieve required SystemUserId and User.
1327        let (system_user, user) = get_system_user_pair(self)?;
1328
1329        // fail if not running as root
1330        fail_if_not_root(&get_current_system_user()?)?;
1331
1332        let secret_handling = self.get_non_admin_secret_handling();
1333
1334        // add a secret for each NetHSM user
1335        for user_id in self.get_user_mapping().get_nethsm_users() {
1336            let secrets_file = match secret_handling {
1337                NonAdministrativeSecretHandling::Plaintext => {
1338                    get_plaintext_secret_file(system_user.as_ref(), &user_id.to_string())
1339                }
1340                NonAdministrativeSecretHandling::SystemdCreds => {
1341                    get_systemd_creds_secret_file(system_user.as_ref(), &user_id.to_string())
1342                }
1343            };
1344            println!(
1345                "Create secret for system user {system_user} and backend user {user_id} in file: {secrets_file:?}"
1346            );
1347            let secret = {
1348                // create initial (unencrypted) secret
1349                let initial_secret: String = thread_rng()
1350                    .sample_iter(&Alphanumeric)
1351                    .take(30)
1352                    .map(char::from)
1353                    .collect();
1354                // Create credentials files depending on secret handling
1355                match secret_handling {
1356                    NonAdministrativeSecretHandling::Plaintext => {
1357                        initial_secret.as_bytes().to_vec()
1358                    }
1359                    NonAdministrativeSecretHandling::SystemdCreds => {
1360                        // Create systemd-creds encrypted secret.
1361                        let creds_command = get_command("systemd-creds")?;
1362                        let mut command = Command::new(creds_command);
1363                        let command = command
1364                            .arg("--user")
1365                            .arg("--name=")
1366                            .arg("--uid")
1367                            .arg(system_user.as_ref())
1368                            .arg("encrypt")
1369                            .arg("-")
1370                            .arg("-")
1371                            .stdin(Stdio::piped())
1372                            .stdout(Stdio::piped())
1373                            .stderr(Stdio::piped());
1374                        let mut command_child =
1375                            command.spawn().map_err(|source| Error::CommandBackground {
1376                                command: format!("{command:?}"),
1377                                source,
1378                            })?;
1379
1380                        // write to stdin
1381                        command_child
1382                            .stdin
1383                            .take()
1384                            .ok_or(Error::CommandAttachToStdin {
1385                                command: format!("{command:?}"),
1386                            })?
1387                            .write_all(initial_secret.as_bytes())
1388                            .map_err(|source| Error::CommandWriteToStdin {
1389                                command: format!("{command:?}"),
1390                                source,
1391                            })?;
1392
1393                        let command_output =
1394                            command_child.wait_with_output().map_err(|source| {
1395                                Error::CommandExec {
1396                                    command: format!("{command:?}"),
1397                                    source,
1398                                }
1399                            })?;
1400
1401                        if !command_output.status.success() {
1402                            return Err(Error::CommandNonZero {
1403                                command: format!("{command:?}"),
1404                                exit_status: command_output.status,
1405                                stderr: String::from_utf8_lossy(&command_output.stderr)
1406                                    .into_owned(),
1407                            });
1408                        }
1409                        command_output.stdout
1410                    }
1411                }
1412            };
1413
1414            // Write secret to file and adjust permission and ownership of file.
1415            let mut file = File::create(secrets_file.as_path()).map_err(|source| {
1416                {
1417                    crate::non_admin_credentials::Error::SecretsFileCreate {
1418                        path: secrets_file.clone(),
1419                        system_user: system_user.clone(),
1420                        source,
1421                    }
1422                }
1423            })?;
1424            file.write_all(&secret).map_err(|source| {
1425                crate::non_admin_credentials::Error::SecretsFileWrite {
1426                    path: secrets_file.clone(),
1427                    system_user: system_user.clone(),
1428                    source,
1429                }
1430            })?;
1431            chown(
1432                &secrets_file,
1433                Some(user.uid.as_raw()),
1434                Some(user.gid.as_raw()),
1435            )
1436            .map_err(|source| Error::Chown {
1437                path: secrets_file.clone(),
1438                user: system_user.to_string(),
1439                source,
1440            })?;
1441            set_permissions(
1442                secrets_file.as_path(),
1443                Permissions::from_mode(SECRET_FILE_MODE),
1444            )
1445            .map_err(|source| Error::ApplyPermissions {
1446                path: secrets_file.clone(),
1447                mode: SECRET_FILE_MODE,
1448                source,
1449            })?;
1450        }
1451        Ok(())
1452    }
1453}
1454
1455impl From<SignstarConfig> for Vec<ExtendedUserMapping> {
1456    /// Creates a `Vec` of [`ExtendedUserMapping`] from a [`SignstarConfig`].
1457    ///
1458    /// A [`UserMapping`] can not be aware of credentials if it does not track at least one
1459    /// [`SystemUserId`] and one [`UserId`]. Therefore only those [`UserMapping`]s for which
1460    /// [`UserMapping::has_system_and_backend_user`] returns `true` are
1461    /// returned.
1462    fn from(value: SignstarConfig) -> Self {
1463        value
1464            .iter_user_mappings()
1465            .filter_map(|mapping| {
1466                if mapping.has_system_and_backend_user() {
1467                    Some(ExtendedUserMapping {
1468                        admin_secret_handling: value.get_administrative_secret_handling(),
1469                        non_admin_secret_handling: value.get_non_administrative_secret_handling(),
1470                        connections: value.iter_connections().cloned().collect(),
1471                        user_mapping: mapping.clone(),
1472                    })
1473                } else {
1474                    None
1475                }
1476            })
1477            .collect()
1478    }
1479}
1480
1481#[cfg(test)]
1482mod tests {
1483    use log::{LevelFilter, debug};
1484    use nethsm::{CryptographicKeyContext, OpenPgpUserIdList};
1485    use rstest::rstest;
1486    use signstar_common::logging::setup_logging;
1487    use tempfile::{NamedTempFile, TempDir};
1488    use testresult::TestResult;
1489
1490    use super::*;
1491
1492    #[rstest]
1493    #[case(UserMapping::NetHsmOnlyAdmin("test".parse()?), FilterUserKeys::All, Vec::new())]
1494    #[case(
1495        UserMapping::SystemNetHsmMetrics {
1496            nethsm_users: NetHsmMetricsUsers::new(
1497                SystemWideUserId::new("metrics".to_string())?,
1498                vec![
1499                    UserId::new("operator".to_string())?,
1500                ],
1501            )?,
1502            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1503            system_user: "system-metrics".parse()?,
1504        },
1505        FilterUserKeys::All,
1506        Vec::new(),
1507    )]
1508    #[case(
1509        UserMapping::SystemNetHsmBackup {
1510            nethsm_user: "backup".parse()?,
1511            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1512            system_user: "system-backup".parse()?,
1513        },
1514        FilterUserKeys::All,
1515        Vec::new(),
1516    )]
1517    #[case(
1518        UserMapping::SystemNetHsmOperatorSigning {
1519            nethsm_user: "operator".parse()?,
1520            key_id: "key1".parse()?,
1521            nethsm_key_setup: SigningKeySetup::new(
1522                "Curve25519".parse()?,
1523                vec!["EdDsaSignature".parse()?],
1524                None,
1525                "EdDsa".parse()?,
1526                CryptographicKeyContext::OpenPgp{
1527                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1528                    version: "v4".parse()?,
1529                },
1530            )?,
1531            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1532            system_user: "system-operator".parse()?,
1533            tag: "tag1".to_string(),
1534        },
1535        FilterUserKeys::All,
1536        vec![(
1537            "operator".parse()?,
1538            "key1".parse()?,
1539            SigningKeySetup::new(
1540                "Curve25519".parse()?,
1541                vec!["EdDsaSignature".parse()?],
1542                None,
1543                "EdDsa".parse()?,
1544                CryptographicKeyContext::OpenPgp{
1545                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1546                    version: "v4".parse()?,
1547                },
1548            )?,
1549            "tag1".to_string(),
1550        )],
1551    )]
1552    #[case::systemwide_operator_filter_namespaced(
1553        UserMapping::SystemNetHsmOperatorSigning {
1554            nethsm_user: "operator".parse()?,
1555            key_id: "key1".parse()?,
1556            nethsm_key_setup: SigningKeySetup::new(
1557                "Curve25519".parse()?,
1558                vec!["EdDsaSignature".parse()?],
1559                None,
1560                "EdDsa".parse()?,
1561                CryptographicKeyContext::OpenPgp{
1562                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1563                    version: "v4".parse()?,
1564                },
1565            )?,
1566            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1567            system_user: "system-operator".parse()?,
1568            tag: "tag1".to_string(),
1569        },
1570        FilterUserKeys::Namespaced,
1571        Vec::new(),
1572    )]
1573    #[case::systemwide_operator_filter_namespace(
1574        UserMapping::SystemNetHsmOperatorSigning {
1575            nethsm_user: "operator".parse()?,
1576            key_id: "key1".parse()?,
1577            nethsm_key_setup: SigningKeySetup::new(
1578                "Curve25519".parse()?,
1579                vec!["EdDsaSignature".parse()?],
1580                None,
1581                "EdDsa".parse()?,
1582                CryptographicKeyContext::OpenPgp{
1583                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1584                    version: "v4".parse()?,
1585                },
1586            )?,
1587            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1588            system_user: "system-operator".parse()?,
1589            tag: "tag1".to_string(),
1590        },
1591        FilterUserKeys::Namespace("ns1".parse()?),
1592        Vec::new(),
1593    )]
1594    #[case::namespace_operator_filter_namespaced(
1595        UserMapping::SystemNetHsmOperatorSigning {
1596            nethsm_user: "ns1~operator".parse()?,
1597            key_id: "key1".parse()?,
1598            nethsm_key_setup: SigningKeySetup::new(
1599                "Curve25519".parse()?,
1600                vec!["EdDsaSignature".parse()?],
1601                None,
1602                "EdDsa".parse()?,
1603                CryptographicKeyContext::OpenPgp{
1604                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1605                    version: "v4".parse()?,
1606                },
1607            )?,
1608            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1609            system_user: "system-operator".parse()?,
1610            tag: "tag1".to_string(),
1611        },
1612        FilterUserKeys::Namespaced,
1613        vec![(
1614            "ns1~operator".parse()?,
1615            "key1".parse()?,
1616            SigningKeySetup::new(
1617                "Curve25519".parse()?,
1618                vec!["EdDsaSignature".parse()?],
1619                None,
1620                "EdDsa".parse()?,
1621                CryptographicKeyContext::OpenPgp{
1622                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1623                    version: "v4".parse()?,
1624                },
1625            )?,
1626            "tag1".to_string(),
1627        )],
1628    )]
1629    #[case::namespace_operator_filter_namespace(
1630        UserMapping::SystemNetHsmOperatorSigning {
1631            nethsm_user: "ns1~operator".parse()?,
1632            key_id: "key1".parse()?,
1633            nethsm_key_setup: SigningKeySetup::new(
1634                "Curve25519".parse()?,
1635                vec!["EdDsaSignature".parse()?],
1636                None,
1637                "EdDsa".parse()?,
1638                CryptographicKeyContext::OpenPgp{
1639                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1640                    version: "v4".parse()?,
1641                },
1642            )?,
1643            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1644            system_user: "system-operator".parse()?,
1645            tag: "tag1".to_string(),
1646        },
1647        FilterUserKeys::Namespace("ns1".parse()?),
1648        vec![(
1649            "ns1~operator".parse()?,
1650            "key1".parse()?,
1651            SigningKeySetup::new(
1652                "Curve25519".parse()?,
1653                vec!["EdDsaSignature".parse()?],
1654                None,
1655                "EdDsa".parse()?,
1656                CryptographicKeyContext::OpenPgp{
1657                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1658                    version: "v4".parse()?,
1659                },
1660            )?,
1661            "tag1".to_string(),
1662        )],
1663    )]
1664    #[case::namespace_operator_filter_tag(
1665        UserMapping::SystemNetHsmOperatorSigning {
1666            nethsm_user: "ns1~operator".parse()?,
1667            key_id: "key1".parse()?,
1668            nethsm_key_setup: SigningKeySetup::new(
1669                "Curve25519".parse()?,
1670                vec!["EdDsaSignature".parse()?],
1671                None,
1672                "EdDsa".parse()?,
1673                CryptographicKeyContext::OpenPgp{
1674                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1675                    version: "v4".parse()?,
1676                },
1677            )?,
1678            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1679            system_user: "system-operator".parse()?,
1680            tag: "tag1".to_string(),
1681        },
1682        FilterUserKeys::Tag("tag1".parse()?),
1683        vec![(
1684            "ns1~operator".parse()?,
1685            "key1".parse()?,
1686            SigningKeySetup::new(
1687                "Curve25519".parse()?,
1688                vec!["EdDsaSignature".parse()?],
1689                None,
1690                "EdDsa".parse()?,
1691                CryptographicKeyContext::OpenPgp{
1692                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1693                    version: "v4".parse()?,
1694                },
1695            )?,
1696            "tag1".to_string(),
1697        )],
1698    )]
1699    #[case::namespace_operator_filter_wrong_tag(
1700        UserMapping::SystemNetHsmOperatorSigning {
1701            nethsm_user: "ns1~operator".parse()?,
1702            key_id: "key1".parse()?,
1703            nethsm_key_setup: SigningKeySetup::new(
1704                "Curve25519".parse()?,
1705                vec!["EdDsaSignature".parse()?],
1706                None,
1707                "EdDsa".parse()?,
1708                CryptographicKeyContext::OpenPgp{
1709                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1710                    version: "v4".parse()?,
1711                },
1712            )?,
1713            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1714            system_user: "system-operator".parse()?,
1715            tag: "tag2".to_string(),
1716        },
1717        FilterUserKeys::Tag("tag1".parse()?),
1718        Vec::new(),
1719    )]
1720    #[case::hermetic_system_metrics(
1721        UserMapping::HermeticSystemNetHsmMetrics {
1722            nethsm_users: NetHsmMetricsUsers::new(
1723                "metrics".parse()?,
1724                vec!["operator".parse()?],
1725            )?,
1726            system_user: "system-metrics".parse()?,
1727        },
1728        FilterUserKeys::All,
1729        Vec::new(),
1730    )]
1731    #[case::share_download(
1732        UserMapping::SystemOnlyShareDownload {
1733            system_user: "system-share".parse()?,
1734            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1735        },
1736        FilterUserKeys::All,
1737        Vec::new(),
1738    )]
1739    #[case::share_upload(
1740        UserMapping::SystemOnlyShareUpload {
1741            system_user: "system-share".parse()?,
1742            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1743        },
1744        FilterUserKeys::All,
1745        Vec::new(),
1746    )]
1747    #[case::wireguard_download(
1748        UserMapping::SystemOnlyWireGuardDownload {
1749            system_user: "system-wireguard".parse()?,
1750            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1751        },
1752        FilterUserKeys::All,
1753        Vec::new(),
1754    )]
1755    fn usermapping_get_nethsm_user_key_and_tag(
1756        #[case] mapping: UserMapping,
1757        #[case] filter: FilterUserKeys,
1758        #[case] output: Vec<(UserId, KeyId, SigningKeySetup, String)>,
1759    ) -> TestResult {
1760        assert_eq!(mapping.get_nethsm_user_key_and_tag(filter), output);
1761        Ok(())
1762    }
1763
1764    #[rstest]
1765    #[case::systemwide_admin(
1766        UserMapping::NetHsmOnlyAdmin("admin".parse()?),
1767        vec![("admin".parse()?, UserRole::Administrator)],
1768    )]
1769    #[case::namespace_admin(
1770        UserMapping::NetHsmOnlyAdmin("ns1~admin".parse()?),
1771        vec![("ns1~admin".parse()?, UserRole::Administrator)],
1772    )]
1773    #[case::metrics(
1774        UserMapping::SystemNetHsmMetrics {
1775            nethsm_users: NetHsmMetricsUsers::new(
1776                SystemWideUserId::new("metrics".to_string())?,
1777                vec![
1778                    UserId::new("operator".to_string())?,
1779                ],
1780            )?,
1781            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1782            system_user: "system-metrics".parse()?,
1783        },
1784        vec![("metrics".parse()?, UserRole::Metrics), ("operator".parse()?, UserRole::Operator)],
1785    )]
1786    #[case::backup(
1787        UserMapping::SystemNetHsmBackup {
1788            nethsm_user: "backup".parse()?,
1789            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1790            system_user: "system-backup".parse()?,
1791        },
1792        vec![("backup".parse()?, UserRole::Backup)],
1793    )]
1794    #[case::systemwide_operator(
1795        UserMapping::SystemNetHsmOperatorSigning {
1796            nethsm_user: "operator".parse()?,
1797            key_id: "key1".parse()?,
1798            nethsm_key_setup: SigningKeySetup::new(
1799                "Curve25519".parse()?,
1800                vec!["EdDsaSignature".parse()?],
1801                None,
1802                "EdDsa".parse()?,
1803                CryptographicKeyContext::OpenPgp{
1804                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1805                    version: "v4".parse()?,
1806                },
1807            )?,
1808            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1809            system_user: "system-operator".parse()?,
1810            tag: "tag1".to_string(),
1811        },
1812        vec![(
1813            "operator".parse()?,
1814            UserRole::Operator,
1815        )],
1816    )]
1817    #[case::namespace_operator(
1818        UserMapping::SystemNetHsmOperatorSigning {
1819            nethsm_user: "ns1~operator".parse()?,
1820            key_id: "key1".parse()?,
1821            nethsm_key_setup: SigningKeySetup::new(
1822                "Curve25519".parse()?,
1823                vec!["EdDsaSignature".parse()?],
1824                None,
1825                "EdDsa".parse()?,
1826                CryptographicKeyContext::OpenPgp{
1827                    user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1828                    version: "v4".parse()?,
1829                },
1830            )?,
1831            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1832            system_user: "system-operator".parse()?,
1833            tag: "tag1".to_string(),
1834        },
1835        vec![(
1836            "ns1~operator".parse()?,
1837            UserRole::Operator,
1838        )],
1839    )]
1840    #[case::hermetic_system_metrics(
1841        UserMapping::HermeticSystemNetHsmMetrics {
1842            nethsm_users: NetHsmMetricsUsers::new(
1843                "metrics".parse()?,
1844                vec!["operator".parse()?],
1845            )?,
1846            system_user: "system-metrics".parse()?,
1847        },
1848        vec![
1849            ("metrics".parse()?, UserRole::Metrics),
1850            ("operator".parse()?, UserRole::Operator),
1851        ],
1852    )]
1853    #[case::share_download(
1854        UserMapping::SystemOnlyShareDownload {
1855            system_user: "system-share".parse()?,
1856            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1857        },
1858        Vec::new(),
1859    )]
1860    #[case::share_upload(
1861        UserMapping::SystemOnlyShareUpload {
1862            system_user: "system-share".parse()?,
1863            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1864        },
1865        Vec::new(),
1866    )]
1867    #[case::wireguard_download(
1868        UserMapping::SystemOnlyWireGuardDownload {
1869            system_user: "system-wireguard".parse()?,
1870            ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1871        },
1872        Vec::new(),
1873    )]
1874    fn usermapping_get_nethsm_users_and_roles(
1875        #[case] mapping: UserMapping,
1876        #[case] output: Vec<(UserId, UserRole)>,
1877    ) -> TestResult {
1878        assert_eq!(mapping.get_nethsm_users_and_roles(), output);
1879        Ok(())
1880    }
1881
1882    /// Ensures that a file with the correct permissions is successfully checked using
1883    /// [`check_secrets_file`].
1884    #[test]
1885    fn check_secrets_file_succeeds() -> TestResult {
1886        setup_logging(LevelFilter::Debug)?;
1887
1888        let temp_file = NamedTempFile::new()?;
1889        let path = temp_file.path();
1890        set_permissions(path, Permissions::from_mode(SECRET_FILE_MODE))?;
1891        debug!(
1892            "Created {path:?} with mode {:o}",
1893            path.metadata()?.permissions().mode()
1894        );
1895
1896        check_secrets_file(path)?;
1897
1898        Ok(())
1899    }
1900
1901    /// Ensures that passing a non-existent file to [`check_secrets_file`] fails.
1902    #[test]
1903    fn check_secrets_file_fails_on_missing_file() -> TestResult {
1904        setup_logging(LevelFilter::Debug)?;
1905
1906        let temp_file = NamedTempFile::new()?;
1907        let path = temp_file.path().to_path_buf();
1908        temp_file.close()?;
1909
1910        if check_secrets_file(&path).is_ok() {
1911            panic!("The path {path:?} is missing and should not have passed as a secrets file.");
1912        }
1913
1914        Ok(())
1915    }
1916
1917    /// Ensures that passing a directory to [`check_secrets_file`] fails.
1918    #[test]
1919    fn check_secrets_file_fails_on_dir() -> TestResult {
1920        setup_logging(LevelFilter::Debug)?;
1921
1922        let temp_file = TempDir::new()?;
1923        let path = temp_file.path();
1924        debug!(
1925            "Created {path:?} with mode {:o}",
1926            path.metadata()?.permissions().mode()
1927        );
1928
1929        if check_secrets_file(path).is_ok() {
1930            panic!("The dir {path:?} should not have passed as a secrets file.");
1931        }
1932
1933        Ok(())
1934    }
1935
1936    /// Ensures that a file without the correct permissions fails [`check_secrets_file`].
1937    #[test]
1938    fn check_secrets_file_fails_on_invalid_permissions() -> TestResult {
1939        setup_logging(LevelFilter::Debug)?;
1940
1941        let temp_file = NamedTempFile::new()?;
1942        let path = temp_file.path();
1943        set_permissions(path, Permissions::from_mode(0o100644))?;
1944        debug!(
1945            "Created {path:?} with mode {:o}",
1946            path.metadata()?.permissions().mode()
1947        );
1948
1949        if check_secrets_file(path).is_ok() {
1950            panic!("The file at {path:?} should not have passed as a secrets file.");
1951        }
1952
1953        Ok(())
1954    }
1955}