1#[cfg(all(feature = "nethsm", feature = "yubihsm2"))]
4pub mod impl_all;
5#[cfg(all(feature = "nethsm", not(feature = "yubihsm2")))]
6pub mod impl_nethsm;
7#[cfg(not(any(feature = "nethsm", feature = "yubihsm2")))]
8pub mod impl_none;
9#[cfg(all(feature = "yubihsm2", not(feature = "nethsm")))]
10pub mod impl_yubihsm2;
11
12#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
13use std::collections::BTreeSet;
14use std::{
15 collections::HashSet,
16 fs::read_to_string,
17 path::{Path, PathBuf},
18 str::FromStr,
19};
20
21use garde::Validate;
22use log::info;
23#[cfg(feature = "nethsm")]
24use nethsm::Connection;
25use serde::{Deserialize, Serialize};
26#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
27use signstar_crypto::{AdministrativeSecretHandling, NonAdministrativeSecretHandling};
28#[cfg(feature = "yubihsm2")]
29use signstar_yubihsm2::Connection as YubiHsm2Connection;
30use strum::{AsRefStr, VariantNames};
31
32#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
33use crate::config::{ConfigAuthorizedKeyEntries, ConfigSystemUserIds};
34#[cfg(feature = "nethsm")]
35use crate::nethsm::{NetHsmConfig, NetHsmUserMapping};
36#[cfg(feature = "yubihsm2")]
37use crate::yubihsm2::{YubiHsm2Config, YubiHsm2UserMapping};
38use crate::{
39 config::{ConfigSystemUserData, Error, SystemConfig, SystemUserData},
40 state::{StateOrigin, StateOriginInfo},
41};
42
43#[derive(Clone, Debug, Eq, PartialEq)]
45pub enum UserBackendConnection {
46 #[cfg(feature = "nethsm")]
52 NetHsm {
53 admin_secret_handling: AdministrativeSecretHandling,
55
56 non_admin_secret_handling: NonAdministrativeSecretHandling,
58
59 connections: BTreeSet<Connection>,
61
62 mapping: NetHsmUserMapping,
64 },
65
66 #[cfg(feature = "yubihsm2")]
72 YubiHsm2 {
73 admin_secret_handling: AdministrativeSecretHandling,
75
76 non_admin_secret_handling: NonAdministrativeSecretHandling,
78
79 connections: BTreeSet<YubiHsm2Connection>,
81
82 mapping: YubiHsm2UserMapping,
84 },
85}
86
87#[derive(Clone, Copy, Debug)]
89pub enum UserBackendConnectionFilter {
90 All,
92
93 Admin,
95
96 NonAdmin,
98}
99
100#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
112fn validate_confs<T, U>(config_a: &T, config_b: &U) -> garde::Result
113where
114 T: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
115 U: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
116{
117 let duplicate_system_user_ids = {
119 let system_config_user_ids = config_a.system_user_ids();
120 let config_user_ids = config_b.system_user_ids();
121 let duplicates = system_config_user_ids
122 .intersection(&config_user_ids)
123 .map(|system_user_id| system_user_id.to_string())
124 .collect::<HashSet<_>>();
125
126 if duplicates.is_empty() {
127 None
128 } else {
129 let mut duplicates = Vec::from_iter(duplicates);
130 duplicates.sort();
131 Some(format!(
132 "the duplicate system user ID{} {}",
133 if duplicates.len() > 1 { "s" } else { "" },
134 duplicates.join(", ")
135 ))
136 }
137 };
138
139 let duplicate_public_keys = {
141 let system_config_public_keys: HashSet<_> = config_a
142 .authorized_key_entries()
143 .iter()
144 .cloned()
145 .map(|authorized_key| authorized_key.as_ref().public_key())
146 .collect();
147 let config_public_keys: HashSet<_> = config_b
148 .authorized_key_entries()
149 .iter()
150 .cloned()
151 .map(|authorized_key| authorized_key.as_ref().public_key())
152 .collect();
153 let duplicates: HashSet<_> = system_config_public_keys
154 .intersection(&config_public_keys)
155 .cloned()
156 .map(|public_key| {
157 let mut public_key = public_key.clone();
158 public_key.set_comment("");
160 format!("\"{}\"", public_key.to_string())
161 })
162 .collect();
163
164 if duplicates.is_empty() {
165 None
166 } else {
167 let mut duplicates = Vec::from_iter(duplicates);
168 duplicates.sort();
169 Some(format!(
170 "the duplicate SSH public key{} {}",
171 if duplicates.len() > 1 { "s" } else { "" },
172 duplicates.join(", ")
173 ))
174 }
175 };
176
177 let messages = [duplicate_system_user_ids, duplicate_public_keys];
178 let error_messages = {
179 let mut error_messages = Vec::new();
180
181 for message in messages.iter().flatten() {
182 error_messages.push(message.as_str());
183 }
184
185 error_messages
186 };
187
188 match error_messages.len() {
189 0 => Ok(()),
190 1 => Err(garde::Error::new(format!(
191 "contains {}",
192 error_messages.join("\n")
193 ))),
194 _ => Err(garde::Error::new(format!(
195 "contains multiple issues:\n⤷ {}",
196 error_messages.join("\n⤷ ")
197 ))),
198 }
199}
200
201#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
213fn validate_config_against_optional_config<T, U>(
214 config_a: &Option<T>,
215) -> impl FnOnce(&U, &()) -> garde::Result + '_
216where
217 T: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
218 U: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
219{
220 move |config_b, _| {
221 let Some(config_a) = config_a else {
222 return Ok(());
223 };
224
225 validate_confs(config_a, config_b)
226 }
227}
228
229#[cfg(all(feature = "nethsm", feature = "yubihsm2"))]
241fn validate_two_optional_configs<T, U>(
242 backend_config_a: &Option<T>,
243) -> impl FnOnce(&Option<U>, &()) -> garde::Result + '_
244where
245 T: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
246 U: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
247{
248 move |backend_config_b, _| {
249 if let Some(backend_config_a) = backend_config_a
250 && let Some(backend_config_b) = backend_config_b
251 {
252 validate_confs(backend_config_a, backend_config_b)?;
253 }
254
255 Ok(())
256 }
257}
258
259#[derive(AsRefStr, Clone, Copy, Debug, Default, strum::Display, VariantNames)]
261#[strum(serialize_all = "lowercase")]
262enum ConfigFileFormat {
263 #[default]
264 Yaml,
265}
266
267#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
271#[serde(rename_all = "snake_case")]
272pub struct Config {
273 #[cfg_attr(
276 feature = "nethsm",
277 garde(custom(validate_config_against_optional_config(&self.nethsm)))
278 )]
279 #[cfg_attr(
281 feature = "yubihsm2",
282 garde(custom(validate_config_against_optional_config(&self.yubihsm2)))
283 )]
284 #[garde(dive)]
285 system: SystemConfig,
286
287 #[cfg(feature = "nethsm")]
293 #[cfg_attr(
295 all(feature = "nethsm", feature = "yubihsm2"),
296 garde(custom(validate_two_optional_configs(&self.yubihsm2)))
297 )]
298 #[garde(dive)]
299 #[serde(skip_serializing_if = "Option::is_none")]
300 nethsm: Option<NetHsmConfig>,
301
302 #[cfg(feature = "yubihsm2")]
308 #[cfg_attr(
310 all(feature = "nethsm", feature = "yubihsm2"),
311 garde(custom(validate_two_optional_configs(&self.nethsm)))
312 )]
313 #[garde(dive)]
314 #[serde(skip_serializing_if = "Option::is_none")]
315 yubihsm2: Option<YubiHsm2Config>,
316}
317
318impl Config {
319 pub const DEFAULT_CONFIG_DIR: &str = "/usr/share/signstar/";
321
322 pub const RUN_OVERRIDE_CONFIG_DIR: &str = "/run/signstar/";
324
325 pub const ETC_OVERRIDE_CONFIG_DIR: &str = "/etc/signstar/";
327
328 pub const CONFIG_NAME: &str = "config";
330
331 pub fn default_system_path() -> PathBuf {
333 PathBuf::from(Self::DEFAULT_CONFIG_DIR).join(PathBuf::from(format!(
334 "{}.{}",
335 Self::CONFIG_NAME,
336 ConfigFileFormat::default()
337 )))
338 }
339
340 pub fn first_existing_system_path() -> Result<PathBuf, crate::Error> {
346 let path = Self::list_config_file_paths()
347 .into_iter()
348 .find(|path| path.is_file());
349 path.ok_or(Error::ConfigIsMissing.into())
350 }
351
352 pub fn list_config_dirs() -> Vec<PathBuf> {
356 [
357 Self::DEFAULT_CONFIG_DIR,
358 Self::RUN_OVERRIDE_CONFIG_DIR,
359 Self::ETC_OVERRIDE_CONFIG_DIR,
360 ]
361 .iter()
362 .map(PathBuf::from)
363 .collect()
364 }
365
366 pub fn list_config_file_paths() -> Vec<PathBuf> {
370 Self::list_config_dirs()
371 .into_iter()
372 .map(|dir| {
373 dir.join(
374 PathBuf::from(Self::CONFIG_NAME)
375 .with_added_extension(ConfigFileFormat::default().as_ref()),
376 )
377 })
378 .collect()
379 }
380
381 fn from_yaml_str(s: &str) -> Result<Self, crate::Error> {
387 let config: Self = serde_saphyr::from_str(s).map_err(|source| Error::YamlDeserialize {
388 context: "creating a Signstar configuration object".to_string(),
389 source,
390 })?;
391
392 config
393 .validate()
394 .map_err(|source| crate::Error::Validation {
395 context: "validating a Signstar configuration object".to_string(),
396 source,
397 })?;
398
399 Ok(config)
400 }
401
402 fn from_yaml_file(path: impl AsRef<Path>) -> Result<Self, crate::Error> {
410 let path = path.as_ref();
411 info!("Reading Signstar configuration file {path:?}");
412
413 let config_data = read_to_string(path).map_err(|source| crate::Error::IoPath {
414 path: path.to_path_buf(),
415 context: "reading it to string",
416 source,
417 })?;
418 Self::from_yaml_str(&config_data)
419 }
420
421 pub fn from_file_path(path: impl AsRef<Path>) -> Result<Self, crate::Error> {
432 let path = path.as_ref();
433 let extension = {
434 let Some(extension) = path.extension() else {
435 return Err(Error::MissingFileExtension {
436 path: path.to_path_buf(),
437 }
438 .into());
439 };
440 extension.to_string_lossy().to_string()
441 };
442
443 if !ConfigFileFormat::VARIANTS.contains(&extension.as_ref()) {
444 return Err(Error::UnsupportedFileExtension {
445 path: path.to_path_buf(),
446 extension,
447 }
448 .into());
449 }
450
451 Self::from_yaml_file(path)
452 }
453
454 pub fn from_system_path() -> Result<Self, crate::Error> {
466 Self::from_yaml_file(Self::first_existing_system_path()?)
467 }
468
469 pub fn to_yaml_string(&self) -> Result<String, crate::Error> {
475 serde_saphyr::to_string(&self).map_err(|source| {
476 Error::YamlSerialize {
477 context: "serializing Signstar config",
478 source,
479 }
480 .into()
481 })
482 }
483
484 pub fn system(&self) -> &SystemConfig {
486 &self.system
487 }
488
489 #[cfg(feature = "nethsm")]
491 pub fn nethsm(&self) -> Option<&NetHsmConfig> {
492 self.nethsm.as_ref()
493 }
494
495 #[cfg(feature = "yubihsm2")]
497 pub fn yubihsm2(&self) -> Option<&YubiHsm2Config> {
498 self.yubihsm2.as_ref()
499 }
500}
501
502impl FromStr for Config {
503 type Err = crate::Error;
504
505 fn from_str(s: &str) -> Result<Self, Self::Err> {
511 Config::from_yaml_str(s)
512 }
513}
514
515#[derive(Clone, Debug)]
517pub struct ConfigBuilder(Config);
518
519impl ConfigBuilder {
520 #[cfg(feature = "nethsm")]
522 pub fn set_nethsm_config(mut self, nethsm: NetHsmConfig) -> Self {
523 self.0.nethsm = Some(nethsm);
524 self
525 }
526
527 #[cfg(feature = "yubihsm2")]
529 pub fn set_yubihsm2_config(mut self, yubihsm2: YubiHsm2Config) -> Self {
530 self.0.yubihsm2 = Some(yubihsm2);
531 self
532 }
533
534 pub fn finish(self) -> Result<Config, crate::Error> {
540 self.0
541 .validate()
542 .map_err(|source| crate::Error::Validation {
543 context: "validating a configuration object".to_string(),
544 source,
545 })?;
546
547 Ok(self.0)
548 }
549}
550
551#[derive(Clone, Debug, Eq, PartialEq)]
553pub struct SystemUserConfigState<'a> {
554 pub(crate) system_user_data: HashSet<SystemUserData<'a>>,
555}
556
557impl<'a> SystemUserConfigState<'a> {
558 pub const STATE_NAME: &'static str = "config";
560}
561
562impl<'a> From<&'a Config> for SystemUserConfigState<'a> {
563 fn from(value: &'a Config) -> Self {
564 Self {
565 system_user_data: value.system_user_data(),
566 }
567 }
568}
569
570impl<'a> StateOriginInfo for SystemUserConfigState<'a> {
571 fn state_name(&self) -> &str {
572 Self::STATE_NAME
573 }
574
575 fn state_origin(&self) -> StateOrigin {
576 StateOrigin::Config
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use std::{collections::BTreeSet, num::NonZeroUsize, thread::current};
583
584 use insta::{assert_snapshot, with_settings};
585 #[cfg(feature = "nethsm")]
586 use nethsm::ConnectionSecurity;
587 use pretty_assertions::assert_eq;
588 use rstest::{fixture, rstest};
589 use signstar_crypto::{AdministrativeSecretHandling, NonAdministrativeSecretHandling};
590 #[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
591 use signstar_crypto::{
592 key::{CryptographicKeyContext, KeyMechanism, KeyType, SignatureType, SigningKeySetup},
593 openpgp::OpenPgpUserIdList,
594 };
595 #[cfg(feature = "yubihsm2")]
596 use signstar_yubihsm2::object::Domain;
597 use tempfile::{NamedTempFile, TempDir};
598 use testresult::TestResult;
599
600 use super::*;
601 use crate::config::{AuthorizedKeyEntry, SystemUserId, SystemUserMapping};
602 #[cfg(feature = "nethsm")]
603 use crate::nethsm::NetHsmMetricsUsers;
604
605 const SNAPSHOT_PATH: &str = "fixtures/file/";
606
607 #[fixture]
609 fn default_system_config() -> TestResult<SystemConfig> {
610 Ok(SystemConfig::new(
611 1,
612 AdministrativeSecretHandling::ShamirsSecretSharing {
613 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
614 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
615 },
616 NonAdministrativeSecretHandling::SystemdCreds,
617 BTreeSet::from_iter([
618 SystemUserMapping::ShareHolder {
619 system_user: "share-holder1".parse()?,
620 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
621 },
622 SystemUserMapping::ShareHolder {
623 system_user: "share-holder2".parse()?,
624 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
625 },
626 SystemUserMapping::ShareHolder {
627 system_user: "share-holder3".parse()?,
628 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
629 },
630 SystemUserMapping::WireGuardDownload {
631 system_user: "wireguard-downloader".parse()?,
632 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
633 },
634 ]),
635 )?)
636 }
637
638 #[fixture]
641 fn raw_user_data_system() -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
642 Ok(vec![
643 (
644 "share-holder1".parse()?,
645 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?),
646 ),
647 (
648 "share-holder2".parse()?,
649 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?),
650 ),
651 (
652 "share-holder3".parse()?,
653 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?),
654 ),
655 (
656 "wireguard-downloader".parse()?,
657 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?),
658 ),
659 ])
660 }
661
662 #[cfg(feature = "nethsm")]
664 #[fixture]
665 fn default_nethsm_config() -> TestResult<NetHsmConfig> {
666 Ok(NetHsmConfig::new(
667 BTreeSet::from_iter([
668 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
669 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
670 ]),
671 BTreeSet::from_iter([
672 NetHsmUserMapping::Admin("admin".parse()?),
673 NetHsmUserMapping::Backup{
674 backend_user: "backup".parse()?,
675 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
676 system_user: "nethsm-backup-user".parse()?,
677 },
678 NetHsmUserMapping::HermeticMetrics {
679 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
680 system_user: "nethsm-hermetic-metrics-user".parse()?,
681 },
682 NetHsmUserMapping::Metrics {
683 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
684 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
685 system_user: "nethsm-metrics-user".parse()?,
686 },
687 NetHsmUserMapping::Signing {
688 backend_user: "signing".parse()?,
689 signing_key_id: "signing1".parse()?,
690 key_setup: SigningKeySetup::new(
691 KeyType::Curve25519,
692 vec![KeyMechanism::EdDsaSignature],
693 None,
694 SignatureType::EdDsa,
695 CryptographicKeyContext::OpenPgp {
696 user_ids: OpenPgpUserIdList::new(vec![
697 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
698 ])?,
699 version: "v4".parse()?,
700 },
701 )?,
702 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
703 system_user: "nethsm-signing-user".parse()?,
704 tag: "signing1".to_string(),
705 }
706 ]),
707 )?)
708 }
709
710 #[cfg(feature = "nethsm")]
713 #[fixture]
714 fn raw_user_data_nethsm() -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
715 Ok(vec![
716 (
717 SystemUserId::root(),
718 None,
719 ),
720 (
721 "nethsm-backup-user".parse()?,
722 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?),
723 ),
724 (
725 "nethsm-hermetic-metrics-user".parse()?,
726 None,
727 ),
728 (
729 "nethsm-metrics-user".parse()?,
730 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?),
731 ),
732 (
733 "nethsm-signing-user".parse()?,
734 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?),
735 ),
736 ])
737 }
738
739 #[cfg(feature = "yubihsm2")]
741 #[fixture]
742 fn default_yubihsm2_config() -> TestResult<YubiHsm2Config> {
743 Ok(YubiHsm2Config::new(
744 BTreeSet::from_iter([
745 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
746 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
747 ]),
748 BTreeSet::from_iter([
749 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
750 YubiHsm2UserMapping::AuditLog {
751 authentication_key_id: "3".parse()?,
752 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
753 system_user: "yubihsm2-metrics-user".parse()?,
754 },
755 YubiHsm2UserMapping::Backup{
756 authentication_key_id: "2".parse()?,
757 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
758 system_user: "yubihsm2-backup-user".parse()?,
759 wrapping_key_id: "1".parse()?,
760 },
761 YubiHsm2UserMapping::HermeticAuditLog {
762 authentication_key_id: "4".parse()?,
763 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
764 },
765 YubiHsm2UserMapping::Signing {
766 authentication_key_id: "5".parse()?,
767 signing_key_id: "1".parse()?,
768 key_setup: SigningKeySetup::new(
769 KeyType::Curve25519,
770 vec![KeyMechanism::EdDsaSignature],
771 None,
772 SignatureType::EdDsa,
773 CryptographicKeyContext::OpenPgp {
774 user_ids: OpenPgpUserIdList::new(vec![
775 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
776 ])?,
777 version: "v4".parse()?,
778 },
779 )?,
780 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
781 system_user: "yubihsm2-signing-user".parse()?,
782 domain: Domain::One,
783 }
784 ]),
785 )?)
786 }
787
788 #[cfg(feature = "yubihsm2")]
791 #[fixture]
792 fn raw_user_data_yubihsm2() -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
793 Ok(vec![
794 (
795 SystemUserId::root(),
796 None,
797 ),
798 (
799 "yubihsm2-metrics-user".parse()?,
800 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?),
801 ),
802 (
803 "yubihsm2-backup-user".parse()?,
804 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?),
805 ),
806 (
807 "yubihsm2-hermetic-metrics-user".parse()?,
808 None,
809 ),
810 (
811 "yubihsm2-signing-user".parse()?,
812 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?),
813 ),
814 ])
815 }
816
817 #[test]
819 fn config_default_system_path() {
820 assert_eq!(
821 Config::default_system_path(),
822 PathBuf::from("/usr/share/signstar/config.yaml")
823 )
824 }
825
826 #[test]
828 fn config_list_config_file_paths() {
829 assert_eq!(
830 Config::list_config_file_paths(),
831 vec![
832 PathBuf::from("/usr/share/signstar/config.yaml"),
833 PathBuf::from("/run/signstar/config.yaml"),
834 PathBuf::from("/etc/signstar/config.yaml"),
835 ]
836 )
837 }
838
839 #[rstest]
841 fn config_from_file_path_fails_on_missing_file_extension() -> TestResult {
842 let temp_dir = TempDir::new()?;
843
844 match Config::from_file_path(temp_dir.path().join("config")) {
845 Ok(config) => panic!(
846 "Should have failed to create a Config object, but succeeded instead: {config:?}"
847 ),
848 Err(crate::Error::Config(Error::MissingFileExtension { .. })) => {}
849 Err(error) => panic!(
850 "Should have failed with a ConfigError::MissingFileExtension, but failed with a different error instead: {error}"
851 ),
852 }
853
854 Ok(())
855 }
856
857 #[rstest]
859 fn config_from_file_path_fails_on_unsupported_file_extension() -> TestResult {
860 let temp_file = NamedTempFile::with_suffix(".toml")?;
861
862 match Config::from_file_path(temp_file.path()) {
863 Ok(config) => panic!(
864 "Should have failed to create a Config object, but succeeded instead: {config:?}"
865 ),
866 Err(crate::Error::Config(Error::UnsupportedFileExtension { .. })) => {}
867 Err(error) => panic!(
868 "Should have failed with a ConfigError::UnsupportedFileExtension, but failed with a different error instead: {error}"
869 ),
870 }
871
872 Ok(())
873 }
874
875 #[cfg(not(any(feature = "nethsm", feature = "yubihsm2")))]
877 mod no_backend {
878 use std::collections::HashSet;
879
880 use pretty_assertions::assert_eq;
881
882 use super::*;
883 use crate::config::{
884 ConfigAuthorizedKeyEntries,
885 ConfigSystemUserIds,
886 SystemUserData,
887 traits::ConfigSystemUserData,
888 };
889
890 #[fixture]
892 fn default_config(default_system_config: TestResult<SystemConfig>) -> TestResult<Config> {
893 Ok(ConfigBuilder::new(default_system_config?).finish()?)
894 }
895
896 #[rstest]
898 fn config_builder_new(default_system_config: TestResult<SystemConfig>) -> TestResult {
899 let _config = ConfigBuilder::new(default_system_config?).finish()?;
900
901 Ok(())
902 }
903
904 #[rstest]
906 fn config_system(default_system_config: TestResult<SystemConfig>) -> TestResult {
907 let system_config = default_system_config?;
908 let config = ConfigBuilder::new(system_config.clone()).finish()?;
909 assert_eq!(config.system(), &system_config);
910
911 Ok(())
912 }
913
914 #[rstest]
918 fn config_to_yaml_string(default_system_config: TestResult<SystemConfig>) -> TestResult {
919 let config = ConfigBuilder::new(default_system_config?).finish()?;
920 let config_str = config.to_yaml_string()?;
921
922 with_settings!({
923 description => "Configuration with only system-wide configuration",
924 snapshot_path => SNAPSHOT_PATH,
925 prepend_module_to_snapshot => false,
926 }, {
927 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
928 });
929
930 Ok(())
931 }
932
933 #[rstest]
938 fn roundtrip_yaml_config(
939 #[files("../fixtures/config/no_backend/*.yaml")] path: PathBuf,
940 ) -> TestResult {
941 let config_string = read_to_string(&path)?;
942 let config = Config::from_file_path(&path)?;
943
944 assert_eq!(config.to_yaml_string()?, config_string);
945
946 Ok(())
947 }
948
949 #[rstest]
952 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
953 let config = default_config?;
954 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
955 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
956 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
957 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
958 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
959 ]);
960
961 assert_eq!(
962 config.authorized_key_entries(),
963 expected.iter().collect::<HashSet<_>>()
964 );
965 Ok(())
966 }
967
968 #[rstest]
970 fn config_system_user_data(
971 default_config: TestResult<Config>,
972 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
973 ) -> TestResult {
974 let config = default_config?;
975 let raw_user_data = raw_user_data_system?;
976 let expected: HashSet<SystemUserData> = HashSet::from_iter([
977 SystemUserData::HostShareholder {
978 system_user: &raw_user_data[0].0,
979 ssh_authorized_key: raw_user_data[0]
980 .1
981 .as_ref()
982 .expect("to have SSH authorized key"),
983 },
984 SystemUserData::HostShareholder {
985 system_user: &raw_user_data[1].0,
986 ssh_authorized_key: raw_user_data[1]
987 .1
988 .as_ref()
989 .expect("to have SSH authorized key"),
990 },
991 SystemUserData::HostShareholder {
992 system_user: &raw_user_data[2].0,
993 ssh_authorized_key: raw_user_data[2]
994 .1
995 .as_ref()
996 .expect("to have SSH authorized key"),
997 },
998 SystemUserData::HostDownloadNetworkConfig {
999 system_user: &raw_user_data[3].0,
1000 ssh_authorized_key: raw_user_data[3]
1001 .1
1002 .as_ref()
1003 .expect("to have SSH authorized key"),
1004 },
1005 ]);
1006
1007 assert_eq!(config.system_user_data(), expected);
1008 Ok(())
1009 }
1010
1011 #[rstest]
1013 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
1014 let config = default_config?;
1015 let expected: HashSet<SystemUserId> = HashSet::from_iter([
1016 "share-holder1".parse()?,
1017 "share-holder2".parse()?,
1018 "share-holder3".parse()?,
1019 "wireguard-downloader".parse()?,
1020 ]);
1021
1022 assert_eq!(
1023 config.system_user_ids(),
1024 expected.iter().collect::<HashSet<_>>()
1025 );
1026 Ok(())
1027 }
1028
1029 #[rstest]
1031 fn system_user_config_state_from_config(default_config: TestResult<Config>) -> TestResult {
1032 let config = default_config?;
1033 let state = SystemUserConfigState::from(&config);
1034
1035 assert_eq!(state.system_user_data, config.system_user_data(),);
1036 Ok(())
1037 }
1038 }
1039
1040 #[cfg(all(feature = "nethsm", not(feature = "yubihsm2")))]
1042 mod nethsm_backend {
1043 use pretty_assertions::assert_eq;
1044
1045 use super::*;
1046 use crate::config::{
1047 SystemUserData,
1048 traits::{ConfigSystemUserData, MappingAuthorizedKeyEntry, MappingSystemUserId},
1049 };
1050
1051 #[fixture]
1053 fn default_config(
1054 default_system_config: TestResult<SystemConfig>,
1055 default_nethsm_config: TestResult<NetHsmConfig>,
1056 ) -> TestResult<Config> {
1057 Ok(ConfigBuilder::new(default_system_config?)
1058 .set_nethsm_config(default_nethsm_config?)
1059 .finish()?)
1060 }
1061
1062 #[fixture]
1065 fn raw_user_data(
1066 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1067 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1068 ) -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
1069 let mut data = raw_user_data_system?;
1070 data.extend(raw_user_data_nethsm?);
1071 Ok(data)
1072 }
1073
1074 #[rstest]
1076 fn user_backend_connection_system_user_id(
1077 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1078 ) -> TestResult {
1079 let raw_user_data_nethsm = raw_user_data_nethsm?;
1080 let data = UserBackendConnection::NetHsm {
1081 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1082 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1083 connections: BTreeSet::from_iter([Connection::new(
1084 "https://nethsm1.example.org/".parse()?,
1085 ConnectionSecurity::Unsafe,
1086 )]),
1087 mapping: NetHsmUserMapping::Backup {
1088 backend_user: "backup".parse()?,
1089 ssh_authorized_key: raw_user_data_nethsm[1]
1090 .1
1091 .clone()
1092 .expect("to have an SSH authorized key"),
1093 system_user: raw_user_data_nethsm[1].0.clone(),
1094 },
1095 };
1096 assert_eq!(data.system_user_id(), Some(&raw_user_data_nethsm[1].0));
1097
1098 Ok(())
1099 }
1100
1101 #[rstest]
1104 fn user_backend_connection_authorized_key_entry(
1105 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1106 ) -> TestResult {
1107 let raw_user_data_nethsm = raw_user_data_nethsm?;
1108 let data = UserBackendConnection::NetHsm {
1109 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1110 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1111 connections: BTreeSet::from_iter([Connection::new(
1112 "https://nethsm1.example.org/".parse()?,
1113 ConnectionSecurity::Unsafe,
1114 )]),
1115 mapping: NetHsmUserMapping::Backup {
1116 backend_user: "backup".parse()?,
1117 ssh_authorized_key: raw_user_data_nethsm[1]
1118 .1
1119 .clone()
1120 .expect("to have an SSH authorized key"),
1121 system_user: raw_user_data_nethsm[1].0.clone(),
1122 },
1123 };
1124 assert_eq!(
1125 data.authorized_key_entry(),
1126 Some(
1127 raw_user_data_nethsm[1]
1128 .1
1129 .as_ref()
1130 .expect("to have an SSH authorized key")
1131 )
1132 );
1133
1134 Ok(())
1135 }
1136
1137 #[rstest]
1143 #[case::two_duplicate_system_users_two_duplicate_ssh_public_keys(
1144 "Configuration with system-wide and NetHSM configuration has two duplicate system users and two duplicate SSH public keys",
1145 NetHsmConfig::new(
1146 BTreeSet::from_iter([
1147 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1148 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1149 ]),
1150 BTreeSet::from_iter([
1151 NetHsmUserMapping::Admin("admin".parse()?),
1152 NetHsmUserMapping::Backup{
1153 backend_user: "backup".parse()?,
1154 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1155 system_user: "share-holder1".parse()?,
1156 },
1157 NetHsmUserMapping::HermeticMetrics {
1158 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1159 system_user: "nethsm-hermetic-metrics-user".parse()?,
1160 },
1161 NetHsmUserMapping::Metrics {
1162 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1163 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1164 system_user: "share-holder2".parse()?,
1165 },
1166 NetHsmUserMapping::Signing {
1167 backend_user: "signing".parse()?,
1168 signing_key_id: "signing1".parse()?,
1169 key_setup: SigningKeySetup::new(
1170 KeyType::Curve25519,
1171 vec![KeyMechanism::EdDsaSignature],
1172 None,
1173 SignatureType::EdDsa,
1174 CryptographicKeyContext::OpenPgp {
1175 user_ids: OpenPgpUserIdList::new(vec![
1176 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1177 ])?,
1178 version: "v4".parse()?,
1179 },
1180 )?,
1181 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1182 system_user: "nethsm-signing-user".parse()?,
1183 tag: "signing1".to_string(),
1184 }
1185 ]),
1186 )?
1187 )]
1188 #[case::one_duplicate_system_user_two_duplicate_ssh_public_keys(
1189 "Configuration with system-wide and NetHSM configuration has one duplicate system user and two duplicate SSH public keys",
1190 NetHsmConfig::new(
1191 BTreeSet::from_iter([
1192 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1193 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1194 ]),
1195 BTreeSet::from_iter([
1196 NetHsmUserMapping::Admin("admin".parse()?),
1197 NetHsmUserMapping::Backup{
1198 backend_user: "backup".parse()?,
1199 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1200 system_user: "share-holder1".parse()?,
1201 },
1202 NetHsmUserMapping::HermeticMetrics {
1203 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1204 system_user: "nethsm-hermetic-metrics-user".parse()?,
1205 },
1206 NetHsmUserMapping::Metrics {
1207 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1208 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1209 system_user: "nethsm-metrics-user".parse()?,
1210 },
1211 NetHsmUserMapping::Signing {
1212 backend_user: "signing".parse()?,
1213 signing_key_id: "signing1".parse()?,
1214 key_setup: SigningKeySetup::new(
1215 KeyType::Curve25519,
1216 vec![KeyMechanism::EdDsaSignature],
1217 None,
1218 SignatureType::EdDsa,
1219 CryptographicKeyContext::OpenPgp {
1220 user_ids: OpenPgpUserIdList::new(vec![
1221 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1222 ])?,
1223 version: "v4".parse()?,
1224 },
1225 )?,
1226 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1227 system_user: "nethsm-signing-user".parse()?,
1228 tag: "signing1".to_string(),
1229 }
1230 ]),
1231 )?
1232 )]
1233 #[case::one_duplicate_system_user_one_duplicate_ssh_public_key(
1234 "Configuration with system-wide and NetHSM configuration has one duplicate system user and one duplicate SSH public key",
1235 NetHsmConfig::new(
1236 BTreeSet::from_iter([
1237 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1238 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1239 ]),
1240 BTreeSet::from_iter([
1241 NetHsmUserMapping::Admin("admin".parse()?),
1242 NetHsmUserMapping::Backup{
1243 backend_user: "backup".parse()?,
1244 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1245 system_user: "share-holder1".parse()?,
1246 },
1247 NetHsmUserMapping::HermeticMetrics {
1248 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1249 system_user: "nethsm-hermetic-metrics-user".parse()?,
1250 },
1251 NetHsmUserMapping::Metrics {
1252 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1253 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1254 system_user: "nethsm-metrics-user".parse()?,
1255 },
1256 NetHsmUserMapping::Signing {
1257 backend_user: "signing".parse()?,
1258 signing_key_id: "signing1".parse()?,
1259 key_setup: SigningKeySetup::new(
1260 KeyType::Curve25519,
1261 vec![KeyMechanism::EdDsaSignature],
1262 None,
1263 SignatureType::EdDsa,
1264 CryptographicKeyContext::OpenPgp {
1265 user_ids: OpenPgpUserIdList::new(vec![
1266 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1267 ])?,
1268 version: "v4".parse()?,
1269 },
1270 )?,
1271 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1272 system_user: "nethsm-signing-user".parse()?,
1273 tag: "signing1".to_string(),
1274 }
1275 ]),
1276 )?
1277 )]
1278 #[case::one_duplicate_ssh_public_key(
1279 "Configuration with system-wide and NetHSM configuration has one duplicate SSH public key",
1280 NetHsmConfig::new(
1281 BTreeSet::from_iter([
1282 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1283 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1284 ]),
1285 BTreeSet::from_iter([
1286 NetHsmUserMapping::Admin("admin".parse()?),
1287 NetHsmUserMapping::Backup{
1288 backend_user: "backup".parse()?,
1289 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1290 system_user: "nethsm-backup-user".parse()?,
1291 },
1292 NetHsmUserMapping::HermeticMetrics {
1293 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1294 system_user: "nethsm-hermetic-metrics-user".parse()?,
1295 },
1296 NetHsmUserMapping::Metrics {
1297 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1298 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1299 system_user: "nethsm-metrics-user".parse()?,
1300 },
1301 NetHsmUserMapping::Signing {
1302 backend_user: "signing".parse()?,
1303 signing_key_id: "signing1".parse()?,
1304 key_setup: SigningKeySetup::new(
1305 KeyType::Curve25519,
1306 vec![KeyMechanism::EdDsaSignature],
1307 None,
1308 SignatureType::EdDsa,
1309 CryptographicKeyContext::OpenPgp {
1310 user_ids: OpenPgpUserIdList::new(vec![
1311 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1312 ])?,
1313 version: "v4".parse()?,
1314 },
1315 )?,
1316 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1317 system_user: "nethsm-signing-user".parse()?,
1318 tag: "signing1".to_string(),
1319 }
1320 ]),
1321 )?
1322 )]
1323 #[case::one_duplicate_system_user(
1324 "Configuration with system-wide and NetHSM configuration has one duplicate system user",
1325 NetHsmConfig::new(
1326 BTreeSet::from_iter([
1327 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1328 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1329 ]),
1330 BTreeSet::from_iter([
1331 NetHsmUserMapping::Admin("admin".parse()?),
1332 NetHsmUserMapping::Backup{
1333 backend_user: "backup".parse()?,
1334 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1335 system_user: "share-holder1".parse()?,
1336 },
1337 NetHsmUserMapping::HermeticMetrics {
1338 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1339 system_user: "nethsm-hermetic-metrics-user".parse()?,
1340 },
1341 NetHsmUserMapping::Metrics {
1342 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1343 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1344 system_user: "nethsm-metrics-user".parse()?,
1345 },
1346 NetHsmUserMapping::Signing {
1347 backend_user: "signing".parse()?,
1348 signing_key_id: "signing1".parse()?,
1349 key_setup: SigningKeySetup::new(
1350 KeyType::Curve25519,
1351 vec![KeyMechanism::EdDsaSignature],
1352 None,
1353 SignatureType::EdDsa,
1354 CryptographicKeyContext::OpenPgp {
1355 user_ids: OpenPgpUserIdList::new(vec![
1356 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1357 ])?,
1358 version: "v4".parse()?,
1359 },
1360 )?,
1361 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1362 system_user: "nethsm-signing-user".parse()?,
1363 tag: "signing1".to_string(),
1364 }
1365 ]),
1366 )?
1367 )]
1368 fn config_builder_fails_validation(
1369 default_system_config: TestResult<SystemConfig>,
1370 #[case] description: &str,
1371 #[case] nethsm_config: NetHsmConfig,
1372 ) -> TestResult {
1373 let error_message = match ConfigBuilder::new(default_system_config?)
1374 .set_nethsm_config(nethsm_config)
1375 .finish()
1376 {
1377 Err(error) => error.to_string(),
1378 Ok(config) => panic!(
1379 "Expected to fail with Error::Validation, but succeeded instead: {}",
1380 config.to_yaml_string()?
1381 ),
1382 };
1383
1384 with_settings!({
1385 description => description,
1386 snapshot_path => SNAPSHOT_PATH,
1387 prepend_module_to_snapshot => false,
1388 }, {
1389 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
1390 });
1391
1392 Ok(())
1393 }
1394
1395 #[rstest]
1397 fn config_nethsm(
1398 default_system_config: TestResult<SystemConfig>,
1399 default_nethsm_config: TestResult<NetHsmConfig>,
1400 ) -> TestResult {
1401 let nethsm_config = default_nethsm_config?;
1402
1403 let config = ConfigBuilder::new(default_system_config?)
1404 .set_nethsm_config(nethsm_config.clone())
1405 .finish()?;
1406
1407 assert_eq!(
1408 &nethsm_config,
1409 config.nethsm().expect("a NetHsmConfig reference")
1410 );
1411
1412 Ok(())
1413 }
1414
1415 #[rstest]
1417 #[case::nethsm_signing(
1418 "nethsm-signing-user",
1419 Some(UserBackendConnection::NetHsm {
1420 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1421 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1422 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1423 },
1424 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1425 connections: BTreeSet::from_iter([
1426 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1427 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1428 ]),
1429 mapping: NetHsmUserMapping::Signing {
1430 backend_user: "signing".parse()?,
1431 signing_key_id: "signing1".parse()?,
1432 key_setup: SigningKeySetup::new(
1433 KeyType::Curve25519,
1434 vec![KeyMechanism::EdDsaSignature],
1435 None,
1436 SignatureType::EdDsa,
1437 CryptographicKeyContext::OpenPgp {
1438 user_ids: OpenPgpUserIdList::new(vec![
1439 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1440 ])?,
1441 version: "v4".parse()?,
1442 },
1443 )?,
1444 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1445 system_user: "nethsm-signing-user".parse()?,
1446 tag: "signing1".to_string(),
1447 }
1448 })
1449 )]
1450 #[case::none("foo", None)]
1451 fn config_user_backend_connection(
1452 default_config: TestResult<Config>,
1453 #[case] system_user: &str,
1454 #[case] expected_connection: Option<UserBackendConnection>,
1455 ) -> TestResult {
1456 let config = default_config?;
1457 assert_eq!(
1458 expected_connection,
1459 config.user_backend_connection(&system_user.parse()?)
1460 );
1461
1462 Ok(())
1463 }
1464
1465 #[rstest]
1468 #[case::filter_all(
1469 UserBackendConnectionFilter::All,
1470 vec![
1471 UserBackendConnection::NetHsm {
1472 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1473 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1474 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1475 },
1476 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1477 connections: BTreeSet::from_iter([
1478 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1479 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1480 ]),
1481 mapping: NetHsmUserMapping::Admin("admin".parse()?)
1482 },
1483 UserBackendConnection::NetHsm {
1484 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1485 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1486 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1487 },
1488 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1489 connections: BTreeSet::from_iter([
1490 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1491 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1492 ]),
1493 mapping: NetHsmUserMapping::Backup{
1494 backend_user: "backup".parse()?,
1495 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1496 system_user: "nethsm-backup-user".parse()?,
1497 }
1498 },
1499 UserBackendConnection::NetHsm {
1500 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1501 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1502 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1503 },
1504 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1505 connections: BTreeSet::from_iter([
1506 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1507 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1508 ]),
1509 mapping: NetHsmUserMapping::HermeticMetrics {
1510 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1511 system_user: "nethsm-hermetic-metrics-user".parse()?,
1512 }
1513 },
1514 UserBackendConnection::NetHsm {
1515 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1516 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1517 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1518 },
1519 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1520 connections: BTreeSet::from_iter([
1521 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1522 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1523 ]),
1524 mapping: NetHsmUserMapping::Metrics {
1525 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1526 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1527 system_user: "nethsm-metrics-user".parse()?,
1528 }
1529 },
1530 UserBackendConnection::NetHsm {
1531 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1532 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1533 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1534 },
1535 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1536 connections: BTreeSet::from_iter([
1537 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1538 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1539 ]),
1540 mapping: NetHsmUserMapping::Signing {
1541 backend_user: "signing".parse()?,
1542 signing_key_id: "signing1".parse()?,
1543 key_setup: SigningKeySetup::new(
1544 KeyType::Curve25519,
1545 vec![KeyMechanism::EdDsaSignature],
1546 None,
1547 SignatureType::EdDsa,
1548 CryptographicKeyContext::OpenPgp {
1549 user_ids: OpenPgpUserIdList::new(vec![
1550 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1551 ])?,
1552 version: "v4".parse()?,
1553 },
1554 )?,
1555 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1556 system_user: "nethsm-signing-user".parse()?,
1557 tag: "signing1".to_string(),
1558 }
1559 },
1560 ],
1561 )]
1562 #[case::filter_admin(
1563 UserBackendConnectionFilter::Admin,
1564 vec![
1565 UserBackendConnection::NetHsm {
1566 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1567 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1568 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1569 },
1570 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1571 connections: BTreeSet::from_iter([
1572 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1573 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1574 ]),
1575 mapping: NetHsmUserMapping::Admin("admin".parse()?)
1576 },
1577 ],
1578 )]
1579 #[case::filter_non_admin(
1580 UserBackendConnectionFilter::NonAdmin,
1581 vec![
1582 UserBackendConnection::NetHsm {
1583 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1584 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1585 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1586 },
1587 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1588 connections: BTreeSet::from_iter([
1589 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1590 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1591 ]),
1592 mapping: NetHsmUserMapping::Backup{
1593 backend_user: "backup".parse()?,
1594 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1595 system_user: "nethsm-backup-user".parse()?,
1596 }
1597 },
1598 UserBackendConnection::NetHsm {
1599 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1600 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1601 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1602 },
1603 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1604 connections: BTreeSet::from_iter([
1605 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1606 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1607 ]),
1608 mapping: NetHsmUserMapping::HermeticMetrics {
1609 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1610 system_user: "nethsm-hermetic-metrics-user".parse()?,
1611 }
1612 },
1613 UserBackendConnection::NetHsm {
1614 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1615 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1616 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1617 },
1618 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1619 connections: BTreeSet::from_iter([
1620 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1621 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1622 ]),
1623 mapping: NetHsmUserMapping::Metrics {
1624 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1625 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1626 system_user: "nethsm-metrics-user".parse()?,
1627 }
1628 },
1629 UserBackendConnection::NetHsm {
1630 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1631 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1632 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1633 },
1634 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1635 connections: BTreeSet::from_iter([
1636 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1637 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1638 ]),
1639 mapping: NetHsmUserMapping::Signing {
1640 backend_user: "signing".parse()?,
1641 signing_key_id: "signing1".parse()?,
1642 key_setup: SigningKeySetup::new(
1643 KeyType::Curve25519,
1644 vec![KeyMechanism::EdDsaSignature],
1645 None,
1646 SignatureType::EdDsa,
1647 CryptographicKeyContext::OpenPgp {
1648 user_ids: OpenPgpUserIdList::new(vec![
1649 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1650 ])?,
1651 version: "v4".parse()?,
1652 },
1653 )?,
1654 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1655 system_user: "nethsm-signing-user".parse()?,
1656 tag: "signing1".to_string(),
1657 }
1658 },
1659 ],
1660 )]
1661 fn config_user_backend_connections(
1662 default_config: TestResult<Config>,
1663 #[case] filter: UserBackendConnectionFilter,
1664 #[case] expected_connections: Vec<UserBackendConnection>,
1665 ) -> TestResult {
1666 let config = default_config?;
1667
1668 assert_eq!(
1669 expected_connections,
1670 config.user_backend_connections(filter)
1671 );
1672
1673 Ok(())
1674 }
1675
1676 #[rstest]
1679 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
1680 let config = default_config?;
1681 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
1682 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
1683 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1684 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1685 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1686 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1687 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1688 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1689 ]);
1690
1691 assert_eq!(
1692 config.authorized_key_entries(),
1693 expected.iter().collect::<HashSet<_>>()
1694 );
1695 Ok(())
1696 }
1697
1698 #[rstest]
1700 fn config_system_user_data(
1701 default_config: TestResult<Config>,
1702 raw_user_data: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1703 ) -> TestResult {
1704 let config = default_config?;
1705 let raw_user_data = raw_user_data?;
1706 let expected: HashSet<SystemUserData> = HashSet::from_iter([
1707 SystemUserData::HostShareholder {
1708 system_user: &raw_user_data[0].0,
1709 ssh_authorized_key: raw_user_data[0]
1710 .1
1711 .as_ref()
1712 .expect("to have SSH authorized key"),
1713 },
1714 SystemUserData::HostShareholder {
1715 system_user: &raw_user_data[1].0,
1716 ssh_authorized_key: raw_user_data[1]
1717 .1
1718 .as_ref()
1719 .expect("to have SSH authorized key"),
1720 },
1721 SystemUserData::HostShareholder {
1722 system_user: &raw_user_data[2].0,
1723 ssh_authorized_key: raw_user_data[2]
1724 .1
1725 .as_ref()
1726 .expect("to have SSH authorized key"),
1727 },
1728 SystemUserData::HostDownloadNetworkConfig {
1729 system_user: &raw_user_data[3].0,
1730 ssh_authorized_key: raw_user_data[3]
1731 .1
1732 .as_ref()
1733 .expect("to have SSH authorized key"),
1734 },
1735 SystemUserData::BackendAdmin {
1736 system_user: raw_user_data[4].0.clone(),
1737 },
1738 SystemUserData::BackendBackup {
1739 system_user: &raw_user_data[5].0,
1740 ssh_authorized_key: raw_user_data[5]
1741 .1
1742 .as_ref()
1743 .expect("to have SSH authorized key"),
1744 },
1745 SystemUserData::BackendHermeticMetrics {
1746 system_user: &raw_user_data[6].0,
1747 },
1748 SystemUserData::BackendMetrics {
1749 system_user: &raw_user_data[7].0,
1750 ssh_authorized_key: raw_user_data[7]
1751 .1
1752 .as_ref()
1753 .expect("to have SSH authorized key"),
1754 },
1755 SystemUserData::BackendSign {
1756 system_user: &raw_user_data[8].0,
1757 ssh_authorized_key: raw_user_data[8]
1758 .1
1759 .as_ref()
1760 .expect("to have SSH authorized key"),
1761 },
1762 ]);
1763
1764 assert_eq!(config.system_user_data(), expected);
1765 Ok(())
1766 }
1767
1768 #[rstest]
1770 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
1771 let config = default_config?;
1772 let expected: HashSet<SystemUserId> = HashSet::from_iter([
1773 "share-holder1".parse()?,
1774 "share-holder2".parse()?,
1775 "share-holder3".parse()?,
1776 "wireguard-downloader".parse()?,
1777 "nethsm-backup-user".parse()?,
1778 "nethsm-hermetic-metrics-user".parse()?,
1779 "nethsm-metrics-user".parse()?,
1780 "nethsm-signing-user".parse()?,
1781 ]);
1782
1783 assert_eq!(
1784 config.system_user_ids(),
1785 expected.iter().collect::<HashSet<_>>()
1786 );
1787 Ok(())
1788 }
1789
1790 #[rstest]
1794 fn config_to_yaml_string(
1795 default_system_config: TestResult<SystemConfig>,
1796 default_nethsm_config: TestResult<NetHsmConfig>,
1797 ) -> TestResult {
1798 let config = ConfigBuilder::new(default_system_config?)
1799 .set_nethsm_config(default_nethsm_config?)
1800 .finish()?;
1801 let config_str = config.to_yaml_string()?;
1802
1803 with_settings!({
1804 description => "Configuration with system-wide and NetHSM configuration",
1805 snapshot_path => SNAPSHOT_PATH,
1806 prepend_module_to_snapshot => false,
1807 }, {
1808 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
1809 });
1810
1811 Ok(())
1812 }
1813
1814 #[rstest]
1819 fn roundtrip_yaml_config(
1820 #[files("../fixtures/config/nethsm_backend/*.yaml")] path: PathBuf,
1821 ) -> TestResult {
1822 let config_string = read_to_string(&path)?;
1823 let config = Config::from_file_path(&path)?;
1824
1825 assert_eq!(config.to_yaml_string()?, config_string);
1826
1827 Ok(())
1828 }
1829
1830 #[rstest]
1834 fn user_backend_connection_secret_handling(
1835 default_config: TestResult<Config>,
1836 ) -> TestResult {
1837 let config = default_config?;
1838 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
1839 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1840 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1841 };
1842 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
1843
1844 let user_backend_connection = config
1845 .user_backend_connection(&"nethsm-signing-user".parse()?)
1846 .expect("there to be a mapping of the requested name");
1847
1848 assert_eq!(
1849 user_backend_connection.admin_secret_handling(),
1850 admin_secret_handling
1851 );
1852 assert_eq!(
1853 user_backend_connection.non_admin_secret_handling(),
1854 non_admin_secret_handling
1855 );
1856
1857 Ok(())
1858 }
1859
1860 #[rstest]
1862 fn system_user_config_state_from_config(default_config: TestResult<Config>) -> TestResult {
1863 let config = default_config?;
1864 let state = SystemUserConfigState::from(&config);
1865
1866 assert_eq!(state.system_user_data, config.system_user_data(),);
1867 Ok(())
1868 }
1869 }
1870
1871 #[cfg(all(feature = "yubihsm2", not(feature = "nethsm")))]
1873 mod yubihsm2_backend {
1874 use pretty_assertions::assert_eq;
1875
1876 use super::*;
1877 use crate::config::{
1878 SystemUserData,
1879 traits::{ConfigSystemUserData, MappingAuthorizedKeyEntry, MappingSystemUserId},
1880 };
1881
1882 #[fixture]
1884 fn default_config(
1885 default_system_config: TestResult<SystemConfig>,
1886 default_yubihsm2_config: TestResult<YubiHsm2Config>,
1887 ) -> TestResult<Config> {
1888 Ok(ConfigBuilder::new(default_system_config?)
1889 .set_yubihsm2_config(default_yubihsm2_config?)
1890 .finish()?)
1891 }
1892
1893 #[fixture]
1896 fn raw_user_data(
1897 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1898 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1899 ) -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
1900 let mut data = raw_user_data_system?;
1901 data.extend(raw_user_data_yubihsm2?);
1902 Ok(data)
1903 }
1904
1905 #[rstest]
1907 fn user_backend_connection_system_user_id(
1908 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1909 ) -> TestResult {
1910 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
1911 let data = UserBackendConnection::YubiHsm2 {
1912 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1913 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1914 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
1915 serial_number: "0123456789".parse()?,
1916 }]),
1917 mapping: YubiHsm2UserMapping::AuditLog {
1918 authentication_key_id: "1".parse()?,
1919 ssh_authorized_key: raw_user_data_yubihsm2[1]
1920 .1
1921 .clone()
1922 .expect("to have an SSH authorized key"),
1923 system_user: raw_user_data_yubihsm2[1].0.clone(),
1924 },
1925 };
1926 assert_eq!(data.system_user_id(), Some(&raw_user_data_yubihsm2[1].0));
1927
1928 Ok(())
1929 }
1930
1931 #[rstest]
1934 fn user_backend_connection_authorized_key_entry(
1935 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1936 ) -> TestResult {
1937 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
1938 let data = UserBackendConnection::YubiHsm2 {
1939 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1940 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1941 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
1942 serial_number: "0123456789".parse()?,
1943 }]),
1944 mapping: YubiHsm2UserMapping::AuditLog {
1945 authentication_key_id: "1".parse()?,
1946 ssh_authorized_key: raw_user_data_yubihsm2[1]
1947 .1
1948 .clone()
1949 .expect("to have an SSH authorized key"),
1950 system_user: raw_user_data_yubihsm2[1].0.clone(),
1951 },
1952 };
1953 assert_eq!(
1954 data.authorized_key_entry(),
1955 Some(
1956 raw_user_data_yubihsm2[1]
1957 .1
1958 .as_ref()
1959 .expect("to have an SSH authorized key")
1960 )
1961 );
1962
1963 Ok(())
1964 }
1965
1966 #[rstest]
1972 #[case::two_duplicate_system_users_two_duplicate_ssh_public_keys(
1973 "Configuration with system-wide and YubiHSM2 configuration has two duplicate system users and two duplicate SSH public keys",
1974 YubiHsm2Config::new(
1975 BTreeSet::from_iter([
1976 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1977 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1978 ]),
1979 BTreeSet::from_iter([
1980 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1981 YubiHsm2UserMapping::AuditLog {
1982 authentication_key_id: "3".parse()?,
1983 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1984 system_user: "share-holder2".parse()?,
1985 },
1986 YubiHsm2UserMapping::Backup{
1987 authentication_key_id: "2".parse()?,
1988 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1989 system_user: "share-holder1".parse()?,
1990 wrapping_key_id: "1".parse()?,
1991 },
1992 YubiHsm2UserMapping::HermeticAuditLog {
1993 authentication_key_id: "4".parse()?,
1994 system_user: "yubihsm2-hermetic-metrics".parse()?,
1995 },
1996 YubiHsm2UserMapping::Signing {
1997 authentication_key_id: "5".parse()?,
1998 signing_key_id: "1".parse()?,
1999 key_setup: SigningKeySetup::new(
2000 KeyType::Curve25519,
2001 vec![KeyMechanism::EdDsaSignature],
2002 None,
2003 SignatureType::EdDsa,
2004 CryptographicKeyContext::OpenPgp {
2005 user_ids: OpenPgpUserIdList::new(vec![
2006 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2007 ])?,
2008 version: "v4".parse()?,
2009 },
2010 )?,
2011 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2012 system_user: "yubihsm2-signing-user".parse()?,
2013 domain: Domain::One,
2014 }
2015 ]),
2016 )?
2017 )]
2018 #[case::one_duplicate_system_user_two_duplicate_ssh_public_keys(
2019 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user and two duplicate SSH public keys",
2020 YubiHsm2Config::new(
2021 BTreeSet::from_iter([
2022 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2023 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2024 ]),
2025 BTreeSet::from_iter([
2026 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2027 YubiHsm2UserMapping::AuditLog {
2028 authentication_key_id: "3".parse()?,
2029 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2030 system_user: "yubihsm2-metrics-user".parse()?,
2031 },
2032 YubiHsm2UserMapping::Backup{
2033 authentication_key_id: "2".parse()?,
2034 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2035 system_user: "share-holder1".parse()?,
2036 wrapping_key_id: "1".parse()?,
2037 },
2038 YubiHsm2UserMapping::HermeticAuditLog {
2039 authentication_key_id: "4".parse()?,
2040 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2041 },
2042 YubiHsm2UserMapping::Signing {
2043 authentication_key_id: "5".parse()?,
2044 signing_key_id: "1".parse()?,
2045 key_setup: SigningKeySetup::new(
2046 KeyType::Curve25519,
2047 vec![KeyMechanism::EdDsaSignature],
2048 None,
2049 SignatureType::EdDsa,
2050 CryptographicKeyContext::OpenPgp {
2051 user_ids: OpenPgpUserIdList::new(vec![
2052 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2053 ])?,
2054 version: "v4".parse()?,
2055 },
2056 )?,
2057 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2058 system_user: "yubihsm2-signing-user".parse()?,
2059 domain: Domain::One,
2060 }
2061 ]),
2062 )?
2063 )]
2064 #[case::one_duplicate_system_user_one_duplicate_ssh_public_key(
2065 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user and one duplicate SSH public key",
2066 YubiHsm2Config::new(
2067 BTreeSet::from_iter([
2068 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2069 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2070 ]),
2071 BTreeSet::from_iter([
2072 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2073 YubiHsm2UserMapping::AuditLog {
2074 authentication_key_id: "3".parse()?,
2075 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2076 system_user: "yubihsm2-metrics-user".parse()?,
2077 },
2078 YubiHsm2UserMapping::Backup{
2079 authentication_key_id: "2".parse()?,
2080 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2081 system_user: "share-holder1".parse()?,
2082 wrapping_key_id: "1".parse()?,
2083 },
2084 YubiHsm2UserMapping::HermeticAuditLog {
2085 authentication_key_id: "4".parse()?,
2086 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2087 },
2088 YubiHsm2UserMapping::Signing {
2089 authentication_key_id: "5".parse()?,
2090 signing_key_id: "1".parse()?,
2091 key_setup: SigningKeySetup::new(
2092 KeyType::Curve25519,
2093 vec![KeyMechanism::EdDsaSignature],
2094 None,
2095 SignatureType::EdDsa,
2096 CryptographicKeyContext::OpenPgp {
2097 user_ids: OpenPgpUserIdList::new(vec![
2098 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2099 ])?,
2100 version: "v4".parse()?,
2101 },
2102 )?,
2103 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2104 system_user: "yubihsm2-signing-user".parse()?,
2105 domain: Domain::One,
2106 }
2107 ]),
2108 )?
2109 )]
2110 #[case::one_duplicate_ssh_public_key(
2111 "Configuration with system-wide and YubiHSM2 configuration has one duplicate SSH public key",
2112 YubiHsm2Config::new(
2113 BTreeSet::from_iter([
2114 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2115 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2116 ]),
2117 BTreeSet::from_iter([
2118 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2119 YubiHsm2UserMapping::AuditLog {
2120 authentication_key_id: "3".parse()?,
2121 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2122 system_user: "yubihsm2-metrics-user".parse()?,
2123 },
2124 YubiHsm2UserMapping::Backup{
2125 authentication_key_id: "2".parse()?,
2126 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2127 system_user: "yubihsm2-backup-user".parse()?,
2128 wrapping_key_id: "1".parse()?,
2129 },
2130 YubiHsm2UserMapping::HermeticAuditLog {
2131 authentication_key_id: "4".parse()?,
2132 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2133 },
2134 YubiHsm2UserMapping::Signing {
2135 authentication_key_id: "5".parse()?,
2136 signing_key_id: "1".parse()?,
2137 key_setup: SigningKeySetup::new(
2138 KeyType::Curve25519,
2139 vec![KeyMechanism::EdDsaSignature],
2140 None,
2141 SignatureType::EdDsa,
2142 CryptographicKeyContext::OpenPgp {
2143 user_ids: OpenPgpUserIdList::new(vec![
2144 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2145 ])?,
2146 version: "v4".parse()?,
2147 },
2148 )?,
2149 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2150 system_user: "yubihsm2-signing-user".parse()?,
2151 domain: Domain::One,
2152 }
2153 ]),
2154 )?
2155 )]
2156 #[case::one_duplicate_system_user(
2157 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user",
2158 YubiHsm2Config::new(
2159 BTreeSet::from_iter([
2160 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2161 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2162 ]),
2163 BTreeSet::from_iter([
2164 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2165 YubiHsm2UserMapping::AuditLog {
2166 authentication_key_id: "3".parse()?,
2167 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2168 system_user: "yubihsm2-metrics-user".parse()?,
2169 },
2170 YubiHsm2UserMapping::Backup{
2171 authentication_key_id: "2".parse()?,
2172 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2173 system_user: "share-holder1".parse()?,
2174 wrapping_key_id: "1".parse()?,
2175 },
2176 YubiHsm2UserMapping::HermeticAuditLog {
2177 authentication_key_id: "4".parse()?,
2178 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2179 },
2180 YubiHsm2UserMapping::Signing {
2181 authentication_key_id: "5".parse()?,
2182 signing_key_id: "1".parse()?,
2183 key_setup: SigningKeySetup::new(
2184 KeyType::Curve25519,
2185 vec![KeyMechanism::EdDsaSignature],
2186 None,
2187 SignatureType::EdDsa,
2188 CryptographicKeyContext::OpenPgp {
2189 user_ids: OpenPgpUserIdList::new(vec![
2190 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2191 ])?,
2192 version: "v4".parse()?,
2193 },
2194 )?,
2195 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2196 system_user: "yubihsm2-signing-user".parse()?,
2197 domain: Domain::One,
2198 }
2199 ]),
2200 )?
2201 )]
2202 fn config_builder_fails_validation(
2203 default_system_config: TestResult<SystemConfig>,
2204 #[case] description: &str,
2205 #[case] yubihsm2_config: YubiHsm2Config,
2206 ) -> TestResult {
2207 let error_message = match ConfigBuilder::new(default_system_config?)
2208 .set_yubihsm2_config(yubihsm2_config)
2209 .finish()
2210 {
2211 Err(error) => error.to_string(),
2212 Ok(config) => panic!(
2213 "Expected to fail with Error::Validation, but succeeded instead: {}",
2214 config.to_yaml_string()?
2215 ),
2216 };
2217
2218 with_settings!({
2219 description => description,
2220 snapshot_path => SNAPSHOT_PATH,
2221 prepend_module_to_snapshot => false,
2222 }, {
2223 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
2224 });
2225
2226 Ok(())
2227 }
2228
2229 #[rstest]
2231 fn config_yubihsm2(
2232 default_system_config: TestResult<SystemConfig>,
2233 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2234 ) -> TestResult {
2235 let yubihsm2_config = default_yubihsm2_config?;
2236
2237 let config = ConfigBuilder::new(default_system_config?)
2238 .set_yubihsm2_config(yubihsm2_config.clone())
2239 .finish()?;
2240
2241 assert_eq!(
2242 &yubihsm2_config,
2243 config.yubihsm2().expect("a YubiHsm2Config reference")
2244 );
2245
2246 Ok(())
2247 }
2248
2249 #[rstest]
2251 #[case::yubihsm2_signing(
2252 "yubihsm2-signing-user",
2253 Some(UserBackendConnection::YubiHsm2 {
2254 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2255 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2256 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2257 },
2258 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2259 connections: BTreeSet::from_iter([
2260 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2261 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2262 ]),
2263 mapping: YubiHsm2UserMapping::Signing {
2264 authentication_key_id: "5".parse()?,
2265 signing_key_id: "1".parse()?,
2266 key_setup: SigningKeySetup::new(
2267 KeyType::Curve25519,
2268 vec![KeyMechanism::EdDsaSignature],
2269 None,
2270 SignatureType::EdDsa,
2271 CryptographicKeyContext::OpenPgp {
2272 user_ids: OpenPgpUserIdList::new(vec![
2273 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2274 ])?,
2275 version: "v4".parse()?,
2276 },
2277 )?,
2278 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2279 system_user: "yubihsm2-signing-user".parse()?,
2280 domain: Domain::One,
2281 }
2282 })
2283 )]
2284 #[case::none("foo", None)]
2285 fn config_user_backend_connection(
2286 default_config: TestResult<Config>,
2287 #[case] system_user: &str,
2288 #[case] expected_connection: Option<UserBackendConnection>,
2289 ) -> TestResult {
2290 let config = default_config?;
2291 assert_eq!(
2292 expected_connection,
2293 config.user_backend_connection(&system_user.parse()?)
2294 );
2295
2296 Ok(())
2297 }
2298
2299 #[rstest]
2302 #[case::filter_all(
2303 UserBackendConnectionFilter::All,
2304 vec![
2305 UserBackendConnection::YubiHsm2 {
2306 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2307 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2308 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2309 },
2310 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2311 connections: BTreeSet::from_iter([
2312 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2313 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2314 ]),
2315 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2316 },
2317 UserBackendConnection::YubiHsm2 {
2318 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2319 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2320 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2321 },
2322 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2323 connections: BTreeSet::from_iter([
2324 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2325 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2326 ]),
2327 mapping: YubiHsm2UserMapping::AuditLog {
2328 authentication_key_id: "3".parse()?,
2329 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2330 system_user: "yubihsm2-metrics-user".parse()?,
2331 },
2332 },
2333 UserBackendConnection::YubiHsm2 {
2334 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2335 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2336 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2337 },
2338 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2339 connections: BTreeSet::from_iter([
2340 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2341 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2342 ]),
2343 mapping: YubiHsm2UserMapping::Backup{
2344 authentication_key_id: "2".parse()?,
2345 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2346 system_user: "yubihsm2-backup-user".parse()?,
2347 wrapping_key_id: "1".parse()?,
2348 },
2349 },
2350 UserBackendConnection::YubiHsm2 {
2351 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2352 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2353 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2354 },
2355 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2356 connections: BTreeSet::from_iter([
2357 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2358 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2359 ]),
2360 mapping: YubiHsm2UserMapping::HermeticAuditLog {
2361 authentication_key_id: "4".parse()?,
2362 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2363 },
2364 },
2365 UserBackendConnection::YubiHsm2 {
2366 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2367 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2368 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2369 },
2370 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2371 connections: BTreeSet::from_iter([
2372 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2373 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2374 ]),
2375 mapping: YubiHsm2UserMapping::Signing {
2376 authentication_key_id: "5".parse()?,
2377 signing_key_id: "1".parse()?,
2378 key_setup: SigningKeySetup::new(
2379 KeyType::Curve25519,
2380 vec![KeyMechanism::EdDsaSignature],
2381 None,
2382 SignatureType::EdDsa,
2383 CryptographicKeyContext::OpenPgp {
2384 user_ids: OpenPgpUserIdList::new(vec![
2385 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2386 ])?,
2387 version: "v4".parse()?,
2388 },
2389 )?,
2390 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2391 system_user: "yubihsm2-signing-user".parse()?,
2392 domain: Domain::One,
2393 }
2394 },
2395 ],
2396 )]
2397 #[case::filter_admin(
2398 UserBackendConnectionFilter::Admin,
2399 vec![
2400 UserBackendConnection::YubiHsm2 {
2401 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2402 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2403 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2404 },
2405 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2406 connections: BTreeSet::from_iter([
2407 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2408 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2409 ]),
2410 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2411 },
2412 ],
2413 )]
2414 #[case::filter_non_admin(
2415 UserBackendConnectionFilter::NonAdmin,
2416 vec![
2417 UserBackendConnection::YubiHsm2 {
2418 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2419 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2420 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2421 },
2422 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2423 connections: BTreeSet::from_iter([
2424 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2425 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2426 ]),
2427 mapping: YubiHsm2UserMapping::AuditLog {
2428 authentication_key_id: "3".parse()?,
2429 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2430 system_user: "yubihsm2-metrics-user".parse()?,
2431 },
2432 },
2433 UserBackendConnection::YubiHsm2 {
2434 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2435 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2436 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2437 },
2438 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2439 connections: BTreeSet::from_iter([
2440 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2441 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2442 ]),
2443 mapping: YubiHsm2UserMapping::Backup{
2444 authentication_key_id: "2".parse()?,
2445 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2446 system_user: "yubihsm2-backup-user".parse()?,
2447 wrapping_key_id: "1".parse()?,
2448 },
2449 },
2450 UserBackendConnection::YubiHsm2 {
2451 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2452 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2453 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2454 },
2455 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2456 connections: BTreeSet::from_iter([
2457 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2458 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2459 ]),
2460 mapping: YubiHsm2UserMapping::HermeticAuditLog {
2461 authentication_key_id: "4".parse()?,
2462 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2463 },
2464 },
2465 UserBackendConnection::YubiHsm2 {
2466 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2467 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2468 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2469 },
2470 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2471 connections: BTreeSet::from_iter([
2472 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2473 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2474 ]),
2475 mapping: YubiHsm2UserMapping::Signing {
2476 authentication_key_id: "5".parse()?,
2477 signing_key_id: "1".parse()?,
2478 key_setup: SigningKeySetup::new(
2479 KeyType::Curve25519,
2480 vec![KeyMechanism::EdDsaSignature],
2481 None,
2482 SignatureType::EdDsa,
2483 CryptographicKeyContext::OpenPgp {
2484 user_ids: OpenPgpUserIdList::new(vec![
2485 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2486 ])?,
2487 version: "v4".parse()?,
2488 },
2489 )?,
2490 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2491 system_user: "yubihsm2-signing-user".parse()?,
2492 domain: Domain::One,
2493 }
2494 },
2495 ],
2496 )]
2497 fn config_user_backend_connections(
2498 default_config: TestResult<Config>,
2499 #[case] filter: UserBackendConnectionFilter,
2500 #[case] expected_connections: Vec<UserBackendConnection>,
2501 ) -> TestResult {
2502 let config = default_config?;
2503
2504 assert_eq!(
2505 expected_connections,
2506 config.user_backend_connections(filter)
2507 );
2508
2509 Ok(())
2510 }
2511
2512 #[rstest]
2516 fn config_to_yaml_string(
2517 default_system_config: TestResult<SystemConfig>,
2518 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2519 ) -> TestResult {
2520 let config = ConfigBuilder::new(default_system_config?)
2521 .set_yubihsm2_config(default_yubihsm2_config?)
2522 .finish()?;
2523 let config_str = config.to_yaml_string()?;
2524
2525 with_settings!({
2526 description => "Configuration with system-wide and YubiHSM2 configuration",
2527 snapshot_path => SNAPSHOT_PATH,
2528 prepend_module_to_snapshot => false,
2529 }, {
2530 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
2531 });
2532
2533 Ok(())
2534 }
2535
2536 #[rstest]
2539 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
2540 let config = default_config?;
2541 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
2542 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
2543 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2544 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2545 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2546 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2547 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2548 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2549 ]);
2550
2551 assert_eq!(
2552 config.authorized_key_entries(),
2553 expected.iter().collect::<HashSet<_>>()
2554 );
2555 Ok(())
2556 }
2557
2558 #[rstest]
2560 fn config_system_user_data(
2561 default_config: TestResult<Config>,
2562 raw_user_data: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2563 ) -> TestResult {
2564 let config = default_config?;
2565 let raw_user_data = raw_user_data?;
2566 let expected: HashSet<SystemUserData> = HashSet::from_iter([
2567 SystemUserData::HostShareholder {
2568 system_user: &raw_user_data[0].0,
2569 ssh_authorized_key: raw_user_data[0]
2570 .1
2571 .as_ref()
2572 .expect("to have SSH authorized key"),
2573 },
2574 SystemUserData::HostShareholder {
2575 system_user: &raw_user_data[1].0,
2576 ssh_authorized_key: raw_user_data[1]
2577 .1
2578 .as_ref()
2579 .expect("to have SSH authorized key"),
2580 },
2581 SystemUserData::HostShareholder {
2582 system_user: &raw_user_data[2].0,
2583 ssh_authorized_key: raw_user_data[2]
2584 .1
2585 .as_ref()
2586 .expect("to have SSH authorized key"),
2587 },
2588 SystemUserData::HostDownloadNetworkConfig {
2589 system_user: &raw_user_data[3].0,
2590 ssh_authorized_key: raw_user_data[3]
2591 .1
2592 .as_ref()
2593 .expect("to have SSH authorized key"),
2594 },
2595 SystemUserData::BackendAdmin {
2596 system_user: raw_user_data[4].0.clone(),
2597 },
2598 SystemUserData::BackendMetrics {
2599 system_user: &raw_user_data[5].0,
2600 ssh_authorized_key: raw_user_data[5]
2601 .1
2602 .as_ref()
2603 .expect("to have SSH authorized key"),
2604 },
2605 SystemUserData::BackendBackup {
2606 system_user: &raw_user_data[6].0,
2607 ssh_authorized_key: raw_user_data[6]
2608 .1
2609 .as_ref()
2610 .expect("to have SSH authorized key"),
2611 },
2612 SystemUserData::BackendHermeticMetrics {
2613 system_user: &raw_user_data[7].0,
2614 },
2615 SystemUserData::BackendSign {
2616 system_user: &raw_user_data[8].0,
2617 ssh_authorized_key: raw_user_data[8]
2618 .1
2619 .as_ref()
2620 .expect("to have SSH authorized key"),
2621 },
2622 ]);
2623
2624 assert_eq!(config.system_user_data(), expected);
2625 Ok(())
2626 }
2627
2628 #[rstest]
2630 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
2631 let config = default_config?;
2632 let expected: HashSet<SystemUserId> = HashSet::from_iter([
2633 "share-holder1".parse()?,
2634 "share-holder2".parse()?,
2635 "share-holder3".parse()?,
2636 "wireguard-downloader".parse()?,
2637 "yubihsm2-metrics-user".parse()?,
2638 "yubihsm2-backup-user".parse()?,
2639 "yubihsm2-hermetic-metrics-user".parse()?,
2640 "yubihsm2-signing-user".parse()?,
2641 ]);
2642
2643 assert_eq!(
2644 config.system_user_ids(),
2645 expected.iter().collect::<HashSet<_>>()
2646 );
2647 Ok(())
2648 }
2649
2650 #[rstest]
2655 #[cfg(not(feature = "_yubihsm2-mockhsm"))]
2656 fn roundtrip_yaml_config(
2657 #[files("../fixtures/config/yubihsm2_backend/*.yaml")] path: PathBuf,
2658 ) -> TestResult {
2659 let config_string = read_to_string(&path)?;
2660 let config = Config::from_file_path(&path)?;
2661
2662 assert_eq!(config.to_yaml_string()?, config_string);
2663
2664 Ok(())
2665 }
2666
2667 #[rstest]
2672 #[cfg(feature = "_yubihsm2-mockhsm")]
2673 fn roundtrip_yaml_config_mockhsm(
2674 #[files("../fixtures/config/yubihsm2_mockhsm_backend/*.yaml")] path: PathBuf,
2675 ) -> TestResult {
2676 let config_string = read_to_string(&path)?;
2677 let config = Config::from_file_path(&path)?;
2678
2679 assert_eq!(config.to_yaml_string()?, config_string);
2680
2681 Ok(())
2682 }
2683
2684 #[rstest]
2688 fn user_backend_connection_secret_handling(
2689 default_config: TestResult<Config>,
2690 ) -> TestResult {
2691 let config = default_config?;
2692 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
2693 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2694 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2695 };
2696 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
2697
2698 let user_backend_connection = config
2699 .user_backend_connection(&"yubihsm2-signing-user".parse()?)
2700 .expect("there to be a mapping of the requested name");
2701
2702 assert_eq!(
2703 user_backend_connection.admin_secret_handling(),
2704 admin_secret_handling
2705 );
2706 assert_eq!(
2707 user_backend_connection.non_admin_secret_handling(),
2708 non_admin_secret_handling
2709 );
2710
2711 Ok(())
2712 }
2713
2714 #[rstest]
2716 fn system_user_config_state_from_config(default_config: TestResult<Config>) -> TestResult {
2717 let config = default_config?;
2718 let state = SystemUserConfigState::from(&config);
2719
2720 assert_eq!(state.system_user_data, config.system_user_data(),);
2721 Ok(())
2722 }
2723 }
2724
2725 #[cfg(all(feature = "nethsm", feature = "yubihsm2"))]
2727 mod all_backends {
2728 use pretty_assertions::assert_eq;
2729
2730 use super::*;
2731 use crate::config::{
2732 MappingAuthorizedKeyEntry,
2733 MappingSystemUserId,
2734 SystemUserData,
2735 traits::ConfigSystemUserData,
2736 };
2737
2738 #[fixture]
2740 fn default_config(
2741 default_system_config: TestResult<SystemConfig>,
2742 default_nethsm_config: TestResult<NetHsmConfig>,
2743 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2744 ) -> TestResult<Config> {
2745 Ok(ConfigBuilder::new(default_system_config?)
2746 .set_nethsm_config(default_nethsm_config?)
2747 .set_yubihsm2_config(default_yubihsm2_config?)
2748 .finish()?)
2749 }
2750
2751 #[fixture]
2754 fn raw_user_data(
2755 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2756 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2757 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2758 ) -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
2759 let mut data = raw_user_data_system?;
2760 data.extend(raw_user_data_nethsm?);
2761 data.extend(raw_user_data_yubihsm2?);
2762 Ok(data)
2763 }
2764
2765 #[rstest]
2767 fn user_backend_connection_system_user_id(
2768 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2769 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2770 ) -> TestResult {
2771 let raw_user_data_nethsm = raw_user_data_nethsm?;
2772 let data = UserBackendConnection::NetHsm {
2773 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2774 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2775 connections: BTreeSet::from_iter([Connection::new(
2776 "https://nethsm1.example.org/".parse()?,
2777 ConnectionSecurity::Unsafe,
2778 )]),
2779 mapping: NetHsmUserMapping::Backup {
2780 backend_user: "backup".parse()?,
2781 ssh_authorized_key: raw_user_data_nethsm[1]
2782 .1
2783 .clone()
2784 .expect("to have an SSH authorized key"),
2785 system_user: raw_user_data_nethsm[1].0.clone(),
2786 },
2787 };
2788 assert_eq!(data.system_user_id(), Some(&raw_user_data_nethsm[1].0));
2789
2790 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
2791 let data = UserBackendConnection::YubiHsm2 {
2792 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2793 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2794 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
2795 serial_number: "0123456789".parse()?,
2796 }]),
2797 mapping: YubiHsm2UserMapping::AuditLog {
2798 authentication_key_id: "1".parse()?,
2799 ssh_authorized_key: raw_user_data_yubihsm2[1]
2800 .1
2801 .clone()
2802 .expect("to have an SSH authorized key"),
2803 system_user: raw_user_data_yubihsm2[1].0.clone(),
2804 },
2805 };
2806 assert_eq!(data.system_user_id(), Some(&raw_user_data_yubihsm2[1].0));
2807
2808 Ok(())
2809 }
2810
2811 #[rstest]
2814 fn user_backend_connection_authorized_key_entry(
2815 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2816 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2817 ) -> TestResult {
2818 let raw_user_data_nethsm = raw_user_data_nethsm?;
2819 let data = UserBackendConnection::NetHsm {
2820 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2821 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2822 connections: BTreeSet::from_iter([Connection::new(
2823 "https://nethsm1.example.org/".parse()?,
2824 ConnectionSecurity::Unsafe,
2825 )]),
2826 mapping: NetHsmUserMapping::Backup {
2827 backend_user: "backup".parse()?,
2828 ssh_authorized_key: raw_user_data_nethsm[1]
2829 .1
2830 .clone()
2831 .expect("to have an SSH authorized key"),
2832 system_user: raw_user_data_nethsm[1].0.clone(),
2833 },
2834 };
2835 assert_eq!(
2836 data.authorized_key_entry(),
2837 Some(
2838 raw_user_data_nethsm[1]
2839 .1
2840 .as_ref()
2841 .expect("to have an SSH authorized key")
2842 )
2843 );
2844
2845 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
2846 let data = UserBackendConnection::YubiHsm2 {
2847 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2848 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2849 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
2850 serial_number: "0123456789".parse()?,
2851 }]),
2852 mapping: YubiHsm2UserMapping::AuditLog {
2853 authentication_key_id: "1".parse()?,
2854 ssh_authorized_key: raw_user_data_yubihsm2[1]
2855 .1
2856 .clone()
2857 .expect("to have an SSH authorized key"),
2858 system_user: raw_user_data_yubihsm2[1].0.clone(),
2859 },
2860 };
2861 assert_eq!(
2862 data.authorized_key_entry(),
2863 Some(
2864 raw_user_data_yubihsm2[1]
2865 .1
2866 .as_ref()
2867 .expect("to have an SSH authorized key")
2868 )
2869 );
2870
2871 Ok(())
2872 }
2873
2874 #[rstest]
2881 #[case::backend_overlap_duplicate_system_users_two_duplicate_ssh_public_keys(
2882 "Configuration with system-wide, NetHSM and YubiHSM2 configuration has two duplicate system users and two duplicate SSH public keys in the backends",
2883 NetHsmConfig::new(
2884 BTreeSet::from_iter([
2885 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2886 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2887 ]),
2888 BTreeSet::from_iter([
2889 NetHsmUserMapping::Admin("admin".parse()?),
2890 NetHsmUserMapping::Backup{
2891 backend_user: "backup".parse()?,
2892 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2893 system_user: "backup-user".parse()?,
2894 },
2895 NetHsmUserMapping::HermeticMetrics {
2896 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2897 system_user: "nethsm-hermetic-metrics-user".parse()?,
2898 },
2899 NetHsmUserMapping::Metrics {
2900 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2901 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2902 system_user: "metrics-user".parse()?,
2903 },
2904 NetHsmUserMapping::Signing {
2905 backend_user: "signing".parse()?,
2906 signing_key_id: "signing1".parse()?,
2907 key_setup: SigningKeySetup::new(
2908 KeyType::Curve25519,
2909 vec![KeyMechanism::EdDsaSignature],
2910 None,
2911 SignatureType::EdDsa,
2912 CryptographicKeyContext::OpenPgp {
2913 user_ids: OpenPgpUserIdList::new(vec![
2914 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2915 ])?,
2916 version: "v4".parse()?,
2917 },
2918 )?,
2919 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2920 system_user: "nethsm-signing-user".parse()?,
2921 tag: "nethsm-signing1".to_string(),
2922 }
2923 ]),
2924 )?,
2925 YubiHsm2Config::new(
2926 BTreeSet::from_iter([
2927 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2928 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2929 ]),
2930 BTreeSet::from_iter([
2931 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2932 YubiHsm2UserMapping::AuditLog {
2933 authentication_key_id: "3".parse()?,
2934 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2935 system_user: "metrics-user".parse()?,
2936 },
2937 YubiHsm2UserMapping::Backup {
2938 authentication_key_id: "2".parse()?,
2939 wrapping_key_id: "2".parse()?,
2940 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2941 system_user: "backup-user".parse()?,
2942 },
2943 YubiHsm2UserMapping::HermeticAuditLog {
2944 authentication_key_id: "4".parse()?,
2945 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2946 },
2947 YubiHsm2UserMapping::Signing {
2948 authentication_key_id: "5".parse()?,
2949 key_setup: SigningKeySetup::new(
2950 KeyType::Curve25519,
2951 vec![KeyMechanism::EdDsaSignature],
2952 None,
2953 SignatureType::EdDsa,
2954 CryptographicKeyContext::OpenPgp {
2955 user_ids: OpenPgpUserIdList::new(vec![
2956 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2957 ])?,
2958 version: "v4".parse()?,
2959 },
2960 )?,
2961 signing_key_id: "1".parse()?,
2962 domain: Domain::One,
2963 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2964 system_user: "yubihsm2-signing-user".parse()? }
2965 ]),
2966 )?,
2967 )]
2968 #[case::backend_overlap_one_duplicate_system_user(
2969 "Configuration with system-wide, NetHSM and YubiHSM2 configuration has one duplicate system user in the backends",
2970 NetHsmConfig::new(
2971 BTreeSet::from_iter([
2972 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2973 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2974 ]),
2975 BTreeSet::from_iter([
2976 NetHsmUserMapping::Admin("admin".parse()?),
2977 NetHsmUserMapping::Backup{
2978 backend_user: "backup".parse()?,
2979 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2980 system_user: "backup-user".parse()?,
2981 },
2982 NetHsmUserMapping::HermeticMetrics {
2983 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2984 system_user: "nethsm-hermetic-metrics-user".parse()?,
2985 },
2986 NetHsmUserMapping::Metrics {
2987 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2988 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2989 system_user: "nethsm-metrics-user".parse()?,
2990 },
2991 NetHsmUserMapping::Signing {
2992 backend_user: "signing".parse()?,
2993 signing_key_id: "signing1".parse()?,
2994 key_setup: SigningKeySetup::new(
2995 KeyType::Curve25519,
2996 vec![KeyMechanism::EdDsaSignature],
2997 None,
2998 SignatureType::EdDsa,
2999 CryptographicKeyContext::OpenPgp {
3000 user_ids: OpenPgpUserIdList::new(vec![
3001 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3002 ])?,
3003 version: "v4".parse()?,
3004 },
3005 )?,
3006 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3007 system_user: "nethsm-signing-user".parse()?,
3008 tag: "nethsm-signing1".to_string(),
3009 }
3010 ]),
3011 )?,
3012 YubiHsm2Config::new(
3013 BTreeSet::from_iter([
3014 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3015 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3016 ]),
3017 BTreeSet::from_iter([
3018 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3019 YubiHsm2UserMapping::AuditLog {
3020 authentication_key_id: "3".parse()?,
3021 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3022 system_user: "yubihsm2-metrics-user".parse()?,
3023 },
3024 YubiHsm2UserMapping::Backup {
3025 authentication_key_id: "2".parse()?,
3026 wrapping_key_id: "2".parse()?,
3027 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3028 system_user: "backup-user".parse()?,
3029 },
3030 YubiHsm2UserMapping::HermeticAuditLog {
3031 authentication_key_id: "4".parse()?,
3032 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
3033 },
3034 YubiHsm2UserMapping::Signing {
3035 authentication_key_id: "5".parse()?,
3036 key_setup: SigningKeySetup::new(
3037 KeyType::Curve25519,
3038 vec![KeyMechanism::EdDsaSignature],
3039 None,
3040 SignatureType::EdDsa,
3041 CryptographicKeyContext::OpenPgp {
3042 user_ids: OpenPgpUserIdList::new(vec![
3043 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3044 ])?,
3045 version: "v4".parse()?,
3046 },
3047 )?,
3048 signing_key_id: "1".parse()?,
3049 domain: Domain::One,
3050 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3051 system_user: "yubihsm2-signing-user".parse()? }
3052 ]),
3053 )?,
3054 )]
3055 #[case::system_overlap_duplicate_system_users_two_duplicate_ssh_public_keys(
3056 "Configuration with system-wide, NetHSM and YubiHSM2 configuration has two duplicate system users and two duplicate SSH public keys in the system and the backends",
3057 NetHsmConfig::new(
3058 BTreeSet::from_iter([
3059 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3060 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3061 ]),
3062 BTreeSet::from_iter([
3063 NetHsmUserMapping::Admin("admin".parse()?),
3064 NetHsmUserMapping::Backup{
3065 backend_user: "backup".parse()?,
3066 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3067 system_user: "share-holder1".parse()?,
3068 },
3069 NetHsmUserMapping::Metrics {
3070 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
3071 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3072 system_user: "share-holder2".parse()?,
3073 },
3074 NetHsmUserMapping::HermeticMetrics {
3075 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
3076 system_user: "nethsm-hermetic-metrics-user".parse()?,
3077 },
3078 NetHsmUserMapping::Signing {
3079 backend_user: "signing".parse()?,
3080 signing_key_id: "signing1".parse()?,
3081 key_setup: SigningKeySetup::new(
3082 KeyType::Curve25519,
3083 vec![KeyMechanism::EdDsaSignature],
3084 None,
3085 SignatureType::EdDsa,
3086 CryptographicKeyContext::OpenPgp {
3087 user_ids: OpenPgpUserIdList::new(vec![
3088 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3089 ])?,
3090 version: "v4".parse()?,
3091 },
3092 )?,
3093 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3094 system_user: "nethsm-signing-user".parse()?,
3095 tag: "nethsm-signing1".to_string(),
3096 }
3097 ]),
3098 )?,
3099 YubiHsm2Config::new(
3100 BTreeSet::from_iter([
3101 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3102 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3103 ]),
3104 BTreeSet::from_iter([
3105 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3106 YubiHsm2UserMapping::Backup {
3107 authentication_key_id: "2".parse()?,
3108 wrapping_key_id: "2".parse()?,
3109 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3110 system_user: "share-holder1".parse()?,
3111 },
3112 YubiHsm2UserMapping::AuditLog {
3113 authentication_key_id: "3".parse()?,
3114 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3115 system_user: "share-holder2".parse()?,
3116 },
3117 YubiHsm2UserMapping::HermeticAuditLog {
3118 authentication_key_id: "4".parse()?,
3119 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
3120 },
3121 YubiHsm2UserMapping::Signing {
3122 authentication_key_id: "5".parse()?,
3123 key_setup: SigningKeySetup::new(
3124 KeyType::Curve25519,
3125 vec![KeyMechanism::EdDsaSignature],
3126 None,
3127 SignatureType::EdDsa,
3128 CryptographicKeyContext::OpenPgp {
3129 user_ids: OpenPgpUserIdList::new(vec![
3130 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3131 ])?,
3132 version: "v4".parse()?,
3133 },
3134 )?,
3135 signing_key_id: "1".parse()?,
3136 domain: Domain::One,
3137 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3138 system_user: "yubihsm2-signing-user".parse()? }
3139 ]),
3140 )?,
3141 )]
3142 fn config_fails_validation(
3143 default_system_config: TestResult<SystemConfig>,
3144 #[case] description: &str,
3145 #[case] nethsm_config: NetHsmConfig,
3146 #[case] yubihsm2_config: YubiHsm2Config,
3147 ) -> TestResult {
3148 let error_message = match ConfigBuilder::new(default_system_config?)
3149 .set_nethsm_config(nethsm_config)
3150 .set_yubihsm2_config(yubihsm2_config)
3151 .finish()
3152 {
3153 Err(error) => error.to_string(),
3154 Ok(config) => panic!(
3155 "Expected to fail with Error::Validation, but succeeded instead: {}",
3156 config.to_yaml_string()?
3157 ),
3158 };
3159
3160 with_settings!({
3161 description => description,
3162 snapshot_path => SNAPSHOT_PATH,
3163 prepend_module_to_snapshot => false,
3164 }, {
3165 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
3166 });
3167
3168 Ok(())
3169 }
3170
3171 #[rstest]
3173 #[case::nethsm_signing(
3174 "nethsm-signing-user",
3175 Some(UserBackendConnection::NetHsm {
3176 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3177 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3178 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3179 },
3180 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3181 connections: BTreeSet::from_iter([
3182 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3183 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3184 ]),
3185 mapping: NetHsmUserMapping::Signing {
3186 backend_user: "signing".parse()?,
3187 signing_key_id: "signing1".parse()?,
3188 key_setup: SigningKeySetup::new(
3189 KeyType::Curve25519,
3190 vec![KeyMechanism::EdDsaSignature],
3191 None,
3192 SignatureType::EdDsa,
3193 CryptographicKeyContext::OpenPgp {
3194 user_ids: OpenPgpUserIdList::new(vec![
3195 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3196 ])?,
3197 version: "v4".parse()?,
3198 },
3199 )?,
3200 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3201 system_user: "nethsm-signing-user".parse()?,
3202 tag: "signing1".to_string(),
3203 }
3204 })
3205 )]
3206 #[case::yubihsm2_signing(
3207 "yubihsm2-signing-user",
3208 Some(UserBackendConnection::YubiHsm2 {
3209 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3210 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3211 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3212 },
3213 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3214 connections: BTreeSet::from_iter([
3215 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3216 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3217 ]),
3218 mapping: YubiHsm2UserMapping::Signing {
3219 authentication_key_id: "5".parse()?,
3220 signing_key_id: "1".parse()?,
3221 key_setup: SigningKeySetup::new(
3222 KeyType::Curve25519,
3223 vec![KeyMechanism::EdDsaSignature],
3224 None,
3225 SignatureType::EdDsa,
3226 CryptographicKeyContext::OpenPgp {
3227 user_ids: OpenPgpUserIdList::new(vec![
3228 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3229 ])?,
3230 version: "v4".parse()?,
3231 },
3232 )?,
3233 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3234 system_user: "yubihsm2-signing-user".parse()?,
3235 domain: Domain::One,
3236 }
3237 })
3238 )]
3239 #[case::none("foo", None)]
3240 fn config_user_backend_connection(
3241 default_config: TestResult<Config>,
3242 #[case] system_user: &str,
3243 #[case] expected_connection: Option<UserBackendConnection>,
3244 ) -> TestResult {
3245 let config = default_config?;
3246 assert_eq!(
3247 expected_connection,
3248 config.user_backend_connection(&system_user.parse()?)
3249 );
3250
3251 Ok(())
3252 }
3253
3254 #[rstest]
3257 #[case::filter_all(
3258 UserBackendConnectionFilter::All,
3259 vec![
3260 UserBackendConnection::NetHsm {
3261 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3262 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3263 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3264 },
3265 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3266 connections: BTreeSet::from_iter([
3267 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3268 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3269 ]),
3270 mapping: NetHsmUserMapping::Admin("admin".parse()?)
3271 },
3272 UserBackendConnection::NetHsm {
3273 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3274 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3275 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3276 },
3277 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3278 connections: BTreeSet::from_iter([
3279 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3280 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3281 ]),
3282 mapping: NetHsmUserMapping::Backup{
3283 backend_user: "backup".parse()?,
3284 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3285 system_user: "nethsm-backup-user".parse()?,
3286 }
3287 },
3288 UserBackendConnection::NetHsm {
3289 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3290 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3291 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3292 },
3293 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3294 connections: BTreeSet::from_iter([
3295 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3296 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3297 ]),
3298 mapping: NetHsmUserMapping::HermeticMetrics {
3299 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
3300 system_user: "nethsm-hermetic-metrics-user".parse()?,
3301 }
3302 },
3303 UserBackendConnection::NetHsm {
3304 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3305 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3306 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3307 },
3308 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3309 connections: BTreeSet::from_iter([
3310 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3311 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3312 ]),
3313 mapping: NetHsmUserMapping::Metrics {
3314 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
3315 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3316 system_user: "nethsm-metrics-user".parse()?,
3317 }
3318 },
3319 UserBackendConnection::NetHsm {
3320 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3321 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3322 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3323 },
3324 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3325 connections: BTreeSet::from_iter([
3326 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3327 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3328 ]),
3329 mapping: NetHsmUserMapping::Signing {
3330 backend_user: "signing".parse()?,
3331 signing_key_id: "signing1".parse()?,
3332 key_setup: SigningKeySetup::new(
3333 KeyType::Curve25519,
3334 vec![KeyMechanism::EdDsaSignature],
3335 None,
3336 SignatureType::EdDsa,
3337 CryptographicKeyContext::OpenPgp {
3338 user_ids: OpenPgpUserIdList::new(vec![
3339 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3340 ])?,
3341 version: "v4".parse()?,
3342 },
3343 )?,
3344 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3345 system_user: "nethsm-signing-user".parse()?,
3346 tag: "signing1".to_string(),
3347 }
3348 },
3349 UserBackendConnection::YubiHsm2 {
3350 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3351 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3352 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3353 },
3354 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3355 connections: BTreeSet::from_iter([
3356 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3357 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3358 ]),
3359 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3360 },
3361 UserBackendConnection::YubiHsm2 {
3362 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3363 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3364 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3365 },
3366 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3367 connections: BTreeSet::from_iter([
3368 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3369 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3370 ]),
3371 mapping: YubiHsm2UserMapping::AuditLog {
3372 authentication_key_id: "3".parse()?,
3373 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3374 system_user: "yubihsm2-metrics-user".parse()?,
3375 },
3376 },
3377 UserBackendConnection::YubiHsm2 {
3378 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3379 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3380 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3381 },
3382 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3383 connections: BTreeSet::from_iter([
3384 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3385 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3386 ]),
3387 mapping: YubiHsm2UserMapping::Backup{
3388 authentication_key_id: "2".parse()?,
3389 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3390 system_user: "yubihsm2-backup-user".parse()?,
3391 wrapping_key_id: "1".parse()?,
3392 },
3393 },
3394 UserBackendConnection::YubiHsm2 {
3395 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3396 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3397 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3398 },
3399 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3400 connections: BTreeSet::from_iter([
3401 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3402 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3403 ]),
3404 mapping: YubiHsm2UserMapping::HermeticAuditLog {
3405 authentication_key_id: "4".parse()?,
3406 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
3407 },
3408 },
3409 UserBackendConnection::YubiHsm2 {
3410 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3411 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3412 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3413 },
3414 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3415 connections: BTreeSet::from_iter([
3416 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3417 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3418 ]),
3419 mapping: YubiHsm2UserMapping::Signing {
3420 authentication_key_id: "5".parse()?,
3421 signing_key_id: "1".parse()?,
3422 key_setup: SigningKeySetup::new(
3423 KeyType::Curve25519,
3424 vec![KeyMechanism::EdDsaSignature],
3425 None,
3426 SignatureType::EdDsa,
3427 CryptographicKeyContext::OpenPgp {
3428 user_ids: OpenPgpUserIdList::new(vec![
3429 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3430 ])?,
3431 version: "v4".parse()?,
3432 },
3433 )?,
3434 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3435 system_user: "yubihsm2-signing-user".parse()?,
3436 domain: Domain::One,
3437 }
3438 },
3439 ],
3440 )]
3441 #[case::filter_admin(
3442 UserBackendConnectionFilter::Admin,
3443 vec![
3444 UserBackendConnection::NetHsm {
3445 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3446 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3447 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3448 },
3449 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3450 connections: BTreeSet::from_iter([
3451 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3452 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3453 ]),
3454 mapping: NetHsmUserMapping::Admin("admin".parse()?)
3455 },
3456 UserBackendConnection::YubiHsm2 {
3457 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3458 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3459 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3460 },
3461 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3462 connections: BTreeSet::from_iter([
3463 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3464 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3465 ]),
3466 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3467 },
3468 ],
3469 )]
3470 #[case::filter_non_admin(
3471 UserBackendConnectionFilter::NonAdmin,
3472 vec![
3473 UserBackendConnection::NetHsm {
3474 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3475 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3476 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3477 },
3478 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3479 connections: BTreeSet::from_iter([
3480 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3481 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3482 ]),
3483 mapping: NetHsmUserMapping::Backup{
3484 backend_user: "backup".parse()?,
3485 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3486 system_user: "nethsm-backup-user".parse()?,
3487 }
3488 },
3489 UserBackendConnection::NetHsm {
3490 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3491 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3492 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3493 },
3494 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3495 connections: BTreeSet::from_iter([
3496 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3497 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3498 ]),
3499 mapping: NetHsmUserMapping::HermeticMetrics {
3500 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
3501 system_user: "nethsm-hermetic-metrics-user".parse()?,
3502 }
3503 },
3504 UserBackendConnection::NetHsm {
3505 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3506 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3507 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3508 },
3509 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3510 connections: BTreeSet::from_iter([
3511 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3512 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3513 ]),
3514 mapping: NetHsmUserMapping::Metrics {
3515 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
3516 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3517 system_user: "nethsm-metrics-user".parse()?,
3518 }
3519 },
3520 UserBackendConnection::NetHsm {
3521 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3522 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3523 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3524 },
3525 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3526 connections: BTreeSet::from_iter([
3527 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3528 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3529 ]),
3530 mapping: NetHsmUserMapping::Signing {
3531 backend_user: "signing".parse()?,
3532 signing_key_id: "signing1".parse()?,
3533 key_setup: SigningKeySetup::new(
3534 KeyType::Curve25519,
3535 vec![KeyMechanism::EdDsaSignature],
3536 None,
3537 SignatureType::EdDsa,
3538 CryptographicKeyContext::OpenPgp {
3539 user_ids: OpenPgpUserIdList::new(vec![
3540 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3541 ])?,
3542 version: "v4".parse()?,
3543 },
3544 )?,
3545 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3546 system_user: "nethsm-signing-user".parse()?,
3547 tag: "signing1".to_string(),
3548 }
3549 },
3550 UserBackendConnection::YubiHsm2 {
3551 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3552 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3553 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3554 },
3555 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3556 connections: BTreeSet::from_iter([
3557 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3558 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3559 ]),
3560 mapping: YubiHsm2UserMapping::AuditLog {
3561 authentication_key_id: "3".parse()?,
3562 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3563 system_user: "yubihsm2-metrics-user".parse()?,
3564 },
3565 },
3566 UserBackendConnection::YubiHsm2 {
3567 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3568 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3569 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3570 },
3571 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3572 connections: BTreeSet::from_iter([
3573 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3574 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3575 ]),
3576 mapping: YubiHsm2UserMapping::Backup{
3577 authentication_key_id: "2".parse()?,
3578 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3579 system_user: "yubihsm2-backup-user".parse()?,
3580 wrapping_key_id: "1".parse()?,
3581 },
3582 },
3583 UserBackendConnection::YubiHsm2 {
3584 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3585 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3586 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3587 },
3588 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3589 connections: BTreeSet::from_iter([
3590 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3591 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3592 ]),
3593 mapping: YubiHsm2UserMapping::HermeticAuditLog {
3594 authentication_key_id: "4".parse()?,
3595 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
3596 },
3597 },
3598 UserBackendConnection::YubiHsm2 {
3599 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3600 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3601 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3602 },
3603 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3604 connections: BTreeSet::from_iter([
3605 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3606 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3607 ]),
3608 mapping: YubiHsm2UserMapping::Signing {
3609 authentication_key_id: "5".parse()?,
3610 signing_key_id: "1".parse()?,
3611 key_setup: SigningKeySetup::new(
3612 KeyType::Curve25519,
3613 vec![KeyMechanism::EdDsaSignature],
3614 None,
3615 SignatureType::EdDsa,
3616 CryptographicKeyContext::OpenPgp {
3617 user_ids: OpenPgpUserIdList::new(vec![
3618 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3619 ])?,
3620 version: "v4".parse()?,
3621 },
3622 )?,
3623 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3624 system_user: "yubihsm2-signing-user".parse()?,
3625 domain: Domain::One,
3626 }
3627 },
3628 ],
3629 )]
3630 fn config_user_backend_connections(
3631 default_config: TestResult<Config>,
3632 #[case] filter: UserBackendConnectionFilter,
3633 #[case] expected_connections: Vec<UserBackendConnection>,
3634 ) -> TestResult {
3635 let config = default_config?;
3636
3637 assert_eq!(
3638 expected_connections,
3639 config.user_backend_connections(filter)
3640 );
3641
3642 Ok(())
3643 }
3644
3645 #[rstest]
3650 fn config_to_yaml_string(
3651 default_system_config: TestResult<SystemConfig>,
3652 default_nethsm_config: TestResult<NetHsmConfig>,
3653 default_yubihsm2_config: TestResult<YubiHsm2Config>,
3654 ) -> TestResult {
3655 let config = ConfigBuilder::new(default_system_config?)
3656 .set_nethsm_config(default_nethsm_config?)
3657 .set_yubihsm2_config(default_yubihsm2_config?)
3658 .finish()?;
3659 let config_str = config.to_yaml_string()?;
3660
3661 with_settings!({
3662 description => "Configuration with system-wide, NetHSM and YubiHSM2 configuration",
3663 snapshot_path => SNAPSHOT_PATH,
3664 prepend_module_to_snapshot => false,
3665 }, {
3666 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
3667 });
3668
3669 Ok(())
3670 }
3671
3672 #[rstest]
3675 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
3676 let config = default_config?;
3677 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
3678 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3679 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3680 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
3681 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
3682 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3683 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3684 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3685 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3686 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3687 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3688 ]);
3689
3690 assert_eq!(
3691 config.authorized_key_entries(),
3692 expected.iter().collect::<HashSet<_>>()
3693 );
3694 Ok(())
3695 }
3696
3697 #[rstest]
3699 fn config_system_user_data(
3700 default_config: TestResult<Config>,
3701 raw_user_data: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
3702 ) -> TestResult {
3703 let config = default_config?;
3704 let raw_user_data = raw_user_data?;
3705 let expected: HashSet<SystemUserData> = HashSet::from_iter([
3706 SystemUserData::HostShareholder {
3707 system_user: &raw_user_data[0].0,
3708 ssh_authorized_key: raw_user_data[0]
3709 .1
3710 .as_ref()
3711 .expect("to have SSH authorized key"),
3712 },
3713 SystemUserData::HostShareholder {
3714 system_user: &raw_user_data[1].0,
3715 ssh_authorized_key: raw_user_data[1]
3716 .1
3717 .as_ref()
3718 .expect("to have SSH authorized key"),
3719 },
3720 SystemUserData::HostShareholder {
3721 system_user: &raw_user_data[2].0,
3722 ssh_authorized_key: raw_user_data[2]
3723 .1
3724 .as_ref()
3725 .expect("to have SSH authorized key"),
3726 },
3727 SystemUserData::HostDownloadNetworkConfig {
3728 system_user: &raw_user_data[3].0,
3729 ssh_authorized_key: raw_user_data[3]
3730 .1
3731 .as_ref()
3732 .expect("to have SSH authorized key"),
3733 },
3734 SystemUserData::BackendAdmin {
3735 system_user: raw_user_data[4].0.clone(),
3736 },
3737 SystemUserData::BackendBackup {
3738 system_user: &raw_user_data[5].0,
3739 ssh_authorized_key: raw_user_data[5]
3740 .1
3741 .as_ref()
3742 .expect("to have SSH authorized key"),
3743 },
3744 SystemUserData::BackendHermeticMetrics {
3745 system_user: &raw_user_data[6].0,
3746 },
3747 SystemUserData::BackendMetrics {
3748 system_user: &raw_user_data[7].0,
3749 ssh_authorized_key: raw_user_data[7]
3750 .1
3751 .as_ref()
3752 .expect("to have SSH authorized key"),
3753 },
3754 SystemUserData::BackendSign {
3755 system_user: &raw_user_data[8].0,
3756 ssh_authorized_key: raw_user_data[8]
3757 .1
3758 .as_ref()
3759 .expect("to have SSH authorized key"),
3760 },
3761 SystemUserData::BackendMetrics {
3762 system_user: &raw_user_data[10].0,
3763 ssh_authorized_key: raw_user_data[10]
3764 .1
3765 .as_ref()
3766 .expect("to have SSH authorized key"),
3767 },
3768 SystemUserData::BackendBackup {
3769 system_user: &raw_user_data[11].0,
3770 ssh_authorized_key: raw_user_data[11]
3771 .1
3772 .as_ref()
3773 .expect("to have SSH authorized key"),
3774 },
3775 SystemUserData::BackendHermeticMetrics {
3776 system_user: &raw_user_data[12].0,
3777 },
3778 SystemUserData::BackendSign {
3779 system_user: &raw_user_data[13].0,
3780 ssh_authorized_key: raw_user_data[13]
3781 .1
3782 .as_ref()
3783 .expect("to have SSH authorized key"),
3784 },
3785 ]);
3786
3787 assert_eq!(config.system_user_data(), expected);
3788 Ok(())
3789 }
3790
3791 #[rstest]
3793 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
3794 let config = default_config?;
3795 let expected: HashSet<SystemUserId> = HashSet::from_iter([
3796 "share-holder1".parse()?,
3797 "share-holder2".parse()?,
3798 "share-holder3".parse()?,
3799 "wireguard-downloader".parse()?,
3800 "nethsm-backup-user".parse()?,
3801 "nethsm-hermetic-metrics-user".parse()?,
3802 "nethsm-metrics-user".parse()?,
3803 "nethsm-signing-user".parse()?,
3804 "yubihsm2-metrics-user".parse()?,
3805 "yubihsm2-backup-user".parse()?,
3806 "yubihsm2-hermetic-metrics-user".parse()?,
3807 "yubihsm2-signing-user".parse()?,
3808 ]);
3809
3810 assert_eq!(
3811 config.system_user_ids(),
3812 expected.iter().collect::<HashSet<_>>()
3813 );
3814 Ok(())
3815 }
3816
3817 #[rstest]
3819 fn config_builder_new(
3820 default_system_config: TestResult<SystemConfig>,
3821 default_nethsm_config: TestResult<NetHsmConfig>,
3822 default_yubihsm2_config: TestResult<YubiHsm2Config>,
3823 ) -> TestResult {
3824 let _config = ConfigBuilder::new(default_system_config?)
3825 .set_nethsm_config(default_nethsm_config?)
3826 .set_yubihsm2_config(default_yubihsm2_config?)
3827 .finish()?;
3828
3829 Ok(())
3830 }
3831
3832 #[rstest]
3838 fn roundtrip_yaml_config(
3839 #[files("../fixtures/config/all_backends/*.yaml")] path: PathBuf,
3840 ) -> TestResult {
3841 let config_string = read_to_string(&path)?;
3842 let config = Config::from_file_path(&path)?;
3843
3844 assert_eq!(config.to_yaml_string()?, config_string);
3845
3846 Ok(())
3847 }
3848
3849 #[rstest]
3853 fn user_backend_connection_secret_handling(
3854 default_config: TestResult<Config>,
3855 ) -> TestResult {
3856 let config = default_config?;
3857 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
3858 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3859 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3860 };
3861 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
3862
3863 for user in ["nethsm-signing-user", "yubihsm2-signing-user"] {
3864 let user_backend_connection = config
3865 .user_backend_connection(&user.parse()?)
3866 .expect("there to be a mapping of the requested name");
3867
3868 assert_eq!(
3869 user_backend_connection.admin_secret_handling(),
3870 admin_secret_handling
3871 );
3872 assert_eq!(
3873 user_backend_connection.non_admin_secret_handling(),
3874 non_admin_secret_handling
3875 );
3876 }
3877
3878 Ok(())
3879 }
3880 }
3881}