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