signstar_config/config/
mapping.rs

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