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