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(¤t_system_user)?;
1194 match_current_system_user(¤t_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}