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
12use log::info;
13use nethsm::{FullCredentials, KeyId, NamespaceId, Passphrase, SystemWideUserId, UserId, UserRole};
14use serde::{Deserialize, Serialize};
15use signstar_common::{
16    common::SECRET_FILE_MODE,
17    system_user::{
18        get_home_base_dir_path,
19        get_plaintext_secret_file,
20        get_systemd_creds_secret_file,
21        get_user_secrets_dir,
22    },
23};
24use signstar_crypto::{key::SigningKeySetup, traits::UserWithPassphrase};
25
26use crate::{
27    AdministrativeSecretHandling,
28    AuthorizedKeyEntry,
29    CredentialsLoading,
30    CredentialsLoadingError,
31    CredentialsLoadingErrors,
32    Error,
33    NonAdministrativeSecretHandling,
34    SignstarConfig,
35    SystemUserId,
36    config::base::BackendConnection,
37    nethsm::config::{FilterUserKeys, NetHsmMetricsUsers},
38    utils::{
39        fail_if_not_root,
40        fail_if_root,
41        get_command,
42        get_current_system_user,
43        get_system_user_pair,
44        match_current_system_user,
45    },
46};
47
48/// The kind of backend user.
49///
50/// This distinguishes between the different access rights levels (i.e. administrative and
51/// non-administrative) of a backend user.
52#[derive(Clone, Copy, Debug, Default)]
53pub enum BackendUserKind {
54    /// Administrative user.
55    Admin,
56    /// Non-administrative user.
57    #[default]
58    NonAdmin,
59}
60
61/// A filter for [`UserMapping`] variants.
62#[derive(Clone, Debug, Default)]
63pub struct UserMappingFilter {
64    /// The kind of backend user.
65    pub backend_user_kind: BackendUserKind,
66}
67
68/// User and data mapping between system users and HSM users.
69#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
70pub enum UserMapping {
71    /// A NetHsm user in the Administrator role, without a system user mapped to it
72    #[serde(rename = "nethsm_only_admin")]
73    NetHsmOnlyAdmin(UserId),
74
75    /// A system user, with SSH access, mapped to a system-wide NetHSM user in the Backup role.
76    #[serde(rename = "system_nethsm_backup")]
77    SystemNetHsmBackup {
78        /// The name of the NetHSM user.
79        nethsm_user: SystemWideUserId,
80        /// The SSH public key used for connecting to the `system_user`.
81        ssh_authorized_key: AuthorizedKeyEntry,
82        /// The name of the system user.
83        system_user: SystemUserId,
84    },
85
86    /// A mapping used for the creation of YubiHSM2 backups.
87    ///
88    /// Maps a system user, with SSH access, to a YubiHSM2 authentication and wrapping key.
89    /// This mapping is used for the purpose of creating backups of all keys (including
90    /// authentication keys) and non-key material (e.g. OpenPGP certificates) of a YubiHSM2.
91    ///
92    /// # Note
93    ///
94    /// This variant implies, that the created user should have all [capabilities] for backup
95    /// related actions (i.e. "export-wrapped", "wrap-data").
96    ///
97    /// [capabilities]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#capability-protocol-details
98    #[cfg(feature = "yubihsm2")]
99    #[serde(rename = "system_yubihsm2_backup")]
100    SystemYubiHsm2Backup {
101        /// The identifier of the authentication key used to create a session with the YubiHSM2.
102        authentication_key_id: u16,
103        /// The identifier of the wrapping key in the YubiHSM2 backend.
104        ///
105        /// This identifies the encryption key used for wrapping backups of all keys of the
106        /// YubiHSM2.
107        wrapping_key_id: u16,
108        /// The SSH public key used for connecting to the `system_user`.
109        ssh_authorized_key: AuthorizedKeyEntry,
110        /// The name of the system user.
111        system_user: SystemUserId,
112    },
113
114    /// A system user, with SSH access, mapped to a system-wide NetHSM user
115    /// in the Metrics role and `n` users in the Operator role with read-only access to zero or
116    /// more keys
117    #[serde(rename = "system_nethsm_metrics")]
118    SystemNetHsmMetrics {
119        /// The NetHSM users in the [`Metrics`][`UserRole::Metrics`] and
120        /// [`operator`][`UserRole::Operator`] role.
121        nethsm_users: NetHsmMetricsUsers,
122        /// The SSH public key used for connecting to the `system_user`.
123        ssh_authorized_key: AuthorizedKeyEntry,
124        /// The name of the system user.
125        system_user: SystemUserId,
126    },
127
128    /// A system user, with SSH access, mapped to a YubiHSM2 authentication key.
129    ///
130    /// # Note
131    ///
132    /// This variant implies, that the created user should have all [capabilities] for backup
133    /// related actions (i.e. "get-log-entries").
134    ///
135    /// [capabilities]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#capability-protocol-details
136    #[cfg(feature = "yubihsm2")]
137    #[serde(rename = "system_yubihsm2_metrics")]
138    SystemYubiHsm2Metrics {
139        /// The identifier of the authentication key used to create a session with the YubiHSM2.
140        authentication_key_id: u16,
141        /// The SSH public key used for connecting to the `system_user`.
142        ssh_authorized_key: AuthorizedKeyEntry,
143        /// The name of the system user.
144        system_user: SystemUserId,
145    },
146
147    /// A system user, with SSH access, mapped to a NetHSM user in the
148    /// Operator role with access to a single signing key.
149    ///
150    /// Signing key and NetHSM user are mapped using a tag.
151    #[serde(rename = "system_nethsm_operator_signing")]
152    SystemNetHsmOperatorSigning {
153        /// The name of the NetHSM user.
154        nethsm_user: UserId,
155        /// The ID of the NetHSM key.
156        key_id: KeyId,
157        /// The setup of a NetHSM key.
158        nethsm_key_setup: SigningKeySetup,
159        /// The SSH public key used for connecting to the `system_user`.
160        ssh_authorized_key: AuthorizedKeyEntry,
161        /// The name of the system user.
162        system_user: SystemUserId,
163        /// The tag used for the user and the signing key on the NetHSM.
164        tag: String,
165    },
166
167    /// A system user, with SSH access, mapped to a YubiHSM2 user in the
168    /// Operator role with access to a single signing key.
169    ///
170    /// Signing key and YubiHSM user are mapped using a permission.
171    ///
172    /// # Note
173    ///
174    /// This variant implies, that the created user should have all [capabilities] for signatures
175    /// (i.e. "sign-ecdsa", "sign-eddsa", "sign-pkcs" and "sign-pss").
176    ///
177    /// [capabilities]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#capability-protocol-details
178    #[cfg(feature = "yubihsm2")]
179    #[serde(rename = "system_yubihsm2_operator_signing")]
180    SystemYubiHsm2OperatorSigning {
181        /// The identifier of the authentication key used to create a session with the YubiHSM2.
182        authentication_key_id: u16,
183        /// The setup of a YubiHSM2 key.
184        backend_key_setup: SigningKeySetup,
185        /// The identifier of the key in the YubiHSM2 backend.
186        backend_key_id: u16,
187        /// The domain the backend key belongs to.
188        backend_key_domain: usize,
189        /// The SSH public key used for connecting to the `system_user`.
190        ssh_authorized_key: AuthorizedKeyEntry,
191        /// The name of the system user.
192        system_user: SystemUserId,
193    },
194
195    /// A system user, without SSH access, mapped to a system-wide NetHSM
196    /// user in the Metrics role and one or more NetHsm users in the Operator role with
197    /// read-only access to zero or more keys
198    #[serde(rename = "hermetic_system_nethsm_metrics")]
199    HermeticSystemNetHsmMetrics {
200        /// The NetHSM users in the [`Metrics`][`UserRole::Metrics`] and
201        /// [`operator`][`UserRole::Operator`] role.
202        nethsm_users: NetHsmMetricsUsers,
203        /// The name of the system user.
204        system_user: SystemUserId,
205    },
206
207    /// A system user, _without_ SSH access, mapped to a YubiHSM2 authentication key.
208    ///
209    /// # Note
210    ///
211    /// This variant implies, that the created user should have all [capabilities] for reading logs
212    /// (i.e. "get-log-entries").
213    ///
214    /// [capabilities]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#capability-protocol-details
215    #[cfg(feature = "yubihsm2")]
216    #[serde(rename = "hermetic_system_yubihsm2_metrics")]
217    HermeticSystemYubiHsm2Metrics {
218        /// The identifier of the authentication key used to create a session with the YubiHSM2.
219        authentication_key_id: u16,
220        /// The name of the system user.
221        system_user: SystemUserId,
222    },
223
224    /// A system user, with SSH access, not mapped to any backend user, that is used for downloading
225    /// shares of a shared secret.
226    #[serde(rename = "system_only_share_download")]
227    SystemOnlyShareDownload {
228        /// The name of the system user.
229        system_user: SystemUserId,
230        /// The list of SSH public keys used for connecting to the `system_user`.
231        ssh_authorized_key: AuthorizedKeyEntry,
232    },
233
234    /// A system user, with SSH access, not mapped to any backend user, that is used for uploading
235    /// shares of a shared secret.
236    #[serde(rename = "system_only_share_upload")]
237    SystemOnlyShareUpload {
238        /// The name of the system user.
239        system_user: SystemUserId,
240        /// The list of SSH public keys used for connecting to the `system_user`.
241        ssh_authorized_key: AuthorizedKeyEntry,
242    },
243
244    /// A system user, with SSH access, not mapped to any backend user, that is used for downloading
245    /// the WireGuard configuration of the host.
246    #[serde(rename = "system_only_wireguard_download")]
247    SystemOnlyWireGuardDownload {
248        /// The name of the system user.
249        system_user: SystemUserId,
250        /// The list of SSH public keys used for connecting to the `system_user`.
251        ssh_authorized_key: AuthorizedKeyEntry,
252    },
253
254    /// A YubiHSM 2 user in the administrator role, without a system user mapped to it.
255    ///
256    /// Wraps a [`u16`] which represents the [authentication key ID] on the HSM backend.
257    ///
258    /// # Note
259    ///
260    /// This variant implies, that the created user should have all [capabilities] necessary for the
261    /// creation of users and keys and restore from backup, i.e.:
262    ///
263    /// - "delete-asymmetric-key"
264    /// - "generate-asymmetric-key"
265    /// - "put-asymmetric-key"
266    /// - "delete-authen-tication-key"
267    /// - "put-authentication-key"
268    /// - "change-authentication-key"
269    /// - "get-option"
270    /// - "set-option"
271    /// - "delete-hmac-key"
272    /// - "generate-hmac-key"
273    /// - "put-mac-key"
274    /// - "sign-hmac"
275    /// - "verify-hmac"
276    /// - "delete-opaque"
277    /// - "generate-opaque"
278    /// - "get-opaque"
279    /// - "put-opaque"
280    /// - "reset-device"
281    /// - "delete-template"
282    /// - "get-template"
283    /// - "put-template"
284    /// - "delete-wrap-key"
285    /// - "exportable-under-wrap"
286    /// - "generate-wrap-key"
287    /// - "import-wrapped"
288    /// - "put-wrap-key"
289    /// - "unwrap-data"
290    /// - "wrap-data"
291    /// - "put-public-wrap-key"
292    /// - "delete-public-wrap-key"
293    /// - "generate-symmetric-key"
294    /// - "put-symmetric-key"
295    /// - "delete-symmetric-key"
296    ///
297    /// [authentication key ID]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#authentication-key-object
298    /// [capabilities]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#capability-protocol-details
299    #[cfg(feature = "yubihsm2")]
300    #[serde(rename = "yubihsm2_only_admin")]
301    YubiHsm2OnlyAdmin(u16),
302}
303
304impl UserMapping {
305    /// Returns the optional system user of the mapping
306    ///
307    /// # Examples
308    ///
309    /// ```
310    /// use signstar_config::{SystemUserId, UserMapping};
311    ///
312    /// # fn main() -> testresult::TestResult {
313    /// let mapping = UserMapping::SystemOnlyShareDownload {
314    ///     system_user: "user1".parse()?,
315    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
316    /// };
317    /// assert_eq!(mapping.get_system_user(), Some(&SystemUserId::new("user1".to_string())?));
318    ///
319    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
320    /// assert_eq!(mapping.get_system_user(), None);
321    /// # Ok(())
322    /// # }
323    /// ```
324    pub fn get_system_user(&self) -> Option<&SystemUserId> {
325        match self {
326            UserMapping::NetHsmOnlyAdmin(_) => None,
327            UserMapping::SystemNetHsmBackup {
328                nethsm_user: _,
329                ssh_authorized_key: _,
330                system_user,
331            }
332            | UserMapping::SystemNetHsmOperatorSigning {
333                nethsm_user: _,
334                key_id: _,
335                nethsm_key_setup: _,
336                ssh_authorized_key: _,
337                system_user,
338                ..
339            }
340            | UserMapping::SystemNetHsmMetrics {
341                nethsm_users: _,
342                ssh_authorized_key: _,
343                system_user,
344            }
345            | UserMapping::HermeticSystemNetHsmMetrics {
346                nethsm_users: _,
347                system_user,
348            }
349            | UserMapping::SystemOnlyShareDownload { system_user, .. }
350            | UserMapping::SystemOnlyShareUpload { system_user, .. }
351            | UserMapping::SystemOnlyWireGuardDownload {
352                system_user,
353                ssh_authorized_key: _,
354            } => Some(system_user),
355            #[cfg(feature = "yubihsm2")]
356            UserMapping::YubiHsm2OnlyAdmin(_) => None,
357            #[cfg(feature = "yubihsm2")]
358            UserMapping::SystemYubiHsm2OperatorSigning { system_user, .. }
359            | UserMapping::SystemYubiHsm2Backup { system_user, .. }
360            | UserMapping::SystemYubiHsm2Metrics { system_user, .. }
361            | UserMapping::HermeticSystemYubiHsm2Metrics { system_user, .. } => Some(system_user),
362        }
363    }
364
365    /// Returns the backend users of the mapping.
366    ///
367    /// Returns a [`Vec`] of [`String`] containing all backend user names.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use signstar_crypto::{key::{CryptographicKeyContext, SigningKeySetup}, openpgp::OpenPgpUserIdList};
373    /// use signstar_config::{BackendUserKind, UserMapping, UserMappingFilter};
374    ///
375    /// # fn main() -> testresult::TestResult {
376    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
377    ///     nethsm_user: "user1".parse()?,
378    ///     key_id: "key1".parse()?,
379    ///     nethsm_key_setup: SigningKeySetup::new(
380    ///         "Curve25519".parse()?,
381    ///         vec!["EdDsaSignature".parse()?],
382    ///         None,
383    ///         "EdDsa".parse()?,
384    ///         CryptographicKeyContext::OpenPgp{
385    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
386    ///             version: "v4".parse()?,
387    ///         },
388    ///     )?,
389    ///     system_user: "ssh-user1".parse()?,
390    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
391    ///     tag: "tag1".to_string(),
392    /// };
393    /// assert_eq!(vec!["user1".to_string()], mapping.backend_users(UserMappingFilter::default()));
394    ///
395    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
396    /// assert_eq!(vec!["user1".to_string()], mapping.backend_users(UserMappingFilter{ backend_user_kind: BackendUserKind::Admin }));
397    /// # Ok(())
398    /// # }
399    /// ```
400    pub fn backend_users(&self, filter: UserMappingFilter) -> Vec<String> {
401        match self {
402            UserMapping::NetHsmOnlyAdmin(user_id) => match filter.backend_user_kind {
403                BackendUserKind::Admin => vec![user_id.to_string()],
404                BackendUserKind::NonAdmin => Vec::new(),
405            },
406            UserMapping::SystemNetHsmBackup { nethsm_user, .. } => match filter.backend_user_kind {
407                BackendUserKind::Admin => Vec::new(),
408                BackendUserKind::NonAdmin => vec![nethsm_user.to_string()],
409            },
410            UserMapping::HermeticSystemNetHsmMetrics { nethsm_users, .. }
411            | UserMapping::SystemNetHsmMetrics { nethsm_users, .. } => {
412                match filter.backend_user_kind {
413                    BackendUserKind::Admin => Vec::new(),
414                    BackendUserKind::NonAdmin => nethsm_users
415                        .get_users()
416                        .iter()
417                        .map(|user| user.to_string())
418                        .collect(),
419                }
420            }
421            UserMapping::SystemNetHsmOperatorSigning { nethsm_user, .. } => {
422                match filter.backend_user_kind {
423                    BackendUserKind::Admin => Vec::new(),
424                    BackendUserKind::NonAdmin => vec![nethsm_user.to_string()],
425                }
426            }
427            UserMapping::SystemOnlyShareDownload { .. }
428            | UserMapping::SystemOnlyShareUpload { .. }
429            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
430            #[cfg(feature = "yubihsm2")]
431            UserMapping::YubiHsm2OnlyAdmin(admin) => match filter.backend_user_kind {
432                BackendUserKind::Admin => vec![admin.to_string()],
433                BackendUserKind::NonAdmin => Vec::new(),
434            },
435            #[cfg(feature = "yubihsm2")]
436            UserMapping::SystemYubiHsm2OperatorSigning {
437                authentication_key_id,
438                ..
439            }
440            | UserMapping::SystemYubiHsm2Backup {
441                authentication_key_id,
442                ..
443            }
444            | UserMapping::SystemYubiHsm2Metrics {
445                authentication_key_id,
446                ..
447            }
448            | UserMapping::HermeticSystemYubiHsm2Metrics {
449                authentication_key_id,
450                ..
451            } => match filter.backend_user_kind {
452                BackendUserKind::Admin => Vec::new(),
453                BackendUserKind::NonAdmin => vec![authentication_key_id.to_string()],
454            },
455        }
456    }
457
458    /// Returns the backend users of the mapping with new passphrases based on a `filter`.
459    ///
460    /// Returns a [`Vec`] of implementations of the [`UserWithPassphrase`] trait.
461    /// For each returned backend user a new [`Passphrase`] is generated using the default settings
462    /// of [`Passphrase::generate`].
463    ///
464    /// With a [`UserMappingFilter`] it is possible to target specific kinds of backend users.
465    ///
466    /// # Examples
467    ///
468    /// ```
469    /// use signstar_crypto::{key::{CryptographicKeyContext, SigningKeySetup}, openpgp::OpenPgpUserIdList};
470    /// use signstar_config::{UserMapping, UserMappingFilter};
471    ///
472    /// # fn main() -> testresult::TestResult {
473    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
474    ///     nethsm_user: "user1".parse()?,
475    ///     key_id: "key1".parse()?,
476    ///     nethsm_key_setup: SigningKeySetup::new(
477    ///         "Curve25519".parse()?,
478    ///         vec!["EdDsaSignature".parse()?],
479    ///         None,
480    ///         "EdDsa".parse()?,
481    ///         CryptographicKeyContext::OpenPgp{
482    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
483    ///             version: "v4".parse()?,
484    ///         },
485    ///     )?,
486    ///     system_user: "ssh-user1".parse()?,
487    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
488    ///     tag: "tag1".to_string(),
489    /// };
490    /// let creds = mapping.backend_users_with_new_passphrase(UserMappingFilter::default());
491    /// println!("{creds:?}");
492    ///
493    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
494    /// let creds = mapping.backend_users_with_new_passphrase(UserMappingFilter::default());
495    /// println!("{creds:?}");
496    /// # Ok(())
497    /// # }
498    /// ```
499    pub fn backend_users_with_new_passphrase(
500        &self,
501        filter: UserMappingFilter,
502    ) -> Vec<Box<dyn UserWithPassphrase>> {
503        match self {
504            UserMapping::NetHsmOnlyAdmin(user_id) => match filter.backend_user_kind {
505                BackendUserKind::Admin => vec![Box::new(nethsm::FullCredentials::new(
506                    user_id.clone(),
507                    Passphrase::generate(None),
508                ))],
509                BackendUserKind::NonAdmin => Vec::new(),
510            },
511            UserMapping::SystemNetHsmBackup { nethsm_user, .. } => match filter.backend_user_kind {
512                BackendUserKind::Admin => Vec::new(),
513                BackendUserKind::NonAdmin => vec![Box::new(nethsm::FullCredentials::new(
514                    nethsm_user.as_ref().clone(),
515                    Passphrase::generate(None),
516                ))],
517            },
518            UserMapping::HermeticSystemNetHsmMetrics { nethsm_users, .. }
519            | UserMapping::SystemNetHsmMetrics { nethsm_users, .. } => {
520                match filter.backend_user_kind {
521                    BackendUserKind::Admin => Vec::new(),
522                    BackendUserKind::NonAdmin => nethsm_users
523                        .get_users()
524                        .iter()
525                        .map(|user| {
526                            Box::new(FullCredentials::new(
527                                user.clone(),
528                                Passphrase::generate(None),
529                            )) as Box<dyn UserWithPassphrase>
530                        })
531                        .collect(),
532                }
533            }
534            UserMapping::SystemNetHsmOperatorSigning { nethsm_user, .. } => {
535                match filter.backend_user_kind {
536                    BackendUserKind::Admin => Vec::new(),
537                    BackendUserKind::NonAdmin => vec![Box::new(nethsm::FullCredentials::new(
538                        nethsm_user.clone(),
539                        Passphrase::generate(None),
540                    ))],
541                }
542            }
543            UserMapping::SystemOnlyShareDownload { .. }
544            | UserMapping::SystemOnlyShareUpload { .. }
545            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
546            #[cfg(feature = "yubihsm2")]
547            UserMapping::YubiHsm2OnlyAdmin(admin) => match filter.backend_user_kind {
548                BackendUserKind::Admin => vec![Box::new(signstar_yubihsm2::Credentials::new(
549                    *admin,
550                    Passphrase::generate(None),
551                ))],
552                BackendUserKind::NonAdmin => Vec::new(),
553            },
554            #[cfg(feature = "yubihsm2")]
555            UserMapping::SystemYubiHsm2OperatorSigning {
556                authentication_key_id,
557                ..
558            }
559            | UserMapping::SystemYubiHsm2Backup {
560                authentication_key_id,
561                ..
562            }
563            | UserMapping::SystemYubiHsm2Metrics {
564                authentication_key_id,
565                ..
566            }
567            | UserMapping::HermeticSystemYubiHsm2Metrics {
568                authentication_key_id,
569                ..
570            } => match filter.backend_user_kind {
571                BackendUserKind::Admin => Vec::new(),
572                BackendUserKind::NonAdmin => vec![Box::new(signstar_yubihsm2::Credentials::new(
573                    *authentication_key_id,
574                    Passphrase::generate(None),
575                ))],
576            },
577        }
578    }
579
580    /// Returns the NetHSM users of the mapping
581    ///
582    /// # Examples
583    ///
584    /// ```
585    /// use nethsm::UserId;
586    /// use signstar_config::UserMapping;
587    ///
588    /// # fn main() -> testresult::TestResult {
589    /// let mapping = UserMapping::SystemOnlyShareDownload {
590    ///     system_user: "user1".parse()?,
591    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
592    /// };
593    /// assert!(mapping.get_nethsm_users().is_empty());
594    ///
595    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
596    /// assert_eq!(mapping.get_nethsm_users(), vec![UserId::new("user1".to_string())?]);
597    /// # Ok(())
598    /// # }
599    /// ```
600    pub fn get_nethsm_users(&self) -> Vec<UserId> {
601        match self {
602            UserMapping::SystemNetHsmBackup { nethsm_user, .. } => vec![nethsm_user.clone().into()],
603            UserMapping::NetHsmOnlyAdmin(nethsm_user)
604            | UserMapping::SystemNetHsmOperatorSigning { nethsm_user, .. } => {
605                vec![nethsm_user.clone()]
606            }
607            UserMapping::SystemNetHsmMetrics { nethsm_users, .. }
608            | UserMapping::HermeticSystemNetHsmMetrics { nethsm_users, .. } => {
609                nethsm_users.get_users()
610            }
611            UserMapping::SystemOnlyShareDownload { .. }
612            | UserMapping::SystemOnlyShareUpload { .. }
613            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
614            #[cfg(feature = "yubihsm2")]
615            UserMapping::YubiHsm2OnlyAdmin(_)
616            | UserMapping::SystemYubiHsm2Backup { .. }
617            | UserMapping::SystemYubiHsm2Metrics { .. }
618            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
619            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => Vec::new(),
620        }
621    }
622
623    /// Returns the list of all tracked [`UserId`]s and their respective [`UserRole`]s.
624    ///
625    /// # Examples
626    ///
627    /// ```
628    /// use nethsm::{UserId, UserRole};
629    /// use signstar_config::UserMapping;
630    ///
631    /// # fn main() -> testresult::TestResult {
632    /// let mapping = UserMapping::SystemOnlyShareDownload {
633    ///     system_user: "user1".parse()?,
634    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
635    /// };
636    /// assert!(mapping.get_nethsm_users_and_roles().is_empty());
637    ///
638    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
639    /// assert_eq!(mapping.get_nethsm_users_and_roles(), vec![(UserId::new("user1".to_string())?, UserRole::Administrator)]);
640    /// # Ok(())
641    /// # }
642    /// ```
643    pub fn get_nethsm_users_and_roles(&self) -> Vec<(UserId, UserRole)> {
644        match self {
645            UserMapping::SystemNetHsmBackup { nethsm_user, .. } => {
646                vec![(nethsm_user.clone().into(), UserRole::Backup)]
647            }
648            UserMapping::NetHsmOnlyAdmin(nethsm_user) => {
649                vec![(nethsm_user.clone(), UserRole::Administrator)]
650            }
651            UserMapping::SystemNetHsmOperatorSigning { nethsm_user, .. } => {
652                vec![(nethsm_user.clone(), UserRole::Operator)]
653            }
654            UserMapping::SystemNetHsmMetrics { nethsm_users, .. }
655            | UserMapping::HermeticSystemNetHsmMetrics { nethsm_users, .. } => {
656                nethsm_users.get_users_and_roles()
657            }
658            UserMapping::SystemOnlyShareDownload { .. }
659            | UserMapping::SystemOnlyShareUpload { .. }
660            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
661            #[cfg(feature = "yubihsm2")]
662            UserMapping::YubiHsm2OnlyAdmin(_)
663            | UserMapping::SystemYubiHsm2Backup { .. }
664            | UserMapping::SystemYubiHsm2Metrics { .. }
665            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
666            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => Vec::new(),
667        }
668    }
669
670    /// Returns a list of tuples containing [`UserId`], [`UserRole`] and a list of tags.
671    ///
672    /// # Note
673    ///
674    /// Certain variants of [`UserMapping`] such as [`UserMapping::SystemOnlyShareDownload`],
675    /// [`UserMapping::SystemOnlyShareUpload`] and [`UserMapping::SystemOnlyWireGuardDownload`]
676    /// always return an empty [`Vec`] because they do not track backend users.
677    ///
678    /// # Examples
679    ///
680    /// ```
681    /// use nethsm::{CryptographicKeyContext, OpenPgpUserIdList, UserId, UserRole};
682    /// use signstar_crypto::key::SigningKeySetup;
683    /// use signstar_config::{AuthorizedKeyEntry, UserMapping};
684    ///
685    /// # fn main() -> testresult::TestResult {
686    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
687    ///     nethsm_user: "user1".parse()?,
688    ///     key_id: "key1".parse()?,
689    ///     nethsm_key_setup: SigningKeySetup::new(
690    ///         "Curve25519".parse()?,
691    ///         vec!["EdDsaSignature".parse()?],
692    ///         None,
693    ///         "EdDsa".parse()?,
694    ///         CryptographicKeyContext::OpenPgp{
695    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
696    ///             version: "v4".parse()?,
697    ///         },
698    ///     )?,
699    ///     system_user: "ssh-user1".parse()?,
700    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
701    ///     tag: "tag1".to_string(),
702    /// };
703    /// assert_eq!(
704    ///     mapping.get_nethsm_user_role_and_tags(),
705    ///     vec![(UserId::new("user1".to_string())?, UserRole::Operator, vec!["tag1".to_string()])]);
706    /// # Ok(())
707    /// # }
708    /// ```
709    pub fn get_nethsm_user_role_and_tags(&self) -> Vec<(UserId, UserRole, Vec<String>)> {
710        match self {
711            UserMapping::SystemNetHsmOperatorSigning {
712                nethsm_user,
713                key_id: _,
714                nethsm_key_setup: _,
715                system_user: _,
716                ssh_authorized_key: _,
717                tag,
718            } => vec![(
719                nethsm_user.clone(),
720                UserRole::Operator,
721                vec![tag.to_string()],
722            )],
723            UserMapping::SystemNetHsmBackup { nethsm_user, .. } => {
724                vec![(nethsm_user.clone().into(), UserRole::Backup, Vec::new())]
725            }
726            UserMapping::NetHsmOnlyAdmin(user_id) => {
727                vec![(user_id.clone(), UserRole::Administrator, Vec::new())]
728            }
729            UserMapping::SystemNetHsmMetrics { nethsm_users, .. } => nethsm_users
730                .get_users_and_roles()
731                .iter()
732                .map(|(user, role)| (user.clone(), *role, Vec::new()))
733                .collect(),
734            UserMapping::HermeticSystemNetHsmMetrics { nethsm_users, .. } => nethsm_users
735                .get_users_and_roles()
736                .iter()
737                .map(|(user, role)| (user.clone(), *role, Vec::new()))
738                .collect(),
739            UserMapping::SystemOnlyShareDownload { .. }
740            | UserMapping::SystemOnlyShareUpload { .. }
741            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
742            #[cfg(feature = "yubihsm2")]
743            UserMapping::YubiHsm2OnlyAdmin(_)
744            | UserMapping::SystemYubiHsm2Backup { .. }
745            | UserMapping::SystemYubiHsm2Metrics { .. }
746            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
747            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => Vec::new(),
748        }
749    }
750
751    /// Returns the SSH authorized key of the mapping if it exists.
752    ///
753    /// Returns [`None`] if the mapping does not have an SSH authorized key.
754    ///
755    /// # Examples
756    ///
757    /// ```
758    /// use std::str::FromStr;
759    ///
760    /// use signstar_config::{AuthorizedKeyEntry, UserMapping};
761    ///
762    /// # fn main() -> testresult::TestResult {
763    /// let mapping = UserMapping::SystemOnlyShareDownload {
764    ///     system_user: "user1".parse()?,
765    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
766    /// };
767    /// assert_eq!(mapping.get_ssh_authorized_key(), Some(&AuthorizedKeyEntry::from_str("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host")?));
768    ///
769    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
770    /// assert_eq!(mapping.get_ssh_authorized_key(), None);
771    /// # Ok(())
772    /// # }
773    /// ```
774    pub fn get_ssh_authorized_key(&self) -> Option<&AuthorizedKeyEntry> {
775        match self {
776            UserMapping::NetHsmOnlyAdmin(_) | UserMapping::HermeticSystemNetHsmMetrics { .. } => {
777                None
778            }
779            UserMapping::SystemNetHsmBackup {
780                nethsm_user: _,
781                system_user: _,
782                ssh_authorized_key,
783            }
784            | UserMapping::SystemNetHsmMetrics {
785                nethsm_users: _,
786                system_user: _,
787                ssh_authorized_key,
788            }
789            | UserMapping::SystemOnlyShareDownload {
790                system_user: _,
791                ssh_authorized_key,
792            }
793            | UserMapping::SystemOnlyShareUpload {
794                system_user: _,
795                ssh_authorized_key,
796            }
797            | UserMapping::SystemOnlyWireGuardDownload {
798                system_user: _,
799                ssh_authorized_key,
800            }
801            | UserMapping::SystemNetHsmOperatorSigning {
802                nethsm_user: _,
803                key_id: _,
804                nethsm_key_setup: _,
805                system_user: _,
806                ssh_authorized_key,
807                ..
808            } => Some(ssh_authorized_key),
809            #[cfg(feature = "yubihsm2")]
810            UserMapping::YubiHsm2OnlyAdmin(_)
811            | UserMapping::HermeticSystemYubiHsm2Metrics { .. } => None,
812            #[cfg(feature = "yubihsm2")]
813            UserMapping::SystemYubiHsm2OperatorSigning {
814                ssh_authorized_key, ..
815            }
816            | UserMapping::SystemYubiHsm2Backup {
817                ssh_authorized_key, ..
818            }
819            | UserMapping::SystemYubiHsm2Metrics {
820                ssh_authorized_key, ..
821            } => Some(ssh_authorized_key),
822        }
823    }
824
825    /// Returns all used [`KeyId`]s of the mapping
826    ///
827    /// # Examples
828    ///
829    /// ```
830    /// use nethsm::{CryptographicKeyContext, KeyId, OpenPgpUserIdList};
831    /// use signstar_crypto::key::SigningKeySetup;
832    /// use signstar_config::{AuthorizedKeyEntry, UserMapping};
833    ///
834    /// # fn main() -> testresult::TestResult {
835    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
836    ///     nethsm_user: "user1".parse()?,
837    ///     key_id: "key1".parse()?,
838    ///     nethsm_key_setup: SigningKeySetup::new(
839    ///         "Curve25519".parse()?,
840    ///         vec!["EdDsaSignature".parse()?],
841    ///         None,
842    ///         "EdDsa".parse()?,
843    ///         CryptographicKeyContext::OpenPgp{
844    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
845    ///             version: "v4".parse()?,
846    ///         },
847    ///     )?,
848    ///     system_user: "ssh-user1".parse()?,
849    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
850    ///     tag: "tag1".to_string(),
851    /// };
852    /// assert_eq!(mapping.get_nethsm_key_ids(None), vec![KeyId::new("key1".to_string())?]);
853    ///
854    /// let mapping = UserMapping::SystemOnlyShareDownload {
855    ///     system_user: "user1".parse()?,
856    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
857    /// };
858    /// assert_eq!(mapping.get_nethsm_key_ids(None), Vec::new());
859    /// # Ok(())
860    /// # }
861    /// ```
862    pub fn get_nethsm_key_ids(&self, namespace: Option<&NamespaceId>) -> Vec<KeyId> {
863        match self {
864            UserMapping::SystemNetHsmOperatorSigning {
865                nethsm_user,
866                key_id,
867                ..
868            } => {
869                if nethsm_user.namespace() == namespace {
870                    vec![key_id.clone()]
871                } else {
872                    Vec::new()
873                }
874            }
875            UserMapping::SystemNetHsmMetrics { .. }
876            | UserMapping::NetHsmOnlyAdmin(_)
877            | UserMapping::HermeticSystemNetHsmMetrics { .. }
878            | UserMapping::SystemNetHsmBackup { .. }
879            | UserMapping::SystemOnlyShareDownload { .. }
880            | UserMapping::SystemOnlyShareUpload { .. }
881            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
882            #[cfg(feature = "yubihsm2")]
883            UserMapping::YubiHsm2OnlyAdmin(_)
884            | UserMapping::SystemYubiHsm2Backup { .. }
885            | UserMapping::SystemYubiHsm2Metrics { .. }
886            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
887            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => Vec::new(),
888        }
889    }
890
891    /// Returns tags for keys and users
892    ///
893    /// Tags can be filtered by [namespace] by providing [`Some`] `namespace`.
894    /// Providing [`None`] implies that the context is system-wide.
895    ///
896    /// # Examples
897    ///
898    /// ```
899    /// use nethsm::{CryptographicKeyContext, OpenPgpUserIdList};
900    /// use signstar_crypto::key::SigningKeySetup;
901    /// use signstar_config::UserMapping;
902    ///
903    /// # fn main() -> testresult::TestResult {
904    /// let mapping = UserMapping::SystemOnlyShareDownload {
905    ///     system_user: "user1".parse()?,
906    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
907    /// };
908    /// assert!(mapping.get_nethsm_tags(None).is_empty());
909    ///
910    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
911    /// assert!(mapping.get_nethsm_tags(None).is_empty());
912    ///
913    /// let mapping = UserMapping::SystemNetHsmOperatorSigning{
914    ///     nethsm_user: "ns1~user1".parse()?,
915    ///     key_id: "key1".parse()?,
916    ///     nethsm_key_setup: SigningKeySetup::new(
917    ///         "Curve25519".parse()?,
918    ///         vec!["EdDsaSignature".parse()?],
919    ///         None,
920    ///         "EdDsa".parse()?,
921    ///         CryptographicKeyContext::OpenPgp{
922    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
923    ///             version: "4".parse()?,
924    ///     })?,
925    ///     system_user: "user1".parse()?,
926    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
927    ///     tag: "tag1".to_string(),
928    /// };
929    /// assert!(mapping.get_nethsm_tags(None).is_empty());
930    /// assert_eq!(mapping.get_nethsm_tags(Some(&"ns1".parse()?)), vec!["tag1"]);
931    /// # Ok(())
932    /// # }
933    /// ```
934    /// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
935    pub fn get_nethsm_tags(&self, namespace: Option<&NamespaceId>) -> Vec<&str> {
936        match self {
937            UserMapping::SystemNetHsmOperatorSigning {
938                nethsm_user,
939                key_id: _,
940                nethsm_key_setup: _,
941                system_user: _,
942                ssh_authorized_key: _,
943                tag,
944            } => {
945                if nethsm_user.namespace() == namespace {
946                    vec![tag.as_str()]
947                } else {
948                    Vec::new()
949                }
950            }
951            UserMapping::SystemNetHsmMetrics { .. }
952            | UserMapping::NetHsmOnlyAdmin(_)
953            | UserMapping::HermeticSystemNetHsmMetrics { .. }
954            | UserMapping::SystemNetHsmBackup { .. }
955            | UserMapping::SystemOnlyShareDownload { .. }
956            | UserMapping::SystemOnlyShareUpload { .. }
957            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
958            #[cfg(feature = "yubihsm2")]
959            UserMapping::YubiHsm2OnlyAdmin(_)
960            | UserMapping::SystemYubiHsm2Backup { .. }
961            | UserMapping::SystemYubiHsm2Metrics { .. }
962            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
963            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => Vec::new(),
964        }
965    }
966
967    /// Returns a list of tuples of [`UserId`], [`KeyId`], [`SigningKeySetup`] and tag for the
968    /// mapping.
969    ///
970    /// Using a `filter` (see [`FilterUserKeys`]) it is possible to have only a subset of the
971    /// available tuples be returned:
972    ///
973    /// - [`FilterUserKeys::All`]: Returns all available tuples.
974    /// - [`FilterUserKeys::Namespaced`]: Returns tuples that match [`UserId`]s with a namespace.
975    /// - [`FilterUserKeys::Namespace`]: Returns tuples that match [`UserId`]s with a specific
976    ///   namespace.
977    /// - [`FilterUserKeys::SystemWide`]: Returns tuples that match [`UserId`]s without a namespace.
978    /// - [`FilterUserKeys::Namespace`]: Returns tuples that match a specific tag.
979    ///
980    /// # Examples
981    ///
982    /// ```
983    /// use nethsm::{CryptographicKeyContext, KeyId, OpenPgpUserIdList, UserId};
984    /// use signstar_crypto::key::SigningKeySetup;
985    /// use signstar_config::{FilterUserKeys, UserMapping};
986    ///
987    /// # fn main() -> testresult::TestResult {
988    /// let mapping = UserMapping::SystemNetHsmOperatorSigning {
989    ///     nethsm_user: "user1".parse()?,
990    ///     key_id: "key1".parse()?,
991    ///     nethsm_key_setup: SigningKeySetup::new(
992    ///         "Curve25519".parse()?,
993    ///         vec!["EdDsaSignature".parse()?],
994    ///         None,
995    ///         "EdDsa".parse()?,
996    ///         CryptographicKeyContext::OpenPgp{
997    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
998    ///             version: "v4".parse()?,
999    ///         },
1000    ///     )?,
1001    ///     system_user: "ssh-user1".parse()?,
1002    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1003    ///     tag: "tag1".to_string(),
1004    /// };
1005    /// assert_eq!(
1006    ///     mapping.get_nethsm_user_key_and_tag(FilterUserKeys::All),
1007    ///     vec![(
1008    ///         "user1".parse()?,
1009    ///         "key1".parse()?,
1010    ///         SigningKeySetup::new(
1011    ///             "Curve25519".parse()?,
1012    ///             vec!["EdDsaSignature".parse()?],
1013    ///             None,
1014    ///             "EdDsa".parse()?,
1015    ///             CryptographicKeyContext::OpenPgp{
1016    ///                 user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1017    ///                 version: "v4".parse()?,
1018    ///             },
1019    ///         )?,
1020    ///         "tag1".to_string(),
1021    ///     )]
1022    /// );
1023    /// assert_eq!(mapping.get_nethsm_user_key_and_tag(FilterUserKeys::Namespace("test".parse()?)), Vec::new());
1024    /// assert_eq!(mapping.get_nethsm_user_key_and_tag(FilterUserKeys::Tag("tag2".parse()?)), Vec::new());
1025    ///
1026    /// let mapping = UserMapping::SystemOnlyShareDownload {
1027    ///     system_user: "user1".parse()?,
1028    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1029    /// };
1030    /// assert_eq!(mapping.get_nethsm_user_key_and_tag(FilterUserKeys::All), Vec::new());
1031    /// # Ok(())
1032    /// # }
1033    /// ```
1034    pub fn get_nethsm_user_key_and_tag(
1035        &self,
1036        filter: FilterUserKeys,
1037    ) -> Vec<(UserId, KeyId, SigningKeySetup, String)> {
1038        match self {
1039            UserMapping::SystemNetHsmOperatorSigning {
1040                nethsm_user,
1041                key_id,
1042                nethsm_key_setup,
1043                system_user: _,
1044                ssh_authorized_key: _,
1045                tag,
1046            } => match filter {
1047                FilterUserKeys::All => {
1048                    vec![(
1049                        nethsm_user.clone(),
1050                        key_id.clone(),
1051                        nethsm_key_setup.clone(),
1052                        tag.clone(),
1053                    )]
1054                }
1055                FilterUserKeys::Namespaced => {
1056                    if nethsm_user.is_namespaced() {
1057                        vec![(
1058                            nethsm_user.clone(),
1059                            key_id.clone(),
1060                            nethsm_key_setup.clone(),
1061                            tag.clone(),
1062                        )]
1063                    } else {
1064                        Vec::new()
1065                    }
1066                }
1067                FilterUserKeys::Namespace(namespace) => {
1068                    if Some(&namespace) == nethsm_user.namespace() {
1069                        vec![(
1070                            nethsm_user.clone(),
1071                            key_id.clone(),
1072                            nethsm_key_setup.clone(),
1073                            tag.clone(),
1074                        )]
1075                    } else {
1076                        Vec::new()
1077                    }
1078                }
1079                FilterUserKeys::SystemWide => {
1080                    if !nethsm_user.is_namespaced() {
1081                        vec![(
1082                            nethsm_user.clone(),
1083                            key_id.clone(),
1084                            nethsm_key_setup.clone(),
1085                            tag.clone(),
1086                        )]
1087                    } else {
1088                        Vec::new()
1089                    }
1090                }
1091                FilterUserKeys::Tag(filter_tag) => {
1092                    if &filter_tag == tag {
1093                        vec![(
1094                            nethsm_user.clone(),
1095                            key_id.clone(),
1096                            nethsm_key_setup.clone(),
1097                            tag.clone(),
1098                        )]
1099                    } else {
1100                        Vec::new()
1101                    }
1102                }
1103            },
1104            UserMapping::SystemNetHsmMetrics { .. }
1105            | UserMapping::NetHsmOnlyAdmin(_)
1106            | UserMapping::HermeticSystemNetHsmMetrics { .. }
1107            | UserMapping::SystemNetHsmBackup { .. }
1108            | UserMapping::SystemOnlyShareDownload { .. }
1109            | UserMapping::SystemOnlyShareUpload { .. }
1110            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
1111            #[cfg(feature = "yubihsm2")]
1112            UserMapping::YubiHsm2OnlyAdmin(_)
1113            | UserMapping::SystemYubiHsm2Backup { .. }
1114            | UserMapping::SystemYubiHsm2Metrics { .. }
1115            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
1116            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => Vec::new(),
1117        }
1118    }
1119
1120    /// Returns all NetHSM [namespaces] of the mapping.
1121    ///
1122    /// # Examples
1123    ///
1124    /// ```
1125    /// use nethsm::{CryptographicKeyContext, OpenPgpUserIdList};
1126    /// use signstar_crypto::key::SigningKeySetup;
1127    /// use signstar_config::UserMapping;
1128    ///
1129    /// # fn main() -> testresult::TestResult {
1130    /// let mapping = UserMapping::SystemOnlyShareDownload {
1131    ///     system_user: "user1".parse()?,
1132    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1133    /// };
1134    /// assert!(mapping.get_nethsm_namespaces().is_empty());
1135    ///
1136    /// let mapping = UserMapping::NetHsmOnlyAdmin("user1".parse()?);
1137    /// assert!(mapping.get_nethsm_namespaces().is_empty());
1138    ///
1139    /// let mapping = UserMapping::SystemNetHsmOperatorSigning{
1140    ///     nethsm_user: "ns1~user1".parse()?,
1141    ///     key_id: "key1".parse()?,
1142    ///     nethsm_key_setup: SigningKeySetup::new(
1143    ///         "Curve25519".parse()?,
1144    ///         vec!["EdDsaSignature".parse()?],
1145    ///         None,
1146    ///         "EdDsa".parse()?,
1147    ///         CryptographicKeyContext::OpenPgp{
1148    ///             user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1149    ///             version: "4".parse()?,
1150    ///     })?,
1151    ///     system_user: "user1".parse()?,
1152    ///     ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1153    ///     tag: "tag1".to_string(),
1154    /// };
1155    /// assert_eq!(mapping.get_nethsm_namespaces(), vec!["ns1".parse()?]);
1156    /// # Ok(())
1157    /// # }
1158    /// ```
1159    /// [namespaces]: https://docs.nitrokey.com/nethsm/administration#namespaces
1160    pub fn get_nethsm_namespaces(&self) -> Vec<NamespaceId> {
1161        match self {
1162            UserMapping::NetHsmOnlyAdmin(nethsm_user)
1163            | UserMapping::SystemNetHsmOperatorSigning { nethsm_user, .. } => {
1164                if let Some(namespace) = nethsm_user.namespace() {
1165                    vec![namespace.clone()]
1166                } else {
1167                    Vec::new()
1168                }
1169            }
1170            UserMapping::HermeticSystemNetHsmMetrics { nethsm_users, .. }
1171            | UserMapping::SystemNetHsmMetrics { nethsm_users, .. } => nethsm_users
1172                .get_users()
1173                .iter()
1174                .filter_map(|user_id| user_id.namespace())
1175                .cloned()
1176                .collect(),
1177            UserMapping::SystemOnlyShareDownload { .. }
1178            | UserMapping::SystemNetHsmBackup { .. }
1179            | UserMapping::SystemOnlyShareUpload { .. }
1180            | UserMapping::SystemOnlyWireGuardDownload { .. } => Vec::new(),
1181            #[cfg(feature = "yubihsm2")]
1182            UserMapping::YubiHsm2OnlyAdmin(_)
1183            | UserMapping::SystemYubiHsm2Backup { .. }
1184            | UserMapping::SystemYubiHsm2Metrics { .. }
1185            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
1186            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => Vec::new(),
1187        }
1188    }
1189
1190    /// Returns whether the mapping has both system and HSM backend users.
1191    ///
1192    /// Returns `true` if the `self` has at least one system and one HSM backend user, and `false`
1193    /// otherwise.
1194    pub fn has_system_and_backend_user(&self) -> bool {
1195        match self {
1196            UserMapping::SystemNetHsmOperatorSigning { .. }
1197            | UserMapping::HermeticSystemNetHsmMetrics { .. }
1198            | UserMapping::SystemNetHsmMetrics { .. }
1199            | UserMapping::SystemNetHsmBackup { .. } => true,
1200            #[cfg(feature = "yubihsm2")]
1201            UserMapping::YubiHsm2OnlyAdmin(_) => false,
1202            #[cfg(feature = "yubihsm2")]
1203            UserMapping::SystemYubiHsm2Backup { .. }
1204            | UserMapping::SystemYubiHsm2Metrics { .. }
1205            | UserMapping::HermeticSystemYubiHsm2Metrics { .. }
1206            | UserMapping::SystemYubiHsm2OperatorSigning { .. } => true,
1207            UserMapping::SystemOnlyShareDownload { .. }
1208            | UserMapping::SystemOnlyShareUpload { .. }
1209            | UserMapping::SystemOnlyWireGuardDownload { .. }
1210            | UserMapping::NetHsmOnlyAdmin(_) => false,
1211        }
1212    }
1213}
1214
1215/// Checks the accessibility of a secrets file.
1216///
1217/// Checks whether file at `path`
1218///
1219/// - exists,
1220/// - is a file,
1221/// - has accessible metadata,
1222/// - and has the file mode [`SECRET_FILE_MODE`].
1223///
1224/// # Errors
1225///
1226/// Returns an error, if the file at `path`
1227///
1228/// - does not exist,
1229/// - is not a file,
1230/// - does not have accessible metadata,
1231/// - or has a file mode other than [`SECRET_FILE_MODE`].
1232pub(crate) fn check_secrets_file(path: impl AsRef<Path>) -> Result<(), Error> {
1233    let path = path.as_ref();
1234
1235    // check if a path exists
1236    if !path.exists() {
1237        return Err(crate::non_admin_credentials::Error::SecretsFileMissing {
1238            path: path.to_path_buf(),
1239        }
1240        .into());
1241    }
1242
1243    // check if this is a file
1244    if !path.is_file() {
1245        return Err(Error::NonAdminSecretHandling(
1246            crate::non_admin_credentials::Error::SecretsFileNotAFile {
1247                path: path.to_path_buf(),
1248            },
1249        ));
1250    }
1251
1252    // check for correct permissions
1253    match path.metadata() {
1254        Ok(metadata) => {
1255            let mode = metadata.permissions().mode();
1256            if mode != SECRET_FILE_MODE {
1257                return Err(Error::NonAdminSecretHandling(
1258                    crate::non_admin_credentials::Error::SecretsFilePermissions {
1259                        path: path.to_path_buf(),
1260                        mode,
1261                    },
1262                ));
1263            }
1264        }
1265        Err(source) => {
1266            return Err(Error::NonAdminSecretHandling(
1267                crate::non_admin_credentials::Error::SecretsFileMetadata {
1268                    path: path.to_path_buf(),
1269                    source,
1270                },
1271            ));
1272        }
1273    }
1274
1275    Ok(())
1276}
1277
1278/// A [`UserMapping`] centric view of a [`SignstarConfig`].
1279///
1280/// Wraps a single [`UserMapping`], as well as the system-wide [`AdministrativeSecretHandling`],
1281/// [`NonAdministrativeSecretHandling`] and [`BackendConnection`]s.
1282#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1283pub struct ExtendedUserMapping {
1284    admin_secret_handling: AdministrativeSecretHandling,
1285    non_admin_secret_handling: NonAdministrativeSecretHandling,
1286    connections: HashSet<BackendConnection>,
1287    user_mapping: UserMapping,
1288}
1289
1290impl ExtendedUserMapping {
1291    /// Creates a new [`ExtendedUserMapping`].
1292    pub fn new(
1293        admin_secret_handling: AdministrativeSecretHandling,
1294        non_admin_secret_handling: NonAdministrativeSecretHandling,
1295        connections: HashSet<BackendConnection>,
1296        user_mapping: UserMapping,
1297    ) -> Self {
1298        Self {
1299            admin_secret_handling,
1300            non_admin_secret_handling,
1301            connections,
1302            user_mapping,
1303        }
1304    }
1305
1306    /// Returns the [`AdministrativeSecretHandling`].
1307    pub fn get_admin_secret_handling(&self) -> AdministrativeSecretHandling {
1308        self.admin_secret_handling
1309    }
1310
1311    /// Returns the [`BackendConnection`]s.
1312    pub fn get_connections(&self) -> HashSet<BackendConnection> {
1313        self.connections.clone()
1314    }
1315
1316    /// Returns the [`NonAdministrativeSecretHandling`].
1317    pub fn get_non_admin_secret_handling(&self) -> NonAdministrativeSecretHandling {
1318        self.non_admin_secret_handling
1319    }
1320
1321    /// Returns the [`UserMapping`].
1322    pub fn get_user_mapping(&self) -> &UserMapping {
1323        &self.user_mapping
1324    }
1325
1326    /// Loads credentials for each backend user associated with a [`SystemUserId`].
1327    ///
1328    /// The [`SystemUserId`] of the mapping must be equal to the current system user calling this
1329    /// function.
1330    /// Relies on [`get_plaintext_secret_file`] and [`get_systemd_creds_secret_file`] to retrieve
1331    /// the specific path to a secrets file for each backend user name mapped to a [`SystemUserId`].
1332    ///
1333    /// Returns a [`CredentialsLoading`], which may contain critical errors related to loading a
1334    /// passphrase from a secrets file for each available backend user.
1335    ///
1336    /// # Note
1337    ///
1338    /// The caller is expected to handle any errors tracked in the returned object based on context.
1339    ///
1340    /// # Errors
1341    ///
1342    /// Returns an error if
1343    ///
1344    /// - the [`ExtendedUserMapping`] provides no [`SystemUserId`],
1345    /// - no system user equal to the [`SystemUserId`] exists,
1346    /// - the [`SystemUserId`] is not equal to the currently calling system user,
1347    /// - or the [systemd-creds] command is not available when trying to decrypt secrets.
1348    ///
1349    /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
1350    pub fn load_credentials(&self) -> Result<CredentialsLoading, Error> {
1351        // Retrieve required SystemUserId and User and compare with current User.
1352        let (system_user, user) = get_system_user_pair(self)?;
1353        let current_system_user = get_current_system_user()?;
1354
1355        // fail if running as root
1356        fail_if_root(&current_system_user)?;
1357        match_current_system_user(&current_system_user, &user)?;
1358
1359        let secret_handling = self.get_non_admin_secret_handling();
1360        let mut credentials: Vec<Box<dyn UserWithPassphrase>> = Vec::new();
1361        let mut errors = Vec::new();
1362
1363        // Iterate over the names of non-administrative backend users of the mapping.
1364        for name in self.get_user_mapping().backend_users(UserMappingFilter {
1365            backend_user_kind: BackendUserKind::NonAdmin,
1366        }) {
1367            let secrets_file = match secret_handling {
1368                NonAdministrativeSecretHandling::Plaintext => {
1369                    get_plaintext_secret_file(system_user.as_ref(), &name)
1370                }
1371                NonAdministrativeSecretHandling::SystemdCreds => {
1372                    get_systemd_creds_secret_file(system_user.as_ref(), &name)
1373                }
1374            };
1375            info!(
1376                "Load secret for system user {system_user} and backend user {name} from file: {secrets_file:?}"
1377            );
1378            // Ensure the secrets file has correct ownership and permissions.
1379            if let Err(error) = check_secrets_file(secrets_file.as_path()) {
1380                errors.push(CredentialsLoadingError::new(name.clone(), error));
1381                continue;
1382            };
1383
1384            let passphrase = match secret_handling {
1385                // Read from plaintext secrets file.
1386                NonAdministrativeSecretHandling::Plaintext => {
1387                    // get passphrase or error
1388                    match read_to_string(&secrets_file).map_err(|source| {
1389                        Error::NonAdminSecretHandling(
1390                            crate::non_admin_credentials::Error::SecretsFileRead {
1391                                path: secrets_file,
1392                                source,
1393                            },
1394                        )
1395                    }) {
1396                        Ok(passphrase) => Passphrase::new(passphrase),
1397                        Err(error) => {
1398                            errors.push(CredentialsLoadingError::new(name.clone(), error));
1399                            continue;
1400                        }
1401                    }
1402                }
1403                // Read from systemd-creds encrypted secrets file.
1404                NonAdministrativeSecretHandling::SystemdCreds => {
1405                    // Decrypt secret using systemd-creds.
1406                    let creds_command = get_command("systemd-creds")?;
1407                    let mut command = Command::new(creds_command);
1408                    let command = command
1409                        .arg("--user")
1410                        .arg("decrypt")
1411                        .arg(&secrets_file)
1412                        .arg("-");
1413                    match command.output().map_err(|source| Error::CommandExec {
1414                        command: format!("{command:?}"),
1415                        source,
1416                    }) {
1417                        Ok(command_output) => {
1418                            // fail if decryption did not result in a successful status code
1419                            if !command_output.status.success() {
1420                                errors.push(CredentialsLoadingError::new(
1421                                    name.clone(),
1422                                    Error::CommandNonZero {
1423                                        command: format!("{command:?}"),
1424                                        exit_status: command_output.status,
1425                                        stderr: String::from_utf8_lossy(&command_output.stderr)
1426                                            .into_owned(),
1427                                    },
1428                                ));
1429                                continue;
1430                            }
1431
1432                            let creds = match String::from_utf8(command_output.stdout) {
1433                                Ok(creds) => creds,
1434                                Err(source) => {
1435                                    errors.push(CredentialsLoadingError::new(
1436                                        name.clone(),
1437                                        Error::Utf8String {
1438                                            path: secrets_file,
1439                                            context: format!(
1440                                                "converting stdout of {command:?} to string"
1441                                            ),
1442                                            source,
1443                                        },
1444                                    ));
1445                                    continue;
1446                                }
1447                            };
1448
1449                            Passphrase::new(creds)
1450                        }
1451                        Err(error) => {
1452                            errors.push(CredentialsLoadingError::new(name.clone(), error));
1453                            continue;
1454                        }
1455                    }
1456                }
1457            };
1458
1459            // Add the credentials to the output.
1460            //
1461            // NOTE: Some UserMappings do not have non-administrative backend users and are only
1462            // matched to allow for variants behind dedicated features to be addressed properly.
1463            match self.get_user_mapping() {
1464                // NOTE: This is a no-op, because an admin user's credentials are not persisted the
1465                // same way as those of a non-admin user.
1466                UserMapping::NetHsmOnlyAdmin(_) => {}
1467                // NOTE: This is a no-op, as these mappings have no backend user.
1468                UserMapping::SystemOnlyShareDownload { .. }
1469                | UserMapping::SystemOnlyShareUpload { .. }
1470                | UserMapping::SystemOnlyWireGuardDownload { .. } => {}
1471                UserMapping::SystemNetHsmBackup { .. }
1472                | UserMapping::SystemNetHsmMetrics { .. }
1473                | UserMapping::SystemNetHsmOperatorSigning { .. }
1474                | UserMapping::HermeticSystemNetHsmMetrics { .. } => {
1475                    credentials.push(Box::new(FullCredentials::new(
1476                        // NOTE: It is not possible to actually trigger this error, as we are
1477                        // deriving `name` from a `UserId` in this case.
1478                        UserId::new(name)
1479                            .map_err(|source| Error::NetHsm(nethsm::Error::User(source)))?,
1480                        passphrase,
1481                    )));
1482                }
1483                #[cfg(feature = "yubihsm2")]
1484                UserMapping::YubiHsm2OnlyAdmin(_) => {}
1485                #[cfg(feature = "yubihsm2")]
1486                UserMapping::SystemYubiHsm2OperatorSigning {
1487                    authentication_key_id,
1488                    ..
1489                }
1490                | UserMapping::SystemYubiHsm2Backup {
1491                    authentication_key_id,
1492                    ..
1493                }
1494                | UserMapping::SystemYubiHsm2Metrics {
1495                    authentication_key_id,
1496                    ..
1497                }
1498                | UserMapping::HermeticSystemYubiHsm2Metrics {
1499                    authentication_key_id,
1500                    ..
1501                } => credentials.push(Box::new(signstar_yubihsm2::Credentials::new(
1502                    *authentication_key_id,
1503                    passphrase,
1504                ))),
1505            }
1506        }
1507
1508        Ok(CredentialsLoading::new(
1509            self.clone(),
1510            credentials,
1511            CredentialsLoadingErrors::new(errors),
1512        ))
1513    }
1514
1515    /// Creates secrets directories for all non-administrative mappings.
1516    ///
1517    /// Matches the [`SystemUserId`] in a mapping with an actual user on the system.
1518    /// Creates the passphrase directory for the user and ensures correct ownership of it and all
1519    /// parent directories up until the user's home directory.
1520    ///
1521    /// # Errors
1522    ///
1523    /// Returns an error if
1524    /// - no system user is available in the mapping,
1525    /// - the system user of the mapping is not available on the system,
1526    /// - the directory could not be created,
1527    /// - the ownership of any directory between the user's home and the passphrase directory can
1528    ///   not be changed.
1529    pub fn create_secrets_dir(&self) -> Result<(), Error> {
1530        // Retrieve required SystemUserId and User and compare with current User.
1531        let (system_user, user) = get_system_user_pair(self)?;
1532
1533        // fail if not running as root
1534        fail_if_not_root(&get_current_system_user()?)?;
1535
1536        // get and create the user's passphrase directory
1537        let secrets_dir = get_user_secrets_dir(system_user.as_ref());
1538        create_dir_all(&secrets_dir).map_err(|source| {
1539            crate::non_admin_credentials::Error::SecretsDirCreate {
1540                path: secrets_dir.clone(),
1541                system_user: system_user.clone(),
1542                source,
1543            }
1544        })?;
1545
1546        // Recursively chown all directories to the user and group, until `HOME_BASE_DIR` is
1547        // reached.
1548        let home_dir = get_home_base_dir_path().join(PathBuf::from(system_user.as_ref()));
1549        let mut chown_dir = secrets_dir.clone();
1550        while chown_dir != home_dir {
1551            chown(&chown_dir, Some(user.uid.as_raw()), Some(user.gid.as_raw())).map_err(
1552                |source| Error::Chown {
1553                    path: chown_dir.to_path_buf(),
1554                    user: system_user.to_string(),
1555                    source,
1556                },
1557            )?;
1558            if let Some(parent) = &chown_dir.parent() {
1559                chown_dir = parent.to_path_buf()
1560            } else {
1561                break;
1562            }
1563        }
1564
1565        Ok(())
1566    }
1567
1568    /// Creates passphrases for all non-administrative mappings.
1569    ///
1570    /// If the targeted [`UserMapping`] is that of non-administrative backend user(s), a new random
1571    /// passphrase (see [`Passphrase::generate`]) is created for each of those backend user(s).
1572    /// Each passphrase is written to disk and finally the list of credentials are returned.
1573    ///
1574    /// - If `self` is configured to use [`NonAdministrativeSecretHandling::Plaintext`], the
1575    ///   passphrase is stored in a secrets file, defined by [`get_plaintext_secret_file`].
1576    /// - If `self` is configured to use [`NonAdministrativeSecretHandling::SystemdCreds`], the
1577    ///   passphrase is encrypted using [systemd-creds] and stored in a secrets file, defined by
1578    ///   [`get_systemd_creds_secret_file`].
1579    ///
1580    /// # Errors
1581    ///
1582    /// Returns an error if
1583    ///
1584    /// - the targeted system user does not exist in the mapping or on the system,
1585    /// - the function is called using a non-root user,
1586    /// - the [systemd-creds] command is not available when trying to encrypt the passphrase,
1587    /// - the encryption of the passphrase using [systemd-creds] fails,
1588    /// - the secrets file can not be created,
1589    /// - the secrets file can not be written to,
1590    /// - or the ownership and permissions of the secrets file can not be changed.
1591    ///
1592    /// [systemd-creds]: https://man.archlinux.org/man/systemd-creds.1
1593    pub fn create_non_administrative_secrets(
1594        &self,
1595    ) -> Result<Vec<Box<dyn UserWithPassphrase>>, Error> {
1596        // Retrieve required SystemUserId and User.
1597        let (system_user, user) = get_system_user_pair(self)?;
1598
1599        // fail if not running as root
1600        fail_if_not_root(&get_current_system_user()?)?;
1601
1602        let secret_handling = self.get_non_admin_secret_handling();
1603        // Get credentials for all backend users (with newly generated passphrases).
1604        let credentials =
1605            self.get_user_mapping()
1606                .backend_users_with_new_passphrase(UserMappingFilter {
1607                    backend_user_kind: BackendUserKind::NonAdmin,
1608                });
1609
1610        // Write the passphrase for each set of credentials to disk.
1611        for creds in credentials.iter() {
1612            let secrets_file = match secret_handling {
1613                NonAdministrativeSecretHandling::Plaintext => {
1614                    get_plaintext_secret_file(system_user.as_ref(), &creds.user())
1615                }
1616                NonAdministrativeSecretHandling::SystemdCreds => {
1617                    get_systemd_creds_secret_file(system_user.as_ref(), &creds.user())
1618                }
1619            };
1620
1621            info!(
1622                "Create secret for system user {system_user} and backend user {} in file: {secrets_file:?}",
1623                creds.user()
1624            );
1625            let secret = {
1626                // Create credentials files depending on secret handling
1627                match secret_handling {
1628                    NonAdministrativeSecretHandling::Plaintext => {
1629                        creds.passphrase().expose_borrowed().as_bytes().to_vec()
1630                    }
1631                    NonAdministrativeSecretHandling::SystemdCreds => {
1632                        // Create systemd-creds encrypted secret.
1633                        let creds_command = get_command("systemd-creds")?;
1634                        let mut command = Command::new(creds_command);
1635                        let command = command
1636                            .arg("--user")
1637                            .arg("--name=")
1638                            .arg("--uid")
1639                            .arg(system_user.as_ref())
1640                            .arg("encrypt")
1641                            .arg("-")
1642                            .arg("-")
1643                            .stdin(Stdio::piped())
1644                            .stdout(Stdio::piped())
1645                            .stderr(Stdio::piped());
1646                        let mut command_child =
1647                            command.spawn().map_err(|source| Error::CommandBackground {
1648                                command: format!("{command:?}"),
1649                                source,
1650                            })?;
1651
1652                        // write to stdin
1653                        command_child
1654                            .stdin
1655                            .take()
1656                            .ok_or(Error::CommandAttachToStdin {
1657                                command: format!("{command:?}"),
1658                            })?
1659                            .write_all(creds.passphrase().expose_borrowed().as_bytes())
1660                            .map_err(|source| Error::CommandWriteToStdin {
1661                                command: format!("{command:?}"),
1662                                source,
1663                            })?;
1664
1665                        let command_output =
1666                            command_child.wait_with_output().map_err(|source| {
1667                                Error::CommandExec {
1668                                    command: format!("{command:?}"),
1669                                    source,
1670                                }
1671                            })?;
1672
1673                        if !command_output.status.success() {
1674                            return Err(Error::CommandNonZero {
1675                                command: format!("{command:?}"),
1676                                exit_status: command_output.status,
1677                                stderr: String::from_utf8_lossy(&command_output.stderr)
1678                                    .into_owned(),
1679                            });
1680                        }
1681                        command_output.stdout
1682                    }
1683                }
1684            };
1685
1686            // Write secret to file and adjust permission and ownership of file.
1687            let mut file = File::create(secrets_file.as_path()).map_err(|source| {
1688                {
1689                    crate::non_admin_credentials::Error::SecretsFileCreate {
1690                        path: secrets_file.clone(),
1691                        system_user: system_user.clone(),
1692                        source,
1693                    }
1694                }
1695            })?;
1696            file.write_all(&secret).map_err(|source| {
1697                crate::non_admin_credentials::Error::SecretsFileWrite {
1698                    path: secrets_file.clone(),
1699                    system_user: system_user.clone(),
1700                    source,
1701                }
1702            })?;
1703            chown(
1704                &secrets_file,
1705                Some(user.uid.as_raw()),
1706                Some(user.gid.as_raw()),
1707            )
1708            .map_err(|source| Error::Chown {
1709                path: secrets_file.clone(),
1710                user: system_user.to_string(),
1711                source,
1712            })?;
1713            set_permissions(
1714                secrets_file.as_path(),
1715                Permissions::from_mode(SECRET_FILE_MODE),
1716            )
1717            .map_err(|source| Error::ApplyPermissions {
1718                path: secrets_file.clone(),
1719                mode: SECRET_FILE_MODE,
1720                source,
1721            })?;
1722        }
1723
1724        Ok(credentials)
1725    }
1726}
1727
1728impl From<SignstarConfig> for Vec<ExtendedUserMapping> {
1729    /// Creates a `Vec` of [`ExtendedUserMapping`] from a [`SignstarConfig`].
1730    ///
1731    /// A [`UserMapping`] can not be aware of credentials if it does not track at least one
1732    /// [`SystemUserId`] and one [`UserId`]. Therefore only those [`UserMapping`]s for which
1733    /// [`UserMapping::has_system_and_backend_user`] returns `true` are
1734    /// returned.
1735    fn from(value: SignstarConfig) -> Self {
1736        value
1737            .iter_user_mappings()
1738            .filter_map(|mapping| {
1739                if mapping.has_system_and_backend_user() {
1740                    Some(ExtendedUserMapping {
1741                        admin_secret_handling: value.get_administrative_secret_handling(),
1742                        non_admin_secret_handling: value.get_non_administrative_secret_handling(),
1743                        connections: value.iter_connections().cloned().collect(),
1744                        user_mapping: mapping.clone(),
1745                    })
1746                } else {
1747                    None
1748                }
1749            })
1750            .collect()
1751    }
1752}
1753
1754#[cfg(test)]
1755mod tests {
1756    use log::{LevelFilter, debug};
1757    use rstest::rstest;
1758    use signstar_common::logging::setup_logging;
1759    use signstar_crypto::{key::CryptographicKeyContext, openpgp::OpenPgpUserIdList};
1760    use tempfile::{NamedTempFile, TempDir};
1761    use testresult::TestResult;
1762
1763    use super::*;
1764
1765    mod nethsm {
1766        use super::*;
1767
1768        /// Ensures that NetHSM specific [`UserMapping`] variants work with
1769        /// [`UserMapping::get_system_user`].
1770        #[rstest]
1771        #[case::admin(UserMapping::NetHsmOnlyAdmin("test".parse()?), None)]
1772        #[case::metrics(
1773            UserMapping::SystemNetHsmMetrics {
1774                nethsm_users: NetHsmMetricsUsers::new(
1775                    SystemWideUserId::new("metrics".to_string())?,
1776                    vec![
1777                        UserId::new("operator".to_string())?,
1778                    ],
1779                )?,
1780                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1781                system_user: "system-metrics".parse()?,
1782            },
1783            Some("system-metrics".parse()?),
1784        )]
1785        #[case::backup(
1786            UserMapping::SystemNetHsmBackup {
1787                nethsm_user: "backup".parse()?,
1788                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1789                system_user: "system-backup".parse()?,
1790            },
1791            Some("system-backup".parse()?),
1792        )]
1793        #[case::operator(
1794            UserMapping::SystemNetHsmOperatorSigning {
1795                nethsm_user: "operator".parse()?,
1796                key_id: "key1".parse()?,
1797                nethsm_key_setup: SigningKeySetup::new(
1798                    "Curve25519".parse()?,
1799                    vec!["EdDsaSignature".parse()?],
1800                    None,
1801                    "EdDsa".parse()?,
1802                    CryptographicKeyContext::OpenPgp{
1803                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1804                        version: "v4".parse()?,
1805                    },
1806                )?,
1807                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1808                system_user: "system-operator".parse()?,
1809                tag: "tag1".to_string(),
1810            },
1811            Some("system-operator".parse()?),
1812        )]
1813        #[case::hermetic_system_metrics(
1814            UserMapping::HermeticSystemNetHsmMetrics {
1815                nethsm_users: NetHsmMetricsUsers::new(
1816                    "metrics".parse()?,
1817                    vec!["operator".parse()?],
1818                )?,
1819                system_user: "system-metrics".parse()?,
1820            },
1821            Some("system-metrics".parse()?),
1822        )]
1823        fn user_mapping_get_system_user(
1824            #[case] mapping: UserMapping,
1825            #[case] result: Option<SystemUserId>,
1826        ) -> TestResult {
1827            assert_eq!(mapping.get_system_user(), result.as_ref());
1828            Ok(())
1829        }
1830
1831        /// Ensures that NetHSM specific [`UserMapping`] variants work with
1832        /// [`UserMapping::backend_users`].
1833        #[rstest]
1834        #[case::admin_filter_default(
1835            UserMapping::NetHsmOnlyAdmin("admin".parse()?),
1836            UserMappingFilter::default(),
1837            &[],
1838        )]
1839        #[case::admin_filter_admin(
1840            UserMapping::NetHsmOnlyAdmin("admin".parse()?),
1841            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
1842            &["admin"],
1843        )]
1844        #[case::metrics_filter_default(
1845            UserMapping::SystemNetHsmMetrics {
1846                nethsm_users: NetHsmMetricsUsers::new(
1847                    SystemWideUserId::new("metrics".to_string())?,
1848                    vec![
1849                        UserId::new("operator".to_string())?,
1850                    ],
1851                )?,
1852                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1853                system_user: "system-metrics".parse()?,
1854            },
1855            UserMappingFilter::default(),
1856            &["metrics", "operator"],
1857        )]
1858        #[case::metrics_filter_admin(
1859            UserMapping::SystemNetHsmMetrics {
1860                nethsm_users: NetHsmMetricsUsers::new(
1861                    SystemWideUserId::new("metrics".to_string())?,
1862                    vec![
1863                        UserId::new("operator".to_string())?,
1864                    ],
1865                )?,
1866                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1867                system_user: "system-metrics".parse()?,
1868            },
1869            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
1870            &[],
1871        )]
1872        #[case::backup_filter_default(
1873            UserMapping::SystemNetHsmBackup {
1874                nethsm_user: "backup".parse()?,
1875                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1876                system_user: "system-backup".parse()?,
1877            },
1878            UserMappingFilter::default(),
1879            &["backup"],
1880        )]
1881        #[case::backup_filter_admin(
1882            UserMapping::SystemNetHsmBackup {
1883                nethsm_user: "backup".parse()?,
1884                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1885                system_user: "system-backup".parse()?,
1886            },
1887            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
1888            &[],
1889        )]
1890        #[case::operator_filter_default(
1891            UserMapping::SystemNetHsmOperatorSigning {
1892                nethsm_user: "operator".parse()?,
1893                key_id: "key1".parse()?,
1894                nethsm_key_setup: SigningKeySetup::new(
1895                    "Curve25519".parse()?,
1896                    vec!["EdDsaSignature".parse()?],
1897                    None,
1898                    "EdDsa".parse()?,
1899                    CryptographicKeyContext::OpenPgp{
1900                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1901                        version: "v4".parse()?,
1902                    },
1903                )?,
1904                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1905                system_user: "system-operator".parse()?,
1906                tag: "tag1".to_string(),
1907            },
1908            UserMappingFilter::default(),
1909            &["operator"],
1910        )]
1911        #[case::operator_filter_admin(
1912            UserMapping::SystemNetHsmOperatorSigning {
1913                nethsm_user: "operator".parse()?,
1914                key_id: "key1".parse()?,
1915                nethsm_key_setup: SigningKeySetup::new(
1916                    "Curve25519".parse()?,
1917                    vec!["EdDsaSignature".parse()?],
1918                    None,
1919                    "EdDsa".parse()?,
1920                    CryptographicKeyContext::OpenPgp{
1921                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
1922                        version: "v4".parse()?,
1923                    },
1924                )?,
1925                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1926                system_user: "system-operator".parse()?,
1927                tag: "tag1".to_string(),
1928            },
1929            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
1930            &[],
1931        )]
1932        #[case::hermetic_system_metrics_filter_default(
1933            UserMapping::HermeticSystemNetHsmMetrics {
1934                nethsm_users: NetHsmMetricsUsers::new(
1935                    "metrics".parse()?,
1936                    vec!["operator".parse()?],
1937                )?,
1938                system_user: "system-metrics".parse()?,
1939            },
1940            UserMappingFilter::default(),
1941            &["metrics", "operator"],
1942        )]
1943        #[case::hermetic_system_metrics_filter_admin(
1944            UserMapping::HermeticSystemNetHsmMetrics {
1945                nethsm_users: NetHsmMetricsUsers::new(
1946                    "metrics".parse()?,
1947                    vec!["operator".parse()?],
1948                )?,
1949                system_user: "system-metrics".parse()?,
1950            },
1951            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
1952            &[],
1953        )]
1954        fn user_mapping_backend_users(
1955            #[case] mapping: UserMapping,
1956            #[case] filter: UserMappingFilter,
1957            #[case] expected_names: &[&str],
1958        ) -> TestResult {
1959            assert_eq!(mapping.backend_users(filter), expected_names);
1960            Ok(())
1961        }
1962
1963        /// Ensures that NetHSM specific [`UserMapping`] variants work with
1964        /// [`UserMapping::backend_users_with_new_passphrase`].
1965        #[rstest]
1966        #[case::admin_filter_default(
1967            UserMapping::NetHsmOnlyAdmin("admin".parse()?),
1968            UserMappingFilter::default(),
1969            0
1970        )]
1971        #[case::admin_filter_admin(
1972            UserMapping::NetHsmOnlyAdmin("admin".parse()?),
1973            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
1974            1
1975        )]
1976        #[case::metrics_filter_default(
1977            UserMapping::SystemNetHsmMetrics {
1978                nethsm_users: NetHsmMetricsUsers::new(
1979                    SystemWideUserId::new("metrics".to_string())?,
1980                    vec![
1981                        UserId::new("operator".to_string())?,
1982                    ],
1983                )?,
1984                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1985                system_user: "system-metrics".parse()?,
1986            },
1987            UserMappingFilter::default(),
1988            2
1989        )]
1990        #[case::metrics_filter_admin(
1991            UserMapping::SystemNetHsmMetrics {
1992                nethsm_users: NetHsmMetricsUsers::new(
1993                    SystemWideUserId::new("metrics".to_string())?,
1994                    vec![
1995                        UserId::new("operator".to_string())?,
1996                    ],
1997                )?,
1998                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
1999                system_user: "system-metrics".parse()?,
2000            },
2001            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
2002            0
2003        )]
2004        #[case::backup_filter_default(
2005            UserMapping::SystemNetHsmBackup {
2006                nethsm_user: "backup".parse()?,
2007                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2008                system_user: "system-backup".parse()?,
2009            },
2010            UserMappingFilter::default(),
2011            1
2012        )]
2013        #[case::backup_filter_admin(
2014            UserMapping::SystemNetHsmBackup {
2015                nethsm_user: "backup".parse()?,
2016                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2017                system_user: "system-backup".parse()?,
2018            },
2019            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
2020            0
2021        )]
2022        #[case::operator_filter_default(
2023            UserMapping::SystemNetHsmOperatorSigning {
2024                nethsm_user: "operator".parse()?,
2025                key_id: "key1".parse()?,
2026                nethsm_key_setup: SigningKeySetup::new(
2027                    "Curve25519".parse()?,
2028                    vec!["EdDsaSignature".parse()?],
2029                    None,
2030                    "EdDsa".parse()?,
2031                    CryptographicKeyContext::OpenPgp{
2032                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2033                        version: "v4".parse()?,
2034                    },
2035                )?,
2036                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2037                system_user: "system-operator".parse()?,
2038                tag: "tag1".to_string(),
2039            },
2040            UserMappingFilter::default(),
2041            1
2042        )]
2043        #[case::operator_filter_admin(
2044            UserMapping::SystemNetHsmOperatorSigning {
2045                nethsm_user: "operator".parse()?,
2046                key_id: "key1".parse()?,
2047                nethsm_key_setup: SigningKeySetup::new(
2048                    "Curve25519".parse()?,
2049                    vec!["EdDsaSignature".parse()?],
2050                    None,
2051                    "EdDsa".parse()?,
2052                    CryptographicKeyContext::OpenPgp{
2053                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2054                        version: "v4".parse()?,
2055                    },
2056                )?,
2057                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2058                system_user: "system-operator".parse()?,
2059                tag: "tag1".to_string(),
2060            },
2061            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
2062            0
2063        )]
2064        #[case::hermetic_system_metrics_filter_default(
2065            UserMapping::HermeticSystemNetHsmMetrics {
2066                nethsm_users: NetHsmMetricsUsers::new(
2067                    "metrics".parse()?,
2068                    vec!["operator".parse()?],
2069                )?,
2070                system_user: "system-metrics".parse()?,
2071            },
2072            UserMappingFilter::default(),
2073            2
2074        )]
2075        #[case::hermetic_system_metrics_filter_admin(
2076            UserMapping::HermeticSystemNetHsmMetrics {
2077                nethsm_users: NetHsmMetricsUsers::new(
2078                    "metrics".parse()?,
2079                    vec!["operator".parse()?],
2080                )?,
2081                system_user: "system-metrics".parse()?,
2082            },
2083            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
2084            0
2085        )]
2086        fn user_mapping_backend_users_with_new_passphrase(
2087            #[case] mapping: UserMapping,
2088            #[case] filter: UserMappingFilter,
2089            #[case] expected_length: usize,
2090        ) -> TestResult {
2091            assert_eq!(
2092                mapping.backend_users_with_new_passphrase(filter).len(),
2093                expected_length
2094            );
2095            Ok(())
2096        }
2097
2098        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2099        /// [`UserMapping::get_nethsm_users`].
2100        #[rstest]
2101        #[case::admin(UserMapping::NetHsmOnlyAdmin("test".parse()?), vec!["test".parse()?])]
2102        #[case::metrics(
2103            UserMapping::SystemNetHsmMetrics {
2104                nethsm_users: NetHsmMetricsUsers::new(
2105                    SystemWideUserId::new("metrics".to_string())?,
2106                    vec![
2107                        UserId::new("operator".to_string())?,
2108                    ],
2109                )?,
2110                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2111                system_user: "system-metrics".parse()?,
2112            },
2113            vec!["metrics".parse()?, "operator".parse()?],
2114        )]
2115        #[case::backup(
2116            UserMapping::SystemNetHsmBackup {
2117                nethsm_user: "backup".parse()?,
2118                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2119                system_user: "system-backup".parse()?,
2120            },
2121            vec!["backup".parse()?],
2122        )]
2123        #[case::operator(
2124            UserMapping::SystemNetHsmOperatorSigning {
2125                nethsm_user: "operator".parse()?,
2126                key_id: "key1".parse()?,
2127                nethsm_key_setup: SigningKeySetup::new(
2128                    "Curve25519".parse()?,
2129                    vec!["EdDsaSignature".parse()?],
2130                    None,
2131                    "EdDsa".parse()?,
2132                    CryptographicKeyContext::OpenPgp{
2133                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2134                        version: "v4".parse()?,
2135                    },
2136                )?,
2137                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2138                system_user: "system-operator".parse()?,
2139                tag: "tag1".to_string(),
2140            },
2141            vec!["operator".parse()?],
2142        )]
2143        #[case::hermetic_system_metrics(
2144            UserMapping::HermeticSystemNetHsmMetrics {
2145                nethsm_users: NetHsmMetricsUsers::new(
2146                    "metrics".parse()?,
2147                    vec!["operator".parse()?],
2148                )?,
2149                system_user: "system-metrics".parse()?,
2150            },
2151            vec!["metrics".parse()?, "operator".parse()?],
2152        )]
2153        fn user_mapping_get_nethsm_users(
2154            #[case] mapping: UserMapping,
2155            #[case] expected: Vec<UserId>,
2156        ) -> TestResult {
2157            assert_eq!(mapping.get_nethsm_users(), expected);
2158            Ok(())
2159        }
2160
2161        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2162        /// [`UserMapping::get_nethsm_users_and_roles`].
2163        #[rstest]
2164        #[case::systemwide_admin(
2165            UserMapping::NetHsmOnlyAdmin("admin".parse()?),
2166            vec![("admin".parse()?, UserRole::Administrator)],
2167        )]
2168        #[case::namespace_admin(
2169            UserMapping::NetHsmOnlyAdmin("ns1~admin".parse()?),
2170            vec![("ns1~admin".parse()?, UserRole::Administrator)],
2171        )]
2172        #[case::metrics(
2173            UserMapping::SystemNetHsmMetrics {
2174                nethsm_users: NetHsmMetricsUsers::new(
2175                    SystemWideUserId::new("metrics".to_string())?,
2176                    vec![
2177                        UserId::new("operator".to_string())?,
2178                    ],
2179                )?,
2180                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2181                system_user: "system-metrics".parse()?,
2182            },
2183            vec![("metrics".parse()?, UserRole::Metrics), ("operator".parse()?, UserRole::Operator)],
2184        )]
2185        #[case::backup(
2186            UserMapping::SystemNetHsmBackup {
2187                nethsm_user: "backup".parse()?,
2188                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2189                system_user: "system-backup".parse()?,
2190            },
2191            vec![("backup".parse()?, UserRole::Backup)],
2192        )]
2193        #[case::systemwide_operator(
2194            UserMapping::SystemNetHsmOperatorSigning {
2195                nethsm_user: "operator".parse()?,
2196                key_id: "key1".parse()?,
2197                nethsm_key_setup: SigningKeySetup::new(
2198                    "Curve25519".parse()?,
2199                    vec!["EdDsaSignature".parse()?],
2200                    None,
2201                    "EdDsa".parse()?,
2202                    CryptographicKeyContext::OpenPgp{
2203                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2204                        version: "v4".parse()?,
2205                    },
2206                )?,
2207                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2208                system_user: "system-operator".parse()?,
2209                tag: "tag1".to_string(),
2210            },
2211            vec![(
2212                "operator".parse()?,
2213                UserRole::Operator,
2214            )],
2215        )]
2216        #[case::namespace_operator(
2217            UserMapping::SystemNetHsmOperatorSigning {
2218                nethsm_user: "ns1~operator".parse()?,
2219                key_id: "key1".parse()?,
2220                nethsm_key_setup: SigningKeySetup::new(
2221                    "Curve25519".parse()?,
2222                    vec!["EdDsaSignature".parse()?],
2223                    None,
2224                    "EdDsa".parse()?,
2225                    CryptographicKeyContext::OpenPgp{
2226                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2227                        version: "v4".parse()?,
2228                    },
2229                )?,
2230                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2231                system_user: "system-operator".parse()?,
2232                tag: "tag1".to_string(),
2233            },
2234            vec![(
2235                "ns1~operator".parse()?,
2236                UserRole::Operator,
2237            )],
2238        )]
2239        #[case::hermetic_system_metrics(
2240            UserMapping::HermeticSystemNetHsmMetrics {
2241                nethsm_users: NetHsmMetricsUsers::new(
2242                    "metrics".parse()?,
2243                    vec!["operator".parse()?],
2244                )?,
2245                system_user: "system-metrics".parse()?,
2246            },
2247            vec![
2248                ("metrics".parse()?, UserRole::Metrics),
2249                ("operator".parse()?, UserRole::Operator),
2250            ],
2251        )]
2252        fn usermapping_get_nethsm_users_and_roles(
2253            #[case] mapping: UserMapping,
2254            #[case] output: Vec<(UserId, UserRole)>,
2255        ) -> TestResult {
2256            assert_eq!(mapping.get_nethsm_users_and_roles(), output);
2257            Ok(())
2258        }
2259
2260        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2261        /// [`UserMapping::get_nethsm_user_key_and_tag`].
2262        #[rstest]
2263        #[case::admin_filter_all(UserMapping::NetHsmOnlyAdmin("test".parse()?), FilterUserKeys::All, Vec::new())]
2264        #[case::metrics(
2265            UserMapping::SystemNetHsmMetrics {
2266                nethsm_users: NetHsmMetricsUsers::new(
2267                    SystemWideUserId::new("metrics".to_string())?,
2268                    vec![
2269                        UserId::new("operator".to_string())?,
2270                    ],
2271                )?,
2272                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2273                system_user: "system-metrics".parse()?,
2274            },
2275            FilterUserKeys::All,
2276            Vec::new(),
2277        )]
2278        #[case::backup_filter_all(
2279            UserMapping::SystemNetHsmBackup {
2280                nethsm_user: "backup".parse()?,
2281                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2282                system_user: "system-backup".parse()?,
2283            },
2284            FilterUserKeys::All,
2285            Vec::new(),
2286        )]
2287        #[case::operator_signing_filter_all(
2288            UserMapping::SystemNetHsmOperatorSigning {
2289                nethsm_user: "operator".parse()?,
2290                key_id: "key1".parse()?,
2291                nethsm_key_setup: SigningKeySetup::new(
2292                    "Curve25519".parse()?,
2293                    vec!["EdDsaSignature".parse()?],
2294                    None,
2295                    "EdDsa".parse()?,
2296                    CryptographicKeyContext::OpenPgp{
2297                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2298                        version: "v4".parse()?,
2299                    },
2300                )?,
2301                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2302                system_user: "system-operator".parse()?,
2303                tag: "tag1".to_string(),
2304            },
2305            FilterUserKeys::All,
2306            vec![(
2307                "operator".parse()?,
2308                "key1".parse()?,
2309                SigningKeySetup::new(
2310                    "Curve25519".parse()?,
2311                    vec!["EdDsaSignature".parse()?],
2312                    None,
2313                    "EdDsa".parse()?,
2314                    CryptographicKeyContext::OpenPgp{
2315                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2316                        version: "v4".parse()?,
2317                    },
2318                )?,
2319                "tag1".to_string(),
2320            )],
2321        )]
2322        #[case::systemwide_operator_filter_namespaced(
2323            UserMapping::SystemNetHsmOperatorSigning {
2324                nethsm_user: "operator".parse()?,
2325                key_id: "key1".parse()?,
2326                nethsm_key_setup: SigningKeySetup::new(
2327                    "Curve25519".parse()?,
2328                    vec!["EdDsaSignature".parse()?],
2329                    None,
2330                    "EdDsa".parse()?,
2331                    CryptographicKeyContext::OpenPgp{
2332                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2333                        version: "v4".parse()?,
2334                    },
2335                )?,
2336                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2337                system_user: "system-operator".parse()?,
2338                tag: "tag1".to_string(),
2339            },
2340            FilterUserKeys::Namespaced,
2341            Vec::new(),
2342        )]
2343        #[case::systemwide_operator_filter_namespace(
2344            UserMapping::SystemNetHsmOperatorSigning {
2345                nethsm_user: "operator".parse()?,
2346                key_id: "key1".parse()?,
2347                nethsm_key_setup: SigningKeySetup::new(
2348                    "Curve25519".parse()?,
2349                    vec!["EdDsaSignature".parse()?],
2350                    None,
2351                    "EdDsa".parse()?,
2352                    CryptographicKeyContext::OpenPgp{
2353                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2354                        version: "v4".parse()?,
2355                    },
2356                )?,
2357                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2358                system_user: "system-operator".parse()?,
2359                tag: "tag1".to_string(),
2360            },
2361            FilterUserKeys::Namespace("ns1".parse()?),
2362            Vec::new(),
2363        )]
2364        #[case::namespace_operator_filter_namespaced(
2365            UserMapping::SystemNetHsmOperatorSigning {
2366                nethsm_user: "ns1~operator".parse()?,
2367                key_id: "key1".parse()?,
2368                nethsm_key_setup: SigningKeySetup::new(
2369                    "Curve25519".parse()?,
2370                    vec!["EdDsaSignature".parse()?],
2371                    None,
2372                    "EdDsa".parse()?,
2373                    CryptographicKeyContext::OpenPgp{
2374                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2375                        version: "v4".parse()?,
2376                    },
2377                )?,
2378                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2379                system_user: "system-operator".parse()?,
2380                tag: "tag1".to_string(),
2381            },
2382            FilterUserKeys::Namespaced,
2383            vec![(
2384                "ns1~operator".parse()?,
2385                "key1".parse()?,
2386                SigningKeySetup::new(
2387                    "Curve25519".parse()?,
2388                    vec!["EdDsaSignature".parse()?],
2389                    None,
2390                    "EdDsa".parse()?,
2391                    CryptographicKeyContext::OpenPgp{
2392                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2393                        version: "v4".parse()?,
2394                    },
2395                )?,
2396                "tag1".to_string(),
2397            )],
2398        )]
2399        #[case::namespace_operator_filter_matching_namespace(
2400            UserMapping::SystemNetHsmOperatorSigning {
2401                nethsm_user: "ns1~operator".parse()?,
2402                key_id: "key1".parse()?,
2403                nethsm_key_setup: SigningKeySetup::new(
2404                    "Curve25519".parse()?,
2405                    vec!["EdDsaSignature".parse()?],
2406                    None,
2407                    "EdDsa".parse()?,
2408                    CryptographicKeyContext::OpenPgp{
2409                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2410                        version: "v4".parse()?,
2411                    },
2412                )?,
2413                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2414                system_user: "system-operator".parse()?,
2415                tag: "tag1".to_string(),
2416            },
2417            FilterUserKeys::Namespace("ns1".parse()?),
2418            vec![(
2419                "ns1~operator".parse()?,
2420                "key1".parse()?,
2421                SigningKeySetup::new(
2422                    "Curve25519".parse()?,
2423                    vec!["EdDsaSignature".parse()?],
2424                    None,
2425                    "EdDsa".parse()?,
2426                    CryptographicKeyContext::OpenPgp{
2427                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2428                        version: "v4".parse()?,
2429                    },
2430                )?,
2431                "tag1".to_string(),
2432            )],
2433        )]
2434        #[case::namespace_operator_filter_matching_tag(
2435            UserMapping::SystemNetHsmOperatorSigning {
2436                nethsm_user: "ns1~operator".parse()?,
2437                key_id: "key1".parse()?,
2438                nethsm_key_setup: SigningKeySetup::new(
2439                    "Curve25519".parse()?,
2440                    vec!["EdDsaSignature".parse()?],
2441                    None,
2442                    "EdDsa".parse()?,
2443                    CryptographicKeyContext::OpenPgp{
2444                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2445                        version: "v4".parse()?,
2446                    },
2447                )?,
2448                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2449                system_user: "system-operator".parse()?,
2450                tag: "tag1".to_string(),
2451            },
2452            FilterUserKeys::Tag("tag1".parse()?),
2453            vec![(
2454                "ns1~operator".parse()?,
2455                "key1".parse()?,
2456                SigningKeySetup::new(
2457                    "Curve25519".parse()?,
2458                    vec!["EdDsaSignature".parse()?],
2459                    None,
2460                    "EdDsa".parse()?,
2461                    CryptographicKeyContext::OpenPgp{
2462                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2463                        version: "v4".parse()?,
2464                    },
2465                )?,
2466                "tag1".to_string(),
2467            )],
2468        )]
2469        #[case::namespace_operator_filter_mismatching_tag(
2470            UserMapping::SystemNetHsmOperatorSigning {
2471                nethsm_user: "ns1~operator".parse()?,
2472                key_id: "key1".parse()?,
2473                nethsm_key_setup: SigningKeySetup::new(
2474                    "Curve25519".parse()?,
2475                    vec!["EdDsaSignature".parse()?],
2476                    None,
2477                    "EdDsa".parse()?,
2478                    CryptographicKeyContext::OpenPgp{
2479                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2480                        version: "v4".parse()?,
2481                    },
2482                )?,
2483                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2484                system_user: "system-operator".parse()?,
2485                tag: "tag2".to_string(),
2486            },
2487            FilterUserKeys::Tag("tag1".parse()?),
2488            Vec::new(),
2489        )]
2490        #[case::hermetic_system_metrics_filter_all(
2491            UserMapping::HermeticSystemNetHsmMetrics {
2492                nethsm_users: NetHsmMetricsUsers::new(
2493                    "metrics".parse()?,
2494                    vec!["operator".parse()?],
2495                )?,
2496                system_user: "system-metrics".parse()?,
2497            },
2498            FilterUserKeys::All,
2499            Vec::new(),
2500        )]
2501        fn user_mapping_get_nethsm_user_key_and_tag(
2502            #[case] mapping: UserMapping,
2503            #[case] filter: FilterUserKeys,
2504            #[case] output: Vec<(UserId, KeyId, SigningKeySetup, String)>,
2505        ) -> TestResult {
2506            assert_eq!(mapping.get_nethsm_user_key_and_tag(filter), output);
2507            Ok(())
2508        }
2509
2510        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2511        /// [`UserMapping::get_nethsm_user_role_and_tags`].
2512        #[rstest]
2513        #[case::system_wide_admin(
2514            UserMapping::NetHsmOnlyAdmin("test".parse()?),
2515            vec![("test".parse()?, UserRole::Administrator, Vec::new())],
2516        )]
2517        #[case::metrics(
2518            UserMapping::SystemNetHsmMetrics {
2519                nethsm_users: NetHsmMetricsUsers::new(
2520                    SystemWideUserId::new("metrics".to_string())?,
2521                    vec![
2522                        UserId::new("operator".to_string())?,
2523                    ],
2524                )?,
2525                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2526                system_user: "system-metrics".parse()?,
2527            },
2528            vec![("metrics".parse()?, UserRole::Metrics, Vec::new()), ("operator".parse()?, UserRole::Operator, Vec::new())],
2529        )]
2530        #[case::backup(
2531            UserMapping::SystemNetHsmBackup {
2532                nethsm_user: "backup".parse()?,
2533                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2534                system_user: "system-backup".parse()?,
2535            },
2536            vec![("backup".parse()?, UserRole::Backup, Vec::new())],
2537        )]
2538        #[case::system_wide_operator(
2539            UserMapping::SystemNetHsmOperatorSigning {
2540                nethsm_user: "operator".parse()?,
2541                key_id: "key1".parse()?,
2542                nethsm_key_setup: SigningKeySetup::new(
2543                    "Curve25519".parse()?,
2544                    vec!["EdDsaSignature".parse()?],
2545                    None,
2546                    "EdDsa".parse()?,
2547                    CryptographicKeyContext::OpenPgp{
2548                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2549                        version: "v4".parse()?,
2550                    },
2551                )?,
2552                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2553                system_user: "system-operator".parse()?,
2554                tag: "tag1".to_string(),
2555            },
2556            vec![("operator".parse()?, UserRole::Operator, vec!["tag1".to_string()])],
2557        )]
2558        #[case::namespace_operator(
2559            UserMapping::SystemNetHsmOperatorSigning {
2560                nethsm_user: "ns1~operator".parse()?,
2561                key_id: "key1".parse()?,
2562                nethsm_key_setup: SigningKeySetup::new(
2563                    "Curve25519".parse()?,
2564                    vec!["EdDsaSignature".parse()?],
2565                    None,
2566                    "EdDsa".parse()?,
2567                    CryptographicKeyContext::OpenPgp{
2568                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2569                        version: "v4".parse()?,
2570                    },
2571                )?,
2572                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2573                system_user: "system-operator".parse()?,
2574                tag: "tag1".to_string(),
2575            },
2576            vec![("ns1~operator".parse()?, UserRole::Operator, vec!["tag1".to_string()])],
2577        )]
2578        #[case::hermetic_system_metrics_filter_all(
2579            UserMapping::HermeticSystemNetHsmMetrics {
2580                nethsm_users: NetHsmMetricsUsers::new(
2581                    "metrics".parse()?,
2582                    vec!["operator".parse()?],
2583                )?,
2584                system_user: "system-metrics".parse()?,
2585            },
2586            vec![("metrics".parse()?, UserRole::Metrics, Vec::new()), ("operator".parse()?, UserRole::Operator, Vec::new())],
2587        )]
2588        fn user_mapping_get_nethsm_user_role_and_tags(
2589            #[case] mapping: UserMapping,
2590            #[case] expected: Vec<(UserId, UserRole, Vec<String>)>,
2591        ) -> TestResult {
2592            assert_eq!(mapping.get_nethsm_user_role_and_tags(), expected);
2593            Ok(())
2594        }
2595
2596        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2597        /// [`UserMapping::get_ssh_authorized_key`].
2598        #[rstest]
2599        #[case::system_wide_admin(UserMapping::NetHsmOnlyAdmin("test".parse()?), None)]
2600        #[case::metrics(
2601            UserMapping::SystemNetHsmMetrics {
2602                nethsm_users: NetHsmMetricsUsers::new(
2603                    SystemWideUserId::new("metrics".to_string())?,
2604                    vec![
2605                        UserId::new("operator".to_string())?,
2606                    ],
2607                )?,
2608                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2609                system_user: "system-metrics".parse()?,
2610            },
2611            Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?),
2612        )]
2613        #[case::backup(
2614            UserMapping::SystemNetHsmBackup {
2615                nethsm_user: "backup".parse()?,
2616                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2617                system_user: "system-backup".parse()?,
2618            },
2619            Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?),
2620        )]
2621        #[case::system_wide_operator(
2622            UserMapping::SystemNetHsmOperatorSigning {
2623                nethsm_user: "operator".parse()?,
2624                key_id: "key1".parse()?,
2625                nethsm_key_setup: SigningKeySetup::new(
2626                    "Curve25519".parse()?,
2627                    vec!["EdDsaSignature".parse()?],
2628                    None,
2629                    "EdDsa".parse()?,
2630                    CryptographicKeyContext::OpenPgp{
2631                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2632                        version: "v4".parse()?,
2633                    },
2634                )?,
2635                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2636                system_user: "system-operator".parse()?,
2637                tag: "tag1".to_string(),
2638            },
2639            Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?),
2640        )]
2641        #[case::namespace_operator(
2642            UserMapping::SystemNetHsmOperatorSigning {
2643                nethsm_user: "ns1~operator".parse()?,
2644                key_id: "key1".parse()?,
2645                nethsm_key_setup: SigningKeySetup::new(
2646                    "Curve25519".parse()?,
2647                    vec!["EdDsaSignature".parse()?],
2648                    None,
2649                    "EdDsa".parse()?,
2650                    CryptographicKeyContext::OpenPgp{
2651                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2652                        version: "v4".parse()?,
2653                    },
2654                )?,
2655                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2656                system_user: "system-operator".parse()?,
2657                tag: "tag1".to_string(),
2658            },
2659            Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?),
2660        )]
2661        #[case::hermetic_system_metrics_filter_all(
2662            UserMapping::HermeticSystemNetHsmMetrics {
2663                nethsm_users: NetHsmMetricsUsers::new(
2664                    "metrics".parse()?,
2665                    vec!["operator".parse()?],
2666                )?,
2667                system_user: "system-metrics".parse()?,
2668            },
2669            None,
2670        )]
2671        fn user_mapping_get_ssh_authorized_key(
2672            #[case] mapping: UserMapping,
2673            #[case] output: Option<AuthorizedKeyEntry>,
2674        ) -> TestResult {
2675            assert_eq!(mapping.get_ssh_authorized_key(), output.as_ref());
2676            Ok(())
2677        }
2678
2679        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2680        /// [`UserMapping::get_nethsm_key_ids`].
2681        #[rstest]
2682        #[case::system_wide_admin(UserMapping::NetHsmOnlyAdmin("test".parse()?), None, Vec::new())]
2683        #[case::metrics(
2684            UserMapping::SystemNetHsmMetrics {
2685                nethsm_users: NetHsmMetricsUsers::new(
2686                    SystemWideUserId::new("metrics".to_string())?,
2687                    vec![
2688                        UserId::new("operator".to_string())?,
2689                    ],
2690                )?,
2691                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2692                system_user: "system-metrics".parse()?,
2693            },
2694            None,
2695            Vec::new()
2696        )]
2697        #[case::backup(
2698            UserMapping::SystemNetHsmBackup {
2699                nethsm_user: "backup".parse()?,
2700                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2701                system_user: "system-backup".parse()?,
2702            },
2703            None,
2704            Vec::new()
2705        )]
2706        #[case::system_wide_operator_target_system_wide(
2707            UserMapping::SystemNetHsmOperatorSigning {
2708                nethsm_user: "operator".parse()?,
2709                key_id: "key1".parse()?,
2710                nethsm_key_setup: SigningKeySetup::new(
2711                    "Curve25519".parse()?,
2712                    vec!["EdDsaSignature".parse()?],
2713                    None,
2714                    "EdDsa".parse()?,
2715                    CryptographicKeyContext::OpenPgp{
2716                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2717                        version: "v4".parse()?,
2718                    },
2719                )?,
2720                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2721                system_user: "system-operator".parse()?,
2722                tag: "tag1".to_string(),
2723            },
2724            None,
2725            vec!["key1".parse()?],
2726        )]
2727        #[case::system_wide_operator_target_namespace(
2728            UserMapping::SystemNetHsmOperatorSigning {
2729                nethsm_user: "operator".parse()?,
2730                key_id: "key1".parse()?,
2731                nethsm_key_setup: SigningKeySetup::new(
2732                    "Curve25519".parse()?,
2733                    vec!["EdDsaSignature".parse()?],
2734                    None,
2735                    "EdDsa".parse()?,
2736                    CryptographicKeyContext::OpenPgp{
2737                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2738                        version: "v4".parse()?,
2739                    },
2740                )?,
2741                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2742                system_user: "system-operator".parse()?,
2743                tag: "tag1".to_string(),
2744            },
2745            Some("ns1".parse()?),
2746            Vec::new(),
2747        )]
2748        #[case::namespace_operator_target_system_wide(
2749            UserMapping::SystemNetHsmOperatorSigning {
2750                nethsm_user: "ns1~operator".parse()?,
2751                key_id: "key1".parse()?,
2752                nethsm_key_setup: SigningKeySetup::new(
2753                    "Curve25519".parse()?,
2754                    vec!["EdDsaSignature".parse()?],
2755                    None,
2756                    "EdDsa".parse()?,
2757                    CryptographicKeyContext::OpenPgp{
2758                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2759                        version: "v4".parse()?,
2760                    },
2761                )?,
2762                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2763                system_user: "system-operator".parse()?,
2764                tag: "tag1".to_string(),
2765            },
2766            None,
2767            Vec::new(),
2768        )]
2769        #[case::namespace_operator_target_namespace(
2770            UserMapping::SystemNetHsmOperatorSigning {
2771                nethsm_user: "ns1~operator".parse()?,
2772                key_id: "key1".parse()?,
2773                nethsm_key_setup: SigningKeySetup::new(
2774                    "Curve25519".parse()?,
2775                    vec!["EdDsaSignature".parse()?],
2776                    None,
2777                    "EdDsa".parse()?,
2778                    CryptographicKeyContext::OpenPgp{
2779                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2780                        version: "v4".parse()?,
2781                    },
2782                )?,
2783                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2784                system_user: "system-operator".parse()?,
2785                tag: "tag1".to_string(),
2786            },
2787            Some("ns1".parse()?),
2788            vec!["key1".parse()?],
2789        )]
2790        #[case::hermetic_system_metrics_filter_all(
2791            UserMapping::HermeticSystemNetHsmMetrics {
2792                nethsm_users: NetHsmMetricsUsers::new(
2793                    "metrics".parse()?,
2794                    vec!["operator".parse()?],
2795                )?,
2796                system_user: "system-metrics".parse()?,
2797            },
2798            None,
2799            Vec::new()
2800        )]
2801        fn user_mapping_get_nethsm_key_ids(
2802            #[case] mapping: UserMapping,
2803            #[case] namespace: Option<NamespaceId>,
2804            #[case] output: Vec<KeyId>,
2805        ) -> TestResult {
2806            assert_eq!(mapping.get_nethsm_key_ids(namespace.as_ref()), output);
2807            Ok(())
2808        }
2809
2810        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2811        /// [`UserMapping::get_nethsm_key_ids`].
2812        #[rstest]
2813        #[case::system_wide_admin(UserMapping::NetHsmOnlyAdmin("test".parse()?), None, Vec::new())]
2814        #[case::metrics(
2815            UserMapping::SystemNetHsmMetrics {
2816                nethsm_users: NetHsmMetricsUsers::new(
2817                    SystemWideUserId::new("metrics".to_string())?,
2818                    vec![
2819                        UserId::new("operator".to_string())?,
2820                    ],
2821                )?,
2822                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2823                system_user: "system-metrics".parse()?,
2824            },
2825            None,
2826            Vec::new()
2827        )]
2828        #[case::backup(
2829            UserMapping::SystemNetHsmBackup {
2830                nethsm_user: "backup".parse()?,
2831                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2832                system_user: "system-backup".parse()?,
2833            },
2834            None,
2835            Vec::new()
2836        )]
2837        #[case::system_wide_operator_target_system_wide(
2838            UserMapping::SystemNetHsmOperatorSigning {
2839                nethsm_user: "operator".parse()?,
2840                key_id: "key1".parse()?,
2841                nethsm_key_setup: SigningKeySetup::new(
2842                    "Curve25519".parse()?,
2843                    vec!["EdDsaSignature".parse()?],
2844                    None,
2845                    "EdDsa".parse()?,
2846                    CryptographicKeyContext::OpenPgp{
2847                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2848                        version: "v4".parse()?,
2849                    },
2850                )?,
2851                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2852                system_user: "system-operator".parse()?,
2853                tag: "tag1".to_string(),
2854            },
2855            None,
2856            vec!["tag1"],
2857        )]
2858        #[case::system_wide_operator_target_namespace(
2859            UserMapping::SystemNetHsmOperatorSigning {
2860                nethsm_user: "operator".parse()?,
2861                key_id: "key1".parse()?,
2862                nethsm_key_setup: SigningKeySetup::new(
2863                    "Curve25519".parse()?,
2864                    vec!["EdDsaSignature".parse()?],
2865                    None,
2866                    "EdDsa".parse()?,
2867                    CryptographicKeyContext::OpenPgp{
2868                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2869                        version: "v4".parse()?,
2870                    },
2871                )?,
2872                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2873                system_user: "system-operator".parse()?,
2874                tag: "tag1".to_string(),
2875            },
2876            Some("ns1".parse()?),
2877            Vec::new(),
2878        )]
2879        #[case::namespace_operator_target_system_wide(
2880            UserMapping::SystemNetHsmOperatorSigning {
2881                nethsm_user: "ns1~operator".parse()?,
2882                key_id: "key1".parse()?,
2883                nethsm_key_setup: SigningKeySetup::new(
2884                    "Curve25519".parse()?,
2885                    vec!["EdDsaSignature".parse()?],
2886                    None,
2887                    "EdDsa".parse()?,
2888                    CryptographicKeyContext::OpenPgp{
2889                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2890                        version: "v4".parse()?,
2891                    },
2892                )?,
2893                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2894                system_user: "system-operator".parse()?,
2895                tag: "tag1".to_string(),
2896            },
2897            None,
2898            Vec::new(),
2899        )]
2900        #[case::namespace_operator_target_namespace(
2901            UserMapping::SystemNetHsmOperatorSigning {
2902                nethsm_user: "ns1~operator".parse()?,
2903                key_id: "key1".parse()?,
2904                nethsm_key_setup: SigningKeySetup::new(
2905                    "Curve25519".parse()?,
2906                    vec!["EdDsaSignature".parse()?],
2907                    None,
2908                    "EdDsa".parse()?,
2909                    CryptographicKeyContext::OpenPgp{
2910                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2911                        version: "v4".parse()?,
2912                    },
2913                )?,
2914                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2915                system_user: "system-operator".parse()?,
2916                tag: "tag1".to_string(),
2917            },
2918            Some("ns1".parse()?),
2919            vec!["tag1"],
2920        )]
2921        #[case::hermetic_system_metrics_filter_all(
2922            UserMapping::HermeticSystemNetHsmMetrics {
2923                nethsm_users: NetHsmMetricsUsers::new(
2924                    "metrics".parse()?,
2925                    vec!["operator".parse()?],
2926                )?,
2927                system_user: "system-metrics".parse()?,
2928            },
2929            None,
2930            Vec::new()
2931        )]
2932        fn user_mapping_get_nethsm_tags(
2933            #[case] mapping: UserMapping,
2934            #[case] namespace: Option<NamespaceId>,
2935            #[case] output: Vec<&str>,
2936        ) -> TestResult {
2937            assert_eq!(mapping.get_nethsm_tags(namespace.as_ref()), output);
2938            Ok(())
2939        }
2940
2941        /// Ensures that NetHSM specific [`UserMapping`] variants work with
2942        /// [`UserMapping::get_nethsm_namespaces`].
2943        #[rstest]
2944        #[case::system_wide_admin(UserMapping::NetHsmOnlyAdmin("test".parse()?), Vec::new())]
2945        #[case::namespace_admin(UserMapping::NetHsmOnlyAdmin("ns1~test".parse()?), vec!["ns1".parse()?])]
2946        #[case::metrics(
2947            UserMapping::SystemNetHsmMetrics {
2948                nethsm_users: NetHsmMetricsUsers::new(
2949                    SystemWideUserId::new("metrics".to_string())?,
2950                    vec![
2951                        UserId::new("operator".to_string())?,
2952                    ],
2953                )?,
2954                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2955                system_user: "system-metrics".parse()?,
2956            },
2957            Vec::new(),
2958        )]
2959        #[case::backup(
2960            UserMapping::SystemNetHsmBackup {
2961                nethsm_user: "backup".parse()?,
2962                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2963                system_user: "system-backup".parse()?,
2964            },
2965            Vec::new(),
2966        )]
2967        #[case::system_wide_operator(
2968            UserMapping::SystemNetHsmOperatorSigning {
2969                nethsm_user: "operator".parse()?,
2970                key_id: "key1".parse()?,
2971                nethsm_key_setup: SigningKeySetup::new(
2972                    "Curve25519".parse()?,
2973                    vec!["EdDsaSignature".parse()?],
2974                    None,
2975                    "EdDsa".parse()?,
2976                    CryptographicKeyContext::OpenPgp{
2977                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2978                        version: "v4".parse()?,
2979                    },
2980                )?,
2981                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
2982                system_user: "system-operator".parse()?,
2983                tag: "tag1".to_string(),
2984            },
2985            Vec::new(),
2986        )]
2987        #[case::namespace_operator(
2988            UserMapping::SystemNetHsmOperatorSigning {
2989                nethsm_user: "ns1~operator".parse()?,
2990                key_id: "key1".parse()?,
2991                nethsm_key_setup: SigningKeySetup::new(
2992                    "Curve25519".parse()?,
2993                    vec!["EdDsaSignature".parse()?],
2994                    None,
2995                    "EdDsa".parse()?,
2996                    CryptographicKeyContext::OpenPgp{
2997                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
2998                        version: "v4".parse()?,
2999                    },
3000                )?,
3001                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3002                system_user: "system-operator".parse()?,
3003                tag: "tag1".to_string(),
3004            },
3005            vec!["ns1".parse()?],
3006        )]
3007        #[case::hermetic_system_metrics_filter_all(
3008            UserMapping::HermeticSystemNetHsmMetrics {
3009                nethsm_users: NetHsmMetricsUsers::new(
3010                    "metrics".parse()?,
3011                    vec!["operator".parse()?],
3012                )?,
3013                system_user: "system-metrics".parse()?,
3014            },
3015            Vec::new(),
3016        )]
3017        fn user_mapping_get_nethsm_namespaces(
3018            #[case] mapping: UserMapping,
3019            #[case] output: Vec<NamespaceId>,
3020        ) -> TestResult {
3021            assert_eq!(mapping.get_nethsm_namespaces(), output);
3022            Ok(())
3023        }
3024
3025        /// Ensures that NetHSM specific [`UserMapping`] variants work with
3026        /// [`UserMapping::has_system_and_backend_user`].
3027        #[rstest]
3028        #[case::system_wide_admin(UserMapping::NetHsmOnlyAdmin("test".parse()?), false)]
3029        #[case::namespace_admin(UserMapping::NetHsmOnlyAdmin("ns1~test".parse()?), false)]
3030        #[case::metrics(
3031            UserMapping::SystemNetHsmMetrics {
3032                nethsm_users: NetHsmMetricsUsers::new(
3033                    SystemWideUserId::new("metrics".to_string())?,
3034                    vec![
3035                        UserId::new("operator".to_string())?,
3036                    ],
3037                )?,
3038                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3039                system_user: "system-metrics".parse()?,
3040            },
3041            true,
3042        )]
3043        #[case::backup(
3044            UserMapping::SystemNetHsmBackup {
3045                nethsm_user: "backup".parse()?,
3046                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3047                system_user: "system-backup".parse()?,
3048            },
3049            true,
3050        )]
3051        #[case::system_wide_operator(
3052            UserMapping::SystemNetHsmOperatorSigning {
3053                nethsm_user: "operator".parse()?,
3054                key_id: "key1".parse()?,
3055                nethsm_key_setup: SigningKeySetup::new(
3056                    "Curve25519".parse()?,
3057                    vec!["EdDsaSignature".parse()?],
3058                    None,
3059                    "EdDsa".parse()?,
3060                    CryptographicKeyContext::OpenPgp{
3061                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3062                        version: "v4".parse()?,
3063                    },
3064                )?,
3065                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3066                system_user: "system-operator".parse()?,
3067                tag: "tag1".to_string(),
3068            },
3069            true,
3070        )]
3071        #[case::namespace_operator(
3072            UserMapping::SystemNetHsmOperatorSigning {
3073                nethsm_user: "ns1~operator".parse()?,
3074                key_id: "key1".parse()?,
3075                nethsm_key_setup: SigningKeySetup::new(
3076                    "Curve25519".parse()?,
3077                    vec!["EdDsaSignature".parse()?],
3078                    None,
3079                    "EdDsa".parse()?,
3080                    CryptographicKeyContext::OpenPgp{
3081                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3082                        version: "v4".parse()?,
3083                    },
3084                )?,
3085                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3086                system_user: "system-operator".parse()?,
3087                tag: "tag1".to_string(),
3088            },
3089            true,
3090        )]
3091        #[case::hermetic_system_metrics_filter_all(
3092            UserMapping::HermeticSystemNetHsmMetrics {
3093                nethsm_users: NetHsmMetricsUsers::new(
3094                    "metrics".parse()?,
3095                    vec!["operator".parse()?],
3096                )?,
3097                system_user: "system-metrics".parse()?,
3098            },
3099            true,
3100        )]
3101        fn user_mapping_has_system_and_backend_user(
3102            #[case] mapping: UserMapping,
3103            #[case] output: bool,
3104        ) -> TestResult {
3105            assert_eq!(mapping.has_system_and_backend_user(), output);
3106            Ok(())
3107        }
3108    }
3109
3110    mod system {
3111        use super::*;
3112
3113        /// Ensures that backend agnostic [`UserMapping`] variants work with
3114        /// [`UserMapping::get_system_user`].
3115        #[rstest]
3116        #[case::share_download(
3117            UserMapping::SystemOnlyShareDownload {
3118                system_user: "system-share".parse()?,
3119                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3120            },
3121            Some("system-share".parse()?),
3122        )]
3123        #[case::share_upload(
3124            UserMapping::SystemOnlyShareUpload {
3125                system_user: "system-share".parse()?,
3126                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3127            },
3128            Some("system-share".parse()?),
3129        )]
3130        #[case::wireguard_download(
3131            UserMapping::SystemOnlyWireGuardDownload {
3132                system_user: "system-wireguard".parse()?,
3133                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3134            },
3135            Some("system-wireguard".parse()?),
3136        )]
3137        fn user_mapping_get_system_user(
3138            #[case] mapping: UserMapping,
3139            #[case] result: Option<SystemUserId>,
3140        ) -> TestResult {
3141            assert_eq!(mapping.get_system_user(), result.as_ref());
3142            Ok(())
3143        }
3144
3145        /// Ensures that backend agnostic [`UserMapping`] variants work with
3146        /// [`UserMapping::backend_users`].
3147        #[rstest]
3148        #[case::share_download_filter_default(
3149            UserMapping::SystemOnlyShareDownload {
3150                system_user: "system-share".parse()?,
3151                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3152            },
3153            UserMappingFilter::default(),
3154        )]
3155        #[case::share_download_filter_admin(
3156            UserMapping::SystemOnlyShareDownload {
3157                system_user: "system-share".parse()?,
3158                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3159            },
3160            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3161        )]
3162        #[case::share_upload_filter_default(
3163            UserMapping::SystemOnlyShareUpload {
3164                system_user: "system-share".parse()?,
3165                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3166            },
3167            UserMappingFilter::default(),
3168        )]
3169        #[case::share_upload_filter_admin(
3170            UserMapping::SystemOnlyShareUpload {
3171                system_user: "system-share".parse()?,
3172                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3173            },
3174            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3175        )]
3176        #[case::wireguard_download_filter_default(
3177            UserMapping::SystemOnlyWireGuardDownload {
3178                system_user: "system-wireguard".parse()?,
3179                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3180            },
3181            UserMappingFilter::default(),
3182        )]
3183        #[case::wireguard_download_filter_admin(
3184            UserMapping::SystemOnlyWireGuardDownload {
3185                system_user: "system-wireguard".parse()?,
3186                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3187            },
3188            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3189        )]
3190        fn user_mapping_backend_users(
3191            #[case] mapping: UserMapping,
3192            #[case] filter: UserMappingFilter,
3193        ) -> TestResult {
3194            assert_eq!(mapping.backend_users(filter).len(), 0);
3195            Ok(())
3196        }
3197
3198        /// Ensures that backend agnostic [`UserMapping`] variants work with
3199        /// [`UserMapping::backend_users_with_new_passphrase`].
3200        #[rstest]
3201        #[case::share_download_filter_default(
3202            UserMapping::SystemOnlyShareDownload {
3203                system_user: "system-share".parse()?,
3204                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3205            },
3206            UserMappingFilter::default(),
3207        )]
3208        #[case::share_download_filter_admin(
3209            UserMapping::SystemOnlyShareDownload {
3210                system_user: "system-share".parse()?,
3211                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3212            },
3213            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3214        )]
3215        #[case::share_upload_filter_default(
3216            UserMapping::SystemOnlyShareUpload {
3217                system_user: "system-share".parse()?,
3218                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3219            },
3220            UserMappingFilter::default(),
3221        )]
3222        #[case::share_upload_filter_admin(
3223            UserMapping::SystemOnlyShareUpload {
3224                system_user: "system-share".parse()?,
3225                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3226            },
3227            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3228        )]
3229        #[case::wireguard_download_filter_default(
3230            UserMapping::SystemOnlyWireGuardDownload {
3231                system_user: "system-wireguard".parse()?,
3232                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3233            },
3234            UserMappingFilter::default(),
3235        )]
3236        #[case::wireguard_download_filter_admin(
3237            UserMapping::SystemOnlyWireGuardDownload {
3238                system_user: "system-wireguard".parse()?,
3239                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3240            },
3241            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3242        )]
3243        fn user_mapping_backend_users_with_new_passphrase(
3244            #[case] mapping: UserMapping,
3245            #[case] filter: UserMappingFilter,
3246        ) -> TestResult {
3247            assert!(mapping.backend_users_with_new_passphrase(filter).is_empty());
3248            Ok(())
3249        }
3250
3251        /// Ensures that backend agnostic [`UserMapping`] variants work with
3252        /// [`UserMapping::get_nethsm_users`].
3253        #[rstest]
3254        #[case::share_download(
3255            UserMapping::SystemOnlyShareDownload {
3256                system_user: "system-share".parse()?,
3257                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3258            },
3259            Vec::new(),
3260        )]
3261        #[case::share_upload(
3262            UserMapping::SystemOnlyShareUpload {
3263                system_user: "system-share".parse()?,
3264                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3265            },
3266            Vec::new(),
3267        )]
3268        #[case::wireguard_download(
3269            UserMapping::SystemOnlyWireGuardDownload {
3270                system_user: "system-wireguard".parse()?,
3271                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3272            },
3273            Vec::new(),
3274        )]
3275        fn user_mapping_get_nethsm_users(
3276            #[case] mapping: UserMapping,
3277            #[case] expected: Vec<UserId>,
3278        ) -> TestResult {
3279            assert_eq!(mapping.get_nethsm_users(), expected);
3280            Ok(())
3281        }
3282
3283        /// Ensures that backend agnostic [`UserMapping`] variants work with
3284        /// [`UserMapping::get_nethsm_users_and_roles`].
3285        #[rstest]
3286        #[case::share_download(
3287            UserMapping::SystemOnlyShareDownload {
3288                system_user: "system-share".parse()?,
3289                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3290            },
3291        )]
3292        #[case::share_upload(
3293            UserMapping::SystemOnlyShareUpload {
3294                system_user: "system-share".parse()?,
3295                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3296            },
3297        )]
3298        #[case::wireguard_download(
3299            UserMapping::SystemOnlyWireGuardDownload {
3300                system_user: "system-wireguard".parse()?,
3301                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3302            },
3303        )]
3304        fn usermapping_get_nethsm_users_and_roles(#[case] mapping: UserMapping) -> TestResult {
3305            let expected: Vec<(UserId, UserRole)> = Vec::new();
3306            assert_eq!(mapping.get_nethsm_users_and_roles(), expected);
3307            Ok(())
3308        }
3309
3310        /// Ensures that backend agnostic [`UserMapping`] variants work with
3311        /// [`UserMapping::get_nethsm_user_key_and_tag`].
3312        #[rstest]
3313        #[case::share_download(
3314            UserMapping::SystemOnlyShareDownload {
3315                system_user: "system-share".parse()?,
3316                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3317            },
3318            FilterUserKeys::All,
3319        )]
3320        #[case::share_upload(
3321            UserMapping::SystemOnlyShareUpload {
3322                system_user: "system-share".parse()?,
3323                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3324            },
3325            FilterUserKeys::All,
3326        )]
3327        #[case::wireguard_download(
3328            UserMapping::SystemOnlyWireGuardDownload {
3329                system_user: "system-wireguard".parse()?,
3330                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3331            },
3332            FilterUserKeys::All,
3333        )]
3334        fn user_mapping_get_nethsm_user_key_and_tag(
3335            #[case] mapping: UserMapping,
3336            #[case] filter: FilterUserKeys,
3337        ) -> TestResult {
3338            let expected: Vec<(UserId, KeyId, SigningKeySetup, String)> = Vec::new();
3339            assert_eq!(mapping.get_nethsm_user_key_and_tag(filter), expected);
3340            Ok(())
3341        }
3342
3343        /// Ensures that backend agnostic [`UserMapping`] variants work with
3344        /// [`UserMapping::get_nethsm_user_role_and_tags`].
3345        #[rstest]
3346        #[case::share_download(
3347            UserMapping::SystemOnlyShareDownload {
3348                system_user: "system-share".parse()?,
3349                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3350            },
3351        )]
3352        #[case::share_upload(
3353            UserMapping::SystemOnlyShareUpload {
3354                system_user: "system-share".parse()?,
3355                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3356            },
3357        )]
3358        #[case::wireguard_download(
3359            UserMapping::SystemOnlyWireGuardDownload {
3360                system_user: "system-wireguard".parse()?,
3361                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3362            },
3363        )]
3364        fn user_mapping_get_nethsm_user_role_and_tags(#[case] mapping: UserMapping) -> TestResult {
3365            let expected: Vec<(UserId, UserRole, Vec<String>)> = Vec::new();
3366            assert_eq!(mapping.get_nethsm_user_role_and_tags(), expected);
3367            Ok(())
3368        }
3369
3370        /// Ensures that backend agnostic [`UserMapping`] variants work with
3371        /// [`UserMapping::get_ssh_authorized_key`].
3372        #[rstest]
3373        #[case::share_download(
3374            UserMapping::SystemOnlyShareDownload {
3375                system_user: "system-share".parse()?,
3376                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3377            },
3378        )]
3379        #[case::share_upload(
3380            UserMapping::SystemOnlyShareUpload {
3381                system_user: "system-share".parse()?,
3382                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3383            },
3384        )]
3385        #[case::wireguard_download(
3386            UserMapping::SystemOnlyWireGuardDownload {
3387                system_user: "system-wireguard".parse()?,
3388                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3389            },
3390        )]
3391        fn user_mapping_get_ssh_authorized_key(#[case] mapping: UserMapping) -> TestResult {
3392            let expected: Option<AuthorizedKeyEntry> = Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?);
3393            assert_eq!(mapping.get_ssh_authorized_key(), expected.as_ref());
3394            Ok(())
3395        }
3396
3397        /// Ensures that backend agnostic [`UserMapping`] variants work with
3398        /// [`UserMapping::get_nethsm_key_ids`].
3399        #[rstest]
3400        #[case::share_download_target_system_wide(
3401            UserMapping::SystemOnlyShareDownload {
3402                system_user: "system-share".parse()?,
3403                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3404            },
3405            None,
3406        )]
3407        #[case::share_download_target_namespace(
3408            UserMapping::SystemOnlyShareDownload {
3409                system_user: "system-share".parse()?,
3410                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3411            },
3412            Some("ns1".parse()?),
3413        )]
3414        #[case::share_upload_target_system_wide(
3415            UserMapping::SystemOnlyShareUpload {
3416                system_user: "system-share".parse()?,
3417                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3418            },
3419            None,
3420        )]
3421        #[case::share_upload_target_namespace(
3422            UserMapping::SystemOnlyShareUpload {
3423                system_user: "system-share".parse()?,
3424                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3425            },
3426            Some("ns1".parse()?),
3427        )]
3428        #[case::wireguard_download_target_system_wide(
3429            UserMapping::SystemOnlyWireGuardDownload {
3430                system_user: "system-wireguard".parse()?,
3431                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3432            },
3433            None,
3434        )]
3435        #[case::wireguard_download_target_namespace(
3436            UserMapping::SystemOnlyWireGuardDownload {
3437                system_user: "system-wireguard".parse()?,
3438                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3439            },
3440            Some("ns1".parse()?),
3441        )]
3442        fn user_mapping_get_nethsm_key_ids(
3443            #[case] mapping: UserMapping,
3444            #[case] namespace: Option<NamespaceId>,
3445        ) -> TestResult {
3446            let expected: Vec<KeyId> = Vec::new();
3447            assert_eq!(mapping.get_nethsm_key_ids(namespace.as_ref()), expected);
3448            Ok(())
3449        }
3450
3451        /// Ensures that backend agnostic [`UserMapping`] variants work with
3452        /// [`UserMapping::get_nethsm_key_ids`].
3453        #[rstest]
3454        #[case::share_download_target_system_wide(
3455            UserMapping::SystemOnlyShareDownload {
3456                system_user: "system-share".parse()?,
3457                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3458            },
3459            None,
3460        )]
3461        #[case::share_download_target_namespace(
3462            UserMapping::SystemOnlyShareDownload {
3463                system_user: "system-share".parse()?,
3464                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3465            },
3466            Some("ns1".parse()?),
3467        )]
3468        #[case::share_upload_target_system_wide(
3469            UserMapping::SystemOnlyShareUpload {
3470                system_user: "system-share".parse()?,
3471                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3472            },
3473            None,
3474        )]
3475        #[case::share_upload_target_namespace(
3476            UserMapping::SystemOnlyShareUpload {
3477                system_user: "system-share".parse()?,
3478                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3479            },
3480            Some("ns1".parse()?),
3481        )]
3482        #[case::wireguard_download_target_system_wide(
3483            UserMapping::SystemOnlyWireGuardDownload {
3484                system_user: "system-wireguard".parse()?,
3485                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3486            },
3487            None,
3488        )]
3489        #[case::wireguard_download_target_namespace(
3490            UserMapping::SystemOnlyWireGuardDownload {
3491                system_user: "system-wireguard".parse()?,
3492                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3493            },
3494            Some("ns1".parse()?),
3495        )]
3496        fn user_mapping_get_nethsm_tags(
3497            #[case] mapping: UserMapping,
3498            #[case] namespace: Option<NamespaceId>,
3499        ) -> TestResult {
3500            let expected: Vec<&str> = Vec::new();
3501            assert_eq!(mapping.get_nethsm_tags(namespace.as_ref()), expected);
3502            Ok(())
3503        }
3504
3505        /// Ensures that backend agnostic [`UserMapping`] variants work with
3506        /// [`UserMapping::get_nethsm_namespaces`].
3507        #[rstest]
3508        #[case::share_download(
3509            UserMapping::SystemOnlyShareDownload {
3510                system_user: "system-share".parse()?,
3511                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3512            },
3513            Vec::new(),
3514        )]
3515        #[case::share_upload(
3516            UserMapping::SystemOnlyShareUpload {
3517                system_user: "system-share".parse()?,
3518                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3519            },
3520            Vec::new(),
3521        )]
3522        #[case::wireguard_download(
3523            UserMapping::SystemOnlyWireGuardDownload {
3524                system_user: "system-wireguard".parse()?,
3525                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3526            },
3527            Vec::new(),
3528        )]
3529        fn user_mapping_get_nethsm_namespaces(
3530            #[case] mapping: UserMapping,
3531            #[case] output: Vec<NamespaceId>,
3532        ) -> TestResult {
3533            assert_eq!(mapping.get_nethsm_namespaces(), output);
3534            Ok(())
3535        }
3536
3537        /// Ensures that backend agnostic [`UserMapping`] variants work with
3538        /// [`UserMapping::has_system_and_backend_user`].
3539        #[rstest]
3540        #[case::share_download(
3541            UserMapping::SystemOnlyShareDownload {
3542                system_user: "system-share".parse()?,
3543                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3544            },
3545            false,
3546        )]
3547        #[case::share_upload(
3548            UserMapping::SystemOnlyShareUpload {
3549                system_user: "system-share".parse()?,
3550                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3551            },
3552            false,
3553        )]
3554        #[case::wireguard_download(
3555            UserMapping::SystemOnlyWireGuardDownload {
3556                system_user: "system-wireguard".parse()?,
3557                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3558            },
3559            false,
3560        )]
3561        fn user_mapping_has_system_and_backend_user(
3562            #[case] mapping: UserMapping,
3563            #[case] output: bool,
3564        ) -> TestResult {
3565            assert_eq!(mapping.has_system_and_backend_user(), output);
3566            Ok(())
3567        }
3568    }
3569
3570    #[cfg(feature = "yubihsm2")]
3571    mod yubihsm {
3572        use super::*;
3573
3574        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
3575        /// [`UserMapping::get_system_user`].
3576        #[rstest]
3577        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1), None)]
3578        #[case::backup(
3579            UserMapping::SystemYubiHsm2Backup{
3580                authentication_key_id: 1,
3581                wrapping_key_id: 2,
3582                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3583                system_user: "backup".parse()?,
3584            },
3585            Some("backup".parse()?),
3586        )]
3587        #[case::metrics(
3588            UserMapping::SystemYubiHsm2Metrics {
3589                authentication_key_id: 1,
3590                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3591                system_user: "metrics".parse()?,
3592            },
3593            Some("metrics".parse()?),
3594        )]
3595        #[case::hermetic_metrics(
3596            UserMapping::HermeticSystemYubiHsm2Metrics {
3597                authentication_key_id: 1,
3598                system_user: "metrics".parse()?,
3599            },
3600            Some("metrics".parse()?),
3601        )]
3602        #[case::operator_signing(
3603            UserMapping::SystemYubiHsm2OperatorSigning {
3604                authentication_key_id: 1,
3605                backend_key_setup: SigningKeySetup::new(
3606                    "Curve25519".parse()?,
3607                    vec!["EdDsaSignature".parse()?],
3608                    None,
3609                    "EdDsa".parse()?,
3610                    CryptographicKeyContext::OpenPgp{
3611                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3612                        version: "v4".parse()?,
3613                    },
3614                )?,
3615                backend_key_id: 1, backend_key_domain: 1,
3616                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3617                system_user: "system-operator".parse()?,
3618            },
3619            Some("system-operator".parse()?),
3620        )]
3621        fn user_mapping_get_system_user(
3622            #[case] mapping: UserMapping,
3623            #[case] result: Option<SystemUserId>,
3624        ) -> TestResult {
3625            assert_eq!(mapping.get_system_user(), result.as_ref());
3626            Ok(())
3627        }
3628
3629        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
3630        /// [`UserMapping::backend_users`].
3631        #[rstest]
3632        #[case::admin_filter_default(UserMapping::YubiHsm2OnlyAdmin(1), UserMappingFilter::default(), &[])]
3633        #[case::admin_filter_admin(UserMapping::YubiHsm2OnlyAdmin(1), UserMappingFilter{backend_user_kind: BackendUserKind::Admin}, &["1"])]
3634        #[case::backup_filter_default(
3635            UserMapping::SystemYubiHsm2Backup{
3636                authentication_key_id: 1,
3637                wrapping_key_id: 2,
3638                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3639                system_user: "backup".parse()?,
3640            },
3641            UserMappingFilter::default(),
3642            &["1"]
3643        )]
3644        #[case::backup_filter_admin(
3645            UserMapping::SystemYubiHsm2Backup{
3646                authentication_key_id: 1,
3647                wrapping_key_id: 2,
3648                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3649                system_user: "backup".parse()?,
3650            },
3651            UserMappingFilter{ backend_user_kind: BackendUserKind::Admin },
3652            &[]
3653        )]
3654        #[case::metrics_filter_default(
3655            UserMapping::SystemYubiHsm2Metrics {
3656                authentication_key_id: 1,
3657                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3658                system_user: "metrics".parse()?,
3659            },
3660            UserMappingFilter::default(),
3661            &["1"]
3662        )]
3663        #[case::metrics_filter_admin(
3664            UserMapping::SystemYubiHsm2Metrics {
3665                authentication_key_id: 1,
3666                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3667                system_user: "metrics".parse()?,
3668            },
3669            UserMappingFilter{ backend_user_kind: BackendUserKind::Admin },
3670            &[]
3671        )]
3672        #[case::hermetic_metrics_filter_default(
3673            UserMapping::HermeticSystemYubiHsm2Metrics {
3674                authentication_key_id: 1,
3675                system_user: "metrics".parse()?,
3676            },
3677            UserMappingFilter::default(),
3678            &["1"]
3679        )]
3680        #[case::hermetic_metrics_filter_admin(
3681            UserMapping::HermeticSystemYubiHsm2Metrics {
3682                authentication_key_id: 1,
3683                system_user: "metrics".parse()?,
3684            },
3685            UserMappingFilter{ backend_user_kind: BackendUserKind::Admin },
3686            &[]
3687        )]
3688        #[case::operator_filter_default(
3689            UserMapping::SystemYubiHsm2OperatorSigning {
3690                backend_key_id: 1,
3691                backend_key_setup: SigningKeySetup::new(
3692                    "Curve25519".parse()?,
3693                    vec!["EdDsaSignature".parse()?],
3694                    None,
3695                    "EdDsa".parse()?,
3696                    CryptographicKeyContext::OpenPgp{
3697                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3698                        version: "v4".parse()?,
3699                    },
3700                )?,
3701                authentication_key_id: 1,
3702                backend_key_domain: 1,
3703                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3704                system_user: "system-operator".parse()?,
3705            },
3706            UserMappingFilter::default(),
3707            &["1"]
3708        )]
3709        #[case::operator_filter_admin(
3710            UserMapping::SystemYubiHsm2OperatorSigning {
3711                backend_key_id: 1,
3712                backend_key_setup: SigningKeySetup::new(
3713                    "Curve25519".parse()?,
3714                    vec!["EdDsaSignature".parse()?],
3715                    None,
3716                    "EdDsa".parse()?,
3717                    CryptographicKeyContext::OpenPgp{
3718                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3719                        version: "v4".parse()?,
3720                    },
3721                )?,
3722                authentication_key_id: 1,
3723                backend_key_domain: 1,
3724                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3725                system_user: "system-operator".parse()?,
3726            },
3727            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3728            &[]
3729        )]
3730        fn user_mapping_backend_users(
3731            #[case] mapping: UserMapping,
3732            #[case] filter: UserMappingFilter,
3733            #[case] expected_names: &[&str],
3734        ) -> TestResult {
3735            assert_eq!(mapping.backend_users(filter), expected_names);
3736            Ok(())
3737        }
3738
3739        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
3740        /// [`UserMapping::backend_users_with_new_passphrase`].
3741        #[rstest]
3742        #[case::admin_filter_default(
3743            UserMapping::YubiHsm2OnlyAdmin(1),
3744            UserMappingFilter::default(),
3745            0
3746        )]
3747        #[case::admin_filter_admin(
3748            UserMapping::YubiHsm2OnlyAdmin(1),
3749            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3750            1
3751        )]
3752        #[case::backup_filter_default(
3753            UserMapping::SystemYubiHsm2Backup{
3754                authentication_key_id: 1,
3755                wrapping_key_id: 2,
3756                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3757                system_user: "backup".parse()?,
3758            },
3759            UserMappingFilter::default(),
3760            1
3761        )]
3762        #[case::backup_filter_admin(
3763            UserMapping::SystemYubiHsm2Backup{
3764                authentication_key_id: 1,
3765                wrapping_key_id: 2,
3766                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3767                system_user: "backup".parse()?,
3768            },
3769            UserMappingFilter{ backend_user_kind: BackendUserKind::Admin },
3770            0
3771        )]
3772        #[case::metrics_filter_default(
3773            UserMapping::SystemYubiHsm2Metrics {
3774                authentication_key_id: 1,
3775                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3776                system_user: "metrics".parse()?,
3777            },
3778            UserMappingFilter::default(),
3779            1
3780        )]
3781        #[case::metrics_filter_admin(
3782            UserMapping::SystemYubiHsm2Metrics {
3783                authentication_key_id: 1,
3784                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3785                system_user: "metrics".parse()?,
3786            },
3787            UserMappingFilter{ backend_user_kind: BackendUserKind::Admin },
3788            0
3789        )]
3790        #[case::hermetic_metrics_filter_default(
3791            UserMapping::HermeticSystemYubiHsm2Metrics {
3792                authentication_key_id: 1,
3793                system_user: "metrics".parse()?,
3794            },
3795            UserMappingFilter::default(),
3796            1
3797        )]
3798        #[case::hermetic_metrics_filter_admin(
3799            UserMapping::HermeticSystemYubiHsm2Metrics {
3800                authentication_key_id: 1,
3801                system_user: "metrics".parse()?,
3802            },
3803            UserMappingFilter{ backend_user_kind: BackendUserKind::Admin },
3804            0
3805        )]
3806        #[case::operator_filter_default(
3807            UserMapping::SystemYubiHsm2OperatorSigning {
3808                backend_key_id: 1,
3809                backend_key_setup: SigningKeySetup::new(
3810                    "Curve25519".parse()?,
3811                    vec!["EdDsaSignature".parse()?],
3812                    None,
3813                    "EdDsa".parse()?,
3814                    CryptographicKeyContext::OpenPgp{
3815                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3816                        version: "v4".parse()?,
3817                    },
3818                )?,
3819                authentication_key_id: 1,
3820                backend_key_domain: 1,
3821                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3822                system_user: "system-operator".parse()?,
3823            },
3824            UserMappingFilter::default(),
3825            1
3826        )]
3827        #[case::operator_filter_admin(
3828            UserMapping::SystemYubiHsm2OperatorSigning {
3829                backend_key_id: 1,
3830                backend_key_setup: SigningKeySetup::new(
3831                    "Curve25519".parse()?,
3832                    vec!["EdDsaSignature".parse()?],
3833                    None,
3834                    "EdDsa".parse()?,
3835                    CryptographicKeyContext::OpenPgp{
3836                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3837                        version: "v4".parse()?,
3838                    },
3839                )?,
3840                authentication_key_id: 1,
3841                backend_key_domain: 1,
3842                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3843                system_user: "system-operator".parse()?,
3844            },
3845            UserMappingFilter{backend_user_kind: BackendUserKind::Admin},
3846            0
3847        )]
3848        fn usermapping_yubihsm_backend_users_with_new_passphrase(
3849            #[case] mapping: UserMapping,
3850            #[case] filter: UserMappingFilter,
3851            #[case] expected_length: usize,
3852        ) -> TestResult {
3853            assert_eq!(
3854                mapping.backend_users_with_new_passphrase(filter).len(),
3855                expected_length
3856            );
3857            Ok(())
3858        }
3859
3860        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
3861        /// [`UserMapping::get_nethsm_users`].
3862        #[rstest]
3863        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1))]
3864        #[case::backup(
3865            UserMapping::SystemYubiHsm2Backup{
3866                authentication_key_id: 1,
3867                wrapping_key_id: 2,
3868                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3869                system_user: "backup".parse()?,
3870             }
3871        )]
3872        #[case::metrics(
3873            UserMapping::SystemYubiHsm2Metrics {
3874                authentication_key_id: 1,
3875                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3876                system_user: "metrics".parse()?,
3877            },
3878        )]
3879        #[case::hermetic_metrics(
3880            UserMapping::HermeticSystemYubiHsm2Metrics {
3881                authentication_key_id: 1,
3882                system_user: "metrics".parse()?,
3883            },
3884        )]
3885        #[case::operator_signing(
3886            UserMapping::SystemYubiHsm2OperatorSigning {
3887                authentication_key_id: 1,
3888                backend_key_setup: SigningKeySetup::new(
3889                    "Curve25519".parse()?,
3890                    vec!["EdDsaSignature".parse()?],
3891                    None,
3892                    "EdDsa".parse()?,
3893                    CryptographicKeyContext::OpenPgp{
3894                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3895                        version: "v4".parse()?,
3896                    },
3897                )?,
3898                backend_key_id: 1, backend_key_domain: 1,
3899                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3900                system_user: "system-operator".parse()?,
3901            },
3902        )]
3903        fn user_mapping_get_nethsm_users(#[case] mapping: UserMapping) -> TestResult {
3904            let expected: Vec<UserId> = Vec::new();
3905            assert_eq!(mapping.get_nethsm_users(), expected);
3906            Ok(())
3907        }
3908
3909        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
3910        /// [`UserMapping::get_nethsm_users_and_roles`].
3911        #[rstest]
3912        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1))]
3913        #[case::backup(
3914            UserMapping::SystemYubiHsm2Backup{
3915                authentication_key_id: 1,
3916                wrapping_key_id: 2,
3917                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3918                system_user: "backup".parse()?,
3919             }
3920        )]
3921        #[case::metrics(
3922            UserMapping::SystemYubiHsm2Metrics {
3923                authentication_key_id: 1,
3924                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3925                system_user: "metrics".parse()?,
3926            },
3927        )]
3928        #[case::hermetic_metrics(
3929            UserMapping::HermeticSystemYubiHsm2Metrics {
3930                authentication_key_id: 1,
3931                system_user: "metrics".parse()?,
3932            },
3933        )]
3934        #[case::operator_signing(
3935            UserMapping::SystemYubiHsm2OperatorSigning {
3936                authentication_key_id: 1,
3937                backend_key_setup: SigningKeySetup::new(
3938                    "Curve25519".parse()?,
3939                    vec!["EdDsaSignature".parse()?],
3940                    None,
3941                    "EdDsa".parse()?,
3942                    CryptographicKeyContext::OpenPgp{
3943                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3944                        version: "v4".parse()?,
3945                    },
3946                )?,
3947                backend_key_id: 1, backend_key_domain: 1,
3948                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3949                system_user: "system-operator".parse()?,
3950            },
3951        )]
3952        fn usermapping_get_nethsm_users_and_roles(#[case] mapping: UserMapping) -> TestResult {
3953            let expected: Vec<(UserId, UserRole)> = Vec::new();
3954            assert_eq!(mapping.get_nethsm_users_and_roles(), expected);
3955            Ok(())
3956        }
3957
3958        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
3959        /// [`UserMapping::get_nethsm_user_key_and_tag`].
3960        #[rstest]
3961        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1), FilterUserKeys::All)]
3962        #[case::backup(
3963            UserMapping::SystemYubiHsm2Backup{
3964                authentication_key_id: 1,
3965                wrapping_key_id: 2,
3966                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3967                system_user: "backup".parse()?,
3968             },
3969            FilterUserKeys::All,
3970        )]
3971        #[case::metrics(
3972            UserMapping::SystemYubiHsm2Metrics {
3973                authentication_key_id: 1,
3974                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
3975                system_user: "metrics".parse()?,
3976            },
3977            FilterUserKeys::All,
3978        )]
3979        #[case::hermetic_metrics(
3980            UserMapping::HermeticSystemYubiHsm2Metrics {
3981                authentication_key_id: 1,
3982                system_user: "metrics".parse()?,
3983            },
3984            FilterUserKeys::All,
3985        )]
3986        #[case::operator_signing(
3987            UserMapping::SystemYubiHsm2OperatorSigning {
3988                authentication_key_id: 1,
3989                backend_key_setup: SigningKeySetup::new(
3990                    "Curve25519".parse()?,
3991                    vec!["EdDsaSignature".parse()?],
3992                    None,
3993                    "EdDsa".parse()?,
3994                    CryptographicKeyContext::OpenPgp{
3995                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
3996                        version: "v4".parse()?,
3997                    },
3998                )?,
3999                backend_key_id: 1, backend_key_domain: 1,
4000                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4001                system_user: "system-operator".parse()?,
4002            },
4003            FilterUserKeys::All,
4004        )]
4005        fn user_mapping_get_nethsm_user_key_and_tag(
4006            #[case] mapping: UserMapping,
4007            #[case] filter: FilterUserKeys,
4008        ) -> TestResult {
4009            let expected: Vec<(UserId, KeyId, SigningKeySetup, String)> = Vec::new();
4010            assert_eq!(mapping.get_nethsm_user_key_and_tag(filter), expected);
4011            Ok(())
4012        }
4013
4014        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
4015        /// [`UserMapping::get_nethsm_user_role_and_tags`].
4016        #[rstest]
4017        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1))]
4018        #[case::backup(
4019            UserMapping::SystemYubiHsm2Backup{
4020                authentication_key_id: 1,
4021                wrapping_key_id: 2,
4022                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4023                system_user: "backup".parse()?,
4024             }
4025        )]
4026        #[case::metrics(
4027            UserMapping::SystemYubiHsm2Metrics {
4028                authentication_key_id: 1,
4029                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4030                system_user: "metrics".parse()?,
4031            },
4032        )]
4033        #[case::hermetic_metrics(
4034            UserMapping::HermeticSystemYubiHsm2Metrics {
4035                authentication_key_id: 1,
4036                system_user: "metrics".parse()?,
4037            },
4038        )]
4039        #[case::operator_signing(
4040            UserMapping::SystemYubiHsm2OperatorSigning {
4041                authentication_key_id: 1,
4042                backend_key_setup: SigningKeySetup::new(
4043                    "Curve25519".parse()?,
4044                    vec!["EdDsaSignature".parse()?],
4045                    None,
4046                    "EdDsa".parse()?,
4047                    CryptographicKeyContext::OpenPgp{
4048                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4049                        version: "v4".parse()?,
4050                    },
4051                )?,
4052                backend_key_id: 1, backend_key_domain: 1,
4053                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4054                system_user: "system-operator".parse()?,
4055            },
4056        )]
4057        fn user_mapping_get_nethsm_user_role_and_tags(#[case] mapping: UserMapping) -> TestResult {
4058            let expected: Vec<(UserId, UserRole, Vec<String>)> = Vec::new();
4059            assert_eq!(mapping.get_nethsm_user_role_and_tags(), expected);
4060            Ok(())
4061        }
4062
4063        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
4064        /// [`UserMapping::get_ssh_authorized_key`].
4065        #[rstest]
4066        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1), None)]
4067        #[case::backup(
4068            UserMapping::SystemYubiHsm2Backup{
4069                authentication_key_id: 1,
4070                wrapping_key_id: 2,
4071                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4072                system_user: "backup".parse()?,
4073             },
4074            Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?),
4075        )]
4076        #[case::metrics(
4077            UserMapping::SystemYubiHsm2Metrics {
4078                authentication_key_id: 1,
4079                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4080                system_user: "metrics".parse()?,
4081            },
4082            Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?),
4083        )]
4084        #[case::hermetic_metrics(
4085            UserMapping::HermeticSystemYubiHsm2Metrics {
4086                authentication_key_id: 1,
4087                system_user: "metrics".parse()?,
4088            },
4089            None,
4090        )]
4091        #[case::operator_signing(
4092            UserMapping::SystemYubiHsm2OperatorSigning {
4093                authentication_key_id: 1,
4094                backend_key_setup: SigningKeySetup::new(
4095                    "Curve25519".parse()?,
4096                    vec!["EdDsaSignature".parse()?],
4097                    None,
4098                    "EdDsa".parse()?,
4099                    CryptographicKeyContext::OpenPgp{
4100                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4101                        version: "v4".parse()?,
4102                    },
4103                )?,
4104                backend_key_id: 1, backend_key_domain: 1,
4105                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4106                system_user: "system-operator".parse()?,
4107            },
4108            Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?),
4109        )]
4110        fn user_mapping_get_ssh_authorized_key(
4111            #[case] mapping: UserMapping,
4112            #[case] output: Option<AuthorizedKeyEntry>,
4113        ) -> TestResult {
4114            assert_eq!(mapping.get_ssh_authorized_key(), output.as_ref());
4115            Ok(())
4116        }
4117
4118        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
4119        /// [`UserMapping::get_nethsm_key_ids`].
4120        #[rstest]
4121        #[case::admin_target_system_wide(UserMapping::YubiHsm2OnlyAdmin(1), None)]
4122        #[case::admin_target_namespace(UserMapping::YubiHsm2OnlyAdmin(1), Some("ns1".parse()?))]
4123        #[case::backup_target_system_wide(
4124            UserMapping::SystemYubiHsm2Backup{
4125                authentication_key_id: 1,
4126                wrapping_key_id: 2,
4127                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4128                system_user: "backup".parse()?,
4129             },
4130            None,
4131        )]
4132        #[case::backup_target_namespace(
4133            UserMapping::SystemYubiHsm2Backup{
4134                authentication_key_id: 1,
4135                wrapping_key_id: 2,
4136                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4137                system_user: "backup".parse()?,
4138             },
4139            Some("ns1".parse()?),
4140        )]
4141        #[case::metrics_target_system_wide(
4142            UserMapping::SystemYubiHsm2Metrics {
4143                authentication_key_id: 1,
4144                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4145                system_user: "metrics".parse()?,
4146            },
4147            None,
4148        )]
4149        #[case::metrics_target_namespace(
4150            UserMapping::SystemYubiHsm2Metrics {
4151                authentication_key_id: 1,
4152                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4153                system_user: "metrics".parse()?,
4154            },
4155            Some("ns1".parse()?),
4156        )]
4157        #[case::hermetic_metrics_target_system_wide(
4158            UserMapping::HermeticSystemYubiHsm2Metrics {
4159                authentication_key_id: 1,
4160                system_user: "metrics".parse()?,
4161            },
4162            None,
4163        )]
4164        #[case::hermetic_metrics_target_namespace(
4165            UserMapping::HermeticSystemYubiHsm2Metrics {
4166                authentication_key_id: 1,
4167                system_user: "metrics".parse()?,
4168            },
4169            Some("ns1".parse()?),
4170        )]
4171        #[case::operator_signing_target_system_wide(
4172            UserMapping::SystemYubiHsm2OperatorSigning {
4173                authentication_key_id: 1,
4174                backend_key_setup: SigningKeySetup::new(
4175                    "Curve25519".parse()?,
4176                    vec!["EdDsaSignature".parse()?],
4177                    None,
4178                    "EdDsa".parse()?,
4179                    CryptographicKeyContext::OpenPgp{
4180                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4181                        version: "v4".parse()?,
4182                    },
4183                )?,
4184                backend_key_id: 1, backend_key_domain: 1,
4185                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4186                system_user: "system-operator".parse()?,
4187            },
4188            None,
4189        )]
4190        #[case::operator_signing_target_namespace(
4191            UserMapping::SystemYubiHsm2OperatorSigning {
4192                authentication_key_id: 1,
4193                backend_key_setup: SigningKeySetup::new(
4194                    "Curve25519".parse()?,
4195                    vec!["EdDsaSignature".parse()?],
4196                    None,
4197                    "EdDsa".parse()?,
4198                    CryptographicKeyContext::OpenPgp{
4199                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4200                        version: "v4".parse()?,
4201                    },
4202                )?,
4203                backend_key_id: 1, backend_key_domain: 1,
4204                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4205                system_user: "system-operator".parse()?,
4206            },
4207            Some("ns1".parse()?),
4208        )]
4209        fn user_mapping_get_nethsm_key_ids(
4210            #[case] mapping: UserMapping,
4211            #[case] namespace: Option<NamespaceId>,
4212        ) -> TestResult {
4213            let expected: Vec<KeyId> = Vec::new();
4214            assert_eq!(mapping.get_nethsm_key_ids(namespace.as_ref()), expected);
4215            Ok(())
4216        }
4217
4218        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
4219        /// [`UserMapping::get_nethsm_tags`].
4220        #[rstest]
4221        #[case::admin_target_system_wide(UserMapping::YubiHsm2OnlyAdmin(1), None)]
4222        #[case::admin_target_namespace(UserMapping::YubiHsm2OnlyAdmin(1), Some("ns1".parse()?))]
4223        #[case::backup_target_system_wide(
4224            UserMapping::SystemYubiHsm2Backup{
4225                authentication_key_id: 1,
4226                wrapping_key_id: 2,
4227                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4228                system_user: "backup".parse()?,
4229             },
4230            None,
4231        )]
4232        #[case::backup_target_namespace(
4233            UserMapping::SystemYubiHsm2Backup{
4234                authentication_key_id: 1,
4235                wrapping_key_id: 2,
4236                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4237                system_user: "backup".parse()?,
4238             },
4239            Some("ns1".parse()?),
4240        )]
4241        #[case::metrics_target_system_wide(
4242            UserMapping::SystemYubiHsm2Metrics {
4243                authentication_key_id: 1,
4244                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4245                system_user: "metrics".parse()?,
4246            },
4247            None,
4248        )]
4249        #[case::metrics_target_namespace(
4250            UserMapping::SystemYubiHsm2Metrics {
4251                authentication_key_id: 1,
4252                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4253                system_user: "metrics".parse()?,
4254            },
4255            Some("ns1".parse()?),
4256        )]
4257        #[case::hermetic_metrics_target_system_wide(
4258            UserMapping::HermeticSystemYubiHsm2Metrics {
4259                authentication_key_id: 1,
4260                system_user: "metrics".parse()?,
4261            },
4262            None,
4263        )]
4264        #[case::hermetic_metrics_target_namespace(
4265            UserMapping::HermeticSystemYubiHsm2Metrics {
4266                authentication_key_id: 1,
4267                system_user: "metrics".parse()?,
4268            },
4269            Some("ns1".parse()?),
4270        )]
4271        #[case::operator_signing_target_system_wide(
4272            UserMapping::SystemYubiHsm2OperatorSigning {
4273                authentication_key_id: 1,
4274                backend_key_setup: SigningKeySetup::new(
4275                    "Curve25519".parse()?,
4276                    vec!["EdDsaSignature".parse()?],
4277                    None,
4278                    "EdDsa".parse()?,
4279                    CryptographicKeyContext::OpenPgp{
4280                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4281                        version: "v4".parse()?,
4282                    },
4283                )?,
4284                backend_key_id: 1, backend_key_domain: 1,
4285                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4286                system_user: "system-operator".parse()?,
4287            },
4288            None,
4289        )]
4290        #[case::operator_signing_target_namespace(
4291            UserMapping::SystemYubiHsm2OperatorSigning {
4292                authentication_key_id: 1,
4293                backend_key_setup: SigningKeySetup::new(
4294                    "Curve25519".parse()?,
4295                    vec!["EdDsaSignature".parse()?],
4296                    None,
4297                    "EdDsa".parse()?,
4298                    CryptographicKeyContext::OpenPgp{
4299                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4300                        version: "v4".parse()?,
4301                    },
4302                )?,
4303                backend_key_id: 1, backend_key_domain: 1,
4304                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4305                system_user: "system-operator".parse()?,
4306            },
4307            Some("ns1".parse()?),
4308        )]
4309        fn user_mapping_get_nethsm_tags(
4310            #[case] mapping: UserMapping,
4311            #[case] namespace: Option<NamespaceId>,
4312        ) -> TestResult {
4313            let expected: Vec<&str> = Vec::new();
4314            assert_eq!(mapping.get_nethsm_tags(namespace.as_ref()), expected);
4315            Ok(())
4316        }
4317
4318        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
4319        /// [`UserMapping::get_nethsm_namespaces`].
4320        #[rstest]
4321        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1))]
4322        #[case::backup(
4323            UserMapping::SystemYubiHsm2Backup{
4324                authentication_key_id: 1,
4325                wrapping_key_id: 2,
4326                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4327                system_user: "backup".parse()?,
4328             },
4329        )]
4330        #[case::metrics(
4331            UserMapping::SystemYubiHsm2Metrics {
4332                authentication_key_id: 1,
4333                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4334                system_user: "metrics".parse()?,
4335            },
4336        )]
4337        #[case::hermetic_metrics(
4338            UserMapping::HermeticSystemYubiHsm2Metrics {
4339                authentication_key_id: 1,
4340                system_user: "metrics".parse()?,
4341            },
4342        )]
4343        #[case::operator_signing(
4344            UserMapping::SystemYubiHsm2OperatorSigning {
4345                authentication_key_id: 1,
4346                backend_key_setup: SigningKeySetup::new(
4347                    "Curve25519".parse()?,
4348                    vec!["EdDsaSignature".parse()?],
4349                    None,
4350                    "EdDsa".parse()?,
4351                    CryptographicKeyContext::OpenPgp{
4352                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4353                        version: "v4".parse()?,
4354                    },
4355                )?,
4356                backend_key_id: 1, backend_key_domain: 1,
4357                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4358                system_user: "system-operator".parse()?,
4359            },
4360        )]
4361        fn user_mapping_get_nethsm_namespaces(#[case] mapping: UserMapping) -> TestResult {
4362            let expected: Vec<NamespaceId> = Vec::new();
4363            assert_eq!(mapping.get_nethsm_namespaces(), expected);
4364            Ok(())
4365        }
4366
4367        /// Ensures that YubiHSM2 specific [`UserMapping`] variants work with
4368        /// [`UserMapping::has_system_and_backend_user`].
4369        #[rstest]
4370        #[case::admin(UserMapping::YubiHsm2OnlyAdmin(1), false)]
4371        #[case::backup(
4372            UserMapping::SystemYubiHsm2Backup{
4373                authentication_key_id: 1,
4374                wrapping_key_id: 2,
4375                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4376                system_user: "backup".parse()?,
4377             },
4378             true
4379        )]
4380        #[case::metrics(
4381            UserMapping::SystemYubiHsm2Metrics {
4382                authentication_key_id: 1,
4383                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4384                system_user: "metrics".parse()?,
4385            },
4386            true
4387        )]
4388        #[case::hermetic_metrics(
4389            UserMapping::HermeticSystemYubiHsm2Metrics {
4390                authentication_key_id: 1,
4391                system_user: "metrics".parse()?,
4392            },
4393            true
4394        )]
4395        #[case::operator_signing(
4396            UserMapping::SystemYubiHsm2OperatorSigning {
4397                authentication_key_id: 1,
4398                backend_key_setup: SigningKeySetup::new(
4399                    "Curve25519".parse()?,
4400                    vec!["EdDsaSignature".parse()?],
4401                    None,
4402                    "EdDsa".parse()?,
4403                    CryptographicKeyContext::OpenPgp{
4404                        user_ids: OpenPgpUserIdList::new(vec!["John Doe <john@example.org>".parse()?])?,
4405                        version: "v4".parse()?,
4406                    },
4407                )?,
4408                backend_key_id: 1, backend_key_domain: 1,
4409                ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH3NyNfSqtDxdnWwSVzulZi0k7Lyjw3vBEG+U8y6KsuW user@host".parse()?,
4410                system_user: "system-operator".parse()?,
4411            },
4412            true,
4413        )]
4414        fn user_mapping_has_system_and_backend_user(
4415            #[case] mapping: UserMapping,
4416            #[case] output: bool,
4417        ) -> TestResult {
4418            assert_eq!(mapping.has_system_and_backend_user(), output);
4419            Ok(())
4420        }
4421    }
4422
4423    /// Ensures that a file with the correct permissions is successfully checked using
4424    /// [`check_secrets_file`].
4425    #[test]
4426    fn check_secrets_file_succeeds() -> TestResult {
4427        setup_logging(LevelFilter::Debug)?;
4428
4429        let temp_file = NamedTempFile::new()?;
4430        let path = temp_file.path();
4431        set_permissions(path, Permissions::from_mode(SECRET_FILE_MODE))?;
4432        debug!(
4433            "Created {path:?} with mode {:o}",
4434            path.metadata()?.permissions().mode()
4435        );
4436
4437        check_secrets_file(path)?;
4438
4439        Ok(())
4440    }
4441
4442    /// Ensures that passing a non-existent file to [`check_secrets_file`] fails.
4443    #[test]
4444    fn check_secrets_file_fails_on_missing_file() -> TestResult {
4445        setup_logging(LevelFilter::Debug)?;
4446
4447        let temp_file = NamedTempFile::new()?;
4448        let path = temp_file.path().to_path_buf();
4449        temp_file.close()?;
4450
4451        if check_secrets_file(&path).is_ok() {
4452            panic!("The path {path:?} is missing and should not have passed as a secrets file.");
4453        }
4454
4455        Ok(())
4456    }
4457
4458    /// Ensures that passing a directory to [`check_secrets_file`] fails.
4459    #[test]
4460    fn check_secrets_file_fails_on_dir() -> TestResult {
4461        setup_logging(LevelFilter::Debug)?;
4462
4463        let temp_file = TempDir::new()?;
4464        let path = temp_file.path();
4465        debug!(
4466            "Created {path:?} with mode {:o}",
4467            path.metadata()?.permissions().mode()
4468        );
4469
4470        if check_secrets_file(path).is_ok() {
4471            panic!("The dir {path:?} should not have passed as a secrets file.");
4472        }
4473
4474        Ok(())
4475    }
4476
4477    /// Ensures that a file without the correct permissions fails [`check_secrets_file`].
4478    #[test]
4479    fn check_secrets_file_fails_on_invalid_permissions() -> TestResult {
4480        setup_logging(LevelFilter::Debug)?;
4481
4482        let temp_file = NamedTempFile::new()?;
4483        let path = temp_file.path();
4484        set_permissions(path, Permissions::from_mode(0o100644))?;
4485        debug!(
4486            "Created {path:?} with mode {:o}",
4487            path.metadata()?.permissions().mode()
4488        );
4489
4490        if check_secrets_file(path).is_ok() {
4491            panic!("The file at {path:?} should not have passed as a secrets file.");
4492        }
4493
4494        Ok(())
4495    }
4496}